리팩터링 스터디 도중 리팩터링 2판를 읽고 요약한 내용입니다. 자세한 내용은 책을 구매하여 확인 부탁드립니다.
언제 리팩터링을 해야 하나
앞으로 후술될 문제점의 해결책은 책 뒷부분에서 자세히 다룰 것이므로 대략적인 해결 방안과 책에서 제시한 리팩터링 기법의 이름만 적어 놓도록 하겠습니다.
기이한 이름
- 상황
- 함수 / 모듈 / 변수 / 클래스 등의 이름만 보고, 어떤 일을 하는지 알기 힘들 때 나는 경우 입니다.
- 코드는 단순 명료하게 작성 되어야 합니다.
- 명료한 코드를 만드는데 가장 중요한 위치를 차지하는것이 올바른 이름 짓는 것 입니다.
- 마땅한 이름이 생각나지 않는다면, 설계에 더 근본적인 문제가 숨어 있을 가능성이 높습니다.
- 해결책
- 이름을 알맞게 바꾸는 작업을 진행 합니다.
- 함수 선언 바꾸기
- 변수 이름 바꾸기
- 필드 이름 바꾸기
- 이름을 알맞게 바꾸는 작업을 진행 합니다.
중복 코드
- 상황
- 똑같은 코드 구조가 여러곳에서 반복 되는 경우입니다.
- 코드 구조가 중복되면, 각각을 볼 때 마다 차이점은 없는지 주의깊게 보아야 하는 부담이 생깁니다.
- 하나를 변경 할 때에, 비슷한 구조의 다른 코드들도 모두 변경 해 주어야 합니다.
- 해결책
- 한 클래스에 딸린 두 메서드가 같은 표현식을 사용한다면
- 함수 추출하기
- 코드가 비슷하긴 한데, 완전히 똑같지는 않다면
- 문장 슬라이드 하기
- 같은 부모로 부터 파생된 서브클래스들에 코드가 중복되어 있다면
- 메서드 올리기
- 한 클래스에 딸린 두 메서드가 같은 표현식을 사용한다면
긴 함수
- 상황
- 하나의 함수가 지나치게 긴 영역을 차지 하는 경우에 해당됩니다.
- 함수가 길다는 것은, 해당 함수가 너무 많은 일을 하고 있는것은 아닌지 의심 해 보아야 합니다.
- 상황에 대한 팁
- 주석을 달아야 할 만한 부분을 모두 함수로 분리합니다.
- 코드가 단 한줄이라도 따로 설명할 필요가 있다면 함수로 추출하는것이 좋습니다.
- 해결책
- 함수를 짧게, 좋은 코드 덩어리로 만들기
- 함수 추출하기
- 매개변수, 임시변수가 너무 많아지는것 방지하기
- 임시 변수를 질의 함수로 바꾸기
- 매개변수 객체 만들기
- 객체 통째로 넘기기
- 여전히 임시 변수, 매개 변수가 너무 많은 경우
- 함수를 명령으로 바꾸기
- 조건문, 반복문을 리팩터링 하기
- 조건문 분해하기
- 조건문을 다형성으로 바꾸기
- 반복문 쪼개기
- 함수를 짧게, 좋은 코드 덩어리로 만들기
긴 매개변수 목록
- 상황
- 함수에 필요한 것들을 전무 매개변수로 전달하는데, 이 매개변수 목록이 너무 많은 경우에 해당됩니다.
- 전역 데이터를 적게 사용하기 위해, 매개변수를 많이 전달하는 경우가 잦아 져 해당 상황이 발생 할 수 있습니다.
- 해결책
- 다른 매개변수에서 값을 얻어올 수 있을때
- 매개변수를 질의 함수로 바꾸기
- 사용 중인 데이터 구조에서 값들을 뽑아 각각을 별개의 매개변수로 전달하는 경우라면
- 객체 통째로 넘기기
- 항상 함께 전달되는 변수가 있다면
- 매개변수 객체 만들기
- 함수의 동작 방식을 정하는 플래그 역할의 매개변수가 있다면
- 플래그 인수 제거하기
- 여러 개의 함수가 특정 매개변수의 값을 공통으로 사용하고 있다면
- 여러 함수를 클래스로 묶기
- 다른 매개변수에서 값을 얻어올 수 있을때
전역 데이터
- 상황
- 전역 데이터를 사용하는 경우에 해당됩니다.
- 전역변수는 코드베이스 어디에서든 건드릴 수 있고, 누가 값을 바꾸었는지 찾아 낼 메커니즘이 없는 것이 문제의 원인입니다.
- 싱글톤 클래스를 활용할 때에도 문제가 생길 수 있습니다.
- 특히, 전역 변수가 mutable 한 경우에 더 문제가 큽니다.
- 해결책
- 다른 코드에서 전역 변수를 변경 할 수 있는 경우
- 함수 캡슐화 하기
- 다른 코드에서 전역 변수를 변경 할 수 있는 경우
가변 데이터
- 상황
- 데이터의 값을 변경 할 수 있는 경우에 해당 됩니다.
- 데이터의 값이 변경됨으로써 원하지 않는 결과가 나올 수 있음을 유의하고 개발을 진행해야 합니다.
- 함수형 프로그래밍에서 처럼, 변수를 변경하는것은 불가능하고, 데이터를 변경하려면 데이터의 복사본을 변경하여 반환 하는 것이 제일 안전합니다.
- 해결책
- 정해놓은 함수를 거쳐야만 값을 수정 할 수 있도록 하기
- 변수 캡슐화 하기
- 하나의 변수에 용도가 다른 값들을 저장한다면 → 용도별로 독립 변수에 저장
- 변수 쪼개기
- 갱신 로직은 다른코드와 떨어뜨려놓기
- 문장 슬라이드 하기
- 함수 추출하기
- API를 만들 때
- 질의 함수와 변경 함수 분리하기
- 대부분의 경우
- setter 제거하기
- 파생 변수를 질의 함수로 바꾸기
- 변수의 유효 범위를 제한하기
- 여러 함수를 클래스로 묶기
- 여러 함수를 변환 함수로 묶기
- 내부 필드에 데이터를 담고 있는 변수라면 → 내부 필드를 직접 수정 하지 않고, 구조체를 통째로 교체하기
- 참조를 값으로 바꾸기
- 정해놓은 함수를 거쳐야만 값을 수정 할 수 있도록 하기
뒤엉킨 변경
- 상황
- 단일 책임의 원칙이 제대로 지켜지지 않는 경우에 해당됩니다.
- 하나의 모듈이 서로다른 이유들로 인해 여러가지 방식으로 변경되는 일이 많을 때 발생합니다.
- 즉, 한 코드에 여러 맥락이 섞인 경우에 발생합니다.
- 해결책
- 순차적으로 실행되는것이 자연스러운 맥락일 경우
- 단계 쪼개기
- 전체 처리 과정 곳곳에서 각기다른 맥락의 함수를 호출하는 경우가 잦을 때
- 함수 옮기기
- 여러 맥락의 일에 관여하는 함수가 있을 때
- 함수 추출하기
- 클래스 추출하기
- 순차적으로 실행되는것이 자연스러운 맥락일 경우
산탄총 수술
- 상황
- 코드를 변경 할 때마다 자잘하게 수정해야 하는 클래스가 많은 경우에 해당됩니다.
- 변경 할 부분이 코드 전반에 퍼져 있는 경우에 버그를 양산하기 쉽습니다.
- 여러 코드에 맥락이 흩뿌려진 경우에 발생합니다.
- 해결책
- 변경되는 대상들을 묶어놓기
- 함수 옮기기
- 필드 옮기기
- 비슷한 데이터를 다루는 함수가 많다면
- 여러 함수를 클래스로 묶기
- 데이터 구조를 변환하거나, 보강하는 함수에는
- 여러 함수를 변환 함수로 묶기
- 묶은 함수들의 출력 결과를 묶어 다음 단계의 로직으로 전달 할 수 있다면
- 단계 쪼개기
- 어설프게 분리된 로직 합치기
- 함수 인라인 하기
- 클래스 인라인 하기
- 변경되는 대상들을 묶어놓기
기능 편애
- 상황
- 어떤 함수가 자기가 속한 모듈의 함수나, 데이터 보다 다른 모듈의 함수나 데이터와 상호작용 할 일이 더 많은 경우에 해당됩니다.
- 실제로는, 영역 안에서 이루어 지는 상호작용은 최대로, 영역 사이에 이루어 지는 상호작용은 최소화 하여야 합니다.
- 외부 객체의 getter / setter 등을 많이 호출 하는 경우도 이에 해당됩니다.
- 해결책
- 특정 함수가 특정 데이터와 가까이 있고 싶어하는 의중이 뚜렷히 들어 난다면
- 함수 옮기기
- 함수의 일부에서만 특정 데이터와 가까이 있고 싶어 한다면
- 함수 추출하기
- 함수 옮기기
- 함수가 사용하는 모듈이 다양하여 어디로 옮길 지 명확하게 들어나지 않는 경우
- 함수 추출하기 → 추출된 함수들을 각각 적합한 모듈로 옮기기
- 특정 함수가 특정 데이터와 가까이 있고 싶어하는 의중이 뚜렷히 들어 난다면
데이터 뭉치
- 상황
- 항상 뭉쳐다니는 데이터 뭉치가 존재하는 경우에 해당합니다.
- 몰려다니는 데이터 뭉치를 분리 해 주어야 합니다.
- 상황 팁
- 데이터 뭉치로 의심되는 뭉치에서, 값 하나를 삭제 했을 때 나머지 데이터가 의미가 없다면 해당 데이터들은 데이터 뭉치입니다.
- 데이터 뭉치를 하나의 클래스로 묶어놓고, 그 클래스에 옮기면 좋은 동작은 없는 지 살펴보는것이 좋습니다.
- 해결책
- 필드 형태의 데이터 뭉치를 찾아 객체로 묶기
- 클래스 추출하기
- 메서드 시그니처에 있는 뭉치 해결하기
- 매개변수 객체 만들기
- 객체 통째로 넘기기
- 필드 형태의 데이터 뭉치를 찾아 객체로 묶기
기본형 집착
- 상황
- 알맞지 않은 데이터 타입을 사용하는 경우에 해당합니다.
- 화폐 데이터를 단순 숫자 형태로 사용하거나, 좌표 데이터를 단순 숫자로 사용하는 경우가 해당됩니다.
- 전화번호를 단순 문자열로 다루는 경우도 이와 마찬가지 입니다.
- 해결책
- 알맞은 데이터 타입을 생성해주기
- 기본형을 객체로 바꾸기
- 조건부 동작을 제어하는 타입 코드로 사용 된 경우
- 타입 코드를 서브 클래스로 바꾸기
- 조건부 로직을 다형성으로 바꾸기
- 자주 몰려다니는 기본형 그룹이 있다면
- 클래스 추출하기
- 매개변수 객체 만들기
- 알맞은 데이터 타입을 생성해주기
반복되는 switch문
- 상황
- switch문이 너무나 많이 사용되는 경우에 해당됩니다.
- 예전에는, 모두 다형성으로 switch문을 없애는 것이 강력히 권고되었지만, switch문 자체에 기능들이 추가되며 강제되고 있지는 않습니다.
- 중복된 switch문이 많다면, 하나의 switch문을 수정 할 때 나머지 switch문도 모두 동일하게 수정 해 주어야 오류 없이 잘 동작합니다.
- 해결책
- 중복된 switch문이 많이 존재하는 경우
- 조건부 로직을 다형성으로 바꾸기
- 중복된 switch문이 많이 존재하는 경우
반복문
- 상황
- 반복문이 사용되고 있는 경우에 해당합니다.
- 없애는 것이 최선입니다.
- 파이프라인으로 반복문을 처리하면, 각 원소들이 어떻게 처리되는지 파악하기 쉽습니다.
- 해결책
- 일급 함수를 지원하는 언어라면
- 반복문을 파이프라인으로 바꾸기
- 일급 함수를 지원하는 언어라면
성의 없는 요소
- 상황
- 의미가 없는 프로그래밍 요소 ( method, class, interface … ) 등이 존재하는 경우가 해당됩니다.
- 해결책
- 제거 할 수 있다면 제거
- 함수 인라인하기
- 클래스 인라인하기
- 상속을 사용 했다면
- 계층 합치기
- 제거 할 수 있다면 제거
추측성 일반화
- 상황
- ‘나중에 필요할 거야’ 라는 생각으로 당장은 필요 없는 모든 종류의 hooking point / 특이 케이스 로직을 작성 해둔 경우에 해당합니다.
- 지우는게 상책입니다.
- 해결책
- 하는일이 거의 없는 추상클래스가 있다면
- 계층 합치기
- 쓸데없이 위임하는 코드
- 함수 인라인하기
- 클래스 인라인하기
- 본문에서 사용되지 않는 매개변수 / 한번도 사용한 적 없는 매개변수가 존재
- 함수 선언 바꾸기
- 테스트 코드 말고는 사용하는곳이 없다면
- 죽은 코드 제거하기
- 하는일이 거의 없는 추상클래스가 있다면
임시 필드
- 상황
- 특정 상황에서만 값이 설정되는 필드를 가진 클래스가 존재 하는 경우에 해당됩니다.
- 객체를 가져 올 때, 모든 필드가 채워 져 있는것이 다른 프로그래머들과 소통하기 좋습니다.
- 비어있는 필드는 최대한 없애는 것이 좋습니다.
- 해결책
- 덩그러니 떨어져 있는 필드가 있다면
- 클래스 추출하기
- 함수 옮기기 → 새 클래스에 옮겨줌
- 임시 필드가 유효한지 검사하는 로직이 있다면
- 특이 케이스 추가하기 → 이후 임시필드 제거
- 덩그러니 떨어져 있는 필드가 있다면
메세지 체인
- 상황
- 클라이언트가 한 객체를 통해 객체를 얻고, 얻은 객체를 통해 다른 객체를 얻고, 얻은 객체를 통해 또 다른 객체를 얻는 경우가 존재 하는 경우에 해당됩니다.
- 클라이언트가 객체 네비게이션 구조에 종속 되었음을 의미합니다.
1
2
3
4
5// 이런 코드가 존재하는 경우 managerName = aPerson.department.manager.name // 체인의 존재를 숨기는 것을 제안하고 있습니다. managerName = aPerson.getManagerName()
- 해결책
- 메세지 체인 없애기
- 최종 경과 객체가 어떻게 쓰이는지 살펴 본 뒤 → 위임 숨기기
- 객체를 사용하는 코드 일부를 추출 / 옮기기
- 함수 추출하기
- 함수 옮기기
- 메세지 체인 없애기
중개자
- 상황
- 한 클래스 내에 위임 객체가 너무 많은 경우에 해당됩니다.
- 해결책
- 실제로 일을 하는 객체와 직접 소통하게 변경하기
- 중개자 제거하기
- 위임 메서드 제거 후 남는 일이 별로 없다면
- 함수 인라인 하기
- 실제로 일을 하는 객체와 직접 소통하게 변경하기
내부자 거래
- 상황
- 모듈 간의 데이터를 주고 받는 것이 숨겨 진 경우에 해당 됩니다.
- 해결책
- 사적으로 처리하는 부분을 줄이기
- 함수 옮기기
- 필드 옮기기
- 여러 모듈이 같은 관심사를 공유한다면
- 공통 부분을 처리하는 제3의 모듈 만들기
- 위임 숨기기 → 다른 모듈이 중간자 역할을 하게 만들기
- 상속 구조에서, 자식 클래스가 부모 클래스에 대해 너무 많은 권한을 갖고 있어 분리 할 필요성이 있다면
- 서브 클래스를 위임으로 바꾸기
- 슈퍼 클래스를 위임으로 바꾸기
- 사적으로 처리하는 부분을 줄이기
거대한 클래스
- 상황
- 한 클래스가 너무 많은 일을 하게되어 필드, 메서드가 비대해진 경우에 해당됩니다.
- 코드량이 너무 많은 클래스도 이에 해당합니다.
- 클라이언트들이 거대 클래스를 어떻게 이용하는지 패턴을 파악하여 쪼개는 단서를 얻을 수 있습니다.
- 해결책
- 필드 일부를 묶을 수 있을 때
- 클래스 추출하기 ( 접두어, 접미어가 같은 필드들을 주로 찾으면 좋습니다 )
- 분리할 컴포넌트가 원래 클래스와 상속 관계로 이루어 질 수 있을 때
- 슈퍼 클래스 추출하기
- 타입 코드를 서브 클래스로 바꾸기
- 코드량이 너무 많을 때
- 함수 추출하기
- 유용한 기능 그룹을 찾았다면 → 분리
- 클래스 추출하기
- 슈퍼 클래스 추출하기
- 타입 코드를 서브 클래스로 바꾸기
- 필드 일부를 묶을 수 있을 때
서로 다른 인터페이스의 대안 클래스들
- 상황
- 서로 다른 인터페이스를 구현하고 있는 클래스를, 하나로 합칠 수 있는 경우에 해당됩니다.
- 해결책
- 인터페이스가 같아 질 때 까지 필요한 동작들을 클래스 안으로 밀어 넣기
- 함수 선언 바꾸기를 통해 method signature 일치시키기
- 함수 옮기기를 이용하여 인터페이스 일치시키기
- 중복 코드가 생기면 슈퍼클래스 추출하기
- 인터페이스가 같아 질 때 까지 필요한 동작들을 클래스 안으로 밀어 넣기
데이터 클래스
- 상황
- data만 다루는 클래스에, public field가 존재하는 경우에 해당됩니다.
- data class를 다루고 있는 곳의 코드를, data class 내부로 옮길 수 있는지 확인 해보는 것이 좋습니다.
- 해결책
- property 캡슐화
- 레코드 캡슐화하기
- setter 제거하기
- data class를 다루고 있는 곳의 코드를 data class 내부로 옮기기
- 전체를 옮기기 힘들다면 함수 추출하기 이후 옮길 수 있는 부분만 옮기기
- property 캡슐화
상속 포기
- 상황
- 부모 클래스의 property / method 중 상속 받고 싶지 않은 필드가 존재 하는 경우에 해당됩니다.
- 하지만, 항상 공통된 부분만 남기는 것이 권장되지는 않습니다.
- 부모의 동작은 필요로 하지만, 인터페이스는 따르고 싶지 않을 때 수정을 하는것이 강력하게 권고됩니다.
- 해결책
- 부모 클래스에 필요한 공통된 부분만 남기기
- 메서드 내리기, 필드 내리기를 활용하여 물려 받지 않을 부모 코드를 모조리 새로만든 서브클래스로 넘기기
- 자식 클래스에서 인터페이스를 따르고 싶어하지 않을 때 → 상속 메커니즘 탈피
- 서브 클래스를 위임으로 바꾸기
- 슈퍼 클래스를 위임으로 바꾸기
- 부모 클래스에 필요한 공통된 부분만 남기기
주석
- 상황
- 주석을 탈취제 처럼 사용하는 경우에 해당됩니다.
- 주석은 향기를 입히는데, 잘못 작성된 코드에 향기를 입히는 경우 리팩터링의 대상이 됩니다.
- 주석이 필요없는 코드로 리팩터링 한 뒤에 주석을 남기는 것이 좋습니다.
- 무엇을 할 지 모를때, 현재 진행 및 미래에도 어떻게 변경될지 모르는 코드를 작성 할 때엔 주석을 남기는 것이 좋습니다.
- 해결책
- 특정 코드 블록이 하는 일에 주석을 남기고 싶을 때
- 함수 추출하기
- 이미 추출된 함수임에도 여전히 설명이 필요할 때
- 함수 선언 바꾸기
- 시스템이 동작하기 위한 선행 조건을 명시하고 싶을 때
- assertion 추가하기
- 특정 코드 블록이 하는 일에 주석을 남기고 싶을 때
"General Study" 카테고리의 최근 포스팅
카테고리 모든 글 보기Refactoring Study - 리팩터링이 필요한 경우 | 2021. 04. 06 |
---|