[Review] Clean Code 내용정리 - 5

Cover image

Clean Code 내용 정리 - 5

14장. 점진적인 개선

좋은 코드가 많지만, 이를 다 적을 수 없어 내용적으로 간략하게만 구성하였다.

이후에 한 번 책을 사서 코드를 보는 것을 추천드립니다.

구현

깔끔한 코드를 짜는 방법에 대해 이야기할 수 있다.

깨끗한 코드를 짜기 위해서는 먼저 지저분한 코드를 짠 뒤에 정리해야 한다.

초안

최초의 코드에 인수 유형 1개만 추가해도 엄청나게 지저분해진다.

이 경우에, 기능을 더 추가하지 않고 리팩토링을 한다. 왜냐하면? 더 인수 유형을 추가할 수도 있기 때문에.

점진적으로 개선

프로그램을 망치는 방법 중 하나는 "개선"이라는 이름에서 구조를 크게 뒤집는 것. 이는 그 전과 똑같이 어려워질 수 있다.

따라서, 테스트 주도 개발(TDD, Test-Driven Development) 기법을 통해 시스템을 망가트리지 않고, 리팩토링을 진행한다.

결론

단순히 돌아가는 코드만으로는 되지 않는다. 이는 이후에 큰 문제를 만든다.

나쁜 코드는 깨끗한 코드로 개선하기에는 엄청난 비용이 발생하지만, 최근에 만든 코드는 당장 정리하기 매우 쉽다.

따라서, 코드는 언제나 최대한 깔끔하고 단순하게 정리해야한다.

15장. JUnit 들여다보기

JUnit 프레임워크

문자열 비교 오류를 파할 때 유용한 코드로 parisonCompactor 등이 있다.

코드 등은 미첨부.

결론

보이스카우트 규칙을 지키기.

즉, 좋은 모듈을 통해서 조금 더 깨끗하게 만들 수 있어야 한다.

16장. SerialDate 리팩터링

대표적인 코드를 분석을 해보면서 좋은 코드로 리팩터링 할 수 있다.

순서는 다음과 같이 진행할 수 있다.

1. 돌려보기

  • 단위 테스트가 다 구현이 되어있는가?

2. 고쳐보기

  • 변경이력은 소스 코드 제어 도구가 있으므로 하지 않아도 된다.
  • 오래된 주석은 고쳐서 개선하기
  • enum을 독자적인 소스파일로 옮기기
  • 추상 메서드를 클래스로 수정
  • 코드 커버리지를 감소시켰다.

결론

따라서 리팩터링을 통해 테스트 커버리지가 증가시키고, 버그 고치고, 코드 크기를 줄이고 코드를 명확하게 할 수 있다.

  • 시간을 투자해서, 보이스카우트 규칙을 따랐다.

17장. 냄새와 휴리스틱

주석

C1. 부적절한 정보 피하기

다른 시스템에 저장할 정보는 주석으로 적절하지 못한다. 일반적으로 작성자, 최종 수정일, SPR 번호 등과 같은 메타 정보만 주석으로 넣고, 추가적으로 코드와 설계에 기술적인 설명을 하는 수단으로 써야 한다.

C2. 쓸모없는 주석 피하기

오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다. 쓸모없는 주석은 없애야 한다.

C3. 중복된 주석 피하기

코드만으로 충분한데 구구절절 설명하는 주석이 중복된 주석은 좋지 않다.

C4. 성의 없는 주석 피하기

단어를 신중하게 선택하고, 간결하고 명료하게 작성해야 한다.

C5. 주석 처리된 코드 피하기

주석으로 처리된 코드는 남들이 읽을 때 매우 눈에 거슬리고 매일매일 낡아간다. 이는 해당 모듈을 오염시키고, 읽는 사람을 헷갈리게 만든다.

환경

E1. 한 단계로 빌드하기

빌드는 간단히 한 단계로 끝나야 한다.

E2. 한 단계로 테스트하기

모든 단위 테스트는 한 명령으로 돌려야 하고, 이 방법이 빠르고 쉽고 명백하며, 가장 중요하다.

함수

F1. 너무 많은 인수 피하기

인수는 작으면 작을수록 좋다

F2. 출력 인수 피하기

출력이 인수는 직관을 정면으로 위배하므로 안 쓰는 것이 좋다.

F3. 플래그 인수 피하기

Boolean 인수는 함수가 여러 기능을 수행한다는 증거이므로, 플래그 인수는 혼란을 피하기 위해서 피해야 한다.

F4. 죽은 함수 피하기

아무도 호출하지 않는 함수는 삭제한다.

일반

G1. 한 소스 파일에 여러 언어를 사용을 줄이기

이상적으로는 소스 파일 하나에 언어 하나만 사용하는 방식이 좋으며, 현실적으로는 이가 어렵기 때문에 소스 파일에서 언어 수와 범위를 줄이는 방법이 중요하다.

G2. 당연한 동작을 구현하기

최소 놀람의 원칙을 지켜서 함수나 클래스는 프로그래머가 당연하게 여길 만한 동작과 기능을 제공해야 한다.

G3. 경계를 올바르게 처리하기

코드는 올바르게 동작해야 한다. 모든 경계 조건, 구석진 곳, 예외 등을 신경을 써야 하며 모든 경계 조건을 테스트하는 케이스가 있어야 한다.

G4. 안전 절차를 지키기

안전 절차를 무시하면 위험하다. 예를 들면 변수를 직접 제어하거나 컴파일러 경고를 꺼버리면 끊임없는 문제가 발생한다.

G5. 중복을 피하기

단 한 번만 사용하는 규칙을 지켜야 한다. 코드에서 중복을 발견할 때마다 이를 추상화시키고, 중복을 다른 클래스로 분리해야 한다.

G6. 추상화 수준이 올바르게 지키기

추상화는 저 차원 상세 개념을 고차원 일반 개념으로 분리한다. 고차원 개념을 표현하는 추상(기초) 클래스와 저차원 개념을 표현하는 파생 클래스를 통해서 추상화를 진행해야 한다.

G7. 기초 클래스는 파생 클래스에 독립적으로

기초 클래스와 파생 클래스로 나누는 큰 이유는 하나는 개념을 분리해 독립성을 보장하기 위해서이다. 따라서 서로 독립적으로 진행해야 하고, 이는 이후에 유지보수에 큰 장점을 가진다.

G8. 과도한 정보를 피하기

잘 정의된 모듈은 인터페이스가 아주 작다. 많은 함수를 제공하지 않기 때문에 결합도가 낮다. 이런 식으로 인터페이스에 노출할 함수를 제한해야 하고, 클래스가 제공하는 메서드 수는 작을수록 좋다. 마찬가지로 변수가 작을 수록 좋다. 더 나아가서 자료를 줌 기는 것 또한 중요하다.

G9. 죽은 코드를 피하기

실행되지 않는 코드는 시스템에서 제거해주어야 한다.

G10. 수직 분리 줄이기

변수와 함수는 사용되는 위치에 가깝게 정의해야 한다. 비공개 함수는 처음으로 호출한 직후에 정의해야 한다.

G11. 일관성 유지

어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현해야 한다. 이러한 일관성을 유지한다면 코드를 읽고 수정하기가 쉬워진다.

G12. 잡동사니 없애기

비어있는 기본 생성자와 같은 잡동사니는 삭제한다.

G13. 인위적인 결합 피하기

서로 무관한 개념은 인위적으로 결합하지 않는다.

G14. 기능 욕심내지 말기

기능을 위해서 다른 클래스의 변수와 함수에 관심을 가져서는 안 된다. 어쩔 수 없는 경우를 제외하고는 최대한 피해야 한다.

G15. 선택자 인수 피하기

선택자 인수는 큰 함수를 작은 함수로 쪼개지 않으려는 게으름의 결과이다.

G16. 분명한 의도 사용

코드를 짤 때는 의도를 최대한으로 밝혀야 한다.

G17. 명백한 위치 선정

코드는 독자가 자연스럽게 기대할 위치에 배치해야 한다

G18. 부적절한 static 함수 피하기

일반적으로 static 함수보다는 인스턴스 함수가 더 좋고, 조금이라도 의심스럽다면 인스턴스 함수를 사용하는 방법이 더 좋다. 반드시 static 함수로 정의해야 한다면 재정의 가능성이 없는지 꼼꼼히 고려해야 한다.

G19. 서술적인 변수 사용하기

서술적인 변수 이름 등인 일반적으로 더 많을수록 좋다. 좋은 변수 이름만 붙여도 모듈이 읽기 쉬운 모듈로 탈바꿈한다.

G20. 이름과 기능이 일치하는 함수

이름만으로 분명하지 않아, 구현을 봐야 한다면 더 좋은 이름을 선정해야 한다.

G21. 알고리즘 이해하기

기능이 뻔히 보일 정도로 함수를 깔끔하게 구해야 하며, 이를 통해 알고리즘이 올바르다는 것을 보여주어야 한다.

G22. 논리적 의존성은 물리적으로도 나타내기

한 모듈이 다른 모듈에 의존한다면 물리적인 의존성도 주어야 한다.

G23. If/Else 혹은 Switch/Case 문보다 다형성을 사용하기

대부분의 개발자가 switch 문을 사용하는 이유는 그 상황에서 가장 올바른 선택보다는 손쉬운 선택이므로 선택한다.

유형보다 함수가 더 쉽게 변하는 경우는 드물기 때문에, 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성하고, 선택 유형 하나에는 switch 문을 한 번만 사용해야 한다.

G24. 표준 표기법을 사용하기

팀은 업계 표준에 기반한 구현 표준을 지켜야 한다.

G25. 매직 숫자는 명명된 상수로 교체하기

예를 들면 86400이라는 숫자는 SECONDS_PER_DAY와 같이 표현해야 한다.

G26. 정확하게 작성하기

코드에서 뭔가를 결정할 때는 정확히 결정해야 한다. 결정을 내리는 이유와 예외를 처리할 방법을 정확하게 알아야 한다.

G27. 관례보다는 구조를 사용하기

명명 관례도 좋지만 구조 자체로 강제로 하는 경우가 더 좋을 수 있다.

G28. 조건을 캡슐화하기

부울 논리를 넣는 것보다 의도를 분명히 밝히는 함수로 표현을 하는 것이 더 좋다.

즉, if(shouldBeDeleted(timer))if(timer.hasExpired() &&! timer.isRecurrent()) 보다 좋다.

G29. 부정 조건은 피하기

부정 조건은 긍정 조건보다 이해하기 어렵다. 가능하다면 긍정 조건을 사용하는 것이 좋다

G30. 함수는 한 가지만 하기

G31. 숨겨진 시간적인 결합 피하기

때로는 시간적인 결합을 필요하다. 단 이때는 시간적인 결합을 숨겨서는 안 된다. 실행되는 순서가 중요하기 때문에 일종의 연결 소자를 통해 시간적인 결합을 노출하는 것도 좋은 방법이 된다.

G32. 일관성 유지하기

코드 구조를 잡을 때는 이유를 고민하고, 그 이유를 코드 구조로 명백하게 표현해야 한다.

G33. 경계 조건을 캡슐화 하기

경계 조건은 빼먹기나 놓치기 십상이다.

G34. 함수는 추상화 수준을 한 단계만 내려가야 한다.

함수 내 모든 문장은 추상화 수준이 동일해야 한다. 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계 낮아야 한다.

G35. 설정 정보는 최상위 단계에 두기

추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저 차원 함수에 숨겨서는 안 된다. 설정 관련 상수는 최상위 단계에 두어야 한다. 그래야 변경하기 쉽고, 인수로 넘길 수 있다.

G36. 추이적 탐색 피하기

일반적으로 한 모듈은 주변 모듈을 모르면 모를수록 좋다. 이를 디미터의 법칙(Law of Demeter)라고 부른다.

자바

J1. 긴 import 목록을 피하기 와일드카드를 사용하는 것이 좋다.

패키지에서 클래스를 둘 이상 사용한다면 와일드카드를 사용해 패키지 전체를 가져오는 것이 좋다

Ex) import package.*;

J2. 상수는 상속하지 않는다.

J3. 상수 VS Enum

자바 5부터는 enum을 제공하기 때문에 enum을 사용하는 방법은 좋다. enum은 이름이 부여된 열거체에 속하며, 메서드와 필도도 사용할 수 있기 때문에 int보다 훨씬 더 유연하고 서술적인 강력한 도구이다.

이름

N1. 서술적인 이름 사용하기

서술적인 이름을 신중하게 고른다. 소프트웨어 가독성의 90%는 이름이 결정한다.

N2. 적절한 추상화 수준에서 이름 선택하기

구현을 드러내는 이름은 피하기. 적절한 추상화 수준에서 이름을 선택하는 다른 연결방식에도 사용 가능하다

N3. 가능하다면 표준 명명법 사용하기

N4. 명확한 이름

함수나 변수의 목적을 명확히 밝히는 이름을 선택하기

N5. 긴 범위는 긴 이름을 사용하기

이름 길이는 범위 길이에 비례해야 한다.

N6. 인코딩을 피하기

이름에 유형 정보나 범위 정보를 넣어서는 안 된다.

N7. 이름으로 부수 효과를 설명하기

함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용하기. 이름에 부수 효과를 숨기지 않기

테스트

T1. 불충분한 테스트

테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다. 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불안정한 테스트이다

T2. 커버리지 도구를 사용하기

커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다. 이를 사용하면 테스트가 불충분한 모듈, 클래스, 함수를 찾기가 쉬워진다.

T3. 사소한 테스트 건너뛰지 말기

사소한 테스트는 짜기 쉽다. 사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다

T4. 무시한 테스트는 모호함을 뜻한다

불분명한 요구사항은 테스트 케이스를 주석으로 처리하고나, 테스트 케이스에 @Ignore를 붙여서 표현해버린다.

T5. 경계 조건을 테스트하기

경계조건은 각별히 신경 써서 테스트하기

T6. 버그 주변은 철저히 테스트하기

버그는 서로 모이는 경향이 있다

T7. 실패 패턴을 살펴보기

테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다. 테스트 케이스를 꼼꼼하게 짜게 되면 실패와 성공 패턴만 봐도 답을 찾을 수도 있다.

T8. 테스트 커버리지 패턴을 살피기

통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.

T9 테스트는 빨라야 한다

느린 테스트 케이스는 실행하지 않게 된다.