냄새나면 당장 갈아라.
- 어떻게 하는지에 대해서 지금까지 배웠다.
- 하지만, "언제" 시작하고 그만할지를 판단하는 것은 또 다른 얘기다.
- 어떤 "냄새"를 맡았을 때 손을 대야할까?
기이한 이름
- 이름으로 이게 뭐하는 것인지 딱 볼 수 있게 작성해야 한다.
- 아쉽게도 이름 짓기는 프로그래밍에서 가장 어렵기로 손꼽히는 두 가지중 하나다.
- 이름이 제대로 안떠오르면 설계에 더 근본적인 문제가 있을 가능성이 높다.
중복 코드
- 코드가 중복되면 하나로 통합하자.
- 중복되면 각각을 볼 때마다 서로 차이점은 없는지 주의 깊게 살펴봐야 한다는 부담이 생긴다.
긴 함수
- 오랜기간 잘 동작하는 프로그램들은 짧은 함수의 다발로 구성되어 있다.
- 짧게 함수를 구성하면 코드를 이해하고, 공유하고, 선택하기 쉬워진다.
- 예전 언어에서는 서브루틴을 호출하는 비용이 커, 짧은 함수를 꺼렸다.
- 하지만, 요즘은 프로세스 안에서 함수 호출 비용이 거의 없다.
- 주석을 달 바에 함수를 쪼개라.
- 함수 이름에 의도를 담아라.
- 이런 추출의 방법은 워낙 많아 뒤쪽에 설명하도록 하겠다.
긴 매개변수 목록
- 상태값을 없애고 파라미터로 다 넘겨버리는게 나은 경우가 많다.
- 그런데 파라미터 개수가 계속해서 늘어나는 것을 그냥 두는 것도 문제다.
- 이런 경우 "매개변수를 질의 함수로 바꾸기", "객체 통째로 넘기기", "매개변수 객체 만들기" 등의 방법을 사용할 수 있다.
전역 데이터
- 전역데이터는 매우 주의해야 한다.
- 코드 베이스 어디에서든 건드릴 수 있어 누가 바꿨는지 찾아낼 메커니즘이 없다.
- 양자 얽힘과 같은 형태라 볼 수 있다. 멀리 떨어트려놓아도 상태를 변경할 수 있으니까.
가변 데이터
- 상태값을 두면 버그로 이어지는 경우가 종종있다.
- 이런 이유로 함수형 프로그래밍에서는 데이터는 불변하며, 복사본을 만들어 반환하는 개념을 기본으로 삼는다.
- 하지만 현실은 모두 함수형 프로그래밍을 적용하기 어렵다. 그래도 위험을 줄일 수는 있다.
- 메서드를 통해 수정할 수 있게 하는 등의 방법을 사용하자.
뒤엉킨 변경
- 코드를 수정할 때, 고쳐야 할 부분 하나를 갖고 고칠 수 있어야 한다.
- 이렇게 안되면 난장판이 일어날 것이다.
- 이런 문제는 SRP가 제대로 안지켜져서 그렇다.
- 뒤엉킨 변경은 무언가 추가될 때, 이에 영향받는 요소가 여러개인 상황을 말한다.
- 정확히 처리해야하는 부분을 분리함으로써 해결하자.
산탄총 수술
- 뒤엉킨 변경과 좀 다르다.
- 코드를 수정할 때마다 흩어져 있는 코드를 함께 수정해야 하는 경우다.
- 이런 경우도 한곳으로 모아서 처리하는 것이 좋다.
| 뒤엉킨 변경 | 산탄총 수술 |
---|
원인 | 맥락을 잘 구분하지 못함 | 맥락을 잘 구분하지 못함 |
해법(원리) | 맥락을 명확히 구분 | 맥락을 명확히 구분 |
발생 과정(현상) | 한 코드에 섞여 들어감 | 여러 코드에 흩뿌려짐 |
해법(실제 행동) | 맥락별로 "분리" | 맥락별로 "모음" |
기능 편애
- 모듈화를 한다면, 경계는 최소화하고, 내부에서 상호작용을 늘려야 한다.
- 그런데 내가 속한 데이터를 처리하는 것보다, 바깥쪽에서 들고와서 처리하는 게 많은 경우가 있다.
- 이런 경우를 기능 편애라 한다.
- 해결법은 쉽다. 들고와서 처리하는 친구들 바깥으로 빼내자.
데이터 뭉치
- 데이터는 뭉쳐다니는 경우가 흔하다.
- 이런 경우 하나로 관리하는 것이 좋다.
- 묶을 때도 클래스와 같은 구조를 선택하여, 해당 데이터 내에서 처리하는 것이 좋은 동작들을 묶어주자.
기본형 집착
- 원시 타입으로 그냥 매핑해두는 프로그래머가 흔하다.
- 내가 풀 문제에 딱 맞는 타입으로 정의하지 않는 사람이 흔하다.
- 이런 경우 아예 객체로 바꿔 의미 있는 자료형으로 바꾸자.
- 이는 문명사회로 이끄는 행위다.
반복되는 switch문
- 객체 지향을 선호하는 사람에게 switch문은 눈살을 찌푸리게 한다.
- 이제는 시대가 바뀌어서 무조건적으로 switch를 보면 바꿔! 라고 말하긴 그렇다.
- 하지만 반복적으로 나온다면 고민해보자.
- 중복된 switch문의 경우에는 case를 추가할 대마다 모두 수정해야 하기 때문이다.
- 이런 경우 다형성으로 바꾼다면 세련된 방식으로 처리할 수 있다.
반복문
- 이제는 일급함수로 바뀌어 버린 함수를 사용하여 파이프라인으로 많이 처리한다.
성의 없는 요소
- 클래스가 있긴 한데, 메서드가 하나인 경우
- 사용하는 것이 별로 의미 없는 경우.
- 이럴 때는 고이 보내주자.
추측성 일반화
- "나중에 필요할 거야"
- 만약에 사용하지 않으면 낭비일 뿐이다.
- 당장 필요없으면 지워버리자.
임시 필드
- 특정 상황에서만 값이 설정되는 필드를 가지는 경우도 있다.
- 예를 들면 10개의 필드가 있는데, A, B를 통해 생성할 수 있다.
- 그런데 A일 때와 B일 때만 각각 넣어지는 값이 있는 것이다.
- 이런 경우 사용자는 코드를 파악하기 어렵다.
- 클래스 추출하기를 통해 아예 분리시키는 것이 좋다.
메시지 체인
a.getB().getC().getD()
이런 식으로 작성된 코드를 말한다.
- 이런 경우 위임 숨기기 방법을 사용한다.
let managerName = person.department.manager.name
let managerName = person.department.managerName
let managerName = person.manager.name
let managerName = person.managerName
중개자
- 캡슐화하는 과정에서는 "위임"이라는 개념이 자주 사용된다.
- 책임을 이동시키는 것이다.
- 보통은 좋으나, 지나치면 문제가 된다.
- 만약 클래스가 제공하는 메서드가 10개인데, 이중 절반을 다른 클래스에게 구현을 위임하고 있다면?
- 이럴 경우 오히려 위임한 책임을 가져오는 것이 나을 수 있다.
내부자 거래
- 개발자는 모듈 사이에 벽을 두껍게 세우기를 좋아한다.
- 그래서 사이의 데이터 거래가 많으면 결합도가 높아진다 말한다.
- 이렇게 처리되는 것을 최소로 줄이고, 투명하게 변경해야 한다.
거대한 클래스
- 클래스에 필드수가 늘어난다면, 분리할 필요가 있다.
- 중복을 제거하면서, 책임을 분리하자.
서로 다른 인터페이스의 대안 클래스들
- 클래스를 사용하면 필요에 따라 다른 클래스로 교체할 수 있다.
- 단 교체하려면 인터페이스가 같아야 한다.
데이터 클래스
- 데이터 클래스는 필드와 게터, 세터만 가지는 클래스를 말한다.
- 단순 저장 용도로만 쓰이다보니 함부로 다루는 경우가 많다.
- 필드의 변경 통제에 관해 확실하게 관리하자.
상속 포기
- 서브 클래스는 부모로 부터 메서드와 데이터를 물려받는다.
- 하지만 모든 유산을 원치않는다면 어떻게 해야 할까?
- 이전에는 "계층 구조"를 잘못 설계했기 때문이라고 보았다.
- 이런 경우 물려받지 않을 코드들을 모조리 새로만든 서브 클래스로 넘겼다.
- 하지만 이제는 이 방법을 권하지 않는다.
- 이렇게 동작은 필요하지만 인터페이스는 따르고 싶지 않은 경우는, 아예 동작을 분리키는 방법을 사용하자.
주석
- 주석은 향기처럼 사용할 수 있다.
- 하지만 우리는 이걸 탈취제처럼 사용한다.
- 주석이 장황한 이유가 곧 코드를 잘못 작성했기 때문인 경우가 많다는 것이다.
주석을 남겨야겠다는 생각이 들면 가장 먼저 주석이 필요없는 코드로 리팩터링 해본다.
Reference