Software Engineering Note

17장. 냄새와 휴리스틱 본문

스터디/Clean Code

17장. 냄새와 휴리스틱

devmoons 2016. 12. 12. 22:40

다양한 코드 냄새에 대한 내용.


수정해야할 부분이 있을때 보통 냄새가 난다는 표현을 한다.


주석


부적절한 정보. 소스코드 관리 시스템, 버그 추적 시스템 등 시스템에 저장할 정보는 주석으로 적절하지 못하다.

중복된 주석. 코드만으로 충분한데 굳이 설명을 붙이는것

주석으로 처리된 코드를 발견하면 즉각 지워버려라! 소스코드관리 시스템에 어차피 남으니까


환경


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

모든 단위 테스트는 한 명령으로 돌려야 한다. 

모든 테스트를 한 번에 실행하는 능력은 아주 근본적이고 아주 중요하다.


함수


너무 많은 인수. 넷 이상은 그 가치가 아주 의심스러우므로 최대한 피한다.

플래그 인수. boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거다.  피할것

죽은함수. 아무도 호출하지 않는 함수는 삭제한다. 소스코드관리 시스템에 어차피 남으니까


일반


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

경계 조건을 올바로 처리해야한다. 모드 경계 조건을 찾아내고, 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라.

테스트 케이스, 경고문구 같은 안전 절차를 무시하지마라.


DRY (Don't Repeat Yourself)

- 코드에서 중복을 발견할때마다 추상화할 기회로 간주하라. 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라.

- 똑같은 조건을 거듭 확인하는 switch/case, if/else 문은 다형성으로 대체해야 한다.

- 알고리즘이 유사하나 코드가 서로 다른 중복은 template method 패턴이나, strategy 패턴으로 중복을 제거한다.

- 어디서든 중복을 발견하면 없애라.


추상화 수준을 잘 유지한다. 모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다.


기초 클래스가 파생 클래스를 사용한다면 뭔가 문제가 있다는 것이다. 일반적으로 기초 클래스는 파생 클래스를 몰라야 마땅하다.

기초 클래스와 파생 클래스를 다른 JAR 파일로 배포하면, 그리고 기초 JAR 파일이 파생 JAR 파일을 전혀 모른다면, 독립적인 개별 컴포넌트 단위로 시스템을 배치할 수 있다.

즉, 변경이 시스템에 미치는 영향이 아주 작아지므로 현장에서 시스템을 유지보수하기가 한결 수월하게 된다.


부실하게 정의된 모듈은 인터페이스가 구질구질하다.

잘 정의된 인터페이스는 많은 함수를 제공하지 않는다. 그래도 결합도가 낮다.

부실하게 정의된 인터페이스는 반드시 호출해야 하는 온갖 함수를 제공한다. 그래서 결합도가 높다.

우수한 소프트웨어 개발자는 클래스나 모듈 인터페이스에 노출할 함수를 제한할 줄 알아야 한다.

정보를 제한해 결합도를 낮춰라.


죽은코드란 실행되지 않는 코드를 지칭하는데, 만나면 장례식을 치뤄줘라.


변수와 함수는 사용되는 위치에 가깝게 정의한다.

지역 변수는 처음으로 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야 한다.

비공개 함수는 처음으로 호출되는 위치를 찾은 후 조금만 아래로 내려가면 쉽게 눈에 띄어야 한다.


일관성 있게 표기법, 이름을 짓고 이것을 따를것


함수, 상수, 변수를 선언할 때는 시간을 들여 올바른 위치를 고민한다. 그저 당장 편한 곳에 선언하고 내버려두면 안된다.


클래스 메소드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다.

- 대체로 다른 클래스에서 정보를 얻어오거나, 메소드를 호출할때는 "위임" 하는 것이 이런 기능욕심을 제거하는데 도움이 되는 것같다.


선택자 인수. 일반적으로 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다.


코드를 짤 때는 의도를 최대한 분명히 밝힌다.


잘못지운 책임. '최소 놀람의 원칙'을 적용해서 독자가 자연스럽게 기대할 위치에 코드를 배치한다.


부적절한 static 함수. 

재정의할 가능성이 없고, 인수외에 다른 정보가 필요하지않은 경우에 한해 static 함수로 선언한다.

일반적으로 static 함수보다 인스턴스 함수가 더 좋다. 조금이라도 의심스럽다면 인스턴스 함수로 정의한다.

반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다.


서술적 변수. 

프로그램 가독성을 높이는 가장 효과적인 방법 중 하나가 계산을 여러 단계로 나누고 중간값으로 서술적인 변수 이름을 사용하는 방법이다.

서술적인 변수 이름은 많이 써도 괜찮다. 일반적으로 많을수록 더 좋다.


이름과 기능이 일치하도록 함수이름을 만들것


알고리즘을 이해하라.

테스트 케이스를 모두 통과한다는 사실만으로는 부족하다. 알고리즘을 '올바르다는 사실'을 알아야 한다.

알고리즘이 올바르다는 사실을 확인하고 이해하려면 기능이 뻔히 보일정도로 함수를 깔끔하고 명확하게 재구성 하는 방법이 최고다.


논리적 의존성은 물리적으로 드러내라.

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

의존하는 모듈이 상대 모듈에 대해 뭔가를 가정하면 안된다.


if/else 혹은 switch/case 문보다 다형성을 사용하라. 유형보다 함수가 더 쉽게 변하는 경우는 극히 드물다.

저자는 'switch문 하나' 규칙을 따른다. 즉, 선택 유형 하나에는 switch문을 한번만 사용한다.

같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다.


표준 표기법을 따르라

표준을 설명하는 문서는 코드 자체로 충분해야 하며 별도 문서를 만들 필요는 없어야 한다. 

팀이 정한 표준은 팀원들 모두가 따라야한다. 

실제 괄호를 넣는 위치는 중요하지않다. 모두가 동의한 위치에 넣는다는 사실이 중요하다.


매직 숫자는 명명된 상수로 교체하라

어떤 상수는 이해하기 쉬우므로, 코드 자체가 자명하다면 상수 뒤로 숨길 필요가 없다.


정확하라

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

호출하는 함수가 null을 반환할지도 모른다면 null을 반드시 점검한다. 

조회 결과가 하나뿐이라 짐작한다면 하나인지 확실히 확인한다.


관례보다는 구조를 사용하라

설계 결정을 강제할 때는 규칙보다 관례를 사용한다. 

명명 관례도 좋지만 구조 자체로 강제하면 더 좋다.

(ex. 추상 메서드가 있는 기초 클래스를 만들어, 파생 클래스가 추상 메서드를 모두 구현하게 강제한다)


부울 논리는 이해하기 어렵다. 조건의 의도를 분명히 밝히는 함수로 표현하라.

부정 조건은 긍정 조건보다 이해하기 어렵다. 가능하면 긍정 조건으로 표현한다.


함수는 한 가지만 해야한다.


숨겨진 시간적인 결합.

때로는 시간적인 결합이 필요하다. 하지만 시간적인 결합을 숨겨서는 안된다.

메소드 호출이 순서가 보장되어야 할때는, 한 메소드의 결과가 다음 메소드의 인자로 넘어가게 의존성을 드러내는게 좋다. 

이렇게 하면 순서를 바꿔 호출할 수가 없다.


일관성을 유지하라

시스템 전반에 걸쳐 구조가 일관성이 있다면 남들도 일관성을 따르고 보존한다.


경계 조건을 캡슐화 하라

경계조건은 한곳에서 별도로 처리한다. 코드 여기저기에서 처리하지않는다.

(ex. 코드 여기저기에 +1, -1 하지말고, 변수로 정의해서 쓴다)


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

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


설정 정보는 최상위 단계에 둔다.


디미터의 법칙 Law of Demeter (부끄럼 타는 코드 작성)

자신이 직접 사용하는 모듈만 알아야 한다는 뜻이다. (a.getB().getC() .. 이런거 하지말라고!)


자바


긴 import 목록을 피하고 와일드 카드를 사용하라


상수는 상속하지 않는다.

상수를 상속 계층 맨 위에 숨기는 짓을 하지마라. 대신 static import를 사용하라.


상수대신 enum을 사용해라. enum문법에서는 메서드와 필드도 사용할 수 있다.


이름


서술적인 이름을 사용하라

이름은 성급하게 정하지않는다. 서술적인 이름을 신중하게 고른다.


적절한 추상화 수준에서 이름을 선택하라

구현을 드러내는 이름은 피하라. 작업대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라.


가능하다면 표준 명명법을 사용하라


명확한 이름. 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다.


긴 범위는 긴 이름을 사용하라

이름 길이는 범위 길이에 비례해야 한다. 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다. (i, j같은)

이름 범위가 길수록 이름을 정확하고 길게 짓는다.


인코딩을 피하라. 이름에 유형 정보나 범위 정보를 넣어서는 안된다. (ex. 접두어 붙이는 관례)


이름으로 부수 효과를 설명하라

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

(얻으려는 객체가 없을때, 새로 생성하는 메소드라면 getXXX() 보다 createOrReturnXXX() 이런식으로 이름을 짓는게 좋다)



테스트


커버리지 도구를 사용하라


경계 조건은 각별히 신경써서 테스트 한다.


버그 주변은 철저히 테스트 하라

버그는 서로 모이는 경향이 있다. 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다. 십중팔구 다른 버그도 발견하리라.


실패 패턴을 살펴라

때로는 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다. (특정 조건을 넘어가는 경우에만 실패한다거나..)


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


'스터디 > Clean Code' 카테고리의 다른 글

부록 A. 동시성2  (0) 2017.02.13
14장. 점진적인 개선  (0) 2015.12.14
13장. 동시성  (0) 2015.10.20
12장. 창발성  (0) 2015.09.06
11장. 시스템  (0) 2015.09.06