코드에서 나쁜 냄새가 나면 리팩터링할 시간.
당신은 당신이 뭘 모르는지 모른다.
→ 어떤 코드가 나쁜 코드인지 모르면, 어떻게 개선해야 하는지도 모름.
→ 리팩터링이 필요한 코드들의 일정한 패턴(악취!)이 있어서 이를 알아야 함.
1. 기이한 이름 Mysterious Name
코드는 단순하고 명료하게 작성해야 함.
코드를 명료하게 표현하는데 가장 중요한 요소 중 하나는 '이름'.
마땅한 이름이 떠오르지 않는다면 설계에 근본적인 문제가 숨어 있을 가능성이 높음.
각각의 이름이 정확히 무얼하는지 이해하기 어렵거나, 구현사항이 이름과 맞지 않은 경우 코드 이해력, 가독성이 떨어짐.
→ 함수, 모듈, 변수, 클래스 명은 그 이름만 봐도 각각 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있도록 엄청나게 신경을 써서 이름을 지어야 함.
2. 중복 코드 Duplicated Code
똑같은 코드 구조가 여러곳에서 반복된다면 하나로 통합하여 더 나은 프로그램으로 만들 수 있음.
코드가 중복되면 볼때마다 서로 차이점은 없는지 주의 깊게 살펴봐야 함. 중복되는 부분을 함수로 만들고 차이나는 부분을 인자로 만들어 리팩터링 함.
→ 조금씩 비슷한 코드가 여러번 반복되는 경우 실수와 에러가 발생할 확률이 높음. 해당 로직에서 문제가 생기면 수정해야 할 곳이 여러 곳이기 때문에 개발을 이어가기 어려움.
3 긴 함수 Long Function
오랜 기간 잘 활용된 프로그램들은 하나 같이 짧은 함수로 구성됨.
간접 호출의 효과. 즉 코드를 이해하고 공유하고 선택하기 쉬워진다는 장점은 함수를 짧게 구성할 때 나오는 것.
✨짧은 함수로 구성된 코드는 이해하기 싶고 이를 만드는 가장 확실한 방법은 좋은 이름. 함수 이름을 잘 지어두면 본문 코드를 볼 이유가 사라짐 → 짧은 함수일 때 좋은 이름을 지정해줄 수 있음.
✨훨씬 적극적으로 함수를 쪼개야 함. 함수로 묶는 코드는 여러 줄일 수도, 단 한 줄 일 수도 있음. 원래 코드보다 길어지더라도 함수로 뽑음.
함수 이름에 '코드의 목적'을 잘 드러내야 함. 함수의 길이가 아닌 함수 목적과 구현 코드의 괴리가 얼마나 큰지 봐야 함.
주석에서 설명하는 코드와 함께 함수로 빼내고 함수이름은 주석내용을 토대로 지음. 코드가 단 한 줄이어도 따로 설명할 필요가 있다면 함수로 추출하는 것이 좋음.
반복문도 그 안에 코드와 추출해서 독립된 함수로 만듬.
4. 긴 매개변수 목록 Long Parameter List
함수로 필요한 변수를 모두 매개변수로 전달. 매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 생기고 그로 인해 함수를 사용하기 어려워지며 잦은 실수가 생길 수 있음.
5. 전역 데이터 Global Data✨
전역 데이터는 주의해야 함. 악취 중 가장 지독한 축. 유령 같은 원격 작용 → 유령같은 버그가 출몰
(전역 데이터의 대표적 형태는 전역 변수지만, 클래스 변수, 싱글톤에서도 같은 문제가 발생)
접근자 함수들을 함수나 모듈에 집어넣고 그 안에서만 사용할 수 있도록 접근 범위를 최소로 줄이는 것이 좋음.
무엇이든 많이 복용하면 독이 될 수 있는데 이는 전역 데이터에 고스란히 적용됨. 전역 데이터가 아주 조금만 있더라도 '캡슐화'를 하는 것이 좋음. 그래야 소프트웨어가 진화하는 데에 따른 변화를 대처할 수 있음.
6. 가변 데이터 Mutable Data
데이터를 변경했더니 예상치 못한 결과나 골치 아픈 버그가 발생할 수 있음.
'함수형 프로그래밍'에서는 데이터는 절대 변하지 않고 데이터를 변경하면 반드시 변경하려는 값에 해당하는 복사본을 만들어서 반환한다는 개념을 기본으로 삼고 있음.
7. 뒤엉킨 변경 Divergent Change
✨코드를 수정할 때 시스템에서 고쳐야 할 딱 한 군데를 찾아 그 부분만 수정할 수 있기를 바람.
→ 한 모듈에서 한 가지 일만 한다면 고쳐야 할 부분만 찾아서 수정할 수 있음
→ 단일 책임 원칙(SRP)
8. 산탄총 수술 Shotgun Surgery
코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때 풍기는 냄새.
9. 기능 편애 Feature Envy
'모듈화'할 때는 코드를 여러 영역으로 나눈 뒤 영역 안에서 이뤄지는 상호작용은 최대한 늘리고 영역 사이에서 이뤄지는 상호작용은 최소로 줄이는데 주력해야 함.
가장 많은 데이터를 포함한 모듈로 옮김.
뒤엉킨 변경 냄새를 없앨 때 활용하는 패턴 → 전략 패턴, 방문자 패턴, 자기 위임.
✨‘함께 변경할 대상을 한 곳에 모으는 것’
10. 데이터 뭉치 Data Clumps
서로 어울려 노는 것을 좋아함. 몰려다니는 데이터 뭉치는 보금자리를 따로 마련해주어야 함.
→ 데이터 뭉치를 클래스로 추출. 클래스를 이용하면 좋은 클래스를 흩뿌릴 기회가 생기기 때문. 상당한 중복을 없애고 향후 개발의 가속화는 유용한 클래스를 탄생시키는 결과. 생산성에 기여하는 클래스가 될 수 있음.
11. 기본형 집착 Primative Obsession
순수 데이터, 순수 기본형에 집착하지 말고 자신에게 주어진 문제에 딱 맞는 기초타입(화폐, 좌표, 구간 등)을 직접 정의할 것.
12. 반복되는 스위치문 Repeated Switches
조건부 로직을 다형성으로 바꾸기.
중복된 switch문이 문제가 되는 이유는 조건절을 하나 추가할 때마다 다른 switch문들도 모두 찾아 수정해야 하기 때문.
→ '다형성'은 반복되는 switch문의 사악한 기운을 제압하여 코드베이스를 최신 스타일로 바꿔주는 세련된 무기.
(Swift에선 상속, Generic, Protocol, associatedtype이 무기)
13. 반복문 Loops
절차형 코드는 Side Effect가 일어날 수 있음.
→ 함수형 프로그래밍 식의 반복 파이프라인(filter, reduce, map)으로 바꾸기를 적용하여 시대에 맞지 않는 반복문을 제거.
14. 성의 없는 요소 Lazy Element
본문 코드를 그대로 쓰는 것과 다름없는 함수. 실질적으로 메서드가 하나뿐인 클래스, 인터페이스 등. 이런 프로그램 요소는 제거해야 함.
15. 추측성 일반화 Speculative Generality
나중에 필요할지도 모르는 미래를 대비해 작성한 코드는 '쓸데없는 낭비' 일뿐.
→ You Ain’t Gonna Need It!
16. 임시 필드 Temporary Field
임시 필드를 많이 사용하게 되면 특정상황에서만 사용하게 되어 흐름이 끊기고 코드 이해도가 떨어지게 됨.
17. 메세지 체인 Message Chains
A를 하고 결과값이 나오면 다음에 B하고 C하는 식으로 다른 객체를 요청하는 작업이 연쇄적으로 이어지는 코드.
클라이언트가 객체 내비게이션 구조에 종속되었음을 의미. 개발자가 순서를 다르게 사용함으로써 에러가 발생하게 될 수도 있음.
18. 중개자 Middle Man
객체의 대표적인 기능 하나로 외부로부터 세부사항을 숨겨주는 '캡슐화'. 캡슐화하는 과정에서는 위임이 자주 활용되는데 지나치면 문제가 됨. 클래스가 제공하는 메서드 중 절반에 다른 클래스의 위임하고 있다면 중개자 문제가 발생.
19. 내부자 거래 Insider Trading
모듈 내부에서 너무 외부의 모듈을 참조하거나 의존(데이터 거래)하면 모듈 간 결합도가 높아짐. 상속 구조에서는 부모자식 사이에 결탁이 생길 때가 있음.
20. 거대한 클래스 Large Class
한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 드러나게 됨.
클래스의 필드가 너무 많으면 중복 코드가 생기기 쉬움. 뒤엉킨 변경이 생길 수 있음.
21. 서로 다른 인터페이스의 대안 클래스들 Alternative Classes with Different Interfaces
하고 있는 내용들(함수 호출 방식, 전달인자 등)은 다른데 하고 있는 일들은 비슷해보이는 클래스들.
→ 대체 불가능해져 재사용성이 떨어지게 됨.
→ 비슷한 일을 한다면 공통된 인터페이스를 공통된 규격으로 하는 것이 중요.
→ 클래스를 사용할 때 큰 장점은 필요에 따라 언제든 다른 클래스로 교체할 수 있다는 것. 단 교체하려면 인터페이스가 같아야 함. 시그니쳐를 일치시켜야 함. 인터페이스가 같아질 때까지 필요한 동작들을 클래스 안으로 밀어 넣음.
22. 데이터 클래스 Data Class
데이터 뭉치와 유사. 데이터 클래스란 데이터 필드와 getter, setter 메서드만으로 구성된 클래스. 그저 데이터 저장 용도로만 쓰이다 보니 다른 클래스가 너무 깊이까지 함부로 다룰 때가 많음. 클라이언트 코드를 데이터 클래스로 옮기기만 해도 대폭 개선됨.
23. 상속 포기 Refused Bequest
부모클래스를 상속해서 재사용하지만 사실 불필요한 부모클래스의 내용이 많거나, 부모의 코드는 필요한데 조금 다른 인터페이스가 필요한 경우 상속을 포기해야 하는 경우가 생김. 계층 구조를 잘못 설계했기 때문. 상속 포기 냄새는 서브클래스가 부모의 동작은 필요로 하지만 인터페이스는 따르고 싶지 않을 때 특히 심하게 냄새가 남.
→ 피할 수 있는, 대체할 방법을 생각해 보기. 상속으로 인해 확장성이 떨어지거나 유지보수하기 어려워지는 경우가 생김.
→ 서브클래스를 위임(혹은 composition)으로 바꾸거나 슈퍼클래스를 위임으로 바꾸기를 활용하여 아예 '상속 메커니즘'에서 벗어나게 해야 함.
24. 주석 Comment
주석은 악취가 아닌 향기를 입힘. 문제는 주석을 탈취제처럼 사용하는 데에 있음(남용). 나쁜 코드를 덮어씌우려는 주석은 나쁜 주석. 주석이 많으면 온갖 악취를 풍기는 코드가 나오기 쉬움.
✨주석을 남겨야겠다는 생각이 들면 가장 먼저 주석이 필요 없는 코드로 리팩터링 해보기.