리팩터링 2판 정리- 코드에서 나는 악취(2)
03 코드에서 나는 악취
1. 기이한 이름
- 함수, 모듈, 변수, 클래스 등은 그 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는지 명확이 알 수 있도록 엄청나게 신경 써서 이름을 지어야 한다.
- 마땅한 이름이 떠오르지 않는다면 설계에 더 근본적인 문제가 숨어 있을 가능성이 높다.
2. 중복 코드
- 똑같은 코드 구조가 여러 곳에서 반복된다면 하나로 통합하여 더 나은 프로그램을 만들 수 있다.
3. 긴 함수
- 경험에 비춰보면 오랜 기간 잘 활용되는 프로그램들은 하나같이 짧은 함수로 구성됐다.
- 간접호출의 효과, 즉 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점은 함수를 짧게 구성할 때 나오는 것이다.
- 짧은 함수로 구성된 코드를 이해하기 쉽게 만드는 가장 확실한 방법은 좋은 이름이다.
- 함수 이름을 잘 지어두면 본문 코드를 볼 이유가 사라진다.
- 적극적으로 함수를 쪼개야 한다. 주석을 달아야 할 만한 부분은 무조건 함수로 만든다.
- 심지어 원래 코드보다 길어지더라도 함수로 뽑는다.
- 핵심은 함수의 길이가 아닌, 함수의 목적과 구현코드의 괴리가 얼마나 큰가다.
- 추출한 반복문 코드에 적합한 이름이 떠오르지 않는다면 성격이 다른 두 가지 작업이 섞여 있기 때문일 수 있다.
4. 긴 매개변수 목록
- 매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많다.
5. 전역 데이터
- 전역 데이터는 코드베이스 어디에서든 건드릴 수 있고 값을 누가 바꿨는지 찾아낼 메커니즘이 없다는게 문제다.
- 다른 코드에서 오염시킬 가능성이 있는 데이터를 발견할 때마다 변수 캡슐화하기를 가장 먼저 적용
- 접근자 함수들을 클래스나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위를 최소로 줄이는 것도 좋다.
- 전역 데이터가 아주 조금만 있더라도 캡슐화하는 편이다. 그래야 소프트웨어가 진화하는 데 따른 변화에 대처할 수 있다.
6. 가변 데이터
- 변수 캡슐화하기를 적용하여 정해놓은 함수를 거쳐야만 값을 수정할 수 있도록 하면 값이 어떻게 수정되는지 감시하거나 코드를 개선하기 쉽다.
- 변수의 유효범위가 단 몇줄이라면 가변 데이터라 해도 문제를 일으킬 일이 별로 없다. 하지만 나중에 유효범위가 넓어질 수 있고, 그러면 위험도 덩달아 커진다.
7. 뒤엉킨 변경
- 뒤엉킨 변경은 단일책임원칙(SRP)이 재대로 지켜지지 않을 때 나타난다.
8. 산탄총 수술
- 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을때
- 작은 함수와 클래스에 지나칠 정도로 집착하지만, 코드를 재구성하는 중간 과정에서는 큰 덩어리로 뭉쳐지는데 개의치 않는다.
9. 기능 편애
- 프로그램을 모듈화할 때는 코드를 여러 영역으로 나눈 뒤 영역 안에서 이뤄지는 상호작용은 최 대한 늘리고 영역 사이에서 이뤄지는 상호작용은 최소로 줄이는 데 주력한다.
- 기능 편애는 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 상호작용 할 일이 더 많을 때 풍기는 냄새다.
- 가장 기본이 되는 원칙은 '함께 변경할 대상을 한데 모으는 것'이다.
10. 데이터 뭉치
- 기능 편애를 없애는 과정에서 새로운 클래스를 만들었다면, 이어서 그 클래스로 옮기면 좋을 동작은 없는지 살펴보자. 이러한 연계 과정은 종종 상당한 중복을 없애고 향후 개발을 가속하는 유용한 클래스를 탄생시키는 결과로 이어지기도 한다.
11. 기본형 집착
- 프로그래머 중에는 자신에게 주어진 문제에 딱 맞는 기초 타입을 직접 정의하기를 몹시 꺼리는 사람이 많다.
12. 반복되는 switch문
- 중복된 switch문에 문제가 되는 이유는 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아서 함께 수정해야 하기 때문이다. 이럴 때 다형성은 반복된 switch문이 내뿜는 사악한 기운을 제압하여 코드베이스를 최신 스타일로 바꿔주는 세련된 무기인 셈이다.
13. 반복문
- 필터나 맵같은 파이프라인 연산을 사용하면 코드에서 각 원소들이 어떻게 처리되는지 쉽게 파악할 수있다.
14. 성의 없는 요소
- 우리는 코드의 구조를 잡을때 때 프로그램 요소를 이용하는 걸 좋아한다. 사정이 어떠하든 이런 프로그램 요소는 고이 보내드리는 게 좋다.
15. 추측성 일반화
- '나중에 필요할 거야'라는 생각으로 당장은 필요 없는 모든 종류의 후킹 포인트와 특이 케이스 처리 로직을 작성해둔 코드에서 풍긴다.
- 추측성 일반화는 테스트 코드 말고는 사용하는 곳이 없는 함수나 클래스에서 흔히 볼 수 있다.
이런 코드를 발견하면 테스트 케이스부터 삭제한 뒤에 죽은 코드 제거하기로 날려버리자.
16. 임시 필드
- 특정 상황에서만 값이 설정되는 필드를 가진 클래스도있다. 하지만 객체를 가져올 때는 당연히 모든 필드가 채워져 잇으리라 기대하는 게 보통이다. 이렇게 임시필드를 갖도록 작성하면 코드를 이해하기 어렵다.
17. 메시지 체인
- 메시지 체인은 클라이언트가 한 객체를 통해 다른 객체를 얻은 뒤 방금 얻은 객체에 또 다른 객체를 요청하는 식으로, 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드를 말한다.
18. 중개자
- 객체의 대표적인 기능 하나로, 외부로부터 세부사항을 숨겨주는 캡슐화가 있다. 캡슐화하는 과정에서는 위임이 자주 활용된다.
- 지나치면 문제가 된다. 클래스가 제공하는 메서드 중 절반이 다른 클래스에 구현을 위임하고 있다면 어떤가? 이럴 때는 중개자 제거하기를 활용하여 실제로 일을 하는 객체와 직접소통하게 하자.
19. 내부자 거래
- 상속 구조에서는 부모 자식 사이에 결탁이 생길 때가 있다. 자식 클래스는 항상 부모 클래스가 공개하고 싶은 것 이상으로 부모에 대해 알려고 한다. 푸모 품을 떠나야 할 때가 온다면 서브클래스 위임으로 바꾸기나 슈퍼클래스를 위임으로 바꾸기를 활용하자.
20. 거대한 클래스
- 한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어난다. 그리고 클래스에 필드가 너무 많으면 중복 코드가 생기기 쉽다.
- 필드가 너무 많은 클래스와 마찬가지로 코드량이 너무 많은 클래스도 중복 코드와 혼동을 일으킬 여지가 크다. 간단한 해법은 그 클래스 안에서 자체적으로 중복을 제거하는 것이다.
21. 서로 다른 인터페이스의 대안 클래스들
- 함수 옮기기를 이용하여 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어 넣는다. 그러다 대안 클래스들 사이에 중복 코드가 생기면 슈퍼클래스 추출하기를 적용할지 고려해본다.
22. 데이터 클래스
- 데이터 클래스란 데이터 필드와 게터/세터 메서드로만 구성된 클래스를 말한다.
- 데이터 저장 용도로만 쓰이다 보니 다른 클래스가 너무 깊이까지 함부로 다룰 때가 많다.
- 이런 클래스에 public 필드가 있다면 누가 보기 전에 얼른 레코드 캡슐화하기로 숨기자. 변경하면 안되는 필드는 세터 제거하기로 접근을 원천 봉쇄한다.
23. 상속 포기
- 수많은 유산중에서 관심 있는 몇 개만 받고 끝내려는 경우는 언제든지 있을 수 있다.
- 먼저 같은 계층에 서브클래스를 하나 새로 만들고, 메서드 내리기와 필드 내리기를 활용해서 물려받지 않을 부모 코드를 모조리 새로 만든 서브클래스로 넘긴다.
24. 주석
- 문제는 주석을 탈취체처럼 사용하는 데 있다. 주석이 장황하게 달린 원인이 코드를 잘못 작성 했기 때문인 경우가 의외로 많다.
- 주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링해본다.
참고
리팩터링 2판