켄트백의 Tidy First?

켄트백의 소프트웨어 설계 시리즈 책으로, 개발자 스스로 할 수 있는 간단한 수준에서의 소프트웨어 설계를 다루고 있습니다. 본 책을 읽고 이해한 내용을 정리한 글입니다.
책에서는 큰 소프트웨어 설계보다 작은, 적절한 수준의 소프트웨어 설계를 통해 변화를 쉽게 만드는 것을 다루고 있습니다.

1장. 코드 정리법

  • 간단한 수준의 리팩토링을 다룹니다.
  • 반드시 작은 단계를 거쳐 코드를 정리해야 합니다. 큰 설계가 일으킬 장애가 무섭다면, 작은 단계로 작업합니다. 그래도 무섭다면 무서워지지 않을 때까지 작은 단계로 작업합니다.

보호 구문,Guard Clause

중첩된 조건은 가독성을 떨어트립니다.

1
2
if(조건)
if(다른 조건 부정)

위와 같은 코드보다는 다음과 같이 정리된 코드가 마치 코드의 세부사항을 살펴보기전에 전제조건이 있습니다 라고 말하는 것처럼 가독성이 좋습니다.

1
2
if(조건A) return 
if(조건B) return

안 쓰는 코드 제거

Reflection을 사용한 코드는 추적이 어려워, 코드 제거 후 테스트가 필요합니다. 코드를 제거하더라도 형상 관리 도구를 통해 원복할 수 있으니, 지우는 것을 걱정할 필요가 없습니다.

대칭으로 맞추기.

초기화 지연 코드를 예시로 같은 역할을 수행하는 코드라도 다른 방식으로 작성될 수 있습니다.

1
2
3
4
5
6
7
8
9
10
foo(){
return foo if foo not null;
foo := ...;
return foo;
}
foo(){
if foo is null
foo:= ...;
return foo;
}

위 코드들은 모두 foo 가 있으면 반환하고 없으면 만들어서 반환하라는 코드입니다. 어떤 방식이든 한 가지 방식을 선택해서 정합니다.
즉 일관성이 있어야 합니다. 일관성이 없으면 코드를 읽을 때 더 많은 노력을 요구합니다.

새로운 인터페이스로 기존 루틴 부르기

루틴을 호출해야 하는데, 기존 인터페이스 떄문에 어려운 경우는 새로 만든 통로 인터페이스(pass-through interface)를 만들어 기존 인터페이스를 호출합니다.

통로 인터페이스를 이용해 설계를 했다면 변경이 용이합니다.

읽는 순서

당연한 말이지만 읽기 좋은 순서로 코드를 정렬하는게 좋습니다.

응집도를 높이는 배치

변경이 같이 되는 코드는 가까이 위치시킵니다. 즉 두 루틴에 결합도가 높다면 바로 옆에 위치 시킵니다.
파일에 결합도가 높다면 같은 디렉토리에 위치시킵니다.

선언과 초기화를 함께 옮기기

변수 선언과 초기화는 동시에 이루어지는 것이 가독성이 좋습니다.

1
2
3
4
5
6
fn()
int a; // 선언
//....
int b;
// a 초기화
b = a;

위 코드보다는 선언과 초기화 위치를 가깝게 두거나, 선언 & 초기화가 동시에 이루어질 수 있도록 합니다.

설명하는 변수

코드의 표현식이 어렵다면, 그 표현식의 의도를 설명하는 변수 이름을 만들어 할당합니다.

1
2
3
4
return new Point(
긴 표현식...,
다른 긴 표현식...
)

위 코드를 다음과 같이 개선합니다.

1
2
3
positionX := 긴 표현식;
positionY := 다른 긴 표현식;
return new Position(positionX, positionY);

설명하는 상수

리터럴로 사용된 곳은 상징적인 상수로 만들어야 합니다.

1
2
if response.code == 404
// 코드

위 코드 보다는 아래 코드가 의미를 파악하기 용이합니다.

1
2
if response.code == PAGE_NOT_FOUND
// 코드

명시적인 매개변수

매개변수를 명시적으로 드러나게 만드는 것이 좋습니다. 이렇게 만들면 코드는 가독성/테스트/분석이 쉬워집니다.

1
2
params = { a: 1, b: 2};
foo(params);

위와 같은 코드보다는 루틴을 쪼개는 게 좋습니다. 즉 매개변수를 받고, 뒷부분에서 명확하게 전달해주는 것이 좋습니다.

1
2
3
4
5
6
params = { a: 1, b: 2};
foo(params);

function foo(params){
foo_body(params.a, params.b);
}

비슷한 코드 끼리

코드의 역할,동작이 구분될 떄에는 두 부분 사이에 빈 줄을 넣어 분리합니다.

도우미 추출

코드를 보다가 루틴 속 코드 중에서 목적이 분명하고, 다른 코드와는 상호작용이 적은 코드 블록은 도우미(helper)로 추출하고 이름을 붙입니다.

이름은 작동방식을 설명하지 말고, 목적에 따라 짓습니다.

1
2
3
4
5
routine(){
//...
helper();
//...
}

또 다른 활용 케이스는 시간적 결합을 표현할때입니다. 예를 들어 특정 로직이, 항상 우선되야 한다면 다음과 같이 구성할 수 있습니다.

1
2
3
4
ab(){
a(); // 항상 우선 호출되야 하는 메소드
b();
}

하나의 더미

코드가 여러 개의 작은 조각으로 나눠어져 있으면 전체적으로 이해하기 어렵습니다.
코드를 하나의 더미처럼 느껴질떄까지 모으는 작업은 사람이 한 번에 머릿속에 기억하고 있어야 할 코드의 상세 내용을 줄여줍니다.

책에서는 작은 코드를 지향하면서 동시에 하나의 더미로 만드는 말을 하고 있는데, 이해가 쉽게 되진 않습니다.
제가 파악한 내용으로는 의미 없이 작은 코드로 쪼개진 코드를 합치자는 내용 같습니다.

설명하는 주석

코드에서 다른 사람이 읽게 됬을 때, 읽게 될 사람이 얻게 될 유용함을 미리 알려줍니다.
결함을 발견했다면 그 즉시 주석을 달아야 합니다.

물론 결함을 발견했다면 주석을 제거해야 겠지만, 주석없이 그냥 두는 것보다는 휠씬 나은 방법입니다.

불필요한 주석 지우기

코드만으로 내용을 모두 이해할 수 있다면 주석은 삭제하는게 좋습니다. 하지만 주석은 코드와 같이 변경되지 않아 코드와 맞지 않는 경우가 있습니다.

즉 주석이 코드와 중복이라면 삭제하는게 관리 포인트를 줄인다는 말입니다.

2장. 관리

2장에서는 코드 정리를 언제 시작할지, 언제 멈춰야 할지를 말합니다.

코드 정리 구분

코드 정리는 별도의 PR로 만들고, 가급적 PR 당 몇 개의 코드 정리만 넣습니다.
즉 코드 동작의 변경에 대한 PR , 코드 정리에 대한 PR을 쪼개서 보내는 게 좋고 이렇게 보내는 것에 대한 장점은
검토 시간 단축으로 더 빠른 검토를 장려합니다.

반면 하나의 큰 PR은 전체 그림을 보여주는데는 좋지만, 코드 검토를 느리게 만들고, 결과적으로 더 큰 PR을 만드는 악순환이 이어질 수 있습니다.

코드 정리의 일괄 처리량

통합과 배포를 하기전에 코드 정리양에 따라 아래의 각 비용은 달라집니다.

  1. 충돌 비용 : 코드 정리 작업이 많아질수록 다른 사람의 진행 중인 작업과 충동할 가능성도 커집니다.
  2. 상호작용 비용 : 코드 정리 작업이 우연히 동작 변경을 하는 경우

코드 정리양이 많아지면 위와 같이 충돌/상호작용 비용은 늘어나지만 변경 사항을 검토하는 비용을 줄일 수 있습니다.

책에서는 너무 많은 일괄 처리량을 가져가기보다 일괄 처리 규모를 줄여 검토 비용과 충돌/상호작용 비용을 줄일 것을 권고하고 있습니다.

리듬

코드 정리 시간은 분 단위를 유지하되, 되도록 한 시간을 넘지 않도록 합니다.
그 이상의 시간이 필요로 한다면 동작 변경을 위한 최소한의 구조 변경 시기를 놓쳤다는 의미일 수 있습니다.

Comments