[리팩터링 2판] 08. 기능 이동

지금까지는 프로그램 요소를 생성 혹은 제거하거나 이름을 변경하는 리팩터링을 다뤘다.
여기에 더해 요소를 다른 컨텍스트(클래스나 모듈 등)로 옮기는 일 역시 리팩터링의 중요한 축이다.
특히 8.8 반복문을 파이프라인으로 바꾸기를 유심히 읽어보자.


8.1. 함수 옮기기

배경

  • 모듈성이란 프로그램의 어딘가를 수정하려 할 때 해당 기능과 깊이 관련된 작은 일부만 이해해도 가능하게 해주는 능력이다.
  • 보통 이해도가 높아질수록 소프트웨어 요소들을 더 잘 묶는 새로운 방법을 깨우치게 되고, 높아진 이해를 반영하려면 요소를 이리저리 옮겨야할 수 있다.
  • 객체 지향 프로그래밍의 핵심 모듈화 컨텍스트는 클래스다.

  • 어떤 함수가 자신이 속한 모듈 A의 요소들보다 다른 모듈 B의 요소들을 더 많이 참조한다면 모듈 B로 옮겨줘야 마땅하다.

  • 함수의 최적 장소를 정하기가 어려울 수 있으나, 선택이 어려울수록 큰 문제가 아닌 경우가 많다.

절차

  • 선택한 함수가 현재 컨텍스트에서 사용 중인 모든 프로그램 요소를 살펴본다. 이 요소들 중에도 함께 옮겨야 할게 있는지 고민해본다.
  • 선택한 함수가 다형 메서드인지 확인한다.
  • 선택한 함수를 타깃 컨텍스트로 복사한다. 타깃 함수가 새로운 터전에 잘 자리 잡도록 다듬는다.
  • 정적 분석을 수행한다.
  • 소스 컨텍스트에서 타깃 함수를 참조할 방법을 찾아 반영한다.
  • 소스 함수를 타깃 함수의 위임 함수가 되도록 수정한다.
  • 테스트한다.
  • 소스 함수를 인라인할지 고민해본다.


8.2. 필드 옮기기

배경

  • 프로그램의 진짜 힘은 데이터 구조에서 나온다.
  • 데이터의 위치를 옮기더라도 접근자만 그에 맞게 수정하면 클라이언트 코드들은 아무 수정 없이도 동작할 것이다.

절차

  • 소스 필드가 캡슐화되어 있지 않다면 캡슐화한다.
  • 테스트한다.
  • 타깃 객체에 필드(와 접근자 메서드들)를 생성한다.
  • 정적 검사를 수행한다.
  • 소스 객체에서 타깃 객체를 참조할 수 있는지 확인한다.
  • 접근자들이 타깃 필드를 사용하도록 수정한다.
  • 테스트한다.
  • 소스 필드를 제거한다.
  • 테스트한다.


8.3. 문장을 함수로 옮기기

배경

  • 특정 함수를 호출하는 코드가 나올 때마다 그 앞이나 뒤에서 똑같은 코드가 추가로 실행되는 모습을 보면, 나는 그 반복되는 부분을 피호출함수로 합치는 방법을 궁리한다.
  • 이렇게 해두면 추후 반복되는 부분에서 무언가 수정할 일이 생겼을 때 단 한 곳만 수정하면 된다.

절차

  • 반복 코드가 함수 호출 부분과 멀리 떨어져 있다면 문장 슬라이드하기(8.6)를 적용해 근처로 옮긴다.
  • 타깃 함수를 호출하는 곳이 한 곳뿐이면, 단순히 소스 위치에서 해당코드를 잘라내어 피호출 함소로 복사하고 테스트한다. 이 경우라면 나머지 단계는 무시한다.
  • 호출자가 둘 이상이면 호출자 중 하나에서 ‘타깃 함수 호출 부분과 그 함수로 옮기려는 문장들을 함께’ 다른 함수로 추출한다. 추출한 함수에 기억하기 쉬운 임시 이름을 지어준다.
  • 다른 호출자 모두가 방금 추출한 함수를 사용하도록 수정한다. 하나씩 수정할 때마다 테스트한다.
  • 모든 호출자가 새로운 함수를 사용하게 되면 원래 함수를 새로운 함수 안으로 인라인한 후 원래 함수를 제거한다.
  • 새로운 함수의 이름을 원래 함수의 이름으로 바꿔준다.


8.4. 문장을 호출한 곳으로 옮기기

배경

  • 여러 곳에서 사용하던 기능이 일부 호출자에게는 다르게 동작하돌고 바뀌어야 한다면 필요한 리팩터링
  • 달라진 동작을 함수에서 꺼내 해당 호출자로 옮겨야한다.

절차

  • 호출자가 한두 개뿐이고 피호출 함수도 간단한 단순한 상황이면, 피호출 함수의 처음(혹은 마지막) 줄들을 잘라내어 호출자들로 복사해 넣는다.
  • 더 복잡한 상황에서는 이동하지 ‘않길’ 원하는 모든 문장을 함수로 추출한 다음 검색하기 쉬운 임시 이름을 짓는다.
  • 원래 함수를 인라인한다.
  • 추출된 함수의 이름을 원래 함수의 이름으로 변경한다.


8.5. 인라인 코드를 함수 호출로 바꾸기

배경

  • 이미 존재하는 함수와 똑같은 일을 하는 인라인 코드를 발견하면 보통은 해당 코드를 함수 호출로 대체하길 원할 것이다.
  • 특히 라이브러리가 제공하는 함수로 대체할 수 있다면 훨씬 좋다.

절차

  • 인라인 코드를 함수 호출로 대체한다.
  • 테스트한다.


8.6. 문장 슬라이드하기

배경

  • 관련된 코드들이 가까이 모여 있다면 이해하기가 더 쉽다.
  • 모든 변수 선언을 함수 첫머리에 모아두는 사람도 있는데, 나는 변수를 처음 사용할 때 선언하는 스타일을 선호한다.

절차

  • 코드 조각(문장들)을 이동할 목표 위치를 찾는다. 코드 조각의 원래 위치와 목표 위치 사이의 코드들을 훑어보면서, 조각을 모으고 나면 동작이 달라지는 코드가 있는지 살핀다. 다음과 같은 간섭이 있다면 이 리팩터링을 포기한다.
    • 코드 조각에서 참조하는 요소를 선언하는 문장 앞으로는 이동할 수 없다.
    • 코드 조각을 참조하는 요소의 뒤로는 이동할 수 없다.
    • 코드 조각에서 참조하는 요소를 수정하는 문장을 건너뛰어 이동할 수 없다.
    • 코드 조각이 수정하는 요소를 참조하는 요소를 건너뛰어 이동할 수 없다.
  • 코드 조각을 원래 위치에서 잘라내어 목표 위치에 붙여 넣는다.
  • 테스트한다.


8.7. 반복문 쪼개기

배경

  • 각각의 반복문으로 분리해두면 수정할 동작 하나만 이해하면 된다.
  • 반복문 쪼개기는 서로 다른 일들이 한 함수에서 이뤄지고 있다는 신호일 수 있다.
  • 리팩터링과 최적화를 구분하자

절차

  • 반복문을 복제해 두 개로 만든다.
  • 반복문이 중복되어 생기는 부수효과를 파악해서 제거한다.
  • 테스트한다.
  • 완료됐으면, 각 반복문을 함수로 추출할지 고민해본다.


8.8. 반복문을 파이프라인으로 바꾸기

배경

  • 대표적인 연산은 mapfilter 이다.

  • map은 함수를 사용해 입력 컬렉션의 각 원소를 변환한다.
  • filter는 또 다른 함수를 사용해 입력 컬렉션을 필터링해 부분집합을 만든다.

절차

  • 반복문에서 사용하는 컬렉션을 가리키는 변수를 하나 만든다.
    • 기존 변수를 단순히 복사한 것일 수도 있다.
  • 반복문의 첫 줄부터 시작해서, 각각의 단위 행위를 적절한 컬렉션 파이프라인 연산으로 대체한다. 이 때 컬렉션 파이프라인 연산은 앞에서 만든 반복문 컬렉션 변수에서 시작하여, 이전 연산의 결과를 기초로 연쇄적으로 수행된다. 하나를 대체할 때마다 테스트한다.
  • 반복문의 모든 동작을 대체했다면 반복문 자체를 지운다.


8.9. 죽은 코드 제거하기

배경

  • 코드가 더 이상 사용되지 않게 됐다면 지워야 한다.

절차

  • 죽은 코드를 외부에서 참조할 수 있는 경우라면 혹시라도 호출하는 곳이 있는지 확인한다.
  • 없다면 죽은 코드를 제거한다.
  • 테스트한다.

댓글남기기