[TIL] Tidy First - Chapter 01

해당 글은 퀜트 백의 Tidy First 책을 읽고 정리한 글이다.

Chapter 01

보호 구문

if문이 사용된 코드가 있다.

특히 중첩된 if문 같은 경우는 헷갈리는 경우가 많다. 이는 다음과 같이 정리하는 것이 좋다.

if (something) return;
if (somethingelse) return;

이렇게 정리하면 간결해진다. 이 부분은 코드의 세부사항을 살표보기 전에 염두에 두어야할 몇 가지 전제 조건이 있습니다.라고 말하는 것처럼 보인다.

보호 구문이 있는 코드라면 전제 조건이 명시적으로 드러날 때 분석하기 쉽다. 하지만 남용하면 안된다. 보호 구문이 너무 많은 코드의 경우, 읽기가 까다로워진다.

항상 그리고 반드시 작은 단계를 거쳐서 코드를 정리해야한다.

참고 링크: [https://github.com/Bogdanp/dramatiq/pull/470/files]

안 쓰는 코드

안 쓰는 코드가 있으면 지우자.

나중에 필요할 것 같은 코드의 경우, 형상 관리 도구를 활용하자.

그리고 코드를 삭제할 때는 조금만 삭제하도록 한다. 그렇게 할 경우, 잘 못 고친 것으로 뒤늦게 밝혀져도 쉽게 복구가 가능하다. 조금의 기준은 판단에 맡긴다.

대칭으로 맞추기

코드는 유기체처럼 성장한다.

이것은 코드를 읽기 어렵게 만들기도 한다. 읽는 입장에서는 일관성이 중요하기 때문이다. 패턴이 보이는 경우 진행되는 것을 알 수 있으므로 확신을 가지고 결정을 할 수 있다.

function foo() {
  let foo
  if (!foo) {
    foo = /* ... */
  }
  return foo
}

function foo() {
  let foo
  return !foo ? foo : foo = /*...*/
}

한 가지 방식을 선택해서 정한다. 다른 방식으로 작성한 코드를 해당 방식으로 고친다. 때로는 공통성이 있는데도 세부 사항에 묻혀 드러나지 않을 수 있다. 비슷해 보이지만 같지않은 루틴을 찾아내어 분리하도록 한다.

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

루틴을 호출해야하는데 기존 인터페이스로 인해 어려워질 경우, 호출하고 싶은 인터페이스를 새로 구현해서 호출하자.

그리고 새로 구현한 인터페이스는 기존 인터페이스를 호출하도록 해서 구현할 수 있다. 이렇게 새로 구현한 통로 인터페이스는 어떤 동작을 변경할 때에 통로 인터페이스를 이용해 설계를 했다면 변경하기가 더 수월하다.

  • 거꾸로 코딩하기 : 루틴의 마지막 줄부터 구현해보자.
  • 테스트 우선 코딩 : 테스트부터 작성하여 통과요건 정하기
  • 도우미 설계 : 특정 업무를 해주는 루틴, 객체, 서비스가 있는 경우 나머지 작업은 쉬워진다.

읽는 순서

코드를 읽기 좋은 순서로 정렬하면, 그 순서대로 코드를 만날 수 있다.

코드를 읽을 때 독자의 입장이 되어 읽어보자.

읽어보면서 다른 코드의 정리작업을 하고 싶어도 일단 읽어가면서 만드는 일은 다음 정리에서 할 수 있도록 한다. 읽는 중에 정리 작업은 코드를 섞게 된다.

언어에 따라서는 요소를 선언하는 순서에 따라 실행 결과가 달라지는 경우가 생길 수 있는데, 이런 부분에서는 되도록 전체 파일의 순서를 바꾸는 대신, 읽는 순서가 영향을 크게 주는 것들부터 바꾸도록 한다.

본인의 경험을 살려서 어떤 순서의 코드가 읽기 좋은지 판단하여 읽기 좋은 순서로 정리할 수 있도록 한다.

응집도를 높이는 배치

코드를 읽다가 변경해야할 동작을 찾았더니 여러 곳에 흩어져있는 코드를 함께 바꿔야한다는 것을 알면 불편한 감정이 생긴다.

이럴 때는 코드의 순서를 바꿔서 변경할 요소들을 가까이 두도록 하자.

두 루틴에 결합도가 있으면 서로 옆에 두자. 같은 디렉토리에 들어있는 파일에도 적용할 수 있다.

결합도를 왜 제거하지 않는가? 결합도를 제거할 수 있다면 제거하는 것이 좋다. 아래 가정을 보면 그것이 가장 좋은 코드 정리법이다.

결합도 제거 비용 + 변경 비용 < 결합도에 따른 비용 + 변경 비용

하지만 결합도 제거가 어려울 수 있다.

  • 당장 어떻게 해야할지 모를때
  • 할 수 있더라도 시간적 여유가 없다면 결합도 제거는 부담스러운 시간 투자가 된다.
  • 코드의 변경이 활발히 이루어지고 있다면 결합도를 제거하는 일은 오히려 갈등이 일어날 수 있다.

응집도를 높이는 순서로 정리하면 코드를 더 쉽게 변경할 수 있다. 때로는 응집도를 조금 개선해서 코드가 명확해지면 결합도를 제거를 못하도록 막고 있었던 부분이 해결될 수도 있다. 그리고 응집도가 좋아지면 결합도 역시 좋아진다.

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

변수 선언과 초기화의 위치가 떨어져있다면 코드는 읽기 더 어려워진다.

function () {
  let a
  /** 변수 a를 사용하지 않는 코드 */
  a = ...
  let b
  /** 변수 a를 사용할 수 있으나 변수 b를 사용하지 않는 코드 */
  b = ...a
  /** 변수 b를 사용하는 코드 */
}

/** 아래로 개선한다. */
function() {
  let a = ...
  /** 변수 a를 사용하지 않는 코드 */
  /** 변수 a를 사용하고 b는 사용하지 않는 코드 */
  let b = ...a
  /** 변수 b를 사용하는 코드 */
}

변수 사이에는 데이터 종속이 있을 수 있다. 코드 정리를 실행할 때, 데이터 종속 순서도 유지되어야 한다. 그리고 이러한 작업은 작은 단계로 진행하도록 한다.

설명하는 변수

코드의 표현식은 계속 성장한다. 그렇게 어렵게 크고 복잡한 코드의 함수를 이해했다면, 전체에서 일부 표현식을 추출한 후 표현식의 의도가 드러나도록 변수 이름을 만들어 할당한다.

const x = /** ...긴 표현식 */
const y = /** ...다른 긴 표현식 */
function() {
  return new Point( x, y )
}

이러한 정리 법은, 힘들게 파악한 내용을 코드에 넣는 것이다. 이렇게 설명하는 변수를 적용하면 표현식과 분리되었기 때문에 다음번 코드를 변경할 때 둘 중 하나만 읽으면 되니까 빨리 변경할 수 있다.

코드정리에 대한 커밋과 동작변경에 대한 커밋이 분리되어야하는 것도 잊지말기!

설명하는 상수

상징적인 상수를 만들자. 리터럴 상수로 사용된 곳은 상징적인 상수로 바꾼다.

const PAGE_NOT_FOUND = 404

명시적인 매개변수

입력을 명확하게 하려면 어떻게 해야할까?

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

function foo(params) {
  /** params.a, params.b.. */
}

/** 이렇게 개선하자 */
function foo(params) {
  foo_body(params.a, params.b);
}

function foo_body(a, b) {
  /**a...b... */
}

매개변수를 명시적으로 드러내게 만든 후 함수를 연쇄적으로 호출할 수 있게하면 코드 읽기, 분석, 테스트가 쉬워진다.

비슷한 코드끼리

긴 코드 덩어리를 읽다가 구분이 되는 경우 두 부분 사이에 빈 줄을 넣어 분리하자.

이것은 매우 간단하다. 이 책의 철학의 일부임. 작은 소프트웨어 설계로 변화를 좀 더 쉽게 만들 수 있다.

복리라는 개념이 있다. 소프트웨어 설계 또한 복리처럼 뒤따르는 소프트웨어 설계를 더 쉽게 만든다. 제대로 된 소프트웨어 설계는 유연성을 가지지만, 그렇지 않은 경우는 변화 자체를 망각하게 된다.

관련 있는 코드를 뭉쳐두면 다양한 길로 나아갈 수 있다.

도우미 추출

루틴 속 코드 중, 목적이 분명하고 나머지 코드와는 상호작용이 적은 코드 블록의 경우 추려내고, helper로 추출한 후에 이름을 붙인다. 이때 helper의 이름은 작동방식이 아닌 목적에 따라 짓도록 한다.

코드정리와 리팩토링을 할 때 자동화 도구가 있으면 더욱 좋음..

helper를 추출할 때, 큰 루틴 안에서 몇줄을 변경해야하는 경우, 해당 줄들을 helper로 추출하고 helper 안의 내용만 변경한 후 해당 부분에 helper 함수를 호출하자.

하나의 더미

코드가 여러 개의 조각으로 나뉘어져있으면 이해하기 어렵다. 코드를 하나의 더미처럼 느껴질 때까지 흩어진 코드를 모으자.

코드를 만드는 데에 가장 큰 비용이 들어가는 일은 코드 작성이 아니라 읽고 이해하는 데에 드는 비용이다. 코드 정리를 선행하면 머릿속에서 기억하고 있어야할 코드의 상세 내용을 줄여준다.

작은 코드조각들을 지향하는 목적은 코드를 한번에 조금씩 이해할 수 있도록 하는 것이지만, 때때로 이 선택은 잘못된 선택일 수 있다. 이 코드조각들이 교류하는 방식은 더욱 알기 어렵게한다. 명확해지려면, 코드를 한데 모아서 이해하기 어려운 부분은 추출해서 새롭게 정리하도록 한다.

다음 증상이 있을 경우 새롭게 정리하자.

  • 길고 반복되는 인자 목록
  • 반복되는 코드, 그 중에서도 반복되는 조건문
  • Helper에 대한 부적절한 이름
  • 공유되어 변경에 노출된 데이터 구조

더 작은 조각을 지향한다지만, 동시에 하나의 더미로 만드는 일을 앞뒤가 맞지 않는다. 하지만 이를 진행하면서 만족감을 느끼게 될 것이다.

더미가 커지면서 머릿속에 형상이 떠오르기 시작하고, 그러면 알아차리게 된다. 그러면 이제 책 제목처럼 고민하게 된다. Tidy First? 코드를 먼저 정리할까? 눈에 보이는 것만 먼저 변경할까?

설명하는 주석

코드를 읽다가 아 이건 이렇게 돌아가는구나 깨닫게 되면 기록해야한다.

명확하지 않은 내용을 골라 적는다. 읽기전에 미리 알았더라면 좋았을 점은 무엇인가? 생각해보고 기록할 수 있다.

코드의 결함이 있는 경우 즉시 해당 위치에 주석을 달아야한다.

불필요한 주석 지우기

코드만으로 내용을 모두 이해할 수 있다면 주석은 삭제하자.

주석과 코드는 작성할 때와 나중에 볼 때, 시간이 흐르고 나면 서로 맞지 않는 경우가 있다. 코드가 변경될 경우에는 원래 있던 주석이 불필요해질 수 있다.