rkaehdaos / learning-martinfowler_refactoring_2nd

learning-martinfowler_refactoring_2nd
0 stars 0 forks source link

6. 기본적인 리팩터링 #6

Closed rkaehdaos closed 2 years ago

rkaehdaos commented 2 years ago

Intro

저수준 -> 고수준

rkaehdaos commented 2 years ago

6.1 함수 추출하기 - Extract Function

이 함수의 표현은 객체지향의 메서드, 절차형의 프로시저/서브루틴과 매칭된다

배경

  • 코드 조각을 찾아 무슨 일을 하는지 파악한 후 독립 함수로 추출하고 목적에 맞는 네이밍
  • 언제 필요?
  • 길이?? : 예를 들어 한 화면을 넘어가면 안되다는 규칙을 떠올릴 수 있음
  • 재사용성?? : 2번 이상 사용될 코드?
  • 책에서 말하는 가장 합리적인 기준? "목적과 구현을 분리"하는 방식
  • 코드가 무슨 일을 하는지 파악하는데 한참 걸린다? → 해당 부분을 함수 추출 후 '무슨 일'에 걸맞는 네이밍 작업
  • 나중에 읽을 때 목적이 한 눈에 들어오고. 함수 내부 본문 코드는 더 신경 쓸 필요가 없음
  • 마틴 파울러 경험 이야기
  • 경험상 대여섯줄 넘어가면 냄새가 난다. 단 한 줄 짜리 함수를 만드는 일도 적지 않다
  • 켄트백의 극단적인 스몰토크 예
  • helight()는 단순히 색상반전을 위한reserve()호출
  • 메서드 이름이 구현 코드보다 길었음
  • 코드의 목적(강조)와 구현(반전)사이의 차가 그만큼 컸으므로 문제가 되지 않는다
  • 함수 호출 많아져 성능 느려짐?
  • 요즘엔 그럴 일이 거의 없음
  • 짧으면 더 캐싱이 쉽다 → 컴파일러가 최적화시 유리 성능 최적화 일반 원칙 : 첫번째 하지마라, 두 번째(전문가 한정), 아직 하지 마라 - M. A. Jackson

짧은 함수의 이점은 이름을 잘 지어야만 발휘된다

rkaehdaos commented 2 years ago
image
rkaehdaos commented 2 years ago

절차

  1. 함수를 새로 만들고 목적을 잘 드러내는 네이밍
    • 단 한 줄 이더라도 함수로 목적과 의도가 더 잘 드러나는 네이밍이 가능하다면 추출
    • 위의 네이밍이 떠오르지 않는다면? → 함수로 추출하면 안된다는 신호
    • 처음부터 좋은 네이밍 안떠오를 수 있으니 일단 추출해서 사용해보고 효과가 미비하다면 원래 상태로 인라인화 한다. 그 과정중에 조금이라도 깨달음이 있으면 낭비가 아님
    • 중첩함수를 지원하는 언어를 사용한다면 원래 함수에 중첩 시킨다. 그러면 스코프나 변수 작업을 줄일 수 있다. 나중에 바깥으로 꺼내야 할때가 오면 언제든 함수 옮기기를 사용하면 된다.
  2. 추출한 코드를 원본 함수에서 복사해서 새 함수에 붙여 넣는다
  3. 추출한 코드 중 원본 함수의 지역변수 참조하거나, 스코프 벗어나는 변수가 있다면 매개변수로 전달한다
  4. 변수를 다 처리했다면 컴파일 한다.
  5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다(위임)
  6. 테스트
  7. 다른 코드에 방금 추출과 똑같거나 비슷한 코드가 있는지 살펴보고 있다면 방금 만든 새 함수를 호출하도록 바꿀지 검토한다
rkaehdaos commented 2 years ago

Clock Wrapper

rkaehdaos commented 2 years ago

예시: 지역 변수의 값 변경

다음 고려점 : 임시 변수의 스코프

rkaehdaos commented 2 years ago

값을 반환할 변수가 여러개라면?

추출함수를 최상위 수준 같은 다른 context로 이동하려면?

rkaehdaos commented 2 years ago

6-2 함수 인라인하기

배경

또 다른 인라인 대상

절차

  1. 다형 메서드인지 확인 (서브 클래스에서 오버라이드 하는 메서드는 x)
  2. 인라인할 함수를 호출하는 곳을 모두 찾는다
  3. 각 호출문을 함수 본문으로 교체
  4. 하나씩 교체ㅐ할 떄마다 테스트
  5. 함수 정의(원래 함수)를 삭제한다

    이 절차를 적용하기 어려울정도로 복잡하다면 인라인하기를 적용하면 안되는 케이스

예시

rkaehdaos commented 2 years ago

변수 추출하기

배경

지역변수 사용

변수 추출 고려 → 표현식에 이름을 붙이고 싶다는 의미

절차

  1. 추출하는 표현식에 부작용이 없는지 확인
  2. 불변 변수를 하나 선언하고 이름을 붙일 표현식의 복제본 대입
  3. 원본 표현식을 새로 만든 변수로 교체
  4. 테스트
  5. 표현식을 사용하는 각각을 새로 만든 변수로 교체(하나 교체시 테스트)
rkaehdaos commented 2 years ago

ex2: 추출하는 표현식이 메서드를 넘어 클래스 전체 영향

//ex2
class Order{
    constructor(aRecord) {
        this._data = aRecord;
    }
    get quantity() {return this._data.quantity;}
    get itemPrice() {return this._data.itemPrice;}
    get price() {
        return order.quantity * order.itemPrice -
            Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 +
            Math.min(order.quantity * order.itemPrice * 0.1, 100);
    }
}
rkaehdaos commented 2 years ago

6-4 inline variable

배경

절차

  1. 대입문 우변(표현식) 부작용 여부 체크
  2. 변수가 불변이 아니라면 불변으로 바꾸고 테스트 (값이 단 한 번만 대입되는지 확인)
  3. 이 변수를 가장 처음 사용하는 코드를 찾아서 우변의 표현식으로 바꾼다
  4. 테스트
  5. 변수를 사용하는 모두 교체할 때까지 반복
  6. 변수 선언문과 대입문을 삭제
  7. 테스트
rkaehdaos commented 2 years ago

6.5 함수 선언 바꾸기 Change Function Declaration

시그니처 바꾸기

함수의 이름 : 연결부에서 가장 중요한 요소

함수의 매개변수 : 외부 세계와 어우러지는 방식을 정의

rkaehdaos commented 2 years ago

절차

간단한 절차

  1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인
  2. 메서드 선언을 원하는 형태로 바꾼다 (선언부 교체)
  3. 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정 (호출부 교체)
  4. 테스트

변경할게 둘 이상?

마이그레이션 절차

  1. 이어지는 단계를 수월하게 만들어야 한다면 함수 본문을 적절히 리팩토링
  2. 함수 본문을 새로운 함수로 추출 (새로 만들 함수 이름이 기존 함수와 이름이 같다면 일단 임시 이름을 붙이자)
  3. 추출한 함수에 매개 변수 추가 필요? → 위의 '간단한 절차'를 따라 추가
  4. 테스트
  5. 기존 함수 인라인
  6. 임시 이름이 있다면 함수 선언 바꾸기 한번 더 적용해서 원래 이름으로 되돌린다
  7. 테스트

상속 구조 속의 클래스 메서드 변경할 시 : 다른 클래스들도 변경 반영되어야

rkaehdaos commented 2 years ago

간단한 절차의 단점

→ 호출문과 선언문을(다형성 구현의 경우 여러 선언문 모두를) 한번에 수정해야 한다 → 많지 않거나 좋은 도구를 쓰면 그리 어렵지 않으나 아주 많다면 힘들어짐 → 또한 같은 이름의 메서드가 여러 클래스에 정의되어있을대가 문제 → 예컨데 changeAddress()가 사람 클래스와 계약 클래스 모두에 정의된 경우 → 하나만 바꾸고 싶을 때 난감함 →이럴 때 마이그레이션 절차대로 해본다

rkaehdaos commented 2 years ago

함수 선언 바꾸기만큼은 직접 고칠수 없는 부분을 리팩터링하기에 좋다

예시

rkaehdaos commented 2 years ago

6-6 변수 캡슐화 하기 - Encapsulate Variable

필드 캡슐화

배경

함수는 데이터보다 다루기 수월하다

데이터는 함수보다 다루기 어렵다 : 이런식으로 처리가 안되므로

따라서 접근할수 있는 스코프가 넓은 데이터를 옮길때는?

데이터 캡슐화의 다른 장점들

객체 지향에서 객체의 데이터를 항상 private로 유지해야 한다는 이야기가 바로 여기!

불변 데이터는 캡슐화 이유가 적다

rkaehdaos commented 2 years ago

절차

  1. 변수로의 접근, 갱신을 전담하는 캡슐화 함수들을 만든다
  2. 정적 검사를 수행한다
  3. 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다 하나씩 바꿀 때마다 테스트한다
  4. 변수의 접근 범위를 제한한다
  5. 테스트한다
  6. 변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려
rkaehdaos commented 2 years ago

값 캡슐화하기

변수 뿐 아니라 변수에 담긴 내용을 변경하는 행위도 제어할 수 있도록 캡슐화를 원한다면?

  1. 그 값을 바꾸지 못하게 만든다

    • getter가 데이터의 복제본을 반환하도록 수정한다
    • export function defaultOwner() {return Object.assign({}m defaulutOwnerData);}
    • 특히 리스트에 이 기법 많이 적용
    • 만약 공유 데이터를 변경하기를 원하는 클라이언트가 있는 경우?
    • 문제가 되는 부분을 테스트로 찾는다
  2. 아니면 아예 변경할 수 없도록 만든다 [레코드 캡슐화 하기]

    image
    • 이렇게 하면 defaulutOwnerData의 속성을 다시 대입하는 연산을 모두 무시한다
  3. setter에서도 복제본을 만드는 편이 좋은지도 살펴보자

rkaehdaos commented 2 years ago

6-7 변수 이름 바꾸기 Rename Variable

명확한 프로그래밍의 핵심은 이름 짓기

함수 호출 한 번으로 끝나지 않고 갑싱 영속되는 필드라면 ? 아주 신중하게 네이밍!

절차

  1. 폭 넓게 쓰이는 변수 ? 변수 캡슐화하기 고려
  2. 이름을 바꿀 변수를 참조하는 곳을 모두 찾아서 하나씩 변경
    • 다른 코드 베이스에서 참조하는 변수→외부에 공개된 변수→ 적용 불가
    • 변수 값이 변하지 않는다면? 다른이름으로 하나씩 테스트하며 점진적 변경
  3. 테스트

예시

가장 간단한 예

상수이름 바꾸기

rkaehdaos commented 2 years ago

6-8 매개변수 객체 만들기

배경

범위 개념은 객체 하나로 묶어 표현하는게 나은 대표적인 예다

rkaehdaos commented 2 years ago

절차

  1. 적당한 데이터 구조가 아직 마련되어 있지 않다면 새로 만든다
    • 클래스 선호 → 나중에 동작까지 함께 묶기 좋기 때문
    • 주로 데이터 구조를 마틴파울러는 VO로 만든다
  2. 테스트한다
  3. 함수선언 바꾸기로 새 데이터 구조를 매개변수로 추가한다
  4. 테스트한다
  5. 함수 호출시 새로운 데이터 구조 인스턴스를 리턴하도록 수정한다. 하나씩 수정할 때마다 테스트 한다
  6. 기존 매개변수를 사용하던 코드를 새 데이터 구조의 원소를 사용하도록 바꾼다
  7. 다 바꿨다면 기존 매개변수를 제거하고 테스트한다
rkaehdaos commented 2 years ago

호출코드

rkaehdaos commented 2 years ago

클래스 선언 이유

rkaehdaos commented 2 years ago

진정한 값 객체로 거듭나기

rkaehdaos commented 2 years ago

6-9 여러 함수를 클래스로 묶기 - Combine Functions into Class

배경

클래스

공동테이터(흔히 호출시 인수로 전달되는)를 중심으로 긴밀히 엮여 작동하는 함수무리는 클래스 하나로

함수로 한데 묶는 다른 방법 존재

클래스 묶을시 장점

중첩함수로 묶기

클래스를 지원하지 않는 언어?

rkaehdaos commented 2 years ago

절차

  1. 함수들이 공유하는 공통 데이터 레코드를 캡슐화
    • 공통 데이터가 레코드로 묶여 있지 않다면 먼저 매개변수 객체 만들기로 데이터를 하나로 묶는 레코드를 만든다
  2. 공통 레코드를 사용하는 함수 각각을 새 클래스로 옮긴다
    • 공통 레코드 벰버는 함수 인출문 인수 목록에서 제거
  3. 데이터를 조작하는 로직들은 함수로 추출해서 새 클래스로 옮긴다.
rkaehdaos commented 2 years ago

예시

rkaehdaos commented 2 years ago

6-10 여러 함수를 변환 함수로 묶기 - Combine Functions into Transform

도출 정보

변환함수

클래스 묶기와 중요한 차이

함수 추출하기로 같은 효과?

rkaehdaos commented 2 years ago

절차

  1. 변환할 레코드를 입력받아 값을 그대로 반환하는 변환함수를 만든다
    • 이 작업은 대체로 깊은 복사로 처리해야
    • 변환 함수가 레코드를 바꾸지 않는지 검사하는 테스트를 마련하면 도움
  2. 묶을 함수 중 하나를 골라 본문 코드를 변환 함수로 옮기고 처리 결과를 레코드에 새필드로 기록한다. 그리고 클라이언트 코드가 이 필드를 사용하도록 수정한다
    • 복잡하면 함수 추출하기부터 한다
  3. 테스트한다
  4. 나머지 관련 함수도 위 과정에 따라 처리
rkaehdaos commented 2 years ago

6.11 단계 쪼개기

배경

분리의 가장 간편한 방법 : 한 스텝을 연이은 스텝 2개로 쪼개기

SW 규모에 상관없이 여러 스텝으로 분리하면 좋을때마다 단계 쪼개기 해보자

rkaehdaos commented 2 years ago

절차

  1. 2개로 쪼갠 스텝에서 뒷단계에 해당하는 코드를 독립함수로 추출한다
  2. 테스트한다
  3. 중간 데이터 구조를 만들어서 앞에서 추출한 함수의 인수로 추가한다
  4. 테스트한다
  5. 추출한 2번째 단계 함수의 매개변수를 하나씩 검토해서 첫번째 단계에사용되는것은 중 중간 데이터 구조로 옮긴다. 하나씩 옮길때마다 테스트한다
    • 간혹 2번째 단계에서 사용하면 안되는 매개변수가 존재한다. 이럴때는
    • 각 매개변수를 사용한 결과를 준간 데이터 구조의 필드로 추출하고
    • 이 필드의 값을 설정하는 문장을 호출하는 곳으로 옮긴다
  6. 첫번째 단계 코드를 함수로 추출하면서 중간 데이터 구조를 반환하도록 한다
    • 이때 첫번째 단계를 변환함수 객체로 추출해도 좋다
rkaehdaos commented 2 years ago

위 코드는 두단계로 이뤄지고 있음

rkaehdaos commented 2 years ago

shippingMethod 1 step에서 사용하지 않으므로 그대로 둔다

quantity

discount

rkaehdaos commented 2 years ago

현재 코드의 2가지 일

  1. 주문목록을 읽어서 카운팅
  2. 명령줄 인수를 담은 배열을 읽어서 프로그램 동작 결정 → 리팩터링 대상으로 적합 → 분리하면 프로그램에서 지정할 수 있는 옵션/스위치가 늘어나도 수정 원할
rkaehdaos commented 2 years ago

단계 쪼개기와 상관없는 작업

first

rkaehdaos commented 2 years ago

run() 은 기본 package 접근 범위 → 테스트 코드에서 쉽게 호출 위해

결과를 받으려면 표준 출력으로 보내는 방식을 수정해야 한다

System.out문장을 호출한 곳으로 옮겨[8.4] 해결한다

rkaehdaos commented 2 years ago

위 단계 작업은 리팩터링시 중요하다

이제 제대로 된 단계 쪼개기 준비 끝

rkaehdaos commented 2 years ago

args

args 추출을 위해 가장 먼저 할일

rkaehdaos commented 2 years ago

이 조건식 추출 값을 중간 데이터 구조로 옮긴다

rkaehdaos commented 2 years ago

다른 방법

rkaehdaos commented 2 years ago

둘중에 어떤거?

rkaehdaos commented 2 years ago
image