코드에서 나는 악취 목록
1. Mysterious Name
코드는 단순하고 명료하게 작성해야 한다. 코드를 명료하게 표현하는 데 가장 중요한 요소는 바로 '이름'이다. 하지만 아쉽게도 이름 짓기는 프로그래밍에서 가장 어렵기로 손꼽히는 일 중 하나다. 아래는 유명한 농담 글을 인용.
There are only two hard things in Computer Science: cache invalidation and naming things.
- Phil Karlton
함수, 모듈, 변수, 클래스 등 그 이름만 보고도 각각 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있어야 한다. 이름만 잘 지어도 나중에 문맥을 파악하느라 헤매는 시간을 크게 절약할 수 있다.
만약 마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높다. 그래서 혼란스러운 이름을 잘 정리하다 보면 코드가 훨씬 간결해질 때가 많다.
2. Duplicated Code
똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있다. 한 클래스에 딸린 두 메서드가 똑같은 표현식을 사용하는 경우가 있다. 이럴 때는 하나의 메서드로 추출하여 양쪽 모두 추출된 메서드를 호출하게 한다. 코드가 비슷하지만 완전히 똑같지는 않다면, 코드를 조금 더 세분화하여 재작성한 뒤 공통된 부분을 추출해본다. 또는 같은 부모로부터 파생된 서브클래스들에게 중복된 코드가 있다면, 부모 클래스로 메서드를 올리자.
3. Long Function
짧은 함수들로 구성된 코드 베이스를 얼핏 훑으면 연산하는 부부이 하나도 없어 보인다. 코드가 끝없이 위임하는 방식으로 작성되어 있기 때문이다. 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점은 함수를 짧게 구성할 때 나오는 것이다.
물론 코드를 읽는 사람 입장에서는 함수가 하는 일을 파악하기 위해 왔다 갔다 해야 하므로 여전히 부담이 된다. 다행히 함수 호출부와 선언부 사이를 빠르게 이동하거나 호출과 선언을 동시에 보여주는 개발 환경을 활용하면 이 부담이 줄어들지만, 짧은 함수로 구성된 코드를 이해하기 쉽게 만드는 가장 확실한 방법은 좋은 이름이다. (1. Mysterious Name) 함수 이름을 잘 지어두면 본문 코드를 볼 이유가 사라진다.
주석을 달아야 할 만한 부분은 무조건 함수로 만든다. 그 함수 본문에는 원래 주석으로 설명하려던 코드가 담기고, 함수 이름은 동작 방식이 아닌 '의도'가 드러나게 짓는다. 심지어 원래 코드보다 길어지더라도 함수로 뽑는다. 단 함수 이름에 코드의 목적이 드러내야 한다. 여기서 핵심은 함수의 길이가 아닌, 함수의 목적(의도)과 구현 코드의 괴리가 얼마나 큰 가다. 즉, '무엇을 하는지'를 코드가 잘 설명해주지 못할수록 함수로 만드는 게 유리하다.
주석은 코드만으로는 목적을 이해하기 어려운 부분에 달려 있는 경우가 많다. 이런 주석을 찾으면 주석이 설명하는 코드와 함께 함수로 빼내고, 함수 이름은 주석 내용을 토대로 짓는다. 코드가 단 한 줄이어도 따로 설명할 필요가 있다면 함수로 추출하는 게 좋다. 조건문이나 반복문도 추출 대상의 실마리를 제공한다.
4. Long Parameter List
매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많다. 종종 다른 매개변수에서 값을 얻어올 수 있는 매개변수가 있을 수 있는데, 이런 매개변수는 최대한 없애주자. 사용 중인 데이터 구조에서 값들을 뽑아 각각을 별개의 매개변수로 전달하는 코드라면 객체를 통째로 넘겨주어 원본 데이터 구조를 사용할 수 있도록 하자. 항상 함께 전달되는 매개변수들이 있다면 매개변수 객체로 만들어 하나로 묶어버린다. 또는 함수의 동작 방식을 정하는 플래그 역할의 매개변수가 있다면 없애주자.
클래스는 매개변수 목록을 줄이는 데 효과적인 수단이기도 하다. 특히 여러 개의 함수가 특정 매개변수들의 값을 공통으로 사용할 때 유용하다. 이럴 때는 여러 함수를 클래스로 묶어 이용하여 공통 값들을 클래스의 필드로 정의한다.
5. Global Data
전역 데이터는 코드 베이스 어디에서든 건드릴 수 있고 값을 누가 바꿨는지 찾아낼 메커니즘이 없다는 게 문제다. 전역 데이터의 대표적인 형태는 전역 변수지만 클래스 변수와 싱글톤에서도 같은 문제가 발생한다.
이를 방지하기 위해 변수를 캡슐화해야 한다. 다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 이 기법을 가장 먼저 적용한다. 이런 데이터를 함수로 감싸는 것만으로도 데이터를 수정하는 부분을 쉽게 찾을 수 있고 접근을 통제할 수 있게 된다. 더 나아가 접근자 함수들을 클래스나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위를 최소로 줄이는 것도 좋다.
6. Mutable Data
데이터를 변경했더니 예상치 못한 결과나 골치 아픈 버그로 이어지는 경우가 종종 있다. 이런 이유로 함수형 프로그래밍에서는 데이터는 절대 변하지 않고, 데이터를 변경하려면 반드시 (원래 데이터는 그대로 둔 채) 변경하려는 값에 해당하는 복사본을 만들어서 반환한다는 개념을 기본으로 삼고 있다.
무분별한 데이터 수정에 따른 위험을 줄이는 방법은 얼마든지 있다. 가령 변수를 캡슐화하여 정해놓은 함수를 거쳐야만 값을 수정할 수 있도록 하면 값이 어떻게 수정되는지 감시하거나 코드를 개선하기 쉽다. 하나의 변수에 용도가 다른 값들을 저장하느라 값을 갱신 하는 경우라면 변수를 쪼개 용도별로 독립 변수에 저장하여 값 갱신에 문제를 일으킬 여지를 없앤다. 갱신 로직은 다른 코드와 떨어뜨려 놓는 것이 좋다. 가능한 한 세터 함수들을 제거한다. 간혹 세터를 호출하는 클라이언트를 찾는 것만으로도 변수의 유효 범위를 줄이는 데 도움될 때가 있다.
값을 다른 곳에서 설정할 수 있는 가변 데이터가 풍기는 악취는 특히 고약하다. 혼동과 버그와 야근을 부를 뿐만 아니라, 쓸데없는 코드이기도 하다.