17 냄새와 휴리스틱
생성일
2024년 3월 18일 오후 9:48
태그
비어 있음
마틴과 저자가 맡은 코드 냄새 + 코드를 짜면서 사용하는 기교와 휴리스틱
🧹 주석
c1 : 부적절한 정보
C2 : 쓸모 없는 주석
쓸모 없어질 주석은 아예 달지 않는 편이 가장 좋다
C3 : 중복된 주석
C4 : 성의 없는 주석
C5 : 주석 처리된 코드
오래된 코드인지 중요한 코드인지 아닌지 알 길이 없다.
코드는 그 자리에 남아 매일매일 낡아가며 더 이상 존재하지 않는 함수를 호출하고 이름이 바뀐 변수를 사용한다.
주석으로 처리된 코드를 발견하면 즉각 지워 버려라 걱정할 필요 없다. 소스 코드 관리 시스템이 기억한다.
🧹 환경
E1 : 여러 단계로 빌드해야 한다.
E2 : 여러 단계로 테스트해야 한다.
모든 테스트를 한 번에 실행하는 능력은 아주 중요하다.
🧹 함수
F1 : 너무 많은 인수
F2 : 출력 인수
F3 : 플래그 인수
boolean 인수는 함수가 여러 기능을 수행한다는 증가다. 혼란을 초래하므로 피해야 한다.
F4 : 죽은 함수
🧹 일반
G1: 한 소스 파일에 여러 언어를 사용한다.
G2: 당연한 동작을 구현하지 않는다.
함수 이름만으로 함수 기능을 직관적으로 예상하게 하기
G3: 경계를 올바로 처리하지 않는다.
G4: 안전 절차 무시
G5: 중복
이 책에 나오는 가장 중요한 규칙 중 하나
DRY 원칙 : Don’t Repeat Yourself
중보글 발견할 때마다 추상화할 기회로 간주하라
코드가 여러 차례 나오는 중복은 간단한 함수로 교체한다.
좀 더 미묨한 유형은 다형성으로 대체한다.
더더욱 미묘한 유형은 알고리즘이 유사하거나 다른 중복이므로 TEMPLATE METHOD 패턴이나 STRATEGY 패턴으로 중복을 제거한다.
G6: 추상화 수준이 올바르지 못하다
모든 저차원 개념은 파생 클래스에 넣고, 모든 고차우너 개념은 기초 클래스에 넣는다.
세부 구현과 관련한 상수, 변수, 유틸리티 함수는 기초 클래스에 넣으면 안된다.
G7: 기초 클래스가 파생 클래스에 의존한다.
G8: 과도한 정보
G9 : 죽은 코드
G10: 수직 분리
G11 : 일관성 부족
어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다.
표기법은 신중하게 선택하며, 일단 선택한 표기법은 신중하게 따른다.
G12: 잡동사니
G13: 인위적 결합
G14: 기능 욕심
클래스 메서드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다. 메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체 내용을 조작한다면 메서드가 그 객체 클래스의 범위를 욕심내는 탓이다.
G15: 선택자 인수
G16: 모호한 의도
G17: 잘못 지운 책임
G18: 부적절한 static 함수
G19: 서술적 변수
G20: 이름과 기능이 일치하는 함수
G21: 알고리즘을 이해하라
G22: 논리적 의존성은 물리적으로 드러내라
G23: if/Else 혹은 Switch/Case 문보다 다형성을 사용하라
저자는 3장에서는 새 유형을 추가할 확률보다 새 함수를 추가할 확률이 높은 코드에서는 switch문이 더 적합하다고 주장했던 이유는 그 상황에서 당장 손쉬운 선택이기 때문이다.
그러므로 switch를 선택하기 전에 다형성을 먼저 고려하라는 의미다.
‘switch 문 하나’ 규칙 : 선택 유형 하나에는 switch 문을 한번만 사용한다.
같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch문을 대신한다.
G24: 표준 표기법을 따르라
G25: 매직 숫자는 명명된 상수로 교체하라
G26: 정확하라
G27: 관례보다 구조를 사용하라
G28: 조건을 캡슐화하라
G29: 부정 조건은 피하라
G30: 함수는 한 가지만 해야 한다.
함수를 짜다보면 한 함수 안에 여러 단락을 이어, 일련의 작업을 수행하고픈 유혹에 빠진다.
한 가지만 수행하는 좀 더 작은 함수 여럿으로 나눠야 마땅하다.
세 가지 임무를 수행한다.
Java
복사
public void pay(){
for (Employee e : employees) { // 1. 직원 목록 for loop 통해 돌기
if (e.isPaypay()) { // 2. 월급일인지 확인
Money pay = e.calculatePay();
e.deliverPay(pay); // 3. 해당 직원에게 급여 지급
}
}
}
Java
복사
public void pay(){
for (Employee e : employees)
payIfNecessary(e);
}
private void payIfNecessary(Employee e) {
if(e.isPayday()){
calculateAndDeliverPay(e)
}
}
private void calculateAndDeliverPay(Employee e) {
Money pay = e.calculatePay();
e.deliverPay(pay);
}
G31: 숨겨진 시간적인 결합
G32: 일관성을 유지하라
G33: 경계 조건을 캡슐화하라
G34: 함수는 추상화 수준을 한 단계만 내려가야 한다.
함수 내 모든 문장은 추상화 수준이 동일해야 한다.
그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.
추상화 수준 분리는 리팩터링을 수행하는 가장 중요한 이유 중 하나다.
추상화 수준이 섞여있다
첫째는 수평선에 크기가 있다는 개념
둘째는 HR태그 자체의 문법
Java
복사
public String render() throws Exception {
StringBuffer html = new StringBuffer("<hr");
if(size > 0)
html.append(" size=\"").append(size + 1).append("\"");
html.append(">");
return html.toString();
size 변수 이름은 목적ㅇ르 반영하게 적절히 변경.
Java
복사
public String render() throws Exception
{
HtmlTag hr = new HtmlTag("hr");
if (extraDashes > 0)
hr.addAttributes("size", hrSize(extraDashes));
return hr.html();
}
private String hrSize(int height)
{
int hrSize = height + 1;
return String.format("%d", hrSize);
}
G35: 설정 정보는 최상위 단계에 둬라
G36: 추이적 탐색을 피하라
🧹 자바
J1: 긴 import 목록을 피하고 와일드카드를 사용하라
import 문은 강한 의존성을 생성하지만 와일드 카드는 그렇지 않다.
패키지에 클래스를 둘 이상 사용한다면 와일드 카드를 사용해 패키지 전체를 가져오라. import 문이 길어지면 가독성이 떨어지기 때문에 사용하는 패키지를 간단하게 명시해준다.
J2: 상수는 상속하지 않는다.
TENTHS_PER_WEEK 와 OVERTIME_RATE 상수는 어디서 왔을까?
Java
복사
public class HourlyEmployee extends Employee {
private int tenthsWorked;
private double hourlyRate;
public Money calculatePay() {
int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
int overTime = tenthsWorked - straightTime;
return new Money(
hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
);
}
...
}
부모클래스인 Employee에도 없다.
Java
복사
public abstract class Employee implements PayrollConstants {
public abstract boolean isPayday();
public abstract Money calculatePay();
public abstract void deliverPay(Money pay);
}
PayrollConstants 인터페이스에 있다.
Java
복사
public interface PayrollConstants {
public static final int TENTHS_PER_WEEK = 400; // 여기
public static final double OVERTIME_RATE = 1.5; / 여기
}
상수를 상속 계층 맨 위에 숨겨놨다. 상속을 이렇게 사용하면 안됨.
대신 static import를 사용하
Java
복사
import static PayrollConstants.*;
public class HourlyEmployee extends Employee {
private int tenthsWorked;
private double hourlyRate;
public Money calculatePay() {
int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
int overTime = tenthsWorked - straightTime;
return new Money(
hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
);
}
...
}
J3: 상수 대 Enum
enum 문법을 자세히 보면 메서드와 필드도 사용할 수 있다.
Java
복사
public class HourlyEmployee extends Employee {
private int tenthsWorked;
HourlyPayGrade grade;
public Money calculatePay() {
int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
int overTime = tenthsWorked - straightTime;
return new Money(
grade.rate() * (tenthsWorked + OVERTIME_RATE * overTime)
);
}
}
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();
}
🧹 이름
N1: 서술적인 이름을 사용하라
N2: 적절한 추상화 수준에서 이름을 선택하라
N3: 가능하다면 표준 명명법을 사용하라
팀마자 특정 프로젝트에 적용할 표준을 나름대로 고안한다.
이를 에릭 에반스는 프로젝트의 유비쿼터스 언어라 부른다.
N4: 명확한 이름
N5: 긴 범위는 긴 이름을 사용하라
N6: 인코딩을 피하라
N7: 이름으로 부수 효과를 설명하라
🧹 테스트
T1: 불충분한 테스트
잠재적으로 깨질 만한 부분을 모두 테스트해야 하낟.
T2: 커버리지 도구를 사용하라!
커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다. 커버리지 도구를 사용하면 테스트가 불충분한 모둘, 클래스, 함수를 찾기가 쉬워진다.
전혀 실행되지 않는 if 혹은 case 문 블록이 금방 드러난다.
T3: 사소한 테스트를 건너뛰지 마라
사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다.
T4: 무시한 테스트는 모호함을 뜻한다.
불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에 @ignore을 붙여 표현한다. 선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.
T5: 경계 조건을 테스트하라
경계 조건에서 실수하는 경우가 흔하다.
T6: 버그 주변은 철저히 테스트하라
버그는 서로 모이는 경향이 있다. 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다.
T7: 실패 패턴을 살펴라
T8: 테스트 커버리지 패턴을 살펴라
T9: 테스트는 빨라야 한다.
리스트 보기
'책 > Clean Code' 카테고리의 다른 글
[Clean Code] 15장 JUnit 들여다 보기 (0) | 2025.03.17 |
---|---|
[Clean Code] 13장 동시성 (0) | 2025.03.17 |
[Clean Code] 11장 시스템 (0) | 2025.03.17 |
[Clean Code] 9장 단위 테스트 (0) | 2025.03.17 |
[Clean Code] 7 장 오류 처리 (0) | 2025.03.17 |