리팩터링 원칙
리팩터링이란 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법이다.
- 좋은 설계, 쉬운 이해, 버그 찾기, 프로그래밍 속도 향상에 목적이 있다.
- 언제 리팩터링 해야 할까?
- 리팩터링 작업 대부분은 드러나지 않게, 기회가 될 때마 다해야한다
- 비슷한 일을 세 번째 하게 되면 리팩터링 한다
- 새로운 기능을 추가학 ㅣ전에 한다
- 코드를 파악할 때 마다 코드의 의도가 더 명확하게 드러나도록 리팩토링 해야 한다
- 코드 리뷰 할 때
- 리팩터링하는 동안에는 코드가 항상 정상 작동하기 때문에 전체 작업이 끝나지 않았더라도 언제든 멈출 수 있다.
코드에서 나는 악취
1. 기이한 이름
함수, 모듈, 변수 , 클래스 등은 그 이름만 보고도 각각이 무슨 일을 하고 어떻게 사용해야 하는지 명확히 알 수 있도록 신경써서 이름을 지어야 한다.
2. 중복 코드
3. 긴 함수
- 함수를 적극적으로!! 쪼개기
- 주석을 달아야 할 만한 부분을 함수로 만들기
- 조건문 반복문 처리 : 조건문 분해하기(10.1), 조건문 통합하기(10.2)
4. 긴 매개변수 목록
- 매개변수는 함수 동작에 변화를 줄 수 있는 일차적인 수단
- 매개변수를 질의 함수로 바꾸기(11.5) : 피호출함수가 매개변수로 받지 않고 함수 내부에서 값을 얻을 수 있으면 이렇게 한다. 단, 매개변수를 제거했을 때 피호출 함수에 원치않는 의존성이 생기지 않는 경우에만!
- 객체를 통째로 넘기기(11.4) : 하나의 레코드에서 값을 가져와 넘길 때에는 레코드를 통째로 넘긴다. 함수 내부에서 레코드를 사용하기 편리하고, 사용하는 값이 추가되거나 바뀔 때 매개변수를 수정하지 않아도 됨
- 매개변수 객체 만들기(6.8)
- 플래그 인수 제거하기(11.3)
5. 전역 데이터
- 전역 데이터는 코드베이스 어디서든 건드릴 수 있고, 값을 누가 바꿨는지 찾아낼 메커니즘이 없다
- 6.6 함수 캡슐화하기 : 데이터로의 접근을 독점하는 함수를 만든다. → 데이터를 변경하고 사용하는 코드를 감시할 수 있다
6. 가변 데이터
- 데이터를 변경했더니 예상치 못한 결과가 나올 가능성이 있다.
- 9.1 변수 쪼개기 : 하나의 변수에 값을 한 번만 할당한다. 두 번 이상 할당해야한다면 여러가지 역할을 하는 것이므로 쪼개야 한다. const로 선언할 것을 권장!
- 8.6 문장 슬라이스 : 관련된 코드들을 모아놓기, 변수를 한 곳에서 선언하기 보다 처음 사용할 때 선언
- 6.1 함수 추출
- 11.1 질의함수와 변경함수 분리하기
7. 뒤엉킨 변경
- 하나의 모듈이 여러 가지 방식으로 변경되는 일이 많은 경우
- 원인: 단일 책임 원칙(SRP) 지켜지지 않아서
- 맥락별로 분리하기
- 맥락이 다르면 독립된 모듈로 분리
- 단계 쪼개기: 맥락끼리 특정한 데이터 구조를 담아 전달
- 함수 옮기기: 맥락이 동일한 함수들을 모듈로 모으기
8. 산탄총 수술
- 코드를 변경할 때마다 자잘하게 수정해야 하는 클래스가 많을 때
- 함께 변경되는 대상을 한 모듈에 묶어두기 : 필드 옮기기(8.2), 함수 옮기기(8.1)
- 비슷한 데이터를 다루는 함수 묶기 : 여러 함수를 클래스로 묶기(6.9), 여러 함수를 변환 함수로 묶기(6.10)
- 인라인 리팩터링
기본적인 리팩터링
- 가장 많이 하는 리팩터링은 함수 추출하기와 변수 추출하기 (리팩터링은 본래 코드를 변경하는 작업인 만큼 반대로 함수, 변수 인라인하기도 많이함)
- 추출은 결국 이름 짓기
- 자주 함께 뭉쳐다니는 인수는 매개변수 객체로 만들면 편리하다
- 함수를 그룹으로 묶을 때는 클래스로 묶기
함수 추출하기
- 이름만 봐도 무엇을 하는지 알 수 있게
- 목적과 구현을 분리
- 함수를 추출할 때 추출한 코드에서만 사용하는 변수가 추출한 함수 밖에 선언되어 있다면 추출한 함수 안에서 선언하도록 수정
- 중첩 함수도 가능 (지원하는 언어에서만)
- 코드를 독립된 함수로 묶는 시점
- 길이가 한 화면 안 넘어가기
- 재사용성
- 두 번 이상 사용될 코드
- 함수가 긴 것 보다 짧게 여러번 호출되는게 오히려 성능에 유리하다
- 함수를 짧게 작성할때의 이점은 이름을 잘 지어야만 발휘된다
매개변수 객체 만들기
- 데이터의 관계 명확해짐, 매개 변수 줄어듦, 데이터의 일관성이 높아짐
- 데이터 구조를 활용하는 형태로 프로그램 동작을 재구성 할 수 있다 = 데이터 구조에 담긴 데이터에 공통으로 적용되는 동작을 추출
여러 함수를 클래스로 묶기
- 같은 인수로 긴밀하게 엮여 작동하는 함수들은 클래스로 묶는다
- 장점 : 함수들이 공유하는 공통 환경을 명확하게 표현할 수 있다, 함수 인수를 줄여서 객체 안에서 호출을 간결하게 할 수 있다
여러 함수를 변환 함수로 묶기
- 원본 데이터를 입력받아서 필요한 정보를 모두 도출한 뒤 각각을 출력 데이터 필드에 넣어서 반환
캡슐화
- 모듈을 분리하는 가장 중요한 기준은 자신을 제외한 다른 부분에 드러내지 않아야 할 비밀을 얼마나 잘 숨기고 있느냐
- 레코드 캡슐화하기, 컬렉션 캡슐화하기로 숨길 수 있다.
- 기본형 데이터는 기본형을 객체로 바꾸기로 캡슐화 할 수 있다.
- 클래스는 본래 정보를 숨기는 용도로 설계되었다.
- 클래스 추출하기, 인라인하기도 활용할 수 있다.
기본형을 객체로 바꾸기
- 단순한 출력 이상의 기능이 필요해지는 순간 데이터를 표현하는 전용 클래스를 정의하는 편
- 특별한 동작이 추가되거나 프로그램이 커질 수록 유용한 도구가 된다.
- 절차
- 데이터 값을 다루기 전 변수부터 캡슐화
- 단순한 값 클래스를 만든다 (인수로 받아서 저장하는 생성자와 값을 반환하는 게터 추가)
- 기존 클래스에서 게터를 통해 새로만든 클래스의 게터를 호출한 결과를 반환하도록 게터를 수정
- 테스트
임시 변수를 질의 함수로 바꾸기
- 함수에서 어떤 결과값을 다시 참조하는 목적으로 임시 변수를 쓴다
- 임시 변수를 사용하면 값을 계산하는 코드 반복을 줄이고 설명할 수도 있어서 유용하다
- 함수로 만들어 사용하는 편이 낫다.
- 각각의 임시 변수들을 함수로 만들어 사용하면 변수를 따로 전달할 필요도 없고 함수의 경계가 더 분명해지고 부자연스러운 의존 관계나 부수효과를 찾는데 도움이된다
- 다른 함수에서도 사용할 수 있어 도움이된다.
- 스냅숏 용도로 쓰이는 변수에는 이 리펙터링을 하면 안된다
기능이동
- 관련된 코드끼리 모이도록 코드 조각을 옮기기
- ex. 변수를 첫머리에 모아두지 않고 처음 사용할 때 선언하기
함수 옮기기
필드 옮기기
- 필요한 경우 : 함수에 어떤 레코드를 넘길 때마다 또 다른 레코드의 필드도 함께 넘기는 경우, 레코드를 변경할 때 다른 것도 변경해야 할 경우, 여러 개에 정의된 똑같은 필드를 갱신해야한다면 한 번만 갱신해도 되게끔
데이터 조직화
변수 쪼개기
- 하나의 변수가 여러 역할을 가지지 않도록
- 변수에 값이 두 번 이상 대입되면 역할이 두 개라는 신호 (수집 변수 제외)
- 웬만하면 불변으로 선언
필드 이름 바꾸기
- 우선 캡슐화 하기
- 객체 안의 필드명과 메서드를 수정
- 이후, 접근자 수정
- 리팩터링 도중 테스트에 실패한다면 더 작은 단계로 나눠야 한다!
파생 변수를 질의 함수로 바꾸기
- 질의 함수: 연산을 통해 값을 구하는 함수
- 계산해 낼 수 있는 변수를 제거하여 함수로 분리하기
참조를 값으로 바꾸기
- 객체 내부의 속성을 변경하는 경우, 참조를 이용하지 않고 값으로 기존 객체를 통째로 대체하기
- 계산해 낼 수 있는 변수를 제거하여 함수로 분리하기
값을 참조로 바꾸기
- 같은 데이터를 여러번 복제하여 사용할 때, 참조를 이용하기
- 값을 복제하면 데이터를 갱신할 때 일관성이 깨지는 문제 발생
- 같은 데이터를 참조할 수 있는 저장소 객체 생성하여 이용
- 유일하게 접근할 수 있는 위치
매직 리터럴 바꾸기
- 코드의 의미를 분명히 드러내는 이름의 상수로 바꾸기
- 리터럴이 함수 하나에서 쓰이거나, 맥락 정보를 충분히 제공할 때에는 굳이 사용할 필요
조건부 로직 간소화
조건문 분해하기
- 복잡한 조건부 로직은 프로그램을 복잡하게 만드는 흔한 원흉
- 조건식을 먼저 함수로 추출하고 다음으로 조건절을 함수로 추출한다
- 해당 리펙토링을 진행하면 조건식과 조건절이 좀 더 명확해진다.
- 조건식과 그 조건식에 딸린 조건절 각각을 함수로 추출 → 조건이 무엇인지 강조하고, 무엇을 분기했는지 명확해짐
조건식 통합하기
- 비교하는 조건은 다르지만 그 결과로 수행하는 동작은 같은 코드들을 통합시킨다.
- OR 연산자를 통해 합친 조건식을 함수로 추출해서 사용한다.
- 동작이 다른 독립적인 조건문은 리펙토링 해서는 안된다.
중첩된 조건문을 보호 구문으로 바꾸기
- 모두 정상 동작으로 이어지는 형태의 조건문은 if else 절
- 정상, 비정상이 나뉘어 진다면 보호 구문으로 바꾸기
특이 케이스 추가하기
- 데이터 구조의 특정 값을 확인한 후 똑같은 동작을 수행 === 중복코드
- 이처럼 특정 값에 대해 똑같이 반응하는 코드가 여러 곳이라면 그 반응들을 한 데로 모으는게 좋다.
- 특수한 경우의 공통 동작을 요소 하나에 모아서 사용것을 특이 케이스 패턴이라 한다.
- 해당 패턴을 사용하면 코드 대부분을 단순한 함수 호출로 변경할 수 있다.
- Null을 특이 케이스로 보는 경우가 많아사 위 패턴을 널 객체 패턴이라고도 한다.
- 절차
- 컨테이너 (데이터구조, 클래스) 에서 특이 케이스를 검사하는 속성을 추가하고 false 값을 반환하게 한다.
- 특이 케이스 객체를 만든다. 이 객체는 특이 케이스인지를 검사하는 속성만 포함하고 true를 반환한다.
- 특이 케이스인지 검사하는 코드를 함수로 추출한다.
- 특이 케이스를 검사하는 함수에서 특이 케이스 객체의 속성을 사용하게 변경한다.
- 특이 케이스 검사 함수를 클래스로 묶거나 여러 함수를 변환함수로 묶기를 적용해서 공통 동작을 새로운 요소로 옮긴다.