리팩터링 2판 스터디: 8장 '기능 이동'

이 글은 리팩터링 2판 스터디 시리즈 중 하나입니다.

리팩터링 2판 스터디의 네 번째 글입니다. 여러 명이 나눠서 정리하는 스터디였기 때문에 이 글을 마지막으로 스터디는 끝나는데, 나머지 내용도 제 블로그에 정리할지는 좀 고민해 봐야겠어요.

이 장은 말 그대로 코드를 옮겨야 하는 상황과 그 방법에 대해 다룹니다. 절차적 프로그래밍에서 코드를 절차상 적절한™ 위치로 옮기는 건 아주 중요하죠.

목차

정리

8.1 함수 옮기기

모듈성을 높이려면 서로 연관된 요소들을 함께 묶고, 요소 사이의 연결 관계를 쉽게 찾고 이해할 수 있도록 해야 한다. 하지만 프로그램을 얼마나 잘 이해했느냐에 따라 구체적인 방법이 달라질 수 있다. 보통은 이해도가 높아질수록 소프트웨어 요소들을 더 잘 묶는 새로운 방법을 깨우치게 된다. 그래서 높아진 이해를 반영하려면 요소들을 이리저리 옮겨야 할 수 있다. – p.278

절차 (p.278)

  1. 선택한 함수가 현재 컨택스트에서 사용 중인 모든 프로그램 요소를 살펴본다. 이 요소들 중에도 함께 옮겨야 할 게 있는지 고민해본다.
  2. 선택한 함수가 다형 메서드인지 확인한다.
    • 객체 지향 언어의 다형성(polymorphism)을 구현하는 메서드인지 확인한다는 의미입니다. 슈퍼클래스나 서브클래스에도 선언된 메서드라면 전부 같이 옮겨 줘야 하니까요.
  3. 선택한 함수를 타깃 컨텍스트로 복사한다(이 때 원래의 함수를 소스 함수라 하고 복사해서 만든 새로운 함수를 타깃 함수라 한다). 타깃 함수가 새로운 터전에 잘 자리잡도록 다듬는다.
  4. 정적 분석을 수행한다.
  5. 소스 컨텍스트에서 타깃 함수를 참조할 방법을 찾아 반영한다.
  6. 소스 함수를 타깃 함수의 위임 함수가 되도록 수정한다.
  7. 테스트한다.
  8. 소스 함수를 인라인(6.2절)할지 고민해본다.

요점

8.2 필드 옮기기

필드 옮기기 리팩터링은 대체로 더 큰 변경의 일환으로 수행된다. 예컨대 필드 하나를 잘 옮기면, 그 필드를 사용하던 많은 코드가 원래 위치보다 옮겨진 위치에서 사용하는 게 더 수월할 수 있다. 그렇다면 리팩터링을 마저 진행하여 호출 코드들까지 모두 변경한다. – p.290

절차 (p.290)

  1. 소스 필드가 캡슐화되어 있지 않다면 캡슐화한다.
  2. 테스트한다.
  3. 타깃 객체에 필드(와 접근자 메서드들)를 생성한다.
  4. 정적 검사를 수행한다.
  5. 소스 객체에서 타깃 객체를 참조할 수 있는지 확인한다.
    • 참조할 수 없으면 참조하게 만듭시다(...)
  6. 접근자들이 타깃 필드를 사용하도록 수정한다.
  7. 테스트한다.
  8. 소스 필드를 제거한다.
  9. 테스트한다.

요점

8.3 문장을 함수로 옮기기

중복 제거는 코드를 건강하게 관리하는 가장 효과적인 방법 중 하나다. 예컨대 특정 함수를 호출하는 코드가 나올 때마다 그 앞이나 뒤에서 똑같은 코드가 추가로 실행되는 모습을 보면, 나는 그 반복되는 부분을 피호출 함수로 합치는 방법을 궁리한다. – p.296

절차 (p.297)

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

요점

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

앞 절의 반대입니다. 앞 절에서는 어떤 함수를 호출할 때 함수 밖에서 항상 동일한 동작을 수행한다면 그 동작을 함수 안으로 옮기는 방식이었는데, 반대로 어떤 함수를 호출할 때 그 함수 안의 한 부분이 경우에 따라 다르게 동작해야 한다면 그 부분을 함수 밖으로 꺼냅니다.

절차 (p.302)

  1. 호출자가 한두 개뿐이고 피호출 함수도 간단한 단순한 상황이면, 피호출 함수의 처음(혹은 마지막) 줄(들)을 잘라내어 호출자(들)로 복사해 넣는다(필요하면 적당히 수정한다). 테스트만 통과하면 이번 리팩터링은 여기서 끝이다.
  2. 더 복잡한 상황에서는, 이동하지 '않길' 원하는 모든 문장을 함수로 추출(6.1절)한 다음 검색하기 쉬운 임시 이름을 지어준다.
  3. 원래 함수를 인라인(6.2절)한다.
  4. 추출된 함수의 이름을 원래 함수의 이름으로 변경한다(함수 이름 바꾸기, 6.5절).
    • 더 나은 이름이 있다면 그 이름을 쓴다.

요점

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

이미 존재하는 함수와 똑같은 일을 하는 인라인 코드를 발견하면 보통은 해당 코드를 함수 호출로 대체하길 원할 것이다. 예외가 있다면 [...] 기존 함수의 코드를 수정하더라도 인라인 코드의 동작은 바뀌지 않아야 할 때뿐이다. 이 경우인가를 판단하는 데는 함수 이름이 힌트가 된다. 이름을 잘 지었다면 인라인 코드 대신 함수 이름을 넣어도 말이 된다. – p.308

절차 (p.309)

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

요점

8.6 문장 슬라이드하기

관련 코드끼리 모으는 작업은 다른 리팩터링(주로 함수 추출하기; 6.1절)의 준비 단계로 자주 행해진다. 관련 있는 코드들을 명확히 구분되는 함수로 추출하는 게 그저 문장들을 한데로 모으는 것보다 나은 분리법이다. 하지만 코드들이 모여 있지 않다면 함수 추출은 애초에 수행할 수조차 없다. – p.310

절차 (p.311)

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

요점

8.7 반복문 쪼개기

종종 반복문 하나에서 두 가지 일을 수행하는 모습을 보게 된다. 그저 두 일을 한꺼번에 처리할 수 있다는 이유에서 말이다. 하지만 이렇게 하면 반복문을 수정해야 할 때마다 두 가지 일 모두를 잘 이해하고 진행해야 한다. 반대로 각각의 반복문으로 분리해 두면 수정할 동작 하나만 이해하면 된다. – p.316

절차 (p.317)

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

요점

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

논리를 파이프라인으로 표현하면 이해하기 훨씬 쉬워진다. 객체가 파이프라인을 따라 흐르며 어떻게 처리되는지를 읽을 수 있기 때문이다. – p.320

절차 (p.320)

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

요점

8.9 죽은 코드 제거하기

코드가 더 이상 사용되지 않게 됐다면 지워야 한다. 혹시 다시 필요해질 날이 오지 않을까 걱정할 필요 없다. 우리에겐 버전 관리 시스템이 있다! – p.327

절차 (p.328)

  1. 죽은 코드를 외부에서 참조할 수 있는 경우라면(예컨대 함수 하나가 통째로 죽었을 때) 혹시라도 호출하는 곳이 있는지 확인한다.
  2. 없다면 죽은 코드를 제거한다.
  3. 테스트한다.

요점



Dani Soohan Park (@heartade)

Follow this blog at Fediverse: @heartade@blog.heartade.dev

Follow my shorter shoutouts at Fediverse: @heartade@social.silicon.moe

Follow me at Bluesky: @heartade.dev