명명과 주석
전역 변수와 같이 범위가 큰 변수의 경우 긴 이름을 사용하는 것이 좋다. - p180
전역 변수일수록 오히려 더 의미 있는 내용을 사용해야 하기 때문에, 길어지더라도 꼭 의미를 다 담는 것이 좋다고 생각한다.
어떤 사람들은 주석이 보충 정보만 제공하면 된다고 생각하기도 한다. 하지만 다음 세 가지 이유를 들어 이러한 견해에 동의하지 않는다. - p182
- 주석에는 이름보다 훨씬 더 많은 정보를 담을 수 있다.
- 주석은 설명과 시연의 역할을 한다.
- 요약 주석은 코드의 논리를 더 명확하게 할 수 있다.
가끔 가다 보면 주석이 없으면, 다음날에도 나의 코드를 까먹는 나 자신을 발견할 수도 있을 것이다. 논리가 간단한 코드라면 너무나 간단하게도 파악을 하겠지만, 논리가 간단하지 않다면 나조차도 까먹을 수 있으니 주석에 대한 것은 나에 대한 예의라고 생각하는 것이 좋을 것 같다.
코드 스타일
들여쓰기에 Tab 키를 사용하면 안 된다는 점은 백 번 강조해도 지나치지 않는다. IDE 마다 Tab 키를 사용한 후 표시되는 너비가 달라, 어떤 경우에는 4칸 들여쓰기가 이루어지고, 어떤 경우에는 2칸 들여쓰기가 이루어지기 때문이다. -p.185
Tab키에 대한 생각은 거의 해본 적이 없는데.. 실제로 몇 칸 들여 쓰는지 알아내면 굳이 Tab을 제한할 필요는 없지 않을까?
코딩 팁
- 복잡한 코드의 모듈화
- 함수의 매개변수 관리
- 함수의 플래그 매개변수 제거
- 깊은 중첩 코드 제거 → if-else, switch-case, for 반복문의 과도한 중첩은 좋지 않으며, 일반적으로 2단계를 넘지 않는 것이 좋다.
- 중첩되는 if, else문 제거한다.
- continue, break, return 키워드를 사용하여 중첩을 바로 종료한다.
- 실행 순서를 조정하여 중첩 단계를 줄인다.
- 중첩 단계를 줄이기 위해 코드의 일부를 함수로 캡슐화한다.
- 설명 변수
- 매직 넘버 대신 상수를 사용한다.
- 설명 변수를 사용하여 복잡한 표현을 설명한다.
if (date.after(SUMMER_START) && date.before(SUMMER_END)) { ... }
else { ... }
// 설명변수 도입
boolean isSummer = date.after(SUMMER_START) && date.before(SUMMMER_END);
if (isSummer) { ... }
else { ... }
리팩터링의 네 가지 요소: 목적, 대상, 시기, 방법
리팩터링은 코드에 대한 이해를 쉽게 하기 위해 소프트웨어의 내부 구조를 개선하는 것으로, 소프트웨어의 외부 동작을 변경하지 않고 수정 비용을 줄이는 것을 목적으로 한다. -p.198
코드가 이미 손대기 어려워지기 시작할 때 쯤, 프로젝트의 유지 보수 비용이 새로운 코드를 작성하는 것보다 많이 든다면, 이 코드는 리팩터링하기에는 이미 늦었다고 할 수 있다. -p.198
단위 테스트 코드를 작성하는 이유
- 단위 테스트는 프로그래머가 코드에서 버그를 찾는 데 도움이 될 수 있다.
- 단위 테스트는 프로그래머가 코드 설계에서 문제를 찾는 데 도움이 될 수 있다.
- 단위 테스트는 통합 테스트를 보완하는 강력한 도구다.
- 단위 테스트 코드를 작성하는 과정은 코드 리팩터링 과정에 해당한다.
- 단위 테스트는 프로그래머가 코드에 빠르게 익숙해지도록 도와준다.
- 단위 테스트는 테스트 주도 개발을 개선하고 대체할 수 있다.
단위 테스트 설계
- 단위 테스트를 설계하는 것은 시간이 많이 걸리는 일인가?
- 단위 테스트 코드의 품질에 대한 요구 사항이 있는가?
- 단위 테스트의 커버리지가 높으면 그것만으로 충분한가?
- 단위 테스트 코드를 작성할 때 코드의 구현 논리를 이해하는 것이 필요한가?
- 단위 테스트 프레임워크를 선택하는 방법은 무엇인가?
단위 테스트를 작성하기 어려운 이유
원래 IT 산업은 지능 집약적 산업이었지만, 지금은 SI 기반의 작업이 늘면서 많은 기업들이 노동집약적인 산업으로 만들어 나가고 있으며, 개발 과정에서 단위 테스트 코드 작성이나 코드 리뷰가 전혀 이루어지고 있지 않고, 설령 진행하고 있더라도 그 수준이 처참한 수준인 경우가 많다. -p.209
현재 회사에서 가장 문제점이라 생각하고, 내가 바꿀 수 없는 문화라고 생각한다. 노동 집약적이라는 말이 정말 딱 맞다고 생각이 되는 업무 환경이라서 이것에 대해 바꾸자고 할 수도, 그렇다고 왜 바꾸지 않느냐고 할 수 없는 환경이기 때문이다. 각자의 사정이 있고 한 명당 맡고 있는 프로젝트가 적게는 두 개 많게는 다섯 개의 프로젝트를 맡고 있는 경우도 있기 때문에 단위 테스트 작성에 많은 한계를 느끼고 있다.
이것은 필시 단위 테스트 하나의 문제가 아니라 코드 품질에 까지 영향을 끼치는 듯 하다.
코드 테스트 용이성
테스트 가능한 코드를 작성하는 방법
- 정상 상황 테스트
- 비정상 상황인 경우도 테스트
- 내가 의도한 문제가 발생하는 상황이 의도대로 되는지도 테스트
- 내가 의도한 실패 상황이 의도대로 되는지도 테스트
테스트가 불가능한 코드
- 보류 중인 동작 → 코드의 출력이 무작위이거나 불확실하며 대부분 시간 및 난수와 관련이 있음을 의미
- 전역 변수
- 정적 메서드 → 정적 메서드를 실행하는 데 너무 오래 걸리고, 외부 리소스에 의존하고, 코드가 복잡하며, 보류 중인 동작이 있는 경우에만 모의 구현 정적 메서드를 통해 단위 테스트를 진행해야 함
- 복잡한 상속 관계 → 합성 관계에 비해 상속 관계가 결합성이 훨씬 높고, 상속 관계로 구현된 코드의 테스트는 훨씬 더 어려움
코드 디커플링 방법
- 캡슐화와 추상화로 디커플링하기
- 중간 계층으로 디커플링하기 → 중간 계층이 과도기적 역할을 할 수 있기 때문에 개발 프로세스와 리팩터링 프로세스가 서로 간섭하지 않고 동시에 진행될 수 있다. 인터페이스에 문제가 생겨 정의를 수정해야 하는 일이 생겼을 때, 새로 개발된 코드에서도 이 인터페이스를 사용하는 경우 충돌이 일어나지 않게 하려면 4단계의 과정을 거쳐 수정하면 리팩터링을 작고 빠르게 수행할 수 있다.
- 중간 계층을 도입하고 이를 사용하여 이전 인터페이스를 감싸는 새 인터페이스를 제공
- 새로 개발된 코드는 중간 계층에서 제공하는 새로운 인터페이스에만 의존
- 이전 인터페이스에 종속된 코드를 전부 변경하여 새 인터페이스를 호출
- 모든 코드가 새 인터페이스를 호출하는지 확인한 후 이전 인터페이스를 제거
- 모듈화와 계층화로 디커플링하기 → 모듈화의 본질은 분할을 통한 제어다. 코드를 개발할 때 우리는 모듈화에 대해 의식해야 하고, 각 모듈을 독립적으로 클래스 라이브러리로 개발해야 하며, 각각의 모듈은 다른 모듈이 클래스의 내부에 접근할 수 있도록 구현 세부 사항을 캡슐화해서 인터페이스 형태로 제공해야 한다.
- 고전적인 코드 설계 원칙과 사상을 사용한 디커플링
- 단일 책임 원칙
- 구현이 아닌 인터페이스 기반의 프로그래밍
- 의존성 주입
- 상속보다는 합성을 더 많이 사용
- LoD를 따르는 것
코드 품질 문제를 찾는 방법
코드 품질 평가 기준을 참조하자.
- 모듈의 구분이 명확하고 코드 구조가 높은 응집도, 낮은 결합도를 충족하고 있는가?
- 코드가 고전적인 설계 원칙(SOLID, DRY, KISS, YAGNI, LoD 등)을 따르고 있는가?
- 디자인 패턴이 제대로 적용되었고, 과도하게 설계되지는 않았는가?
- 코드를 확장하기 쉬운가?
- 코드를 재사용할 수 있는가? 혹시 ‘바퀴를 재발명’하고 있지는 않은가?
- 코드는 테스트하기 쉬우며, 단위 테스트가 정상 상황과 비정상 상황을 포괄적으로 다루고 있는가?
- 코드가 적절한 명명, 적절한 주석, 균일한 코드 스타일과 같은 코딩 규칙을 준수하고 있는가?
BO, VO, Entity와 같이 데이터의 저장에만 관여하는 일부 객체는 의존성 주입을 사용하여 생성하는 대신 new 예약어를 통해 클래스에서 직접 생성하면 된다. -p.236
논의
- 통일된 비즈니스 용어집을 진짜 사용하는 팀이 있는가?