이정우
1. 📌 핵심 개념 정리
✅ 요약하기
각자 해당 챕터에서 중요하다고 느낀 개념이나 아이디어를 간략하게 정리하고 개선 전, 후에 대한 예시 코드를 비교하며 개념을 설명합니다.
- 주석
- C1: 부적절한 정보
- 다른 시스템에 저장할 정보는 주석은 적절하지 못하다.
- 주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다.
- C2: 쓸모없는 주석
- 오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다.
- 쓸모 없어진 주석은 재빨리 삭제하는 편이 가장 좋다.
- C3: 중복된 주석
- 코드만으로 충분한데 구구절절 설명하는 주석이 중복된 주석이다.
/** * @param sellRequest * @return * @throws ManagedComponentException * **/ public SellResponse beginSellItem(SellRequest sellRequest) throws ManagedComponentException { }
- 주석은 코드만으로 다하지 못하는 설명을 부언한다.
- 코드만으로 충분한데 구구절절 설명하는 주석이 중복된 주석이다.
- C4: 성의 없는 주석
- 작성할 가치가 있는 주석은 잘 작성할 가치도 있다.
- 단어를 신중하게 선택한다.
- 문법과 구두점을 올바로 사용한다.
- 주절대지 않는다.
- 당연한 소리를 반복하지 않는다.
- 시간을 들여 최대한 간결하고 명료하게 작성하자.
- C5: 주석 처리된 코드
- 주석으로 처리된 코드를 발견하면 즉각 지워버려라. 오히려 주석 때문에 읽는 사람이 헷갈릴 수 있다.
- 지워버려도 상관없다. 소스 코드 관리 시스템이 기억한다. 누군가 정말로 필요하다면 이전 버전을 가져오면 된다.
- C1: 부적절한 정보
- 환경
- E1: 여러 단계로 빌드 해야 한다
- 빌드는 간단히 한 단계로 끝나야 한다.
- 소스 코드 관리 시스템에서 이것저것 따로따로 체크아웃할 필요가 없어야 한다.
- 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드 할 수 있어야 한다.
- E2: 여러 단계로 테스트해야 한다
- 모든 단위 테스트는 한 명령으로 돌려야 한다.
- IDE에서 버튼 하나로 모든 테스트를 돌린다면 가장 이상적이다.
- 모든 테스트를 한 번에 실행하는 능력은 아주 근본적이고 아주 중요하므로 빠르고, 쉽고 명백해야 한다.
- E1: 여러 단계로 빌드 해야 한다
- 함수
- F1: 너무 많은 인수
- 함수에서 인수 개수는 작을수록 좋다.
- 아예 없으면 가장 좋다.
- 넷 이상은 그 가치가 아주 의심스러우므로 최대한 피한다.
- F2: 출력인수
- 출력 인수는 직관을 정면으로 위배한다.
- 함수에서 뭔가의 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경한다.
- F3: 플래그 인수
- boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거다.
- 플래그 인수는 혼란을 초래하므로 피해야 마땅하다.
- F4: 죽은 함수
- 아무도 호출하지 않는 함수는 삭제한다.
- 죽은 코드는 낭비다. 과감히 삭제하라.
- 소스 코드 관리 시스템이 모두 기억하므로 걱정할 필요 없다.
- F1: 너무 많은 인수
- 일반
-
한 소스 파일에 여러 언어를 사용한다
- 이상적으로는 소스 파일 하나에 언어 하나만 사용한 방식이 가장 좋다.
- 불가피한 경우라면 소스 파일에서 언어의 수와 범위를 최대한 줄이도록 애써야 한다.
-
경계를 올바로 처리하지 않는다
- 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하고 테스트케이스를 작성 하라.
-
안전 절차 무시
- serialVersionUID를 직접 제어할 필요가 있을지도 모르지만 그래도 직접 제어는 언제나 위험하다.
- 실패하는 테스트 케이스를 일단 제겨두고 나중으로 미루는 태도는 위험하다.
-
중복
- 코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라.
- 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라.
- 가장 뻔한 유형
- 똑같은 코드가 여러 차례 나오는 중복
- 좀 더 미묘한 유형
- 여러 모듈에서 일련의 switch/case 나 if/else 문으로 똑같은 조건을 거듭 확인하는 중복
- 이런 중복은 다형성으로 대체해야 한다.
- 더더욱 미묘한 유형
- 알고리즘이 유사하나 코드가 서로 다른 중복
- 중복은 중복이므로 TEMPLATE METHOD 패턴이나 STRATEGY 패턴으로 중복을 제거한다.
- 어디서든 중복을 발견하면 없애라.
-
추상화 수준이 올바르지 못하다
- 추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다.
- 고차원 개념과 저차원 개념을 섞어서는 안 된다.
-
기초 클래스가 파생 클래스에 의존한다
- 개념을 기초 클래스와 파생 클래스로 나누는 가장 흔한 이유는 고차원 기초 클래스 개념을 저차원 파생 클래스 개념으로부터 분리해 독립성을 보장하기 위해서다.
- 그러므로 기초 클래스가 파생 클래스를 사용한다면 뭔가 문제가 있다는 말이다.
-
과도한 정보
- 잘 정의된 모듈은 인터페이스가 아주 작다. 하지만 작은 인터페이스로도 많은 동작이 가능하다.
- 잘 정의된 인터페이스는 많은 함수를 제공하지 않는다. 그래서 결합도(coupling)가 낮다.
- 우수한 소프트웨어 개발자는 클래스나 모듈 인터페이스에 노출할 함수를 제한할 줄 알아야 한다.
- 클래스가 제공하는 메서드 수, 변수 수, 인스턴스 변수 수는 작을수록 좋다.
- 자료, 유틸리티 함수, 상수와 임시 변수를 숨겨라.
- 인터페이스를 매우 작게, 매우 깐깐하게 만들어라. 정보를 제한해 결합도를 낮춰라.
-
죽은 코드
- 죽은 코드란 실행되지 않는 코드를 가리킨다.
- 불가능한 조건을 확인하는 if 문과 throw 문이 없는 try 문에서 catch 블록이 좋은 예다.
- 아무도 호출하지 않는 유틸리티 함수와 switch/case 문에서 불가능한 case 조건도 또 다른 좋은 예다.
- 발견 시 시스템에서 제거하라.
-
수직 분리
- 변수와 함수는 사용되는 위치에 가깝게 정의한다.
- 지역 변수는 처음으로 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야 한다.
- 비공개 함수는 처음으로 호출한 직후에 정의한다.
- 비공개 함수는 정의하는 위치와 호출하는 위치를 가깝게 유지한다.
- 비공개 함수는 처음으로 호출되는 위치를 찾은 후 조금만 아래로 내려가면 쉽게눈에 띄어야한다.
-
기능 욕심
- 마틴 파울러가 말하는 코드 냄새 중 하나다.
- 클래스 메서드는 다른 클래스의 변수와 함수에 관심을 가져서는 안 된다.
- 기능 욕심은 한 클래스의 속 사정을 다른 클래스에 노출하므로, 제거하는 편이 좋다.
-
잘못 지운 책임
- 소프트웨어 개발자가 내리는 가장 중요한 결정 중 하나가 코드를 배치하는 위치다.
- 독자에게 직관적인 위치가 아니라 개발자에게 편한 함수에 배치한다.
- 예를 들어 보고서 모듈에 getTotalHours라는 함수가 있고, 근무 시간을 입력받는 모듈에 saveTimeCard라는 함수가 있다 가정한다. 이름만 보았을 때 어느 쪽이 총계를 계산해야 옳을까? 답은 명백하다.
-
논리적 의존성은 물리적으로 드러내라
- 한 모듈이 다른 모률에 의존한다면 물리적인 의존성도 있어야 한다.
- 논리적인 의존성만으로는 부족하다.
- 의존하는 모든 정보를 명시적으로 요청하는 편이 좋다.
-
if/else 혹은 switch/case보다는 다형성을 사용하라
- 선택 유형 하나에는 switch 문을 한 번만 사용한다.
- 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다.
-
매직 숫자는 명명된 상수로 교체하라
- 매직 숫자는 상수로 변경하라.
- 코드 자체가 자명하다면, 상수 뒤로 숨길 필요가 없다.
- 하단 예제에 TWO (int 2)라는 상수가 반드시 필요할까? 어떤 공식은 그냥 숫자를 쓰는 편이 훨씬 좋다.
double circumference = radius * Math.PI * 2;
- 매직 숫자라는 용어는 단지 숫자만 의미하지 않는다. 의미가 분명하지 않은 토큰을 모두 가리킨다.
-
조건을 캡슐화하라
- 조건의 의도를 분명히 밝히는 함수로 표현하라.
// 나쁜 코드 if (timer.hasExpired() && !timer.isRecurrent()) // 좋은 코드 if (shouldBeDeleted(timer))
-
숨겨진 시간적인 결합
- 시간적인 결합이 필요하다, 하지만 시간적인 결합을 숨겨서는 안 된다.
- 함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러낸다.
// 나쁜 코드 public class MoogDriver { Gradient gradient; List<Spline> splines; public void dive(String reason) { staturateGradient(); reticulateSplines(); diveForMoog(reason); } }
// 좋은 코드 public class MoogDriver { Gradient gradient; List<Spline> splines; public void dive(String reason) { Gradient gradient = staturateGradient(); List<Spline> splines = reticulateSplines(gradient); diveForMoog(splines, reason); } }
- 위에서 인스턴스 변수를 그대로 두었다는 사실에 주목한다.
- 해당 클래스의 private 메서드에 필요한 변수일지도 모른다.
- 그렇더라도 제자리를 찾은 변수들이 시간적인 결합을 좀 더 명백히 드러낼 것이다.
-
일관성을 유지하라
- 코드 구조를 잡을 때는 이유를 고민해야 한다.
- 그리고 그 이유를 코드 구조로 명백히 표현해야 한다.
- 구조에 일관성이 없어 보인다면 남들이 맘대로 바꿔도 괜찮다고 생각한다.
- 시스템 전반에 걸쳐 구조가 일관성이 있다면 남들도 일관성을 따르고 보존한다.
-
경계 조건을 캡슐화하라
- 경계 조건은 빼먹거나 놓치기 십상이다.
- 경계 조건은 한 곳에서 별도로 처리한다.
// Bad if (level + 1 < tags.length) { parts = new Parse(body, tags, level + 1); body = null }
// Good, 경계 조건 변수로 캡슐화 int nextLevel = level + 1; if (nexLevel < tags.length) { parts = new Parse(body, tags, nextLevel); body = null }
-
함수는 추상화 수준을 한 단계만 내려가야 한다
- 함수 내 모든 문장은 추상화 수준이 동일해야 한다.
- 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.
-
설정 정보는 최상위 단계에 둬라
- 추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨기면 안 된다.
- 대신 고차원 함수에서 저차원 함수를 호출할 때 인수로 넘긴다.
-
추이적 탐색을 피하라
- 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다.
- 좀 더 구체적으로, A가 B를 사용하고 B가 C를 사용한다 하더라도 A가 C를 알아야 할 필요는 없다는 뜻이다. 이를 디미터의 법칙(Law of Demeter)이라 부른다.
- 실용주의 프로그래머들은 부끄럼 타는 코드 작성이라고도 한다.
-
- 자바
-
J1: 긴 import 목록을 피하고 와일드카드를 사용하라
- 패키지에서 클래스를 둘 이상 사용한다면 와일드카드를 사용해 패키지 전체를 가져오라.
- 와일드카드 import 문은 때로 이름 충돌이나 모호성을 초래한다.
- 다소 번거롭지만 여전히
와일드카드 import 문
이명시적인 import 문
보다 좋다.
-
J2: 상수는 상속하지 않는다.
- 어떤 프로그래머는 상수를 인터페이스에 넣은 다음 그 인터페이스를 상속해 해당 상수를 사용한다.
public class HourlyEmployee extends Employee { int str = Math.min(tents, TENTS_PER_WEEK); // 이 상수는 어디에서 왔을까? } public abstract class Employee implements Payroll { } public interface Payroll { public static final int TENTS_PER_WEEK = 400; // 우엑! 이렇게 사용하면 안된다. }
- 언어의 범위 규칙을 속이는 행위다.
- 대신 static import를 사용하라.
-
J3: 상수 대 Enum
- 자바 5는 enum을 제공한다.
- 마음껏 활용하라! public static final int라는 옛날 기교를 더 이상 사용할 필요가 없다.
- int 보다 훨씬 더 유연하고 서술적인 강력한 도구다.
-
- 이름
-
N1: 서술적인 이름을 사용하라
- 이름은 서술적인 이름을 신중하게 고른다.
- 단순히 ‘듣기 좋은’ 충고가 아니다. 소프트웨어 가독성의 90%는 이름이 결정한다.
-
N2: 적절한 추상화 수준에서 이름을 선택하라
- 구현을 드러내는 이름은 피하라.
- 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라.
- 코드를 살펴볼 때마다 추상화 수준이 너무 낮은 변수 이름을 발견하리라 발견할 때마다 기회를 잡아 바꿔놓아야 한다.
-
N3: 가능하다면 표준 명명법을 사용하라
- 기존 명명법을 사용하는 이름은 이해하기 더 쉽다.
DECORATOR 패턴
을 활용한다면 장식하는 클래스 이름에Decorator
라는 단어를 사용해야 한다.ToString
과 같이 관례가 존재하는 이름은 관례를 따른다.
-
N4: 명확한 이름
- 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다.
- 길더라도 명확하고 서술적으로 지어야 한다.
-
N5: 긴 범위는 긴 이름을 사용하라
- 이름 길이는 범위 길이에 비례해야 한다.
- 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다.
- 하지만 범위가 길어지면 긴 이름을 사용한다.
- 이름 범위가 길수록 이름을 정확하고 길게 짓는다.
-
N6: 인코딩을 피하라
- 이름에 유형 정보나 범위 정보를 넣어서는 안 된다.
-
N7: 이름으로 부수 효과를 설명하라
- 함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다.
- 이름에 부수 효과를 숨기지 않는다.
- 실제로 여러 작업을 수행하는 함수에다 동사 하나만 달랑 사용하면 곤란하다.
// 나쁜 코드 getOos(); // 좋은 코드 // 없으면 새로운 객체를 만드는 메서드 createOrReturnOos();
-
- 테스트
-
T1: 불충분한 테스트
- 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다.
- 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다.
-
T2: 커버리지 도구를 사용하라
- 커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다.
- 대다수 IDE는 테스트 커버리지를 시각적으로 표현한다.
-
T3: 사소한 테스트를 건너뛰지 마라
- 사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다.
-
T4: 무시한 테스트는 모호함을 뜻한다.
- 선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.
-
T5: 경계 조건을 테스트하라
- 경계 조건은 각별히 신경 써서 테스트한다.
- 알고리즘의 중앙 조건은 올바로 놓고 경계 조건에서 실수하는 경우가 흔하다.
-
T6: 버그 주변은 철저히 테스트하라
- 버그는 서로 모이는 경향이 있다.
- 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다.
- 십중팔구 다른 버그도 발견하리라.
-
T7: 실패 패턴을 살펴라
- 때로는 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다.
- 테스트 케이스를 최대한 꼼꼼히 짜라는 이유도 여기에 있다.
- 합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다.
-
T8: 테스트 커버리지 패턴을 살펴라
- 통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.
-
T9: 테스트는 빨라야 한다
- 느린 테스트 케이스는 실행하지 않게 된다.
- 일정이 촉박하면 느린 테스트 케이스를 제일 먼저 건너뛴다.
- 그러므로 테스트 케이스가 빨리 돌아가게 최대한 노력한다.
-
- 결론
- 이장에서 소개한 휴리스틱과 냄새 목록이 완전하다 말하기는 어렵다.
- 여기서 소개한 목록은 가치 체계를 피력할 뿐이다.
- 사실상 가치 체계는 이 책의 주제이자 목표다.
- 일군의 규칙만 따른다고 깨끗한 코드가 얻어지지 않는다.
- 휴리스틱 목록을 익힌다고 소프트웨어 장인이 되지는 못한다.
- 전문가 정신과 장인 정신은 가치에서 나온다.
- 그 가치에 기반한 규율과 절제가 필요하다.
2. 🤔 이해가 어려운 부분
🔍 질문하기
책을 읽으며 이해하기 어려웠던 개념이나 명확하지 않았던 내용을 정리합니다.
- 개념 또는 원칙의 이름
- 어려웠던 부분
해당 개념이 헷갈리거나 명확하지 않았던 점을 구체적으로 설명합니다. - 궁금한 점
해당 개념이 어떤 원리로 동작하는지, 실무에서 어떻게 활용되는지 등을 질문 형태로 정리합니다.
- 어려웠던 부분
- 개념 또는 원칙의 이름
- 어려웠던 부분
. - 궁금한 점
.
- 어려웠던 부분
- 개념 또는 원칙의 이름
- 어려웠던 부분
. - 궁금한 점
.
- 어려웠던 부분
3. 📚 참고 사항
📢 논의하기
관련된 자료가 있다면 공유하고, 더 깊이 논의하고 싶은 아이디어나 의견을 정리합니다.
- 관련 자료 공유
- 추가 자료
관련 블로그 글이나 공식 문서 링크를 제공합니다.
- 추가 자료
- 논의하고 싶은 주제
- 주제
논의하고 싶은 내용을 간략히 정리합니다. - 설명
논의하고 싶은 이유를 작성합니다.
- 주제