본문 바로가기

Clean Code

[리팩터링 2판 - 실전편] 08장 기능이동


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

 

8.1 함수 옮기기 Move Function

좋은 소프트웨어 설계의 핵심은 모듈화가 얼마나 잘 되어있느냐를 뜻하는 modularity이다. 모듈성이란 프로그램의 어딘가를 수정하려 할 때 해당 기능과 깊이 관련된 작은 일부만 이해해도 가능하게 해주는 능력이다. 모듈성을 높이려면 서로 연관도니 요소들을 함께 묶고, 요소 사이의 연결 관계를 쉽게 찾고 이해할 수 있도록 해야한다.

이해도가 높아질수록 소프트웨어 요소들을 더 잘 묶는 새로운 방법을 깨우치게 된다. 그래서 높아진 이해를 반영하려면 요소들을 이리저리 옮겨야 할 수 있다.

어떤 함수가 자신이 속한 모듈 A의 요소들보다 다른 모듈 B의 요소를 더 많이 참조한다면 모듈 B로 옮겨줘야 마땅하다. 이렇게하면 캡슐화가 좋아져서, 이 소프트웨어의 나머지 부분은 모듈 B의 세부사항에 덜 의존하게 된다.

호출자들의 현재 모듈이나 다음 업데이트 때 바뀌리라 예상되는 위치에 따라서도 함수를 옮겨야 할 수 있다. 다른 함수안에서 도우미 역할로 정의된 함수 중 독립적으로도 고유한 가치가 있는 것은 접근하기 더 쉬운 장소로 옮기는 게 낫다.

함수들을 한 컨텍스트에 두고 작업해보는 것도 괜찮다. 그곳이 얼마나 적합한지는 차차 깨달아갈 것임을 알고 있고, 잘 맞지 않다고 판단되면 위치는 언제든 옮길 수 있으니 말이다.

중첩함수를 사용하다보면 숨겨진 데이터끼리 상호 의존하기가 아주 쉬우니 중첩함수는 되도록 만들지 말자.

 

8.2 필드 옮기기 Move Field

데이터 구조를 잘못 선택하면 아귀가 맞지 않는 데이터를 다루기 위한 코드로 범벅이 된다. 이해하기 어려운 코드가 만들어지는 데서 끝나지 않고, 데이터 구조 자체도 그 프로그램이 어떤 일을 하는지 파악하기 어렵게 한다. 그래서 데이터 구조가 중요하다.

모든 기술과 경험에도 불구하고 초기 설계에서는 실수가 빈번했다. 현재 데이터 구조가 적절치 않음을 깨닫게 되면 곧바로 수정해야 한다.

함수에 어떤 레코드를 넘길 때마다 또 다른 레코드의 필드도 함께 넘기고 있다면 데이터 위치를 옮겨야 할 것이다. 함수에 항상 함께 건네지는 데이터 조각들은 상호 관계가 명확하게 드러나도록 한 레코드에 담는 게 가장 좋다.

 

8.3 문장을 함수로 옮기기 Move Statements into Function

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

 

8.4 문장을 호출한 곳으로 옮기기 Move Statements to Callers

초기에는 응집도 높고 한 가지 일만 수행하던 함수가 어느새 둘 이상의 다른 일을 수행하게 바뀔 수 있다.

여러 곳에서 사용하던 기능이 일부 호출자에게는 다르게 동작하도록 바뀌어야 한다면 이런 일이 벌어진다. 달라진 동작을 함수에서 꺼내 해당 호출자로 옮겨야한다. 옮긴 뒤에는 필요할 때마다 독립적으로 수정할 수 있다.

 

8.5 인라인 코드를 함수 호출로 바꾸기 Replace Inline Code with Function Call

함수는 여러 동작을 하나로 묶어준다. 함수의 이름이 코드의 동작 방식보다는 목적을 말해주기 때문에 함수를 활용하면 코드를 이해하기가 쉬워진다.

이름을 잘 지었다면 인라인 코드 대신 함수 이름을 넣어도 말이 된다. 말이 되지 않는다면 함수 이름이 적절하지 않거나 그 함수의 목적이 인라인 코드의 목적과 다르게 때문일 것이다. (이 경우에는 함수 호출로 대체하면 안된다.)

라이브러리가 제공하는 함수로 대체할 수 있다면 훨씬 좋다. 함수 본문을 작성할 필요조차 없어지기 때문이다. 보통은 직접 짠 코드보다 라이브러리가 제공하는 API가 더 효율적인 가능성이 크다. 물론 외부 라이브러리에 지나치게 의존하면 설계 유연성이 떨어지니 처한 상황에 맞게 신중히 판단해야 한다.

 

8.6 문장 슬라이드하기 Slide Statements

관련된 코드들이 가까이 모여 있다면 이해하기 쉽다. 예컨대 하나의 데이터 구조를 이용하는 문장들은 다른 데이터를 이용하는 코드 사이에 흩어져 있기보다는 한데 모여 있어야 좋다.

모든 변수 선언을 함수 첫머리에 모아두는 사람도 있는데 나는 변수를 처음 사용할 때 선언하는 스타일을 선호한다.

사이드이펙트가 없는 코드끼리는 마음 가는 대로 재배치할 수 있다. 현명한 프로그래머들이 되도록 사이드펙트가 없는 코드들로 프로그래밍 하는 이유 중 하나다. 사이드이펙트가 있는 코드를 슬라이드하거나 사이드이펙트가 있는 코드를 건너뛰어야 한다면 훨씬 신중해야 한다.

나는 항상 단계를 잘게 나눠 리팩터링하는데, 리팩터링을 처음 접하는 이들이 보기에는 어리석어 보일 정도로 작은 단계들을 밟는다. 

 

8.7 반복문 쪼개기 Split Loop

두 일을 한꺼번에 처리할 수 있다는 이유에서 반복문 하나에서 두 가지 일을 수행하는 모습을 보게 된다. 이렇게 하면 반복문을 수정해야 할 때마다 두 가지 일 모두를 잘 이해하고 진행해야 한다.

반복문을 두 번 실행해야 하므로 이 리팩터링을 불편해하는 프로그래머도 많다. 다시 한번 이야기하지만, 리팩터링과 최적화를 구분하자. 최적화는 코드를 깔끔히 정리한 이후에 수행하자. 반복문을 두 번 실행하는 게 병목이라고 밝혀지면 그때 다시 하나로 합치기는 식은 죽 먹기다. 하지만 심지어 긴 리스트를 반복하더라도 병목으로 이어지는 경우는 ㅁ우 드물다. 오히려 반복문 쪼개기가 다른 더 강력한 최적화를 적용할 수 있는 길을 열어주기도 한다.

 

8.8 반복문을 파이프라인으로 바꾸기 Replace Loop with Pipeline

컬렉션 파이프라인을 이용하면 처리과정을 일련의 연산으로 표현할 수 있다. 각 연산은 컬렉션을 입력받아 다른 컬렉션을 내뱉는다. 논리를 파이프라인으로 표현하면 이해하기 훨씬 쉬워진다. 객체가 파이프라인을 따라 흐르며 어떻게 처리되는지를 읽을 수 있기 때문이다.

 

 

8.9 죽은 코드 제거하기 Remove Dead Code

쓰이지 않는 코드가 몇 줄 있다고 해서 시스템이 느려지는 것도 아니고 메모리를 많이 잡아먹지도 않는다. 최신 컴파일러들은 이런 코드를 알아서 제거해준다.

그렇더라도 사용되지 않는 코드가 있다면 그 소프트웨어의 동작을 이해하는 데는 커다란 걸림돌이 될 수 있다. 코드들 스스로는 '절대 호출되지 않으니 무시해도 되는 함수다'라는 신호를 주지 않기 때문이다. 코드가 더 이상 사용되지 않게 됐다면 지워야 한다.

혹시 다시 필요해질 날이 오지 않을까 걱정할 필요 없다. 우리에겐 버전 관리 시스템이 있다. 어느 revision에서 삭제했는지를 커밋 메시지로 남겨놓자.