이 책은 총 3부로 구성되어있다.
- 1부: 정리를 하는 방법
- 2부: 정리를 언제해야하는가?
- 3부: 정리는 왜 해야하는가?
책의 순서와 달리 역순으로 정리하고자한다.
후기
코드 정리라는 것이 무엇인지 어떻게 해야할지보다,
우리가 직면하는 상황에서 내가 어떻게 행동하는 것이 좋을지에 대한 부분을 생각하게 만드는 책.
경제 개념을 은유하여서, 소프트웨어의 가치를 만들어가는 메이커로서 어떻게 판단하면 좋을지 고민하게 만드는 것 같다.
3부. 이론
사실 왜 해야하는가? 라고 적었지만, 2부의 언제해야하는가에 대한 이유를 설명하는 장이다.
22장. 요소들을 유익하게 관계 맺는 일
22장에서는 소프트웨어 설계에 대한 정의를 내린다.
좋은 설계란 무엇인가?
조영호님의 영상, 오프라인 강연에서 조영호님이 이에 대해서 이렇게 말씀하셨다.
변경하기 쉬운 방향으로 코드를 배치하는 것
22장에서는 소프트웨어 설계에 대해서
요소들을 유익하게 관계 맺는 일 이라고 한다.
이 두가지 표현은 사실 같은 말처럼 느껴진다.
- 요소(코드)들을
- 유익하게(변경하기 쉬운 방향으로)
- 관계를 맺는 일 (배치하는 것)
23장. 구조와 동작
동작은 현재의 기능을 말하고,
구조는 미래의 선택 가능성을 말한다.
이 두개 모두 가치가 있으며, 이 둘의 차이에 대해서 이해하는 것이 중요하다고 설명한다.
24~27장. 현금흐름 할인과 옵션
24~27장에서는 소프트웨어의 동작과 구조를 현금흐름 할인과 옵션이라는 경제개념으로 은유한다.
- 현금흐름 할인
개념: 시간에 따라 같은 돈이라도 가치가 다르다. 일반적으로 같은 돈이라면, 가까운 시일내에 받는 돈이 더 높은 가치를 가진다. (물론, 디플레이션 환경에서는 다르겠지만)
은유: 이는 설계보다는 빠르게 동작하는 코드를 만드는 것이 더 나을 수 있음을 의미한다.
- 옵션
개념: 미래에 가격이 오를 수 있는 물건에 대해서 프리미엄을 지불하여, 미래에 특정 가격으로 물건을 살 수 있는 권리를 콜옵션이라고 한다.
은유: 이는 설계로인한 현재의 비용을 지불하여, 미래의 동작 변경에 의해 발생하는 더 높은 비용을 낮출 수 있다. 이로인해, 미래에 더 적은 비용으로 많은 선택가능성이 생김.
결국 코드 정리라는 것은 현금흐름 할인과 옵션이라는 경제적인 은유를 통해 고려해봐야하는 것이다.
당장에 아래와 같다면, 코드 정리를 하는 것이 옳다.
(코드 정리 비용) + (코드 정리 후 동작 변경) < 바로 동작변경
하지만 아래의 식이 나오는 경우에는 위 두개를 고려해야한다.
(코드 정리 비용) + (코드 정리 후 동작 변경) < 바로 동작변경
당장 코드 정리를 통한 비용이 커보여도, 그로인해 미래에 더 싼 변경 변경 비용을 통해 더 많은 선택가능성을 열어둔다면? 지금 더 비싸보여도, 지불하는 것이 합리적이다.
그리고 이 두가지 측면 외에도, 소프트웨어 설계는 동료와 그리고 나 자신과의 관계를 위한 활동이기도 하다.
이 모든 것이 순간순간 판단 내리기는 어려우나, 지속적으로 판단력을 기르면 (이를 고려하면서 경험한다면)
나중에 직감적으로 판단할 수 있을 것이다.
28장. 되돌릴 수 있는 구조 변경
동작과 달리 구조는 가역성을 가진다는 것이다.
구조의 변경은 쉽게 되돌릴 수 있기 때문에, 잘못된 구조 변경으로 인한 비용은 크지 않다는 것을 설명한다.
29 ~ 31장. 결합도
결합도는 단순 코드 의존성이 아니다.
변경시에 해당 의존성을 가진 요소들의 동작이 변경되어야 결합도이다.
결합도는 본질적으로 연쇄적인 성격이 있어서(A 변경 → B 변경 → C 변경), 변경을 어렵게 만든다.
따라서 이런 변경에 앞서서 코드 정리를 하자.
소프트웨어의 비용은 변경 비용이다.
그리고 대부분의 소프트웨어 비용은 큰 변경에 들어간 비용의 합과 비슷하다.
그렇다면, 왜 특정 변경들은 이렇게 큰 변경 범위를 만들까?
→ 바로 결합도 때문에 변경이 전파되기 때문에, 큰 변경을 만들기 때문이다.
결국, 결합도는 소프트웨어의 비용과 동치다.
하지만, 이 결합도는 시스템에 항상 존재하고 이 모든 결합도를 제거하는 것은 현실적이지 않다.
결합도를 제거하는 비용과, 결합도를 제거하지 않았을 때의 비용을 고려하여 적절한 지점을 찾아야한다.
32장. 응집도
결합되는 코드는 가까운 곳에 배치하고,
관련이 없는 요소는 관련 있는 곳으로 배치를 옮겨라.
2부. 관리
16장 코드 정리 구분
PR 은 동작 변경과 구조 변경을 구분하라.
그렇게하여 PR 의 크기를 작게 만들면, PR 을 통해 유용한 피드백을 받을 수 있다.
17장. 연쇄적인 정리
코드 정리는 또 다른 코드 정리 가능성을 열어준다.
따라서, 코드 정리는 연쇄적으로 일어나는 경우가 많다.
하지만, 한번에 너무 큰 스텝으로 변경하지 말자.
큰 스텝으로 실패하는 것보다, 작은 스텝을 여러번 하는 것이 낫다.
18장. 코드 정리의 일괄 처리량
한번의 코드 정리가 크기가 커지면, 아래의 비용이 늘어난다.
- 충돌 : 코드 병합시 충돌
- 상호작용 : 동작 변경
- 추측 : 코드의 정리는 다음 동작 변경에 필요한 만큼만 해야한다. 코드 정리는 연쇄적으로 일어나서 코드 정리가 커질 수 있다.
코드 정리가 작을 수록 이를 검토하고 배포하는 프로세스가 길어진다(비용증가).
하지만, 코드의 동작을 변경하지 않는다면 코드 정리의 검토 프로세스를 줄여 비용을 낮춰라.
19장. 리듬
대다수의 변경은 일부분에 집중되는 경향이 있다.
적절한 시기에 코드 정리를 해준다면, 이후에 코드 정리가 엄청 커지지 않을 것이다.
리듬과 사실 무슨 관계인지 모르겠다..
20장. 얽힘 풀기
20장의 의도를 잘 이해할 수는 없는데,
코드를 정리하다보면, 동작 변경도 같이하고 그러는 경우가 생김.
이때 어떻게 대처할 것인가?
진행하던 작업을 버리고, 정리 → 동작변경 순으로 작업하고 PR 올려라
결국, 코드 정리라는 것도 동료들과의 관계의 문제이다.
21장. 코드 정리 시점
코드 정리하지마세요
- 앞으로 변경할 일 없을 때
나중에 정리하세요
- 지금 코드 정리로 보상이 크지 않을 때
동작 변경 후 정리하세요
- 방금 수정한 코드를 언젠가 다시 변경할 것으로 예측될 때
- 변경 후 코드 정리 시간이 오래걸리지 않을 때
- 변경 후 정리할 포인트에 대한 맥락들이 정리를 쉽게 만들 때
정리 후에 변경하세요
- 코드 정리로 인해, 다음의 동작 변경이 쉬워질 때
- 미래의 추측이 아니라, 현재 이해가 어려울 때
1부. 코드 정리법
코드 정리는 리팩토링의 부분집합으로 볼 수 있다.
1장. 보호구문 (gaurd)
가드를 통해서, 루틴의 전제조건을 먼저 정의하면 코드를 읽기가 쉽고 분석이 용이해진다.
2장. 안 쓰는 코드
안 쓰는 코드는 지워라.
만약 필요하면, git 으로 되돌리면 된다.
3장. 대칭으로 맞추기
코드 스타일을 맞춰라. 코드 스타일이 다르면, 이해하는데 적응이 어렵다.
4장. 새로운 인터페이스로 기존 루틴 부르기
기존의 코드의 인터페이스가 적절하지 않다고 판단되면,
새로운 인터페이스를 도입해서 pass-through 하는 식으로 수정해라.
그리고 차근차근 기존의 호출부분들을 옮겨라.
5장. 읽는 순서
코드를 읽기 좋은 순서로 배치하라.
예를들어 아래처럼 public, private 위치를 어떻게 할 것인지와 같은 것들을 읽기 좋은 순서로 배치하라. (인터페이스 먼저 혹은 구성요소 먼저)
무엇이 좋고 나쁘기 보다, 자신의 경험상 읽기 불편했다면 그 경험을 토대로 타인을 위해 중요한 요소를 먼저 배치하는 식으로 해보기를 추천함.
class Example {
public a(){}
public b(){}
private c(){}
private d(){}
// vs
public a(){}
private c(){}
public b(){}
private d(){}
}
6장. 응집도를 높이는 배치
기능 변경이 여러 곳에 위치한 코드들을 수정해야한다면?
→ 해당 코드들을 file, directory 단위로 가까운 위치로 배치하라.
7장. 선언과 초기화를 함께 옮기기
변수 선언과 초기화가 가까운 방향으로 코드를 정리하라.
그렇지 않으면 코드를 이해하거나 따라가기 어렵다.
8장. 설명하는 변수
expression 으로 이해하기 힘들 때, 변수를 통해 설명하자.
// as-is
if(expression()) {}
// to-be
val isEnabled = expression()
if(isEnabled) {}
9장. 설명하는 상수
숫자, 문자열 → 리터럴, enum 으로 의미를 명확히하자
10장. 명시적인 매개변수
불필요한 매개변수를 넘기지말자.
// as-is
myMethod(profile)
fun myMethod(profile:UserProfile){
user.age // 이것만 사용중
}
// to-be
myMethod(profile.age)
fun myMethod(age: Long) {}
11장. 비슷한 코드끼리
빈 줄을 넣어서라도, 비슷한 묶음의 코드끼리 묶어서 구분하라.
12장. 도우미 추출
기존의 코드에서 목적이 분명하고, 다른 코드들과 상호작용이 적은 경우 helper 메서드로 분리하라.
코드 수정, 응집도, 새로운 인터페이스로 추출 등 다방면으로 이용될 수 있다.
13장. 하나의 더미
작은 메서드들로 분리하여, 추상화의 정도를 맞추는 것은 인지부하에 도움이 된다.
하지만, 작은 메서드들로 구성되었다고, 코드가 이해하기 쉬운 것은 아니다.
오히려 이 메서드들이 잘못 구성되었다면 오히려 코드를 이해하기 어렵게 만든다.
예를들어, 낮은 응집도의 메서드, 데이터의 변경, 부적절한 메서드 네이밍은 오히려, 메서드들로 분리하여 인지부하를 높일 수 있다.
14~15장. 주석
주석이 코드와 동어반복을 하는 경우 제거하라.
반대로, 코드 자체로 그 맥락을 충분히 설명하지 못한다면, 주석을 통해 맥락을 제공하라.