Chapter 02. 리팩터링 원칙
💡 리팩터링의 정의
✏️ 리팩터링
[명사] 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
- 동작을 보존하는 작은 단계들을 거쳐 코드를 수정하고 이러한 단계들을 순차적으로 연결하여 변화를 만들어 내는 일
- 리팩터링하는 동안에는 코드가 항상 정상 작동하기 때문에 전체 작업이 끝나지 않았더라도 언제든 멈출 수 있다
- 성능 최적화와 비슷하다. 둘 다 코드를 변경하지만 리팩터링의 목적은 코드를 이해하고 수정하기 쉽게 만드는 것이기 때문에 성능이 좋아질 수도, 나빠질 수도 있는 반면, 성능 최적화는 오로지 속도 개선에만 신경을 쓴다
🎩 두 개의 모자
- 기능을 추가할 때는 기존 코드를 건드리지 않고 새 기능을 추가하기만 한다
- 리팩터링할 때는 기능 추가 없이 코드 재구성만을 한다
- 무슨 모자를 쓰고 작업하고 있는지 분명하게 하자
👀 리팩터링하는 이유
- 설계가 좋아진다
- 코드만으로 설계를 파악하기 어려워질수록 유지하기 어려워지고, 설계가 부패되는 속도는 더욱 빨라진다
- 중복코드를 제거하면 예측 가능한 고유한 일을 수행하고 있음을 보장할 수 있게 된다
- 이해해야 할 코드량이 줄게 된다
- 이해하기 쉬워진다
- 개발자(우리 모두)에게 더 잘 읽히는 코드
- 코드만 봐도 내용을 쉽게 알 수 있는 코드
- 버그를 찾기 쉬워진다.
- “난 뛰어난 프로그래머가 아니에요. 단지 뛰어난 습관을 지닌 괜찮은 프로그래머일 뿐이에요” - 켄트 벡
- 프로그래밍 속도가 높아진다
- 모듈화와 명확한 설계, 좋은 품질의 베이스 코드는 새로운 기능을 추가하기 쉽게 만든다
- 또한 이러한 점은 모두 품질 향상에 직결된다
- 리팩터링을 통해 계속해서 설계를 개선하고, 좋은 상태를 유지하게 할 수 있다
- 설계 지구력 가설 (Design Stamina Hypothesis)
🤔 언제 리팩터링해야 할까?
3의 법칙
1. 일단 그냥 한다.
2. 비슷한 일을 두 번째로 하게 되면 중복이 생기지만 일단 계속 진행한다.
3. 비슷한 일을 세 번째 하게 되면 리팩터링한다.
- 코드베이스에 기능을 새로 추가하기 직전이 리팩터링하기 가장 좋은 시점이다
- 필요한 코드가 비슷하게 존재한다고 복제하지 말고 차라리 리팩터링해 사용하자
- 코드를 파악 또는 수정할 때 리팩터링한다.
- 긴 함수 잘게 나누기
- 역할이 이해된 변수는 적절한 이름으로 바꿔주기
- 내가 이해한 것을 코드에 반영해두면 더 오래 보존할 수 있을 뿐만 아니라 동료들도 알 수 있다
- 밖을 잘 내다보기 위한 ‘창문 닦기’ - 랄프 존슨
- 쓰레기 줍기 리팩터링
- 간단히 수정할 수 있는 것은 즉시 고치기
- 시간이 좀 걸리는 일은 짧은 메모만 남긴 다음 이후에 처리하기
- 항상 처음 봤을 때보다 깔끔하게 정리하고 떠나기 (Boy scout rules for camping)
- 계획된 리팩터링과 수시로 하는 리팩터링
- 프로그래밍 과정에 자연스럽게 녹인 듯 리팩터링하기
- 수시로, 드러나지 않게, 기회가 될 때마다
- 오래 걸리는 리팩터링
- 팀 전체가 리팩터링에 매달리기보다는 몇 주에 걸쳐 조금씩 해결해나가는 편이 효과적일 때가 많다
- 코드 리뷰에 리팩터링 활용하기
- 리팩터링은 코드 리뷰의 결과를 더 구체적으로 도출하는 데 도움된다
- 작성자와 참석자가 함께 코드를 훑어가면서 리팩터링하는 짝 프로그래밍 (pair programming) 방식 추천
- 관리자에게 뭐라고 말해야 할까?
- 개발자에게 주어진 임무는 새로운 기능을 빠르게 구현하는 것이고, 가장 빠른 방법은 리팩터링이다.
- 리팩터링하지 말아야 할 때
- 아직 세세하게 이해가 안 된, 자주 쓰는 코드
- 새로 작성하는 게 나아 보이는 코드
🔥 리팩터링 시 고려할 문제
리팩터링의 궁극적인 목적은 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것이다.
- 리팩터링은 경제적인 이유로 한다
- 리팩터링은 개발 기간을 단축하고자 하는 것이다
- 코드 소유권은 개인보다는 팀 단위가 좋다
- 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있게 한다
- 브랜치는 자주 통합되어야 리팩터링에 좋다
- 성공/실패 여부를 판단하는 테스트 코드 작성
- 레거시 코드가 있다면 테스트 작성부터
- 테스트 코드가 없다면 그것은 레거시 코드다
🦾 리팩터링, 아키텍쳐, 애그니 (YAGNI)
- 유연성 메커니즘 (Flexibility Mechanism)
- 향후 변경에 유연하게 대처할 수 있도록 범용적으로 작성
- 하지만 이는 요구사항의 변화, 메커니즘 결함 등으로 오히려 변화에 대응하는 능력을 떨어뜨릴 수 있다
- 그저 현재 파악한 요구사항만 멋지게 설계하고 해결하자
- 진행하면서 요구사항을 알게 된다면 그에 맞게 리팩터링하자
- 리팩터링을 미루면 훨씬 힘들어진다는 확신이 들 때만 유연성 메커니즘을 미리 추가한다
- 이런 식으로 설계하는 방식을 간결한 설계 (Simple Design), 점진적 설계 (Incremental Design), YAGNI (you aren't going to need it) 등으로 부른다
- 진화형 아키텍쳐 (Evolutionary Architecture)
- 아키텍쳐 관련 결정을 시간을 두고 반복해 내릴 수 있다는 장점을 활용하는 패턴과 실천법을 추구한다
🛠️ 리팩터링과 소프트웨어 개발 프로세스
- 익스트림 프로그래밍(XP)이란, 지속적 통합(CI), 자가 테스트 코드, 리팩터링 등의 기법들을 묶은 프로세스다
- 자가 테스트 코드와 리팩터링을 묶어서 테스트 주도 개발(TDD)라 칭한다
- 리팩터링의 첫 번째 토대는 자가 테스트 코드다
- 프로그래밍 도중 발생한 오류를 확실히 걸러내는 테스트를 자동으로 수행할 수 있어야 한다
- CI를 통해 팀원간의 코드가 계속 통합되어야 높은 효과가 있다
- 자가 테스트 코드 + CI + 리팩터링 => 애그니 가능
- 자가 테스트 코드 + CI + 리팩터링 => 배포 주기를 짧게 가져갈 수 있도록 만들어준다
🏌🏻 리팩터링과 성능
- 리팩터링을 하면 성능을 좋게 만들기 쉬워진다
- 섣불리 추측하지 말고 실제로 무슨 일이 벌어지고 있는지 성능을 측정해봐야 한다
- 대부분 프로그램은 극히 일부에서 대부분의 시간을 소비한다. 때문에 코드 전체를 고르게 최적화하는 것은 시간 낭비일 수 있다.
- 프로그램을 잘 리팩터링해두면 최적화에 두 가지 면에서 도움이 된다
- 성능 튜닝에 투입할 시간을 벌 수 있다
- 성능을 더 세밀하게 분석할 수 있다
Chapter 03. 코드에서 나는 악취
1. 기이한 이름
- 이름만 잘 지어도 나중에 문맥을 파악하느라 헤매는 시간을 크게 절약할 수 있다.
- 함수 선언 바꾸기, 변수 이름 바꾸기, 필드 이름 바꾸기
2. 중복 코드
- 똑같은 코드 구조가 반복된다면 하나로 통합해 더 나은 프로그램을 만들 수 있다.
- 함수 추출하기, 문장 슬라이드하기, 메서드 올리기
3. 긴 함수
- 짧은 함수는 코드를 이해하고, 공유하고, 선택하기 쉽다.
- 함수 이름을 잘 지어두면 굳이 내용을 보지 않아도 된다.
- 함수 이름은 동작 방식이 아닌 의도(목적)가 드러나게 짓는다.
- 함수 추출하기, 임시 변수를 질의 함수로 바꾸기, 매개변수 객체 만들기 등.
4. 긴 매개변수 목록
- 매개변수 목록이 길어지면 그 자체로 이해하기 어려울 때가 많다.
- 매개변수를 질의 함수로 바꾸기, 객체 통째로 넘기기, 매개변수 객체 만들기 등
5. 전역 데이터
- 캡슐화
6. 가변 데이터
- 변수 캡슐화하기
- 용도가 다른 하나의 변수는 쪼개서 사용하기
- 파생 변수를 질의 함수로 바꾸기, 질의 함수와 변경 함수 분리하기 등
7. 뒤엉킨 변경
- 단일 책임 원칙 (Single Responsibility Principle)이 제대로 지켜지지 않을 때 나타난다.
- 단계 쪼개기, 함수 추출하기 등 맥락별로 구분하기
8. 산탄총 수술
- 변경할 부분이 코드 전반에 퍼져 있다면 찾기도 어렵고 꼭 수정해야 할 곳은 지나치기 쉽다.
- 함수 옮기기, 필드 옮기기, 여러 함수를 변환 함수로 묶기 등.
9. 기능 편애
- 흔히 어떤 함수가 자기가 속한 모듈의 함수나 데이터보다 다른 모듈의 함수나 데이터와 더 많은 상호작용을 할 때 생긴다.
- 데이터 근처로 함수 옮기기, 함수 추출하기 등.
10. 데이터 뭉치
- 데이터는 뭉쳐 다니는 경향이 있다.
- 클래스 추출하기, 매개변수 객체 만들기, 객체 통째로 넘기기 등.
11. 기본형 집착
- 기본형에 집착하지 말고 객체나 클래스로 만들어 사용하자.
12. 반복되는 switch문
- 중복된 switch문이 생길 경우 다른 조건절도 모두 찾아 함께 수정해야 하는 문제가 있다.
- 조건부 로직을 다형성으로 바꾸기
13. 반복문
- 반복문보다는 filter, map 등의 파이프라인 연산을 이용하자.
14. 성의 없는 요소
- 구조를 잡기 위해 사용된 프로그램 요소가 필요 없다면 인라인으로 처리해주자.
15. 추측성 일반화
- ‘나중에 필요할 거야' 라는 생각으로 작성한 당장 필요 없는 로직과 후킹 포인트가 문제
- 계층 합치기, 인라인하기, 함수 선언 바꾸기, 죽은 코드 제거하기 등.
16. 임시 필드
- 클래스의 특정 필드가 특정 상황에서만 값이 설정되는 경우
- 클래스 추출하기, 함수 옮기기 등
17. 메세지 체인
- 객체를 통해 다른 객체를 얻는 과정이 연쇄적으로 이어질 때 발생
- 위임 숨기기, 함수 추출하기, 함수 옮기기
18. 중개자
- 중개자 제거하기를 통해 실제 일을 하는 객체와 클래스가 직접 소통하도록 바꿔주자.
19. 내부자 거래
- 모듈 사이의 데이터 거래가 많으면 결합도가 높아진다.
- 적절하게 함수와 필드를 옮겨서 결합도를 낮춰보자.
- 여러 모듈이 같은 관심사를 공유한다면 공통 부분을 정식으로 처리하는 제3의 모듈을 만들자.
20. 거대한 클래스
- 한 클래스가 너무 많은 일을 하려다 보면 필드 수가 상당히 늘어난다.
- 그리고 클래스에 필드가 너무 많으면 중복 코드가 생기기 쉽다.
- 코드량이 많다면 중복을 제거하고 작은 메서드로 추출하자.
21. 서로 다른 인터페이스의 대안 클래스들
- 서로 다른 클래스의 두 메서드가 하는 일은 비슷하지만 인터페이스가 다른 경우, 이를 통일시키고 가능하면 추출하자.
22. 데이터 클래스
- 필드를 캡슐화하고, 변경하지 못하는 필드는 세터를 제거하자.
23. 상속 포기
- 부모 클래스의 특정 부분을 상속받기 원치 않는 경우 발생
- 상속하지 않을 부모 코드를 따로 분리해 공통 부분만 남기도록 한다.
24. 주석
- 주석을 남겨야겠다는 생각이 들면, 가장 먼저 주석이 필요 없는 코드로 리팩터링해본다.
- 주석은 탈취제가 아니다.
Chapter 04. 테스트 구축하기
👍🏻 자가 테스트 코드의 가치
- 모든 테스트를 완전히 자동화하고 그 결과까지 스스로 검사하게 만들자.
- 테스트 코드가 있으면 회귀 버그, 즉 새로운 기능 추가로 뜻하지 않게 잘 동작하던 기능에 생긴 버그를 즉시 찾아낼 수 있다.
- 테스트 코드로 스펙 정리 →구현(코딩) →테스트 →리팩터링 = TDD
❗️ 테스트 시 유의할 점
- 실패해야 할 상황에서는 반드시 실패하게 만들자.
- 일시적으로 오류를 주입해보기
- 자주 테스트하라. 작성 중인 코드는 최소한 몇 분 간격으로 테스트하고, 적어도 하루 한 번은 전체 테스트를 돌려보자.
- 추가한 코드에 문제가 없는지
- 리팩터링하면서 실수한 것은 없는지
- 완벽하게 만드느라 테스트를 수행하지 못하느니, 불완전한 테스트라도 작성해 실행하는 게 낫다.
- 문제가 생길 가능성이 있는 경계 조건을 생각해보고 그 부분을 집중적으로 테스트하자.
- 어차피 모든 버그를 잡아낼 수는 없다고 생각하여 테스트를 작성하지 않는다면 대다수의 버그를 잡을 수 있는 기회를 날리는 셈이다.
- 버그 리포트를 받으면 가장 먼저 그 버그를 드러내는 단위 테스트부터 작성하자.
- 테스트 때문에 개발 속도가 느려진다고 생각되면 테스트를 과하게 작성한 건 아닌지 의심해보자.
- 테스트는 함수에서 가장 위험한 부분, 즉 오류가 생길 수 있는 부분에 집중한다.
반응형
'Programming > 13. Book' 카테고리의 다른 글
리팩터링 - 8, 9장 (0) | 2024.03.02 |
---|---|
리팩터링 - 5, 6장 (0) | 2024.03.02 |
모던 리액트 Deep Dive - 11장 (0) | 2024.03.02 |
모던 리액트 Deep Dive - 10장 (0) | 2024.03.02 |
모던 리액트 Deep Dive - 5장 (0) | 2024.03.02 |
댓글