Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions ch08-기능_이동/박선화/박선화.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 8장 기능 이동

**옮기기**

- 함수 옮기기
- 다른 클래스나 모듈로 함수 옮기기
- 필드 옮기기
- 문장 옮기기
- 문장을 함수 안이나 바깥으로 옮기기
- 문장을 함수로 옮기기
- 문장을 호출한 곳으로 옮기기
- 문장을 같은 함수 안에서 옮기기
- 문장 슬라이드하기
- 한 덩어리의 문장들이 기존 함수와 같은 일을 할 때는 인라인 코드를 함수 호출로 바꾸기

**반복문 관련 리팩터링**

- 각각의 반복문이 단 하나의 일만 수행하도록 보장하는 반복문 쪼개기
- 반복문을 완전히 없애버리는 반복문을 파이프라인으로 바꾸기

그 외

- 죽은 코드 제거하기

---

## (1) 함수 옮기기

좋은 소프트웨어 설계의 핵심은 모듈화가 얼마나 잘 되어 있느냐를 뜻하는 모듈성이다. <br />
**모듈성**이란 프로그램의 어딘가를 수정할 때 해당 기능과 관련된 작은 일부만 이해해도 가능하게 해주는 능력이다.

모듈성을 높이려면 서로 연관된 요소들을 함께 묶고, 요소 사이의 연결 관계를 쉽게 찾고 이해할 수 있도록 해야 한다. <br />
보통은 프로그램에 대한 이해도가 높아질수록 모듈화도 잘 할 수 있게 된다.

모든 함수는 어떤 컨텍스트 안에 존재한다. 객체 지향 프로그래밍의 핵심 모듈화 컨텍스트는 클래스다.
프로그래밍 언어들은 저마다의 모듈화 수단을 제공한다.

- 어떤 함수가 자신이 속한 모듈 A의 요소들보다 다른 모듈 B의 요소들을 더 많이 참조한다면 모듈 B로 옮겨줘야 캡슐화가 좋아진다.
- 호출자들의 현재 위치 (호출자가 속한 모듈)나 다음 업데이트 때 바뀌리라 예상되는 위치에 따라서도 함수를 옮겨야 할 수 있다.
- 다른 함수 안에서 도우미 역할로 정의된 함수 중 독립적으로도 고유한 가치가 있는 것은 접근하기 더 쉬운 장소로 옮기는 게 낫다.

함수를 옮길지 말지를 정하기는 쉽지 않다. 그럴 때는 대상 함수의 현재 컨텍스트와 후보 컨텍스트를 둘러보면 도움이 된다.

- 대상 함수를 호출하는 함수들은 무엇인가
- 대상 함수가 호출하는 함수들은 또 무엇이 있는가
- 대상 함수가 사용하는 데이터는 무엇인가

여러 함수를 묶을 새로운 컨텍스트가 필요해질 때는 여러 함수를 클래스로 묶기나 클래스 추출하기로 해결할 수 있다.

### [예제] 중첩 함수를 최상위로 옮기기

p280

### [예제] 다른 클래스로 옮기기

p285

## (2) 필드 옮기기

주어진 문제에 적합한 데이터 구조를 활용하면 동작 코드는 자연스럽게 단순하고 직관적으로 짜여진다. 그래서 데이터 구조가 중요하다.

프로젝트 초기에 가장 적합한 데이터 구조를 알아내고자 할 때는 도메인 주도 설계같은 기술이 도움이 된다.

```javascript
class Customer {
get plan() {
return this._plan
}
get discountRate() {
return this._discountRate
}
}
```

```javascript
class Customer {
get plan() {
return this._plan
}
get discountRate() {
return this.plan.discountRate
}
}
```

필드 옮기기 리팩터링

- 함수에 어떤 레코드를 넘길 때마다 또 다른 레코드의 필드도 함께 넘기고 있다면 데이터 위치를 옮겨야 한다.
- 함께 넘기는 데이터 조각들은 상호 관계가 명확하게 드러나도록 한 레코드에 담는 게 가장 좋다.
- 한 레코드를 변경할 때 다른 레코드의 필드까지 변경해야만 한다면 필드의 위치가 잘못되었다는 신호다.
- 구조체 여러 개에 정의된 똑같은 필드들을 갱신해야 한다면 한 번만 갱신해도 되는 다른 위치로 옮겨야 한다.

레코드 대신 클래스나 객체가 와도 똑같다. 클래스는 함수가 곁들여진 레코드라 할 수 있다.

클래스의 데이터들은 접근자 메서드들 뒤에 감춰져(캡슐화되어) 있으므로 클래스에 곁들여진 함수(메서드)들은 데이터를 이리저리 옮기는 작업을 쉽게 해준다. 데이터의 위치를 옮기더라도 접근자만 그에 맞게 수정하면 클리이언트 코드들은 아무 수정 없이도 동작할 것이다. 따라서 클래스를 사용하면 이 리팩터링을 수행하기가 더 쉬워진다.

## (3) 문장을 함수로 옮기기

중복 제거는 코드를 건강하게 관리하는 가장 효과적인 방법 중 하나다. 특정 함수를 호출하는 코드가 나올 때마다 그 앞이나 뒤에서 똑같은 코드가 추가로 실행되는 모습을 보면, 반복되는 부분을 피호출 함수로 합친다. 그러면 추후 수정할 일이 생겼을 때 단 한 곳만 수정하면 된다. 나중에 이 코드의 동작을 여러 변형들로 나눠야 하는 순간이 오면 (반대 리팩터링인) 문장을 호출한 곳으로 옮기기를 적용하여 쉽게 다시 뽑아낼 수 있다.

절차

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

## (4) 문장을 호출한 곳으로 옮기기

초기에는 응집도 높고 한 가지 일만 수행하던 함수가 어느새 둘 이상의 다른 일을 수행하게 바뀔 수 있다.
여러 곳에서 사용하던 기능이 일부 호출자에게는 다르게 동작하도록 바뀌어야 한다면 달라진 동작을 함수에서 꺼내 해당 호출자로 옮겨야 한다.

우선 문장 슬라이드하기를 적용해 달라지는 동작을 함수의 시작 혹은 끝으로 옮긴 다음, 바로 이어서 문장을 호출한 곳으로 옮기기 리팩터링을 적용하면 된다.

작은 변경이라면 문장을 호출한 곳으로 옮기는 것으로 충분하지만, 호출자와 호출 대상의 경계를 완전히 다시 그어야 할 때도 있다. 후자의 경우 함수 인라인하기부터 적용한 다음, 문장 슬라이드하기와 함수 추출하기로 더 적합한 경계를 설정하면 된다.

## (5) 인라인 코드를 함수 호출로 바꾸기

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

함수는 중복을 없애는 데도 효과적이다. 똑같은 코드를 반복하는 대신 함수를 호출하면 된다. 동작을 변경할 때도, 비슷해 보이는 코드들을 일일이 찾아 수정하는 대신 함수 하나만 수정하면 된다.

이미 존재하는 함수와 똑같은 일을 하는 인라인 코드를 발견하면 보통은 해당 코드를 함수 호출로 대체하길 원할 것이다.
특히 라이브러리가 제공하는 함수로 대체할 수 있다면 훨씬 좋다. 함수 본문을 작성할 필요조차 없어지기 때문이다.

## (6) 문장 슬라이드하기

관련된 코드들이 가까이 모여 있다면 이해하기가 더 쉽다. 하나의 데이터 구조를 이용하는 문장들은 한데 모여 있어야 좋다.

관련 코드끼리 모으는 작업은 다른 리팩터링(주로 함수 추출하기)의 준비 단계로 자주 행해진다.

**[절차]**

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

테스트가 실패한다면 더 작게 나눠 시도해봐라. 이동 거리를 줄이는 방법과 한 번에 옮기는 조각의 크기를 줄이는 방법이 있다.

### [예시] 조건문이 있을 때의 슬라이드

p313

## (7) 반복문 쪼개기

반복문 하나에서 두 가지 일을 수행한다면 반복문을 수정해야 할 때마다 두 가지 일 모두를 잘 이해하고 진행해야 한다. 반대로 각각의 반복문으로 분리해두면 수정할 동작 하나만 이해하면 된다.

반복문을 분리하면 사용하기도 쉬워진다. 한 가지 값만 계산하는 반복문이라면 그 값만 곧바로 반환할 수 있다. 반면 여러 일을 수행하는 반복문이라면 구조체를 반환하거나 지역 변수를 활용해야 한다.

리팩터링과 최적화를 구분하자. 최적화는 코드를 깖끔히 정리한 이후에 수행하자. 반복문을 두 번 실행하는 게 병목이라 밝혀지면 그때 다시 하나로 합치기는 쉽다. 하지만 긴 리스트를 반복하더라도 병목으로 이어지는 경우는 매우 드물다. 오히려 반복문 쪼개기가 다른 더 강력한 최적화를 적용할 수 있는 길을 열어주기도 한다.

## (8) 반복문을 파이프라인으로 바꾸기

과어에는 객체 컬렉션을 순화할 때 반복문을 사용하라고 배웠겠지만 언어는 계속해서 발전해왔다.

컬렉션 파이프라인을 이용하면 처리 과정을 일련의 연산으로 표현할 수 있다. 이때 각 연산은 컬렉션을 입력받아 다른 컬렉션을 내뱉는다.

- map : 함수를 사용해 입력 컬렉션의 각 원소를 변환한다.
- filter : 또 다른 함수를 사용해 입력 컬렉션을 필터링해 부분집합을 만든다. 이 부분집합은 파이프라인의 다음 단계를 위한 컬렉션으로 쓰인다.

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

## (9) 죽은 코드 제거하기

쓰이지 않는 코드가 몇 줄 있다고 해서 시스템에 느려지는 것도 아니고 메모리르 ㄹ많이 잡아먹지도 않는다. 사실 최신 컴파일러들은 이런 코드를 알아서 제거해준다. 하지만 그렇더라도 사용되지 않는 코드가 있다면 그 소프트웨어의 동작을 이해하는 데는 커다란 걸림돌이 될 수 있다.

코드가 더 이상 사용되지 않게 됏다면 지워야 한다. 혹시 다시 필요해질까 걱정된다면 버전 관리 시스템을 이용하면 된다.

한때는 죽은 코드를 주석처리하는 방법이 널리 쓰였다. 버전 관리 시스템이 보편화되지 않았거나 아직은 쓰기 불편했던 시절엔 유용한 방법이었다. 지금은 코드가 몇 줄 안 되는 초기 단계부터 버전 관리 시스템을 사용하므로, 더 이상은 필요치 않다.