리팩터링 6장

박경준·2022년 11월 27일
0

리팩터링 책 요약

목록 보기
6/7

기본적인 리팩터링

1. 함수 추출하기

코드를 언제 독립된 함수로 묶어야 할지에 관한 의견으로는 '함수 하나가 한 화면을 넘어가면 안된다', '재사용성', '목적과 구현을 분리' 등이 있다. 그 중에 목적과 구현을 분리 하는 기준이 가장 합리적이다. 짧은 함수의 이점은 이름을 잘 지어야만 발휘되므로 이름 짓기에 특별히 신경써야 한다.

  • 절차

    1. 함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다. 어떻게가 아닌 무엇을 하는지가 드러나야 한다. 추출할 함수가 목적이 더 잘 드러나는 이름이 없다면 함수로 추출하면 안되는 신호다. 일단 함수로 추출해보고 효과가 크지 않으면 다시 인라인으로 돌려도 된다. 그 과정에서 깨달은게 있다면 시간낭비가 아니다.
    2. 추출할 코드를 원본 함수에서 복사하여 새 함수에 붙여넣는다
    3. 추출한 코드 중 원본 함수의 지역 변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다. 있다면 매개변수로 전달한다. 추출한 코드 안에서 값이 바뀌는 변수 중에서 값으로 전달되는 것들은 주의해서 처리한다. 이런 변수가 하나뿐이라면 추출한 코드를 질의 함수로 취급해서 그 결과를 해당 변수에 대입한다. 값을 수정하는 변수가 많다면 함수 추출을 멈추고 변수 쪼개기나 임시 변수를 질의 함수로 바꾸기와 같은 기법을 적용한다.
    4. 변수를 다 처리했다면 컴파일한다.
    5. 원본 함수에서 추출한 코드 부분을 새로 만든 함수를 호출하는 문장으로 바꾼다.
    6. 테스트
    7. 다른 코드에 방금 추출한 것과 똑같거나 비슷한 코드가 있다면 방금 추출한 새 함수를 호출하도록 바꿀지 검토한다.
    • 함수를 중첩되게 정의하면 원본 함수의 지역 변수를 추출 함수에서 모두 사용할 수 있다.

2. 함수 인라인하기

때로는 함수 본문이 이름만큼 명확한 경우도 있다. 이럴때 간접 호출은 쓸데없이 거슬릴 뿐이다.

  • 절차

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

3. 변수 추출하기

표현식이 너무 복잡해서 이해하기 어려울 때가 있다. 이럴때 지역 변수를 이용해 표현식을 쪼개 관리하기 더 쉽게 만들 수 있다. 그러면 복잡한 로직을 구성하는 단계마다 이름을 붙일 수 있어서 코드의 목적을 훨씬 명확하게 드러낼 수 있다. 이 과정은 디버깅에 도움이 되기도 한다.

  • 절차

    1. 추출하려는 표현식에 부작용은 없는지 확인한다.
    2. 불변 변수를 하나 선언하고 이름을 붙일 표현식의 복제본을 대입한다.
    3. 원본 표현식을 새로 만든 변수로 교체한다.
    4. 테스트
    5. 표현식을 여러 곳에서 사용한다면 각각을 새로 만든 변수로 교체한다. 하나 교체할 때마다 테스트한다.

4. 변수 인라인하기

변수는 함수 안에서 표현식을 가리키는 이름으로 쓰이며, 대체로 긍정적인 효과를 준다. 하지만 그 이름이 원래 표현식과 다를 바 없다면 인라인하는 것이 좋다.

  • 절차

    1. 대입문의 우변(표현식)에서 부작용이 생기지는 않는지 확인한다.
    2. 변수가 불변으로 선언되지 않았다면 불변으로 만든 후 테스트 한다. 이렇게해야 변수에 값이 단 한 번만 대입되는지 확인할 수 있다.
    3. 이 변수를 가장 처음 사용하는 코드를 찾아서 대입문 우변의 코드로 바꾼다.
    4. 테스트
    5. 변수를 사용하는 부분을 모두 교체할 때까지 이 과정을 반복한다.
    6. 변수 선언문과 대입문을 지운다.
    7. 테스트

5. 함수 선언 바꾸기

함수는 프로그램을 작은 부분으로 나누는 주된 수단이다. 함수 선언은 각 부분이 서로 맞물리는 방식을 표현하며, 실질적으로 소프트웨어 시스템의 구성 요소를 조립하는 연결부 역할을 한다. 연결부에서 가장 중요한 요소는 함수의 이름이다. 이름이 좋으면 함수의 구현 코드를 살펴볼 필요 없이 호출문만 보고도 무슨 일을 하는지 파악할 수 있다. 주석으로 함수의 목적을 설명해보면 주석이 멋진 이름으로 바뀌어 되돌아올 때가 있다.

  • 간단한 절차

    1. 매개변수를 제거하려거든 먼저 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
    2. 메서드 선언을 원하는 형태로 바꾼다.
    3. 기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.
    4. 테스트
  • 마이그레이션 절차

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

6. 변수 캡슐화하기

데이터는 함수보다 다루기가 까다롭다. 데이터는 참조하는 모든 부분을 한 번에 바꿔야 코드가 제대로 작동한다. 유효범위가 넓어질수록 다루기 어려워진다. 전역 데이터가 골칫거리인 이유도 바로 여기에 있다. 그래서 접근할 수 있는 범위가 넓은 데이터를 옮길 때는 먼저 그 데이터로의 접근을 독점하는 함수를 만드는 식으로 캡슐화하는 것이 가장 좋다. 데이터 재구성이라는 어려운 작업을 함수 재구성이라는 더 단순한 작업으로 변환하는 것이다. 데이터 캡슐화는 데이터를 변경하고 사용하는 코드를 감시할 수 있는 확실한 통로가 되어주기 때문에 데이터 변경 전 검증이나 변경 후 추가 로직을 쉽게 끼워넣을 수 있다. 변수 캡슐화는 데이터에 대한 결합도가 높아지는 일을 막는다. 불변 데이터는 가변 데이터보다 캡슐화할 이유가 적다.

  • 절차

    1. 변수로의 접근과 갱신을 전담하는 캡슐화 함수들을 만든다.
    2. 정적 검사를 수행한다.
    3. 변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다. 하나씩 바꿀 때마다 테스트한다.
    4. 변수의 접근 범위를 제한한다. 변수로의 직접 접근을 막을 수 없을 때도 있다. 그럴때는 변수 이름을 바꿔서 테스트해보면 해당 변수를 참조하는 곳을 쉽게 찾아낼 수 있다.
    5. 테스트
    6. 변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려해본다.

7. 변수 이름 바꾸기

변수는 프로그래머가 하려는 일에 관해 많은 것을 설명해준다. 자바스크립트처럼 동적 타입 언어라면 aCustomer처럼 타입을 드러내는 변수명이 좋다. 함수 호출 한번으로 끝나지 않고 값이 영속되는 필드라면 이름에 더 신경 써야한다.

  • 절차

    1. 폭넓게 쓰이는 변수라면 변수 캡슐화하기를 고려한다.
    2. 이름을 바꿀 변수를 참조하는 곳을 모두 찾아서, 하나씩 변경한다. 다른 코드베이스에서 참조하는 변수는 외부에 공개된 변수이므로 이 리팩터링을 적용할 수 없다.
    3. 테스트

8. 매개변수 객체 만들기

데이터 항목 여러 개가 이 함수에서 저 함수로 함께 몰려다니는 경우를 자주 본다. 나는 이런 데이터 무리를 발견하면 데이터 구조 하나로 모아주곤 한다. 데이터 구조에 담길 데이터에 공통으로 적용되는 동작을 추출해서 함수로 만드는 것이다. 이 과정에서 새로 만든 데이터 구조가 문제 영역을 훨씬 간결하게 표현하는 새로운 추상개념으로 격상되면서 코드의 개념적인 그림을 다시 그릴수도 있다. 클래스로 만들어두면 관련 동작들을 이 클래스로 옮길 수 있다는 이점이 생긴다. (201p.)

  • 절차

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

9. 여러 함수를 클래스로 묶기

클래스는 데이터와 함수를 하나의 공유 환경으로 묶은 후, 다른 프로그램 요소와 어우러질 수 있도록 그중 일부를 외부에 제공한다. 클래스로 묶으면 이 함수들이 공유하는 공통 환경을 더 명확하게 표현할 수 있고,각 함수에 전달되는 인수를 줄여서 객체 안에서의 함수호출을 간결하게 만들수 있다. 함수를 한데 묶는 방법에는 여러 함수를 변환 함수로 묶기도 있다.

  • 절차

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

10. 여러 함수를 변환 함수로 묶기

도출된 정보를 모아두면 검색과 갱신을 일관된 장소에서 처리할 수 있고 로직 중복도 막을 수 있다. 이렇게 하기 위해서는 변환 함수를 사용한다. 이 방식과 여러 함수를 클래스로 묶기는 비슷하지만 큰 차이가 있다. 원본 데이터가 코드 안에서 갱신될 때는 클래스로 묶는 편이 훨씬 낫다. 변환 함수로 묶으면 가공한 데이터를 새로운 레코드에 저장하므로, 원본 데이터가 수정되면 일관성이 깨질 수 있다.

  • 절차

    1. 변환할 레코드를 입력받아서 값을 그대로 반환하는 변환 함수를 만든다. 이 작업은 대체로 깊은 복사로 처리해야 한다. 변환 함수가 원본 레코드를 바꾸지 않는지 검사하는 테스트를 마련해두면 도움될 때가 많다.
    2. 묶을 함수 중 함수 하나를 골라서 본문 코드를 변환 함수로 옮기고, 처리 결과를 레코드에 새 필드로 기록한다. 그런 다음 클라이언트 코드가 이 필드를 사용하도록 수정한다. 로직이 복잡하면 함수 추출하기부터 한다.

11. 단계 쪼개기

코드를 수정해야 할때 두 대상을 동시에 생각할 필요 없이 하나에만 집중하기 위해서 동작을 연이은 두 단계로 쪼갠다. 다른 단계로 볼 수 있는 코드 영역들이 마침 서로 다른 데이터와 함수를 사용한다면 단계 쪼개기에 적합하다는 뜻이다.

  • 절차

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

0개의 댓글