Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- springboot
- 11286번
- Design Pattern
- 냄새와 휴리스틱
- 2206번
- 프로그래머스
- Design Patterns
- java의 정석
- java
- 클린코드
- 자바의 정석
- 10830번
- 가장 긴 증가하는 부분 수열2
- 9장
- Spring
- 1043번
- Adapater Pattern
- BOJ
- 17장
- 11758번
- 백준
- 1300번
- SerialDate 리펙터링
- DxTrace
- programmers
- Dxerr.h
- 코딩테스트
- 2156번
- 2166번
- 코딩 테스트
Archives
- Today
- Total
Don't give up!
[클린코드] 17장 냄새와 휴리스틱 본문
이 글은 로버트 마틴의 '클린 코드'를 읽고 TIL(Today I Learned)로써 정리한 내용을 기록하는 글입니다.
자세한 내용은 책을 통해 확인하실 수 있습니다.
알라딘: 클린 코드 Clean Code (aladin.co.kr)
냄새와 휴리스틱
- 휴리스틱(heuristics)이란 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나, 체계적이면서 합리적인 판단이 굳이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있게 보다 용이하게 구성된 간편추론의 방법.
- 발견법이라고도 불리며 컴퓨터 공학에서는 해결법이 정확히 해결되는지에 대한 문제를 무시하고 일반적으로 좋은 해결법이나 보다 간단한 해결법으로 풀고자 하는 문제 해결법으로 사용된다.
- 저자는 다양한 프로그램을 검토하고 리펙터링하면서 나쁜 코드가 갖는 나쁜 냄새와 어떻게 개선해야 하는지에 대해 이야기한다.
주석
- 부적절한 정보 : 다른 시스템에 저장할 정보는 주석으로 적절하지 못하다. 주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다.
- 쓸모 없는 주석 : 오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다. 쓸모 없어질 주석은 아예 달지 않는 편이 가장 좋다.
- 중복된 주석 : 코드만으로 충분한데 구구절절 설명하는 주석은 중복된 주석이다. 주석은 코드만으로 다하지 못하는 설명을 부언한다.
- 성의 없는 주석 : 주석을 달 참이라면 시간을 들여 최대한 멋지게 작성하라. 간결하게 명료하게 작성하는 것이 좋다.
- 주석 처리된 코드 : 주석으로 처리된 코드는 읽는 이로 하여금 신경을 거슬리게 한다. 주석으로 처리된 코드를 발견하면 즉각 지워라. 소스 코드 관리 시스템이 기억하므로 누군가 필요하다면 이전 버전을 가져올 것이다.
환경
- 여러 단계로 빌드 : 빌드는 간단히 한 단계로 끝나야 한다. 소스 코드 관리 시스템에서 이것저것 따로따로 체크하웃할 필요가 없어야 하며 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드할 수 있어야 한다.
- 여러 단계로 테스트 : 모든 테스트를 한 번에 실행하는 능력은 아주 근본적이고 아주 중요하다. 따라서 그 방법이 빠르고, 쉽고 명백해야 한다.
함수
- 너무 많은 인수 : 인수 개수는 적을 수록 좋다. 아예 없으면 가장 좋으며 넷 이상은 그 가치가 의심스러우므로 최대한 피한다.
- 출력 인수 : 출력 인수는 직관을 정면으로 위배한다. 함수에서 무언가의 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경한다.
- 플래그 인수 : boolean 인수는 함수가 여러 기능을 수행한다는 증거이다. 플래그 인수는 혼란을 초래하므로 피해야 마땅하다.
- 죽은 함수 : 아무도 호출하지 않는 함수는 삭제하라. 죽은 코드는 낭비다.
일반
- 한 소스 파일에 여러 언어를 사용 : 현실적으로는 여러 언어가 불가피하지만 각별한 노력을 기울여 소스 파일에서 언어 수와 범위를 최대한 줄이도록 애써야 한다. 혼란스럽고, 조잡하다.
- 당연한 동작을 구현하지 않음 : 함수나 클래스는 프로그래머가 당연하게 여길만한 동작과 기능을 제공해야 한다.(놀람 최소화 원칙, POLA) 당연한 동작을 구현하지 않으면 코드를 읽거나 사용하는 사람이 더 이상 함수 이름만으로 함수 기능을 직관적으로 예상하기 어렵다.
- 경계를 올바로 처리하지 않음 : 우리는 올바른 동작이 아주 복잡하다는 사실을 자주 간과한다. 모든 경계 조건, 모든 구석진 곳, 모든 기벽, 모든 예외는 우아하고 직관적인 알고리즘을 좌초시킬 암초다. 스스로의 직관에 의존하지 마라. 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하라.
- 안전 절차 무시 : 안전 절차를 무시하면 위험하다. 컴파일러 경고 일부를 꺼버리면 빌드가 쉬워질지 모르지만 자칫하면 끝없는 디버깅에 시달린다. 실패하는 테스트 케이스를 일단 제껴두고 나중으로 미루는 태도는 신용카드가 공짜 돈이라는 생각만큼 위험하다.
- 중복 : 코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라. 중복된 코드를 하위 루틴이나 다른 클래스로 분리하라. 추상화 수준을 높이면 구현이 빨라지고 오류가 적어진다.
- 추상화 수준이 올바르지 않음 : 추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다. 추상화로 개념을 분리할 때는 철저해야 한다. 모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다. 잘못된 추상화 수준은 거짓말이나 꼼수로 해결하지 못한다.
- 기초 클래스가 파생 클래스에 의존 : 개념을 기초 클래스와 파생 클래스로 나누는 가장 흔한 이유는 고차원 기초 클래스 개념을 저차원 파생 클래스로부터 분리해 독립성을 보장하기 위함. 일반적으로 기초 클래스는 파생 클래스를 아예 몰라야 마땅하다.
- 과도한 정보 : 잘 정의된 모듈은 인터페이스가 아주 작지만 많은 동작이 가능하다. 잘 정의된 인터페이스는 많은 함수를 제공하지 않는다. 메서드나 인스턴스 변수가 넘쳐나는 클래스는 피하라. 인터페이스를 매우 작게 그리고 매우 깐깐하게 만들어라. 정보를 제한해 결합도를 낮추어라.
- 죽은 코드 : 실행되지 않는 코드는 시스템에서 제거하라. 죽은 코드는 설계가 변해도 제대로 수행되지 않는다.
- 수직 분리 : 변수와 함수는 사용되는 위치에 가깝게 정의해야 한다. 비공개 함수는 처음으로 호출되는 위치를 찾은 후 조금만 아래로 내려가면 쉽게 눈에 띄어야 한다.
- 일관성 부족 : 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현하는 것이 좋다. 간단한 일관성만으로도 코드를 읽고 수정하기 쉬워진다.
- 인위적 결합 : 일반적으로 인위적인 결합은 직접적인 상호작용이 없는 두 모듈 사이에서 일어난다. 함수, 상수, 변수를 선언할 때는 시간을 들여 올바른 위치를 고민하자. 당장 편한 곳에 선언하고 내버려두어서는 안된다.
- 기능 욕심 : 클래스 메서드는 다른 클래스의 변수와 함수에 관심을 가져서는 안된다. 기능 욕심은 한 클래스의 속사정을 다른 클래스에 노출하므로 별다른 문제가 없다면 제거하는 편이 좋다.
- 선택자 인수 : 선택자 인수는 목적을 기억하기 어려울 뿐 아니라 각 선택자 인수가 여러 함수를 하나로 조합한다. enum, int 등 함수 동작을 제어하려는 인수는 바람직하지 않다. 일반적으로, 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다.
- 모호한 의도 : 행을 바꾸지 않고 표현한 수식, 헝가리식 표기법, 매직 번호 등은 모두 저자의 의도를 흐린다. 독자에게 의도를 분명히 표현하도록 시간을 투자할 가치가 있다.
- 잘못 지운 책임 : 코드는 독자가 자연스럽게 기대할 위치에 배치해야 한다. 놀람 최소화 원칙에 기반하여 적절히 코드를 배치해야 한다.
- 부적절한 static 함수 : 함수를 재정의할 가능성이 존재한다면 static 함수로 정의하여서는 안된다. 조금이라도 의심스럽다면 인스턴스 함수로 정의한다.
- 서술적 변수 : 서술적인 변수 이름은 많을수록 좋다. 계산을 몇 단계로 나누고 중간값에 좋은 변수 이름만 붙여도 해독하기 어렵던 모듈이 순식간에 읽기 쉬운 모듈로 탈바꿈한다.
- 이름과 기능이 일치하지 않는 함수 : 이름만으로 분명하지 않기에 구현을 살피거나 문서를 뒤적여야 한다면 더 좋은 이름으로 바꾸거나 아니면 더 좋은 이름을 붙이기 쉽도록 기능을 정리해야 한다.
- 알고리즘에 대한 이해 부족 : 구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라. 테스트 케이스를 모두 통과한다는 사실만으로 부족하다. 작성자가 알고리즘이 올바르다는 사실을 알아야 한다. 알고리즘이 올바르다는 사실을 확인하고 이해하려면 기능이 뻔히 보일 정도로 함수를 깔끔하고 명확하게 재구성하는 방법이 최고다.
- 물리적으로 드러나지 않은 모듈 의존성 : 한 모듈이 다른 모듈에 의존한다면 물리적인 의존성도 있어야 한다. 의존하는 모든 정보를 명시적으로 요청하는 편이 좋다.
- If/Else 혹은 Switch/Case 문 : 유형보다 함수가 더 쉽게 변하는 경우는 극히 드물다. 선택 유형 하나에는 switch문을 한 번만 사용한다. 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다.
- 표준 표기법을 따르지 않음 : 팀은 업계 표준에 기반한 구현 표준을 따라야 한다. 표준을 설명하는 문서는 코드 자체로 충분해야 하며 별도 문서를 만들 필요는 없어야 한다.
- 정확하지 않은 코드 : 검색 결과 중 첫 번째 결과만 유일한 결과로 간주하는 행동은 순진하다. 코드에서 무언가를 결정할 때는 정확히 결정해야 한다. 결정을 내리는 이유와 예외를 처리할 방법을 분명히 알아야 한다.
- 관례를 사용함 : 설계 결정을 강제할 때는 규칙보다 관례를 사용한다. 명명 관례도 좋지만 구조 자체로 강제하면 더 좋다.
- 캡슐화하지 않은 조건 : boolean 논리는 이해하기 어렵다. 조건의 의도를 분명히 밝히는 함수로 표현하라.
- 부정 조건 : 부정 조건은 긍정 조건보다 이해하기 어렵다. 가능하면 긍정 조건으로 표현하라.
- 숨겨진 시간적인 결합 : 시간적인 결합은 때때로 필요하지만 이를 숨겨서는 안 된다. 함수를 짤 때는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러내야 한다.
- 일치하지 않는 추상화 수준 : 함수 내 모든 문장은 추상화 수준이 동일해야 한다. 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다. 필요한 추상화 수준을 분리한 후 테스트 케이스를 통과한다는 목표를 세워라.
자바
- 긴 import 목록 : import 문은 패키지를 단순히 검색 경로에 추가하므로 진정한 의존성이 생기지 않는다. 와일드 카드를 사용하면 모듈 간에 결합성이 낮아진다.
- 상속하는 상수 : 상수를 인터페이스에 넣고 그 인터페이스를 상속해 해당 상수를 사용하는 것은 언어의 범위 규칙을 속이는 행위다.
이름
- 서술적인 이름 : 서술적인 이름은 신중하게 고른다. 신중하게 선택한 이름은 추가 설명을 포함한 코드보다 강력하다. 신중하게 선택한 이름을 보고 독자는 모듈 내 다른 함수가 하는 일을 짐작한다.
- 범위 길이에 비례하지 않은 이름 길이 : 이름 길이는 범위 길이에 비례해야 한다. 이름이 짧은 변수나 함수는 범위가 길어지면 의미를 잃는다.
- 인코딩을 피하라 : 이름에 유형 정보나 범위 정보를 넣어서는 안 된다. 오늘날 환경은 이름을 조작하지 않고도 모든 정보를 제공한다.
테스트
- 불충분한 테스트 : 테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다. 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다.
- 커버리지 도구를 사용하라 : 커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다. 대다수 IDE는 테스트 커버리지를 시각적으로 표현한다.
- 사소한 테스트를 건너뛰지 마라 : 사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다.
- 무시한 테스트는 모호함을 뜻한다 : 불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에 @Ignore를 붙여 표현한다. 선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.
- 실패 패턴을 살펴라 : 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다. 합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다.
- 테스트는 빨라야 한다 : 느린 테스트 케이스는 실행하지 않게 된다. 일정이 촉박하면 느린 테스트 케이스를 제일 먼저 건너뛴다. 그러므로 테스트 케이스가 빨리 돌아가게 최대한 노력한다.
'개발서적 > 클린 코드' 카테고리의 다른 글
[클린코드] 16장 SerialDate 리펙터링 (0) | 2021.08.05 |
---|---|
[클린코드] JUnit 들여다보기 (0) | 2021.08.04 |
[클린코드] 14장 점진적인 개선 (0) | 2021.08.03 |