코드는 클린해야 한다. 즉, 명료해야 한다.
그래야 미래의 내가 잘 이해할 수 있다.
코드를 이해할 때 1주일 걸릴 것이 2 ~ 3시간으로 줄어들 수 있다.
코드가 명확하지 않으면 그 코드를 사용하기도 어려워진다.
이름만으로 이해가 되지 않아 사용할 때마다 본문을 다시 읽어봐야 하는 것이다.
코드를 클린하게 만들기 위해 가장 중요한 요소 중 하나는 이름이다.
이름을 잘 지으면 코드를 이해하기도, 사용하기도 쉬워진다.
마땅한 이름이 떠오르지 않는다면 함수가 한 가지 일을 하는 게 맞는지 점검할 때이다.
중복 코드는 코드의 유연함, 수정을 어렵게 만든다.
100 개의 중복 코드를 수정한다고 생각해보라.
그리고 중복 코드는 하나의 기능 단위에 대한 단서가 되기도 한다.
긴 함수는 코드를 이해하기 어렵다.
그리고 이 함수가 어떤 일을 하는가에 답하기 어렵다.
여러가지 기능을 겸직할 가능성이 높기 때문이다.
짧은 함수는 코드를 이해하기 쉽고, 공유가 쉽고, 선택하기 쉽다.
공유하기 쉽고 선택하기 쉬운 이유는 기능이 작을수록 재사용성이 올라가고 이름 짓기도 쉽기 때문이다.
여러 메서드를 조합하는 식으로 함수를 구성하다보면 함수 사이를 많이 왔다갔다 해야하지 않나하는 생각이 들 수 있다.
이는 메서드 이름을 신중히 선택함으로써 보완할 수 있다.
매개 변수가 많으면 특히 함수를 사용하기 어렵다.
정보량이 너무 많아 뇌정지가 올 수 있고 매개 변수의 순서로 인한 버그 발생 가능성도 높아지기 때문이다.
C++ STL algorithm의 경우 보통 매개변수가 3개 이상인데 비숙련자는 상당히 사용하기 어렵다.
또한 함수를 이해하기도 어렵다.
우린 함수명 뿐만 아니라 매개변수명도 포함해서 함수 호출문을 이해하기 때문이다.
그리고 매개변수가 많다는 것은 클래스 분리의 신호가 될 수 있다.
startDate, endDate를 넘겨주기 보다는 Duration 객체를 넘겨주는 것이 낫다.
이렇게 데이터를 묶어 클래스로 만들면 데이터에 대한 의미있는 메서드를 통해 한 단계 높은 추상화를 제공할 수 있기 때문이다.
위의 코드보다는 아래의 코드가 명료함을 알 수 있다.
boolean isOutDated(int date, int startDate, int endDate) {
if (date < startDate || date > endDate)
...
}
boolean isOutDated(int date, int startDate, int endDate) {
if (duration.contains(date))
...
}
전역 데이터는 심각한 악취이다.
어디서든 사용될 수 있고 어디서든 변경될 수 있는 전역 데이터와 관련된 버그가 발생하면 우리는 디버거를 켜고 프로그램을 진행하며 값을 추적해야 한다.
이와 반대로 캡슐화된 값을 봐야 할 유효한 범위가 한정되어 있기 때문에 추적하기가 수월하다.
그리고 테스트 작성도 어렵게 만든다.
어떤 모듈이 전역 데이터에 의존한다면 해당 모듈을 격리시키기 어려워진다.
어떤 값을 여러 모듈에서 사용하는데 값이 계속 변화한다면 버그가 발생했을 때 디버깅하기가 굉장히 어렵다.
그래서 값을 변경해야 한다면 특정 함수를 거쳐야만 값을 수정할 수 있도록 하면 코드를 변경하기 쉽다.
뒤엉킨 변경은 한 모듈이 여러 책임을 맡고 있을 때 풍기는 악취이다.
이럴 땐 코드 수정을 할 때 곤욕을 치룰 수 있다.
일례로 데이터 베이스를 수정했는데 상품 관련 버그가 발생하는 경우를 들 수 있다.
한 클래스의 메서드가 많으며 '이 클래스의 역할이 뭐지'라는 물음에 답하기 어려우면 뒤엉킨 변경을 의심할 때이다.
이것도 뒤엉킨 변경처럼 SRP 원칙이 잘 지켜지지 않은 예인데 정반대의 양상이다.
산탄총 수술은 한 기능을 수정할 때 여러 모듈을 변경해야 하는 경우 풍기는 악취이다.
이럴 경우 수정해야할 부분을 찾아다녀야 하며 놓치기도 쉽다.