# 긴 코드 조각내기

* **다섯 줄 제한**으로 지나치게 <mark style="color:purple;">**긴 함수 식별하기**</mark>
* 세부 사항을 보지 않고 코드 작업하기
* **함수 추출**로 <mark style="color:purple;">**긴 함수 분해하기**</mark>
  * 함수 추출을 통해 변수명을 변경해서 가독성을 높이자.
* **호출 또는 전달**, **한 가지만 할 것**으로 <mark style="color:purple;">**추상화 수준 맞추기**</mark>
* **if 문은 함수의 시작에만 배치**로 <mark style="color:purple;">**if 문 분리하기**</mark>

## 긴 코드 조각 내기

> DRY (Don't Repeat Yourself, 똑같은 일에 두 번 반복하지 말 것), \
> KISS (Keep It Simple, Stupid, 단순함을 지킬 것), 지침을 따르더라도 코드는 쉽게 지저분해진다.

이 경우 주 원인은 다음과 같다.

* 함수가 여러 가지 다른 일을 수행
* 낮은 수준의 원시 연산(배열 조작, 산술 연산 등)을 사용
* 주석과 적절한 메서드와 변수명 같이 사람이 읽을 수 있는 텍스트가 부족

### 다섯 줄 제한

> 어떤 함수도 다섯 줄 이상을 가질 수 없다 **(함수는 `{`, `}` 를 제외하고 5줄 이상이 되어서는 안된다.)**
>
> **다섯 줄 제한은 궁극의 목표!!**

* 문장이라고 하는 코드 한 줄은 하나의 **`if`**, **`for`**, **`while`** 또는 **`세미콜론`**&#xC73C;로 끝나는 모든 것을 말함
  * 즉, 할당, 함수 호출, return 같은 것들이다. 공백과 중괄호는 제외한다.

{% hint style="warning" %}
단순히 코드가 수치상으로 5줄 이상을 말하는 것은 아님!&#x20;

코드의 라인은 린트 또는 프리티어 규칙에 의해 달라질 수 있기 때문에 코드 상에 한줄이 아닌 위 규칙에 따른 한 문장을 의미한다.

```typescript
function isTrue(bool: boolean) { //총 4줄(문장)
  if (bool) // if - 1문장
    return true; // return - 2문장
  else return false; // else - 3문장, return - 4문장
}
```

{% endhint %}

#### 스멜

* 함수가 길다는 것 자체가 스멜
  * 길다는건 메서드는 한 가지 작업만 해야 한다는 다른 스멜을 생각해 볼 수 있다.
* 한 번에 긴 메서드의 모든 논리를 머릿속에 담아야해서 작업하기 어렵다.

#### 의도

* 관심을 가지지 않으면 시간이 지남에 따라 더 많은 기능이 추가되면서 메서드가 커지는 경향이 늘 생김
  * 그로 인해 코드를 점점 더 이해하기 어렵게 된다.
* 각각 5줄의 코드가 있는 4개의 메서드가 20줄인 하나의 함수보다 훨씬 빠르고 이해하기 쉽다.
  * 각 함수의 이름으로 코드의 의도를 전달할 수 있기 때문
* 또한 작은 함수에 적절한 이름을 붙이면 큰 함수의 이름을 정하는 데도 도움이 된다.

### 함수 분해하기 & 함수 추출하기

> 다섯 줄 제한 규칙은 이해하기 쉽지만 항상 지킬 수 있는 것은 아님
>
> **함수 전체를 소화하려 하지 말고, 작게 잘라서 이해하기 쉽게 하나씩 처리!**

* 코드를 이해하기 위한 첫 번째 단계는 항상 함수명을 고려하는 것&#x20;
* 각 줄을 모두 이해 하려다 보면 시간이 많이 걸리고 비생산적이게 된다.
* 그래서 **코드의 '형태'**&#xB97C; 살펴보는것으로 시작

#### Step

1. 동일한 작업을 하는 데 필요한 줄들을 그룹으로 묶어 주석달기
2. 식별된 그룹의 모든 줄을 선택한 다음 잘라내어 별도 함수로 추출
3. 추출한 함수를 호출하는 코드를 추가 및 주석 제거
4. 추출한 함수에서 1\~3 동일한 절차 반복

{% hint style="success" %}
함수 분해하기는 단순히 코드 줄만 이동하는것이기 때문에 비교적 리팩터링 위험도가 낮다.
{% endhint %}

{% hint style="info" %}
if의 일부 분기만 return 문을 가지고 있을 경우 함수를 추출하는 데 방해가 될 수 있으므로 함수의 끝에서 시작해 위로 작업해가는 것이 좋다. 이는 return 문을 가진 조건을 메서드의 앞쪽에 배치하게 해서 결과적으로 모든 분기에서 return 할 수 있게 한다.
{% endhint %}

{% hint style="warning" %}
자신감이 떨어지는 예쁜 코드를 만드는 것보다 특이하게 생긴 안전한 코드를 만드는 편이 더 낫다.
{% endhint %}

### 추상화 수준을 맞추기 위한 함수 분해

> 함수 내에서는 객체에 있는 함수를 호출하거나 객체를 인자로 전달할 수 있지만 둘을 섞어 사용해서는 안된다.
>
> **호출 또는 전달, 한 가지만 할 것!&#x20;**<mark style="color:red;">**함수의 내용은 동일한 추상화 수준에 있어야 한다.**</mark>

* 더 많은 함수를 도입하고 여러 가지를 매개변수로 전달하기 시작하면 결국 책임이 고르지 않게 된다.
* 예를 들면, 함수에서 배열에 인덱스를 설정하는 것과 같은 직접적인 작업을 수행하거나 동일한 배열을 더 복잡한 함수에 인자로 전달할 수 있다.
  * 그러면 코드는 <mark style="color:purple;">**직접 조작하는 낮은 수준의 작업과 다른 함수에 인자로 전달하는 높은 수준의 호출이 공존해서 함수 이름 사이의 불일치로 가독성이 떨어지게 된다.**</mark>
* <mark style="color:red;">**동일한 수준의 추상화를 유지하는 편이 코드를 읽기 훨씬 더 쉽다.**</mark>

```javascript
// 높은 수준의 추상화 sum(arr)와 낮은 수준의 arr.length 모두 사용한 케이스
function average(arr: number[]) {
  return sum(arr) / arr.length
}

// 추상화 동일하게 refactoring
function average(arr: number[]) {
  return sum(arr) / size(arr);
}
```

#### 스멜

> **함수의 내용은 동일한 추상화 수준에 있어야 한다.**

* 전달된 인자의 메서드가 어떻게 사용되었는지를 식별하는 것은 간단한 일
* 인자로 전달된 변수 옆의 `.`로 쉽게 찾을 수 있다.

#### 의도

* 함수에서 몇 가지 세부적인 부분을 추출해서 추상화를 도입할 때 이 규칙은 연관된 다른 세부적인 부분도 추출하게됨
* 이렇게 하면 메서드 내부의 추상화 수준이 항상 동일하게 유지된다.

### 좋은 함수 이름의 속성

> 함수를 추출할 때 마다 함수의 이름을 지어 코드를 더 읽기 쉽게 만들 수 있다.
>
> 함수명을 지을 때는 항상 나중에 함수가 더 작아졌을 때 이름을 개선할 수 있는지를 평가해봐야 한다.

#### 좋은 함수 이름이란?

* 정직해야한다. -> 함수의 의도를 설명해야함
* 완전해야 한다. -> 함수가 하는 모든 것을 담아야 한다.
* 도메인에서 일하는 사람이 이해할 수 있어야 한다. -> 작업 중인 도메인에서 사용하는 단어를 사용

```typescript
// Before
function draw() {
  let canavs = document.getElementById("GameCanvas") as HTMLCanvasElement;
  let g = canavs.getContext('2d');
  
  // 추상화 수준이 다르다.
  g.clearRect(0, 0, canvas.width, canavs.height); // 객체의 메서드 호출 (저수준)
  drawMap(g); // 객체를 인자로 전달 (고수준)
  drawPlayer(g);
}

// After
function createGraphics() {
  let canavs = document.getElementById("GameCanvas") as HTMLCanvasElement;
  let g = canavs.getContext('2d');
  g.clearRect(0, 0, canvas.width, canavs.height);
  
  return g;
}

function draw() {
  const g = createGrpahics(g);
  drawMap(g);
  drawPlayer(g);
}
```

{% hint style="danger" %}
만약 `g.clearRect` 줄만을 추출한다면 결국 `canvas`를 인자로 전달하면서 `canavs.getContext`를 호출하게 돼서 다시 규칙이 위반된다.
{% endhint %}

{% hint style="info" %}
함수 네이밍으로 자주 사용되는 접두사 키워드들

`handle`, `update`, `contain`, `get`, `delete`
{% endhint %}

### if 문은 함수의 시작에만 배치

> if 문이 있는 경우 해당 if 문은 함수의 첫 번째 항목 이어야 한다.

* 함수는 한가지 일만 해야 한다.
* **무언가를 확인하는 것은 한 가지 일이된다.**
* 따라서 함수에 `if`가 있는 경우 함수의 첫 번째 항목 이어야 한다.
* 또한 그 후에 아무것도 해서는 안된다는 의미에서 유일한 것이어야 한다.
* `if` 문이 함수가 하는 유일한 일이어야 한다는 말은 곧 그 본문을 추출할 필요가 없고, 또한 `else` 문과 분리해서는 안된다는 말이다.
* 본문과 `else`는 모두 코드 구조의 일부이며, 이 구조에 의존해서 작업하므로 코드를 이해할 필요가 없다.

{% hint style="danger" %}
동작과 구조는 밀접하게 연결되어 있으며, 리팩터링할 때 동작을 변경해서는 안되므로 구조도 변경해선 안됨
{% endhint %}

```typescript
// Before
// 두 가지 분명한 작업 존재
// 1.숫자를 반복, 2.숫자가 소수인지 확인
function reportPrimes(n: number) {
  for (let i=2; i<n; i++) {
    if (isPrime(i)) console.log(i);
  }
}

// After
function reportPrimes(n: number) {
  for (let i=2; i<n; i++) {
    reportIfPrime(i)
  }
}

function reportIfPrime(n: number) {
  if(isPrime(n)) {
    console.log(i)
  }
}
```

{% hint style="info" %} <mark style="color:red;">**\[remind]**</mark>**&#x20;무언가를 확인하는 것은 하나의 작업이며, 하나의 함수에서 처리해야 한다.**
{% endhint %}

#### 스멜

> **다섯 줄 제한**과 같이 이 규칙은 함수가 한가지 이상의 작업을 수행하는 스멜을 막기 위해 존재

#### 의도

* 이 규칙은 if 문이 하나의 작업이기 때문에 이를 분리할 때 이어지는 else if, if 문과 분리할 수 없는 원자 단위로 본다.
* 이것은 if 문이 else if와 함께 문맥을 형성할 때 <mark style="color:orange;">**함수 추출로 수행할 수 있는 가장 작은 단위가 if 문과 이어지는 else if 까지 포함된다는 것을 의미**</mark>

{% hint style="info" %}
if와 else if를 분리할 수 없기 때문에 다섯 줄 제한을 지키기 위해 메서드 추출을 적용할 수 없지만 우아한 해결책이 따로 존재함 (링크 예정)
{% endhint %}
