본문 바로가기
웹/책 & 강의

클린코드

by sun__ 2022. 7. 26.

복습 겸 일부 정리. 모든 장은 결론부터 서술됨

 

6장. 객체와 자료구조

객체는 동작을 공개하고 자료를 숨긴다. 그래서 기존 동작을 변경하지 않으면서 새 객체 타입을 추가하기는 쉬운 반면, 기존 객체에 새 동작을 추가하기는 어렵다. 이런 어려움을 해소하고자 방문자 패턴 등을 사용한다. 자료구조는 별다른 동작 없이 자료를 노출한다. 무지성으로 get, set 함수를 제공하는 클래스도 자료구조로 분류한다. 기존 자료구조에 새 동작을 추가하기는 쉬우나, 기존 함수에 새 자료 구조를 추가하기는 어렵다. 우수한 개발자는 편견없이 이 사실을 이해해 직면한 문제에 최적인 해결책을 선택해야 한다.

 

디미터 법칙은 "모듈은 자신이 조작하는 객체의 속사정을 몰라야 한다"는 법칙이다. 좀 더 정확하겐, "클래스 C의 메서드 f는 (클래스 C, f가 생성한 객체, f 인수로 넘어온 객체, C인스턴스 변수에 저장된 객체) 객체의 메서드만 호출해야 한다"

 

절반은 객체, 절반은 자료구조인 구조를 잡종 구조라고 한다. 이런 잡종 구조는 객체와 자료구조의 단점만 모아둔 것이므로 피해야 한다. 

 

자료구조체는 공개변수만 있고 함수가 없는 클래스이다. 이런 자료구조체를 때론 DTO(data transfer object)라고 한다. 흔히 DTO는 DB에 저장된 가공되지 않은 정보를 애플리케이션 코드에서 사용할 객체로 변환하는 일련의 단계에서 가장 처음으로 사용하는 구조체이다.

 

활성 레코드는 DTO의 특수한 형태이다. save나 find같은 탐색함수를 제공하기도 한다. 활성 레코드는 DB테이블이나 다른 소스에서 자료를 직접 변환한 결과이다. 이런 활성 레코드를 객체로 취급하는 경우가 흔한데 이는 잡종구조를 야기하므로 바람직하지 않다. 활성 레코드는 자료구조로 취급한다.

 

 

8장. 경계 (외부 패키지, 프레임워크 통합 등)

경계에 위치하는 코드는 깔끔히 분리한다. 또한 기대치를 정의하는 테스트 케이스(학습테스트)를 작성한다. 이쪽 코드에서 외부 패키지를 세세하게 알아야 할 필요가 없도록 한다. 통제가 불가능한 외부 패키지에 의존하지 말고 통제가 가능한 우리 코드에 의존해야 한다. 

 

외부 패키지를 호출하는 코드를 가능한 줄여 경계를 관리하자. Map에서 봤듯 새로운 클래스로 경계를 감싸거나 Adapter패턴을 사용해서 우리가 원하는 인터페이스를 패키지가 제공하는 인터페이스로 변환하자. 경계 인터페이스를 사용하는 일관성도 높아지며, 외부 패키지가 변했을 때 변경할 코드도 줄어든다.

 

 

9장. 단위 테스트

테스트 코드는 실제 코드만큼 중요하다. 테스트 코드는 실제 코드의 유연성, 유지보수성, 재사용성을 보존하고 강화한다. 그러므로 테스트코드는 지속적으로 깨끗하게 관리하자. 표현력을 높이고(적은 단어로 나타내고자 하는 것을 표현하기) 간결하게 정리하자. 테스트 API를 구현해 도메인 특화 언어(DSL)을 만들자. 그러면 테스트 코드를 짜기가 쉬워진다.

 

TDD의 법칙

  1. 컴파일은 되면서 실행은 실패하는 단위 테스트를 작성한다.
  2. 단위 테스트를 통과하는 실제 코드를 작성한다.

방대한 테스트코드는 관리문제를 야기한다. 이를 해결하기 위해 개발자는 가독성 좋은 테스트 코드를 작성해야 한다. 최소의 표현으로 많은 것을 나타내야 한다.

 

given-when-then의 관례에 맞게 단위 테스트를 구성하면 좋다. 이 때, 자세한 부분을 추상화해서 함수들로 나타내면 그게 DSL이 되는 것이라고 이해했다.

 

테스트 하나 당 개념 하나만 테스트 해야한다. 좀 더 엄격하게 테스트 당 assert하나만 하라고 주장하는 사람도 있다고 한다.

 

깨끗한 테스트는 FIRST(Fast, Independent, Repeatable, Self-Validating, Timely) 규칙을 만족한다.

 

10장. 클래스

깨끗한 클래스를 위해선 클래스를 작게 유지해야한다. 함수는 행 수로 크기를 측적했다면 클래스는 맡은 책임의 수로 크기를 측정한다. 책임이란 클래스나 모듈을 변경할 이유를 의미한다. 클래스 이름은 해당 클래스의 책임을 기술해야 한다.

  • 단일 책임 원칙: 클래스나 모듈을 변경할 이유는 단 하나여야 한다.

 

클래스는 인스턴스 변수 수가 작아야 하고 응집도가 높아야 한다. 메서드가 변수를 더 많이 사용한다면 응집도가 높은 것이다. 모든 인스턴스 변수를 메서드마다 사용한다면 최고의 응집도를 갖는 셈(현실적으론 불가능)이다. 이렇게 응집도를 크게 유지하면 작은 클래스 여럿이 나온다. 클래스가 응집력을 잃는다면 쪼개라.

 

클래스는 변경하기 쉬워야 한다. OCR(확장에 열려있고 수정에 닫혀있어야 함) 원칙에 따라 클래스를 설계하면 된다. 새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소인 시스템 구조가 바람직하다.

 

요구사항은 변하기 마련이므로 코드도 변하기 마련이다. 상세한 구현에 의존하는 코드보단 인터페이스와 추상클래스를 사용해서 구현이 미치는 영향을 격리해야 한다. 이런식으로 결합도를 줄이면 DIP(의존성 역전) 원칙을 따르는 클래스가 나온다.

 

11장. 시스템

시스템 수준에서도 적절한 추상화와 모듈화를 통해 깨끗함을 유지해야 한다.

 

시스템 제작(생성)과 시스템 사용을 분리하라.

시스템 제작은 애플리케이션 객체를 생성하고 의존성을 서로 연결하는 시작 단계와, 시작 단계 이후에 이어지는 런타임 로직을 분리하라는 의미이다. 한가지 방법으로, 생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정하는 것을 추천한다. 애플리케이션은 main이나 객체가 생성되는 과정을 전혀 모른다는 뜻이다.

 

때로는 객체 생성 시점을 애플리케이션이 결정해야 하는 경우도 있다. 이때도 마찬가지로 애플리케이션은 객체가 생성되는 과정을 전혀 몰라야 한다. 추상 팩토리 패턴으로 이를 달성한다.

https://velog.io/@suuntree/%EC%B6%94%EC%83%81-%ED%8C%A9%ED%86%A0%EB%A6%AC

 

추상 팩토리 패턴

Refactoring Guru당신이 오늘의 집 서비스의 시스템 담당자라고 가정해 봅시다. 시스템 내부엔 위 그림과 같은 가구 클래스가 존재합니다. 다양한 가구 종류가 추가(의자, 소파 등)될 수도 있고, 다양

velog.io

 

의존성 주입으로도 사용과 제작을 분리할 수 있다. 아래 사진에서 memberUse2가 의존성 주입의 아주 단순한 예이다

https://www.youtube.com/watch?v=fGOU7JqNHyE

 

시스템 컴포넌트에 관심사를 적절히 분리해서 관리한다면 소프트웨어 아키텍처는 점진적으로 발전할 수 있다. 유사한 개념으로 관심사를 분리하는 방식은 테스트 주도 아키텍처 구축이 가능하다.

 

시스템은 도메인 특화 언어(DSL)이 필요하다. DSL이란 간단한 스크립트 언어나 표준 언어로 구현한 API를 가리킨다. 좋은 DSL은 도메인 개념과 그 개념을 구현한 코드 사이에 존재하는 의사소통 간극을 줄여준다. DSL은 시스템의 추상화 수준을 코드레벨에서의 코드 관용구나 디자인패턴 사용 이상으로 끌어올려준다.

 

시스템을 설계하든 개별 모듈을 설계하든, 실제로 돌아가나는 가장 단순한 수단을 사용해야 한다는 사실(오버엔지니어링을 피하라는 맥락인듯)을 명심하자

 

 

 

12장. 창발성

창발(創發) : 하위 계층(구성 요소)에는 없는 특성이나 행동이 상위 계층(전체 구조)에서 자발적으로 돌연히 출현하는 현상

  • 특정 규칙(하위계층)만 잘 따르면 SW설계 품질(상위계층)이 자연히 좋아진다.

 

켄트 백이 제시한 단순한 설계 규칙 네가지 (중요 순)

  • 모든 테스트를 실행한다
    • 시스템이 의도한 대로 돌아가는지 검증할 방법. 검증이 불가능한 시스템은 출시해서도 안된다.
    • 테스트가 가능한 시스템은 자연히 설계 품질이 좋아진다. 크기가 작고 SRP를 준수하는 클래스가 나온다.
    • 테스트 케이스를 많이 작성할수록 개발자는 DIP와 같은 원칙을 적용하고, 의존성 주입, 인터페이스, 추상화 등과 같은 도구를 통해 결합도를 낮춘다.
  • 중복을 없앤다
  • 프로그래머의 의도를 표현한다.
    • 코드를 명백하게 짤수록 다른 사람이 그 코드를 이해하기 쉬워진다. 그래야 결함이 줄어들고 유지보수 비용이 줄어든다.
    • 좋은 이름을 선택하라
    • 함수와 클래스의 크기를 가능한 줄여라
    • 표준 명칭을 사용해라. 예를들어, 표준패턴을 사용해서 구현했다면 클래스 이름에 패턴 이름을 넣어줘라
    • 단위 테스트 케이스를 꼼꼼히 작성해라
  • 클래스와 메서드 수를 최소로 줄여라
    • 때로는 무의미한 독단적인 정책을 채택하려는 우를 범한다. (자료클래스와 동작클래스는 무조건 분리해야해!, 클래스마다 무조건 인터페이스를 생성해야해!)

 

13장. 동시성

결론

  • 다중 스레드 코드를 제대로 구현하기 위해선 무엇보다도 먼저, SRP를 준수한다. 스레드를 아는 코드와 스레드를 모르는 코드를 분리한다. 스레드 코드를 테스트할 때는 전적으로 스레드만 테스트한다. 즉, 스레드 코드는 최대한 집약되고 작아야 한다. 
  • 동시성 오류를 일으키는 잠정적인 원인을 철저히 이해한다. 루프 반복을 끝내거나 프로그램을 깔끔하게 종료하는 등 경계 조건의 경우가 까다로우므로 특히 주의해야 한다.
  • 사용하는 라이브러리와 기본 알고리즘을 이해한다.
  • 보호할 코드 영역을 찾고 이 부분만을 잠근다. 잠긴 영역에서 다른 잠긴 영역을 호출하지 않아야 한다. 그러려면 공유하는 정보와 공유하지 않는 정보를 제대로 이해해야 한다. 공유하는 객체 수와 범위를 최대한 줄인다. 클라이언트에게 공유상태를 관리하는 책임을 떠넘기지 않아야 한다.

 

+동시성2

TODO