generated from Learning-Is-Vital-In-Development/study-template
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3d339b1
commit 34382fd
Showing
1 changed file
with
133 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
## 17장 냄새와 휴리스틱 | ||
|
||
### 주석 | ||
|
||
- 부적절한 정보 | ||
- 다른 시스템에 저장할 정보는 주석으로 적절하지 못하다. 예를 들어 변경 이력은 장황한 날짜와 따분한 내용으로 소스 코드만 번잡하게 만든다. 일반적으로 작성자, 최종 수정일 등의 메타 정보만 주석으로 넣는다. 주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다. | ||
- 쓸모 없는 주석 | ||
- 오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다. | ||
- 중복된 주석 | ||
- 코드만으로 충분한데 구구절절 설명하는 주석이 중복된 주석이다. | ||
- 성의 없는 주석 | ||
- 주석을 사용 시 단어를 신중하게 선택한다. 문법과 구두점을 올바로 사용한다. 주절대지 않는다. 당연한 소리를 반복하지 않는다. 간결하고 명료하게 작성한다. | ||
- 주석 처리된 코드 | ||
- 주석으로 처리된 코드는 흉물 그 자체이다. 주석으로 처리된 코드는 발견하면 즉각 지워버려라. | ||
|
||
### 환경 | ||
|
||
- 여러 단계로 빌드해야 한다. | ||
- 빌드는 간단히 한 단계로 끝나야 한ㄴ다. 소스 코드 관리 시스템에서 이것저것 따로 체크하웃할 필요가 없어야 한다 불가해한 명령이나 스크립트를 잇달아 실행해 각 요소를 따로 빌드할 필요가 없어야 한다. | ||
- 여러 단계로 테스트해야 한다. | ||
- 모든 단위 테스트는 한 명령으로 돌려야 한다. 아무리 열악한 환경이라도 셀에서 명령 하나로 가능해야 한다. 모든 테스트를 한 번에 실행하는 능력은 아주 근본적이고 아주 중요하다. | ||
|
||
### 함수 | ||
|
||
- 너무 많은 인수 | ||
- 함수에서 인수 개수는 작을수록 좋다. 넷 이상은 그 가치가 아주 의심스러우므로 최대한 피한다. | ||
- 출력 인수 | ||
- 출력 인수는 직관을 정면으로 위배한다. 일반적으로 독자는 인수를 입력으로 간주한다. 함수에서 뭔가의 상태를 변경해야 한다면 함수에 속한 객체의 상태를 변경한다. | ||
- 플래그 인수 | ||
- boolean 인수는 함수가 여러 기능을 수행해야 한다는 명백한 증거이다. 플래그 인수는 혼란을 초래하므로 피해야 마땅하다. | ||
- 죽은 함수 | ||
- 아무도 호출하지 않는 함수는 삭제한다. | ||
|
||
### 일반 | ||
|
||
- 한 소스 파일에 여러 언어를 사용한다. | ||
- 이상적으로 소스 파일 하나에 언어 하나만 사용하는 방식이 가장 좋다. 현실적으로는 여러 언어가 불가피하다. 하지만 각별한 노력을 기울여 소스 파일에서 언어 수와 범위를 최대한 줄이도록 애써야 한다. | ||
- 당연한 동작을 구현하지 않는다. | ||
- 최소 놀람의 원칙에 의거해 함수나 클래스는 다른 프로그래머가 당연하게 여길 만한 동작과 기능을 제공해야 한다. | ||
- 경계를 올바로 처리하지 않는다. | ||
- 모든 경계 조건, 구석진 곳, 기벽, 예외는 우아하고 직관적인 알고리즘을 좌초시킬 암초다. 스스로의 직관에 의지하지 말고 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라 | ||
- 안전 절차 무시 | ||
- 실패하는 테스트 케이스를 일단 제껴두고 나중으로 미루는 태도는 신용카드가 공짜 돈이라는 생각만큼 위험하다. 안전을 최우선으로 하자 | ||
- 중복 | ||
- 코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라. | ||
- 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라. 이렇듯 추상화로 중복을 정리하면 설계 언어의 어휘가 늘어난다. 이로 인해 추상화 수준을 높였으므로 구현이 빨라지고 오류가 적어진다. | ||
- 똑같은 코드가 여러 번 중복되어 나타날 경우 간단한 함수로 교체한다. | ||
- 알고리즘이 유사하나 코드가 서로 다른 중복의 경우 템플릿 메서드 패턴이나 전략 패턴으로 중복을 제거한다. | ||
- 스위치문이나 if문으로 똑같은 조건을 거듭 확인하는 중복은 다형성으로 대체한다. | ||
- 추상화 수준이 올바르지 못하다. | ||
- 추상화로 개념을 분리할 때는 철저해야 한다. 모든 저차원 개념은 파생 클래스에 넣고 모든 고차원 개념은 기초 클래스에 넣는다. 예를 들어 세부 구현과 관련한 상수, 변수, 유틸 함수는 기초 클래스에 넣지말고 기초 클래스는 구현 정보에 무지해야 마땅하다. | ||
- 기초 클래스가 파생 클래스에 의존한다. | ||
- 개념을 기초 클래스와 파생 클래스로 나누는 가장 흔한 이유는 고차원 기초 클래스 개념을 저차원 파생 클래스 개념으로부터 분리해 독립성을 보장하기 위해서다. 그러므로 기초 클래스가 파생 클래스를 사용한다면 뭔가 문제가 있다는 말이다. 일반적으로 기초 클래스는 파생 클래스를 아예 몰라야 마땅하다. | ||
- 과도한 정보 | ||
- 잘 정의된 인터페이스는 많은 함수를 제공하지 않는다. 그래서 결합도가 낮다. 부실하게 정의된 인터페이스는 반드시 호출해야 하는 온갖 함수를 제공한다. 그래서 결합도가 높다. 클래스가 제공한느 메서드 수는 작을수록 좋다. 함수가 아닌 변수 수도 작을수록 좋다. 클래스에 들어 있는 인스턴스 변수 수도 작을 수록 좋다. | ||
- 죽은 코드 | ||
- 죽은 코드란 실행되지 않는 코드를 의미한다. 불가능한 조건을 확인하는 if문과 throw문이 없는 try문에서 catch 블록이 좋은 예이다. 아무도 호출하지 않는 유틸 홤수와 switch/case 문에서 불가능한 case 조건도 또다른 좋은 예이다. 이런 죽은 코드를 발견하면 시스템에서 제거하라 | ||
- 수직 분리 | ||
- 변수와 함수는 사용되는 위치에 가깝게 정의한다. 지역 변수는 처음으로 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야 한다. 선언한 위치로부터 몇백줄 아래에서 사용하면 안된다. | ||
- 일관성 부족 | ||
- 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다. 간단한 일관성 만으로도 코드를 읽고 수정하기가 대단히 쉬워진다. | ||
- 잡동사니 | ||
- 사용하지 않는 코드는 제거해야 마땅하다. | ||
- 인위적 결합 | ||
- 서로 무관한 개념을 인위적으로 결합하지 않는다. 예를 들어 일반적인 enum은 특정 클래스에 속할 이유가 없다. enum이 클래스에 속한다면 enum을 사용하는 코드가 특정 클래스를 알아야만 한다. 범용 static 함수도 마찬가지로 특정 클래스에 속할 이유가 없다. | ||
- 기능 욕심 | ||
- 클래스 메서드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다. 메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체 내용을 조작한다면 메서드가 그 객체 클래스의 범위를 욕심내는 탓이다. | ||
- 선택자 인수 | ||
- 선택자 인수는 목적을 기억하기 어려울 뿐만 아니라, 각 선택자 인수가 여러 함수를 하나로 조합하기에 지양해야 한다. | ||
- 모호한 의도 | ||
- 코드를 짤 때는 의도를 분명히 해야 한다. 행을 바꾸지 않고 표현한 수식, 헝가리식 표기법, 매직 번호 등은 모두 저자의 의도를 흐린다. | ||
- 부적절한 static 함수 | ||
- 일반적으로 static 함수보다 인스턴스 함수가 더 좋다. 조금이라도 의심스럽다면 인스턴스 함수로 정의한다. 반드시 static 함수로 정의해야 겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다. | ||
- 서술적 변수 | ||
- 서술적인 변수 이름은 많이 써도 괜찮다. 일반적으로는 많을수록 더 좋다. 계산을 몇 단계로 나누고 중간값에 좋은 변수 이름만 붙여도 해독하기 어렵던 모듈이 순식간에 읽기 쉬운 모듈로 탈바꿈한다. | ||
- 이름과 기능이 일치하는 함수 | ||
- 이름만으로 분명하지 않기에 구현을 살피거나 문서를 뒤적여야 한다면 더 좋은 이름으로 바꾸거나 아니면 더 좋은 이름을 붙이기 쉽도록 기능을 정리해야 한다. | ||
- 알고리즘을 이해하라 | ||
- 알고리즘이 올바르다는 사실을 확인하고 이해하려면 기능이 뻔히 보일 정도로 함수를 깔끔하고 명확하게 재구성하는 방법이 최고다. | ||
- if/else문, switch/case문보다 다형성을 사용하라 | ||
- 대다수 개발자가 스위치문을 사용하는 이유는 그 상황에서 가장 올바른 선택이라기보다는 당장 손쉬운 선택이기 때문이다. 그러므로 다형성을 먼저 고려해봐라 | ||
- 유형보다 함수가 더 쉽게 변하는 경우는 극히 드물다. 그러므로 스위치 문을 의심해봐야 한다. | ||
- 선택 유형 하나에는 스위치문을 한 번만 사용한다. 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 스위치문을 대신한다. | ||
- 표준 표기법을 따르라 | ||
- 팀은 업계 표준에 기반한 구현 표준을 따라야 한다. 구현 표준은 인스턴스 변수 이름을 선언하는 위치, 클래스, 메서드, 변수 이름을 정하는 방법, 괄호를 넣는 위치 등을 명시해야 하며, 표준을 설명하는 문서는 코드 자체로 충분해야 한다. | ||
- 매직 숫자는 명명된 상수로 교체하라 | ||
- 일반적으로 코드에서 숫자를 사용하지 말라는 규칙이다. 숫자는 명명된 상수 뒤로 숨기라는 의미다. | ||
- 관례보다 구조를 사용하라 | ||
- 설계 결정을 강제할 때는 규칙보다 관례를 사용한다. 예를 들어 스위치문을 매번 똑같이 구현하기보단 파생 클래스는 추상 메서드를 모두 구현하지 않으면 문제가 생기는 상황이 있다. | ||
- 조건을 캡슐화하라 | ||
- if문에 사용되는 조건에 단순 메서드를 사용하기 보다는 조건의 의도를 분명히 밝히는 함수로 표현하는 것이 좋다. | ||
- 부정 조건은 피하라 | ||
- 부정 조건은 긍정 조건보다 이해하기 어려우며 가능하면 긍정 조건으로 표현하라 | ||
- 함수는 한 가지만 해야 한다. | ||
- 함수를 짜다보면 한 함수에 여러 역할을 갖는다. 한 가지만 수행하는 좀 더 작은 함수 여럿으로 나눠야 마땅하다. | ||
- 숨겨진 시간적인 결합 | ||
- 함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러낸다. | ||
- 일관성을 유지하라 | ||
- 코드 구조에 일관성이 없어보인다면 남들이 맘대로 수정하기 일쑤이다. 시스템 전반에 걸쳐 구조가 일관성이 있다면 남들도 일관성을 따르고 보존한다. | ||
- 경계 조건을 캡슐화하라 | ||
- 경계 조건은 빼먹거나 놓치기 쉽상이다. 경계 조건은 한 곳에서 별도로 처리한다. 코드 여기 저기에서 처리하지 않는다. | ||
- 함수는 추상화 수준을 한 단계만 내려가야 한다. | ||
- 함수 내 모든 문장은 추상화 수준이 동일해야 한다. 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다. | ||
- 설정 정보는 최상위 단계에 둬라 | ||
- 추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨겨선 안된다. 대신 고차원 함수에서 저차원 함수를 호출할 때 인수로 넘긴다. | ||
- 추이적 탐색을 피하라 | ||
- 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다. 좀 더 구체적으로 a가 b를 사용하고 b가 c를 사용해도 a가 c를 알 필요는 없다는 의미이다. 이를 디미터의 법칙이라 부르고 실용주의 프로그래머들은 부끄럼타는 코드 작성이라고 한다. 무엇이라 부르든 오지는 자신이 직접 사용하는 모듈만 알아야 한다는 뜻이다. 내가 아는 모듈이 연이어 자신이 아는 모듈을 따라가며 시스템 전체를 휘저을 필요가 없다는 의미다. | ||
|
||
### 자바 | ||
|
||
- 긴 import 목록을 피하고 와일드 카드를 사용하라 | ||
- 긴 import 목록은 읽기에 부담스럽기에 지양하자 | ||
- 상수는 상속하지 않는다. | ||
- 이런 상황은 여러 차례 접했는데, 상수를 상속 계층 맨 위에 숨겨놓지 말자. 언어의 범위 규칙을 속이는 행위이다. 대신 static import를 사용하자 | ||
- 상수 대 enum | ||
- 자바 5는 enum을 제공한다. public static final int라는 옛날 기교를 더 이상 사용할 필요가 없다. int는 코드에서 의미를 잃기도 하지만 enum은 이름이 부여된 열거체에 속하기 때문이다. | ||
|
||
### 이름 | ||
|
||
- 서술적인 이름을 사용하라 | ||
- 이름은 성급하게 정하지 않는다. 서술적인 이름을 신중하게 고른다. sw가 진화하면 의미도 변하므로 선택한 이름이 적합한지 자주 되돌아본다. | ||
- 적절한 추상화 수준에서 이름을 선택하라 | ||
- 구현을 드러내는 이름은 피하라. 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라 | ||
- 가능하다면 표준 명명법을 사용하라 | ||
- 예를 들어 데코레이터, 전략 패턴을 활용한다면 사용하는 클래스 이름 끝에 특정 키워드 단어를 포함시켜준다. | ||
- 명확한 이름 | ||
- 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다. | ||
- 긴 범위는 긴 이름을 사용하라 | ||
- 이름 길이는 범위 길이에 비례해야 한다. for문에 쓰이는 초기식 변수명도 i로 무조건 지양하는 것이 아닌, 구현 코드가 3~5줄 내외일 경우 i로 사용해도 무방하다. | ||
- 인코딩을 피하라 | ||
- 이름에 유형 정보나 범위 정보를 넣어서는 안된다. 프로젝트 이름이나 하위 시스템 이름에 특정 접두어를 붙이는 것은 지양하자 | ||
- 이름으로 부수 효과를 설명하라 | ||
- 함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다. 이름에 부수 효과를 숨기지 않는다. 실제로 여러 작업을 수행하는 함수에다 동사 하나만 달랑 사용하면 곤란하다. |