본문 바로가기

Clean Code

[리팩터링 2판 - 실전편] 06장 기본적인 리팩터링


마틴파울러 리팩터링 2판 정주행 스터디 - 실전편

 

6.1 함수 추출하기 Extract Function

추출은 결국 이름짓기이며, 코드 이해도가 높아지다보면 이름을 바꿔야할 때가 많다.

코드를 보고 무슨 일을 하는지 파악하는 데 한참이 걸린다면 그 부분을 함수로 추출한 뒤 '무슨 일'에 걸맞는 이름을 짓는다. 이렇게 해두면 나중에 코드를 다시 읽을 때 함수의 목적이 눈에 확 들어오고, 본문 코드에 대해서는 더 이상 신경 쓸 일이 거의 없다.

내 경험상 함수 안에 들어가 ㄹ코드가 대여섯 줄을 넘어갈 때부터 슬슬 냄새를 풍기기 시작했고, 단 한 줄짜리 함수를 만드는 일도 적지 않았다. 함수가 짧으면 캐싱하기가 더 쉽기 때문에 컴파일러가 최적화하는데 유리할 때가 많다.

highlight() 메서드가 있었는데 구현 코드르 보니 단순히 reverse()라는 메서드만 호출하고 있었다. 메서드 이름이 구현 코드보다 길었지만 그건 문제가 되지 않았다. 코드의 목적(highlight)과 구현(reverse) 사이의 차이가 그만큼 컸기 때문이다.

함수를 새로 만들고 목적을 잘 드러내는 이름을 붙인다. '어떻게'가 아니라 '무엇을' 하는지가 드러나야 한다.
추출한 함수의 지역변수를 참조하거나 추출한 함수의 유효범위를 벗어나는 변수는 없는지 검사한다.
있다면 매개변수로 전달한다.

 

6.2 함수 인라인하기 Inline Function

때로는 함수 본문이 이름만큼 명확한 경우도 있다. ㄷ또는 함수 본문 코드를 이름만큼 깔끔하게 리팩터링할 떄도 있다.

리팩터링 과정에서 잘못 추출된 함수들을 다시 인라인한다. 가령 다른 함수로 단순히 위임하기만 하는 함수들이 너무 많아서 위임관계가 복잡하게 얽혀있으면 인라인해버린다.

실수하지 않으려면 한 번에 한 문장씩 옮기는 것이 좋다. 어느 정도 자신감이 붙으면 다시 작업을 크게 묶어서 처리한다.

다형 메서드(polymorphic method)인지 확인한다.
인라인할 함수를 호출하는 곳을 '모두' 찾아 호출문을 함수 본문으로 교체한다.

 

6.3 변수 추출하기 Extract Variable

표현식이 너무 복잡해서 이해하기 어려울 때 지역 변수를 활용하면 관리하기 더 쉽게 만들 수 있다. 이 과정에서 추가한 변수는 디버깅에도 도움이 된다.

현재 함수 안에서만 의미가 있다면 변수로 추출하는 것이 좋다. 그러나 함수를 벗어난 넓은 문맥에서까지 의미가 된다면 넓은 범위에 통용되는 이름을 생각해 변수가 아닌 함수로 추출해야 한다.

이름이 통용되는 문맥을 넓히면 다른 코드에서 사용할 수 있기 때문에 같은 표현식을 중복해서 작성하지 않아도 된다. 그래서 중복이 적으면서 의도가 잘 드러나는 코드를 작성할 수 있다.

추출하려는 표현식에 사이드이펙트는 없는지 확인한다.
표현식을 여러 곳에서 사용한다면 각각을 새로 만든 변수로 교체한다.

 

6.4 변수 인라인하기 Inline Variable

변수는 그 이름이 원래 표현식과 다를 바 없을 때도 있다. 또 변수가 주변 코드를 리팩터링하는 데 방해가 되기도 한다. 이럴 때는 변수를 인라인하는 것이 좋다.

대입문의 우변(표현식)에서 사이드이펙트가 생기지 않는지 확인한다.
변수가 단 한번만 대입되는지 확인한다. (불변인지)
변수를 가장 처음 사용하는 코드를 찾아서 대입문 우변의 코드로 바꾼다.

 

6.5 함수 선언 바꾸기 Change Function Declaration

'함수 이름 바꾸기' 또는 '시그니처 바꾸기'라고도 한다.

함수는 프로그램을 작은 부분으로 나누는 주된 수단이다. 함수 선언은 각 부분이 서로 맞물리는 방식을 표현하며, 실질적으로 소프트웨어 시스템의 구성 요소를 조립하는 연결부 역할을 한다.

연결부를 잘 정의하면 시스템에 새로운 부분을 추가하기가 쉬워지는 반면, 잘못 정의하면 지속적인 방해 요인으로 작용하여 소프트웨어 동작을 파악하기 어려워지고 요구사항이 바뀔 때 적절히 수정하기 어렵게 한다.

연결부에서 가장 중요한 요수는 함수의 이름이다. 적합한 이름을 단번에 지은 적이 거의 없다. 이름이 잘못된 함수를 발견하면 더 나은 이름이 떠오르는 즉시 바꾸라는 명령으로 받아들인다. 그래야 나중에 그 코드를 다시 볼 때 무슨 일을 하는지 '또' 고민하지 않게 된다.

함수의 매개변수는 함수가 외부 세계와 어우러지는 방식을 정의한다. 매개변수는 함수를 사용하는 문맥을 설정한다. 매개변수를 올바르게 선택하기 문제의 정닶은 바로 정답이 없다는 것이다. 어떻게 연결하는 거싱 더 나은지 더 잘 이해하게 될 때마다 그에 맞게 코드를 개선할 수 있도록 함수 선언 바꾸기 리팩터링과 친숙해져야만 한다.

매개변수를 제거하려는 경우, 함수 본문에서 제거 대상 매개변수를 참조하는 곳은 없는지 확인한다.
메서드 선언을 원하는 형태로 바꾼다.
기존 메서드 선언을 참조하는 부분을 모두 찾아서 바뀐 형태로 수정한다.

 

6.6 변수 캡슐화하기 Encapsulate Variable

데이터는 함수보다 다루기가 까다로운데, 그 이유는 (함수와 같은 방식으로 이름을 바꾸거나 다른 모듈로 옮기는 등의 방식으로) 처리할 수 없기 때문이다. 데이터는 참조하는 모든 부분을 한 번에 바꿔야 코드가 제대로 작동한다.

접근할 수 있는 범위가 넓은 데이터를 옮길 때, 먼저 그 데이터로의 접근을 독점하는 함수를 만드는 식으로 캡슐화하는 것이 가장 좋은 방법일 때가 많다. 데이터 재구성이라는 어려운 작업을 함수 재구성이라는 더 단순한 작업으로 변환하는 것이다.

데이터 캡슐화는 데이터를 변경하고 사용하는 코드를 감시할 수 있는 확실한 통로가 되어주기 때문에 데이터 변경 전 검증이나 변경 후 추가 로직을 쉽게 끼워넣을 수 있다. 나는 유효범위가 함수 하나보다 넓은 가변 데이터는 모두 이런 식으로 캡슐화해서 그 함수를 통해서만 접근하게 만드는 습관이 있다.

불변 데이터는 가변 데이터보다 캡슐화할 이유가 적다. 데이터가 변경될 일이 없어서 갱신 전 검증 같은 추가 로직이 자리할 공간을 마련할 필요가 없기 때문이다.

기본 캡슐화 기법은 데이터 항목을 참조하는 부분만 캡슐화한다. 변수에 담긴 내용을 변경하는 행위까지 캡슐화하는 가장 간단한 방법은 그 값을 바꿀 수 없게 만드는 것이다.

변수로의 접근과 갱신을 전담하는 '캡슐화 함수'를 만든다.
변수를 직접 참조하던 부분을 모두 적절한 캡슐화 함수 호출로 바꾼다.
변수 값이 레코드라면 레코드 캡슐화하기를 적용할지 고려해본다.

 

 

6.7 변수 이름 바꾸기 Rename Variable

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

맥락으로부터 변수의 목적을 명확히 알 수 있어서 한 글자로 된 이름을 짓기도 한다. 함수 호출 한 번으로 끝나지 않고 값이 영속되는 필드라면 이름에 더 신경써야한다.

이름을 바꿀 변수를 참조하는 곳을 모두 찾아서 하나씩 변경한다.

 

6.8 매개변수 객체 만들기 Introduce Parameter Object

데이터 뭉치를 데이터 구조로 묶으면 데이터 사이의 관계가 명확해진다는 이점을 얻는다. 게다가 매개변수 수가 줄어든다. 같은 데이터 구조를 사용하는 모든 함수가 원소를 참조할 때 항상 똑같은 이름을 사용하기 때문에 일관성도 높여준다.

적당한 데이터 구조(객체)로 묶이지 않았다면 객체를 새로 만든다.
함수 선언 바꾸기(6.5)로 새 데이터 구조를 매개변수로 추가한다.
함수 호출 시 새로운 데이터 구조 인스턴스를 넘기도록 수정한다.
기존 매개변수를 사용하던 코드를 새 데이터 구조의 원소를 사용하도록 바꾼다.
기존 매개변수를 제거한다.

 

6.9 여러 함수를 클래스로 묶기 Combine Functions into Class

공통 데이터를 중심으로 긴밀하게 엮여 작동하는 함수 무리를 발견하면 클래스 하나로 묶고 싶어진다. 클래스로 묶으면 이 함수들이 공유하는 공통 환경을 더 명확하게 표현할 수 있고, 각 함수에 전달되는 인수를 줄여서 객체 안에서의 함수 호출을 간결하게 만들 수 있다.

클래스로 묶을 때의 두드러진 장점은 클라이언트가 객체의 핵심 데이터를 변경할 수 있고, 파생 객체들을 일관되게 관리할 수 있다는 것이다.

함수들이 공유하는 공통 데이터 레코드를 캡슐화 한다.
함수 옮기기(6.1)로 공통 레코드를 사용하는 함수 각각을 새 클래스로 옮긴다. 
데이터를 조직하는 로직들은 함수로 추출해서 새 클래스로 옮긴다.

 

6.10 여러 함수를 변환 함수로 묶기  Combine Functions into Transform

소프트웨어는 데이터를 입력받아서 여러 가지 정보를 도출하곤 한다. 이렇게 도출된 정보는 여러 곳에서 사용될 수 있는데, 그러다 보면 이 정보가 사용되는 곳마다 같은 도출 로직이 반복되기도 한다. 이런 도출 작업을을 한데로 모아두면 검색과 갱신을 일관된 장소에서 처리할 수 있고 로직 중복도 막을 수 있다.

변환 함수는 원본 데이터를 입력 받아서 필요한 정보를 모두 도출한 뒤, 각각을 출력 데이터의 필드에 넣어 반환한다. 이렇게 해두면 도출 과정을 검토할 일이 생겼을 때 변환 함수만 살펴보면 된다.

이 리팩터링 대신 6.9 여러 함수를 클래스로 묶기로 처리해도 된다. 원본 데이터가 코드 안에서 갱신될 때는 클래스로 묶는 편이 훨씬 낫다.

변환할 레코드를 입력받아 값을 그대로 반환하는 변환 함수를 만든다.
묶을 함수 중 함수 하나를 고랄 본문 코드를 변환함수로 옮기고 처리 결과를 레코드에 새 필드로 기록한다. 그런 다음 클라이언트가 이 필드를 사용하도록 수정한다.

 

6.11 단계 쪼개기  Split Phase

서로 다른 두 대상을 한꺼번에 다루는 코드를 발견하면 각각을 별개 모듈로 나누는 방법을 모색한다. 코드를 수정해야할 때 두 대상을 동시에 생각할 필요없이 하나에만 집중하기 위해서다.

모듈이 잘 분리되어있다면 다른 모듈의 상세 내용은 전혀 기억하지 못해도 원하는 대로 수정을 끝마칠 수도 있다.

두 번째 단계에 해당하는 코드를 독립된 함수로 추출한다.
중간 데이터 구조를 만들어서 앞에서 추출한 함수의 인수로 추가한다.
첫번째 단계에 해당하는 코드를 독립된 함수로 추출한다. 이 함수가 중간 데이터 구조를 반환하도록 만든다.