파이브 라인스 오브 코드 - 3장

이마닷·2024년 8월 11일
0
post-thumbnail

3장. 긴 코드 조각내기

코드는 쉽게 지저분해지고 혼란스러워질 수 있습니다. 이 혼란의 주 원인은 다음과 같습니다.

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

이번 장에서는 너무 많은 역할을 하는 메서드를 식별하는 구체적인 방법을 설명합니다.

이 책에서는 메서드(클래스에 정의됨)와 함수(정적으로 혹은 클래스 외부에 정의됨)를 구분해서 사용한다는 점에 유의하십시오.

3.1 첫 번째 규칙: 왜 다섯줄인가?

규칙: 다섯줄 제한

정의
메서드는 '{'와 '}'를 제외하고 5줄 이상이 되어서는 안됩니다.

설명
문장이라고도 하는 코드 한 줄은 if, for, while 또는 세미콜론으로 끝나는 모든 것을 말합니다. 즉, 할당, 메서드 호출, return 같은 것입니다. 공백과 중괄호({})는 제외합니다.
...
특정 수치로 줄 수를 제한하는 것보다, 제한이 있다는 것 자체가 중요합니다. 경험 상 기본 데이터 구조를 탐색하는 데 필요한 값으로 제한을 설정하는 것이 효과적입니다

스멜
메서드가 길다는 것 자체가 스멜입니다. 그럴 경우 한 번에 긴 메서드의 모든 논리를 머릿속에 담아야 해서 작업하기가 어렵습니다.
그렇다면 '길다는게 무슨 뜻일까?'라는 당연한 의문이 생깁니다.
이 질문에 답하기 위해 메서드는 한 가지 작업만 해야 한다는 다른 스멜을 생각해볼 수 있습니다. 다섯줄 제한이 하나의 의미있는 작업에 딱 맞는 크기라면 이 제한 역시 다른 스멜의 제거를 방해할 수 있습니다.

의도
관심을 가지지 않으면 시간이 지남에 따라 더 많은 기능이 추가되면서 메서드가 커지는 경향이 있습니다. 그리고 그로 인해 코드를 점점 더 이해하기 어렵게 됩니다. 각각 5줄의 코드가 있는 4개의 메서드가 20줄인 하나의 메서드보다 훨씬 빠르고 이해하기 쉽습니다.

3.2 함수 분해를 위한 리팩터링 패턴 소개

동일한 작업을 하는 데 필요한 줄의 그룹을 식별해봅시다. 이러한 그룹을 명확히 하기 위해 그룹이라고 생각하는 곳에 빈 줄을 추가합니다. 때때로 그룹과 관련된 것을 기억하는데 도움이 되는 주석을 추가합니다.
일반적으로 주석을 달았다는 사실을 잊어버리거나, 주석이 나쁜 코드에 탈취제처럼 사용되는 경향이 있어서 주석 다는 일은 피하려고 하는데, 잠시 후 보겠지만 이 경우 주석은 일시적인 것입니다.

리팩터링 패턴: 메서드 추출

설명
메서드 추출은 한 메서드의 일부를 취해서 자체 메서드로 추출합니다. 이것은 기계적으로 수행할 수 있는데, 실제로 많은 최신 IDE가 이 리팩터링 패턴을 지원합니다.
if의 일부 분기만 return 문을 가지고 있을 경우, 메서드를 추출하는 데 방해가 될 수 있으므로 메서드의 끝에서 시작해 위로 작업해가는 것이 좋습니다. 이는 return 문을 가진 조건을 메서드의 앞쪽에 배치하게 해서 결과적으로 모든 분기에서 return 할 수 있게 됩니다.

절차
1. 추출할 줄의 주변을 빈 줄 또는 주석으로 표시합니다.
2. 원하는 이름으로 새로운 빈 메서드를 만듭니다.
3. 그룹의 맨 위에서 새로운 메서드를 호출합니다.
4. 그룹의 모든 줄을 선택해서 잘라내어 새로운 메서드의 본문에 붙여넣습니다.
5. 컴파일 합니다.
6. 매개변수를 도입하여 호출하는 쪽의 오류를 발생시킵니다.
7. 이러한 매개변수 중 하나(p)를 반환 값으로 할당해야 할 경우:

  • a) 새로운 메서드의 마지막에 return p;를 추가합니다.
  • b) 새로운 메서드를 호출하는 쪽에서 p = newMethod()와 같이 반환값을 할당합니다.
  1. 컴파일합니다.
  2. 호출 시 인자를 전달해서 오류를 잡습니다.
  3. 사용하지 않는 빈 줄과 주석을 제거합니다.

중요한 교훈은 변환이 약간 번거롭기는 하지만 안전하다는 것입니다. 좀 더 고급스러워 보이려고 노력하다가 어려움을 겪을 수 있는데, 보통은 그럴 만한 가치가 없습니다.

자신감이 떨어지는 예쁜 코드를 만드는 것보다 특이하게 생긴 안전한 코드를 만드는 편이 낫습니다.

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

규칙: 호출 또는 전달, 한 가지만 할 것

정의
함수 내에서는 객체에 있는 메서드를 호출하거나 객체를 인자로 전달할 수 있지만 둘을 섞어 사용해서는 안됩니다.

  • 예를 들어, 메서드 내에서 사용되는 특정 변수 g가 있고, g가 다른 함수의 매개변수로 전달되기도 하고, g 자체도 다른 메서드를 호출한다면 이는 규칙에 어긋난다.
  // 다른 추상화 수준의 두 코드 라인이 한 함수 안에 존재해서는 안된다
  g.someMethod();
  ...
  otherMethod(g);   

설명
더 많은 메서드를 도입하고 여러가지를 매개변수로 전달하기 시작하면 결국 책임이 고르지 않게 될 수 있습니다.
...
그러면 코드는 직접 조작하는 낮은 수준의 작업과 다른 함수에 인자로 전달하는 높은 수준의 호출이 공존해서 메서드 이름 사이의 불일치로 가독성이 떨어질 수 있습니다. 동일한 수준의 추상화를 유지하는 편이 코드를 읽기가 훨씬 더 쉽습니다.

  • 배열의 평균을 구하는 함수 - 규칙에 위반되는 코드
function average(arr: number[]) {
  return sum(arr) / arr.length; 
  // sum(arr)는 높은 수준의 추상화, arr.length는 낮은 수준의 추상화
}
  • 규칙에 따라 리팩터링 한 코드
function average(arr: number[]) {
  return sum(arr) / size(arr);
}

스멜
'함수의 내용은 동일한 추상화 수준에 있어야 한다'는 말은 그 자체가 스멜일 정도로 강력합니다.

3.4 좋은 함수 이름의 속성

좋은 이름을 짓는 보편적인 규칙을 제공할 수는 없지만, 좋은 이름이 가져야 할 몇가지 속성은 제공할 수 있습니다.

  • 정직해야 합니다. 함수의 의도를 설명해야 합니다.
  • 완전해야 합니다. 함수가 하는 모든 것을 담아야 합니다.
  • 도메인에서 일하는 살마이 이해할 수 있어야 합니다. 작업 중인 도메인에서 사용하는 단어를 사용하십시오. 그렇게 하면 의사소통이 더욱 효율적이게 되고 팀원 및 고객과 코드에 대해 더 쉽게 이야기 할 수 있다는 장점이 있습니다.

3.5 너무 많은 일을 하는 함수 분리하기

규칙: if 문은 함수의 시작에만 배치

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

설명
이미 우리는 함수가 한 가지 일만 해야 한다는 것을 압니다. 무언가를 확인하는 것은 한 가지 일입니다. 따라서 함수에 if가 있는 경우 함수의 첫 번째 항목이어야 합니다. 또한 그 후에 아무것도 해서는 안된다는 의미에서 유일한 것이어야 합니다.
무언가를 확인하는 것은 하나의 작업이며, 하나의 함수에서 처리해야 합니다.

의도
이 규칙은 if 문이 하나의 작업이기 때문에 이를 분리할 때, 이어지는 else if는 if문과 분리할 수 없는 원자 단위로 봅니다. 이것은 if 문이 else if와 함께 문맥을 형성할 때 메서드 추출로 수행할 수 있는 가장 작은 단위가 if문과 이어지는 else if까지 포함한다는 것을 의미합니다.

profile
적당히 잘하고 싶어요

0개의 댓글