김주엽
1. 📌 핵심 개념 정리
✅ 요약하기
- 주석
- 부적절한 정보
- 변경 이력과 같은 주석은 적절하지 못하다.
- 일반적으로 작성자, 최종 수정일, SPR(Software Problem Report) 번호 등과 같은 메타 정보를 주석으로 남긴다.
- 주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다.
- 쓸모 없는 주석
- 오래된 주석, 엉뚱한 주석, 잘못된 주석은 쓸모가 없다.
- 쓸모 없어질 주석을 작성하지 않는 것이 가장 좋지만 작성했다면 재빨리 삭제하는 것이 좋다.
- 쓸모 없는 주석은 코드를 그릇된 방향으로 이끈다.
- 중복된 주석
- 코드만으로 충분한데 구구절절 설명하는 주석을 중복된 주석이라고 한다.
- 예시
i++; // i 증가
- 예시
- 함수 서명만 달랑 기술하는
Javadoc
주석- 예시
/** * @param sellRequest * @return * @throws ManagedComponentException * **/ public SellResponse beginSellItem(SellRequest sellRequest) throws ManagedComponentException { }
- 예시
- 코드만으로 충분한데 구구절절 설명하는 주석을 중복된 주석이라고 한다.
- 주석 처리된 코드
- 코드를 읽다가 주석으로 처리된 코드가 나오면 아주 거슬린다.
- 오래된 코드인지 중요한 코드인지 구분할 수 없기에 아무도 삭제하지 않는다.
- 누군가에게 필요하거나 다른 사람이 사용할 코드라 판단하기 때문이다.
- 주석으로 처리된 코드는 읽는 사람을 헷갈리게 만든다.
- 이전 코드는 코드 관리 시스템이 기억하기에 걱정말고 바로 지우도록 하자.
- 부적절한 정보
- 환경
- 여러 단계로 빌드해야 한다.
- 빌드는 간단히 한 단계로 끝나야 한다.
- 불가해한 명령이나 스크립트를 실행해 각 요소를 따로 빌드할 필요가 없어야 한다
- 시스템에 필요한 파일을 찾느라 여기저기 찾으러 다닐 필요가 없어야 한다.
- 한 명령으로 전체를 체크아웃해서 빌드할 수 있어야 한다.
- 여러 단계로 테스트해야 한다.
- 모든 단위 테스트는 한 명령으로 돌려야 한다.
- IDE에서 버튼 하나로 테스트가 가능한 것이 가장 이상적이다.
- 아무리 열악한 환경이라도 셸에서 명령 하나로 가능해야 한다.
- 모든 단위 테스트는 한 명령으로 돌려야 한다.
- 여러 단계로 빌드해야 한다.
- 함수
- 너무 많은 인수
- 함수에서 인수는 작을수록 좋고 아예 없으면 가장 좋다.
- 넷 이상은 그 가치가 아주 의심스럽기에 최대한 피한다.
- 출력 인수
- 일반적으로 독자는 인수를 입력으로 간주한다.
- 함수에서 뭔가의 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경한다.
- 플래그 인수
boolean
인수는 함수가 여러 기능을 수행한다는 명백한 증거다.- 플래그 인수는 혼란을 초래하므로 피하라.
- 죽은 함수
- 아무도 호출하지 않는 함수는 과감하게 삭제한다.
- 소스 코드 관리 시스템이 모두 기억하기에 걱정할 필요 없다.
- 너무 많은 인수
- 일반
- 한 소스 파일에 여러 언어를 사용한다.
- 오늘날 프로그래밍 환경은 한 소스 파일 내에서 다양한 언어를 지원한다.
- 자바 소스 파일에서 XML, HTML, YAML, Javadoc 등을 포함한다.
- JSP 파일에서 HTML, 자바, 태그 라이브러리 구문, 영어 주석, JavaScript 등을 포함한다.
- 이는 좋게 말하면 혼란스럽고 나쁘게 말하면 조잡하다.
- 한 소스 파일에 언어 하나만 사용하는 방식이 가장 좋다.
- 현실적으로 어렵다면 소스 파일에서 언어 수와 범위를 최대한 줄이도록 한다.
- 오늘날 프로그래밍 환경은 한 소스 파일 내에서 다양한 언어를 지원한다.
- 당연한 동작을 구현하지 않는다.
- 최소 놀람의 원칙(The Principle of Least Surprise)에 의거해 함수나 클래스는 당연한 동작과 기능을 제공한다.
- 당연한 동작을 구현하지 않으면 코드를 읽거나 사용하는 사람이 함수 기능을 예상하기 어렵다.
- 저자를 신뢰하기 어려워 코드를 모두 살펴보게 된다.
- 경계를 올바로 처리하지 않는다.
- 대부분의 개발자가 머릿속에서 코드를 돌려보고 끝낸다.
- 자신의 직관에 의존할 뿐 모든 경계와 구석진 곳에서 코드를 증명하려 애쓰지 않는다.
- 모든 경계 조건을 찾아내고 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라.
- 중복
- 이 책에서 나오는 가장 중요한 규칙 중 하나로 DRY(Don't Repeat Yourself) 원칙이라 부른다.
- 익스트림 프로그래밍의 핵심 규칙 중 하나로 론 제프리스는 모든 테스트를 통과한다는 규칙 다음으로 중요하게 꼽았다.
- 코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라.
- 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라.
- 가장 흔한 유형은 똑같은 코드가 여러 차례 나오는 중복이다.
- 개발자가 마우스로 긁어다 여기저기로 복사한 듯 보이는 코드를 말한다.
- 이런 중복은 간단한 함수로 교체한다.
- 좀 더 미묘한 유형은 여러 모듈에서 일련의
switch
나if
문으로 같은 조건을 확인하는 중복이다.- 다형성을 이용해서 대체하라.
- 마지막 유형으로는 알고리즘은 유사하나 코드가 서로 다른 중복이다.
Template Method
혹은Strategy
패턴으로 중복을 제거한다.
- 최근 15년 동안 나온 디자인 패턴은 대다수가 중복을 제거하는 패턴이다.
- 추상화 수준이 올바르지 못한다.
- 추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다.
- 모든 저차원 개념은 파생 클래스에 넣고 모든 고차원 개념은 기초 클래스에 넣는다.
- 세부 구현과 관련된 상수, 변수, 유틸리티 함수는 기초 클래스에 넣으면 안 된다.
- 기초 클래스는 구현 정보에 대해 알 수 없어야 한다.
- 고차원 개념과 저차원 개념을 섞는 경우를 피하라.
- 예시
public interface Stack { Object pop() throws EmptyException; void push(Object o) throws FullException; double percentFull(); class EmptyException extends Exception {} class FullException extends Exception {} }
percentFull
함수는 추상화 수준이 올바르지 못하다. Stack을 구현하는 방법은 다양하기에BoundedStack
과 같은 파생 인터페이스에 넣어야 마땅하다.
- 예시
- 잘못된 추상화는 임시변통으로 고치기는 불가능하다.
- 기초 클래스가 파생 클래스에 의존한다.
- 기초 클래스가 파생 클래스를 사용한다면 문제가 있다는 말이다.
- 일반적으로 기초 클래스는 파생 클래스를 아예 몰라야 마땅하다.
- 간혹 파생 클래스 개수가 확실히 고정되었다면 기초 클래스에 파생 클래스를 선택하는 코드가 들어간다.
- FSM(Finite State Machine) 구현에서 많이 볼 수 있는 사례다.
- 그러나 FSM은 기초 클래스와 파생 클래스가 굉장히 밀접하고 언제나 같은 JAR 파일로 배포한다.
- 과도한 정보
- 잘 정의된 모듈은 인터페이스가 아주 작다.
- 잘 정의된 인터페이스는 많은 함수를 제공하지 않아 결합도가 낮다.
- 우수한 개발자라면 클래스나 모듈 인터페이스에 노출할 함수를 제한할 줄 알아야 한다.
- 클래스가 제공하는 메서드 수는 작을수록 좋다.
- 함수가 아는 변수, 클래스의 인스턴스 변수 수도 작을수록 좋다.
- 자료, 유틸리티 함수, 상수와 임시 변수를 숨겨라.
- 하위 클래스에서 필요하다는 이유로
protected
변수, 함수를 마구 생성하지 마라. - 인터페이스를 매우 작고 깐깐하게 만들어서 결합도를 낮춰라.
- 일관성 부족
- 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다.
- 예를 들어 한 함수에서
response
라는 변수에HttpServletResponse
를 저장했다면 다른 함수에서도 일관성 있게 동일한 변수 이름 혹은 유사한 이름을 사용하라.
- 기능 욕심
- 클래스 메서드는 자기 클래스의 변수와 메서드에 관심을 가져야지 다른 클래스의 변수, 함수를 참조해서는 안 된다.
- 메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체를 조작하는 것을 말한다.
- 서술적 변수
- 프로그램 가독성을 높이는 가장 효과적인 방법 중 하나가 서술적인 변수 이름을 사용하는 방법이다.
- 서술적인 변수 이름은 많을수록 더 좋다.
- If/Else 혹은 Switch/Case 문보다 다형성을 사용하라
- 대다수 개발자가
switch
문을 사용하는 이유는 손쉬운 선택이기 때문이다.switch
문을 사용하기 전에 다형성을 먼저 고려하라.
- 유형보다 함수가 더 쉽게 변하는 경우는 극히 드물다.
- 선택 유형 하나에는
switch
문을 한 번만 사용하고 같은 선택을 수행하는 다른 코드에는 다형성 객체를 사용하라.
- 선택 유형 하나에는
- 대다수 개발자가
- 조건을 캡슐화하라
- bool 논리는 이해하기 어렵다.
- 조건의 의도를 분명히 밝히는 함수로 표현하라
- 예시
라는 코드는 다음 코드보다 좋다.if (shouldBeDeleted(timer))
if (timer.hasExpired() && !timer.isRecurrent())
- 부정 조건은 피하라
- 부정 조건은 긍정 조건보다 이해하기 어렵다.
- 가능하면 긍정 조건으로 표현한다.
- 함수는 한 가지만 해야 한다.
- 한 가지만 수행하는 좀 더 작은 함수 여럿으로 나누는 것이 마땅하다.
- 예시
위의 함수는 다음과 같이 함수 셋으로 나누는 편이 좋다.public void pay() { for (Employee e : employees) { if (e.isPayday()) { Money pay = e.calculatePay(); e.deliveryPay(pay); } } }
위에서 각 함수는 한 가지 일만 수행한다.public void pay() { for (Employee e : employees) { payIfNeessary(e); } } public void payIfNecessary(Employee e) { if (e.isPayday()) { cacluateAndDeliverPay(e); } } private void calculateAndDeliveryPay(Employee e) { Money pay = e.calculatePay(); e.deliverPay(pay); }
- 숨겨진 시간적인 결합
- 때로는 시간적인 결합이 필요하지만 이를 숨겨서는 안 된다.
- 함수를 작성할 때 함수 인수를 적절히 배치해 함수가 호출되는 순서를 노출시켜라.
- 예시
public class MoogDriver { public void dive(String reason) { Gradient gradient = staturateGradient(); List<Spline> splines = reticulateSplines(gradient); diveForMoog(splines, reason); } }
- 추이적 탐색을 피하라
- 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다.
- A가 B를 사용하고 B가 C를 사용하더라도 A가 C를 알 필요는 없다는 뜻이다.
- 이를 디미터의 법칙이라 부르는데 가장 중요한 것은 자신이 직접 사용하는 모듈만 알아야 한다는 뜻이다.
- 한 소스 파일에 여러 언어를 사용한다.
- 자바
- 긴 import 목록을 피하고 와일드카드를 사용하라
- 패키지에서 클래스를 둘 이상 사용한다면 와일드카드(*) 를 사용해 패키지 전체를 가져온다.
- 예시
import package.*;
- 예시
- 긴 import 목록은 읽기에 부담스럽다.
- 사용하는 패키지를 간단히 명시하면 충분하다.
- 패키지에서 클래스를 둘 이상 사용한다면 와일드카드(*) 를 사용해 패키지 전체를 가져온다.
- 상수 대 Enum
- 자바 5부터
enum
을 지원하기에public static final int
라는 옛날 관습을 더 이상 사용할 필요가 없다. - 메서드와 필드도 사용할 수 있기에 유연하고 서술적인 강력한 도구다.
- 좋은 예시
public enum HourlyPayGrade { APPRENTICE { public double rate() { return 1.0; } }, LIEUTENANT_JOURNEYMAN { public double rate() { return 1.2; } }, JOURNEYMAN { public double rate() { return 1.5; } }, MASTER { public double rate() { return 2.0; } }; public abstract double rate(); }
- 자바 5부터
- 긴 import 목록을 피하고 와일드카드를 사용하라
- 테스트
- 불충분한 테스트
- 테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다.
- 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다.
- 커버리지 도구를 사용하라!
- 커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다.
- 테스트가 불충분한 모듈, 클래스, 메서드를 찾기가 쉬워진다.
- 대다수의 IDE는 테스트 커버리지를 시각적으로 표현한다.
- 버그 주변은 철저히 테스트하라
- 버그는 서로 모이는 경향이 있다.
- 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하도록 한다.
- 실패 패턴을 살펴라
- 합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다.
- 테스트는 빨라야 한다
- 느린 테스트 케이스는 실행하지 않게 된다.
- 최대한 빠르게 돌아가도록 노력해서 테스트 코드를 작성하라.
- 불충분한 테스트
2. 🤔 이해가 어려운 부분
🔍 질문하기
- 기초 클래스가 파생 클래스에 의존한다.
- 어려웠던 부분
FSM이 왜 기초 클래스에서 파생 클래스를 선택하는 예외적 사례인지를 이해하기 어려웠다. - 이해한 점
- FSM이란 유한 상태 기계(Finite State Machine) 라는 뜻으로 상태(State)와 입력(Input)에 따라 다음 상태로 전이된다.
- 게임에서 몬스터 AI, 캐릭터 상태 관리(Idle, Walk, Jump, Attack 등) 등에 자주 사용된다.
- FSM은 각 상태(파생 클래스) 끼리 다음 상태를 직접 선택해야 하기 때문에 이런 의존이 허용된다.
- 예시 코드
// 상태 기초 클래스 public abstract class State { public abstract void Enter(); public abstract void Update(); public abstract void Exit(); } // Idle 상태 public class IdleState : State { public override void Enter() { } public override void Update() { } public override void Exit() { } } // Run 상태 public class RunState : State { public override void Enter() { } public override void Update() { } public override void Exit() { } } // Player 클래스에서 상태 전이 public class Player : MonoBehaviour { private State currentState; void Start() { currentState = new IdleState(); // 기초 클래스가 파생 클래스에 의존하게 됨. currentState.Enter(); } void Update() { currentState.Update(); // 예시: 오른쪽 키 누르면 Run 상태로 전환 if (Input.GetKeyDown(KeyCode.RightArrow)) { currentState.Exit(); currentState = new RunState(); // 기초 클래스가 파생 클래스에 의존하게 됨. currentState.Enter(); } } }
- 어려웠던 부분
- 자바
- 어려웠던 부분
저자가 얘기한 긴 import 목록을 피하고 와일드카드를 사용하라는 내용을 동의하기 어려웠다. - 궁금한 점
요즘 IDE는 import문 최적화같은 기능을 제공하는데 꼭 와일드카드로 작성해야 할까?
- 어려웠던 부분
3. 📚 참고 사항
📢 논의하기
- 관련 자료 공유
- 추가 자료
FSM
- 추가 자료
- 논의하고 싶은 주제
- 주제
FSM에서 상태가 고정된 전제를 바탕으로 기초 클래스가 파생 클래스를 참조해도 되는가? - 설명
상태 종류가 고정된다는 가정하에 기초 클래스가 파생 클래스에 의존하는 구조가 허용되지만 만약 추후 상태가 늘어난다면 OCP를 위반하게 되는 것이 아닌지 논의하고 싶다.
- 주제
No Comments