본문 바로가기
글쓰기

[번역] 안녕, 클린코드

by zian지안 2020. 1. 13.

원문은 Dan Abramov의 블로그 포스팅 "Goodbye Clean Code"입니다. 의역과 발 번역이 난무하니 참고해 주시기 바랍니다. 개발에 관련된 용어는 가급적 통용되는 용어로 남겨놓았습니다. 번역에 대한 문제점에 대해서는 언제든지 의견 주시기 바랍니다.

번역 포스팅이 문제가 될 시 언제든 삭제하겠습니다.


늦은 저녁이었다.

동료가 일주일 내내 작성한 코드를 체크인했다. 우리는 그래픽 편집기를 만들고 있었는데, 사각형이나 원형 도형들의 가장자리를 드래그해서 크기를 조절하는 기능을 개발했다

코드는 잘 동작했다.

하지만 중복된 코드가 있었다. 각각의 도형들(사각형, 원형)에는 서로 다른 핸들(번역자 주: 도형의 크기나 위치를 조절하기 위한 사용자 인터페이스)이 있었고, 핸들을 다른 방향으로 드래그하여 도형의 크기나 위치가 변경하도록 되어있었다. 사용자가 Shift버튼을 누른 채라면, 비율을 유지한 채 크기를 변경해야 핸다. 수학적인 계산을 많이 요구하는 일이었다.

코드는 다음과 같았다.

let Rectangle = {
  resizeTopLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeTopRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
};

let Oval = {
  resizeLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeTop(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottom(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
};

let Header = {
  resizeLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },  
}

let TextBlock = {
  resizeTopLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeTopRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
};

반복적인 수학계산이 정말 나를 힘들게 했다.

코드가 '클린 하지' 않았다.

비슷한 방향으로 움직이는 기능에서 중복된 코드가 있었다. 예를 들어 Oval.resizeLeft()는 Header.resizeLeft()와 유사했다. 두 코드 모드 왼쪽 핸들을 드래그하는 기능을 처리하고 있었기 때문이었다.

또 다른 중복은 비슷한 모양의 도형일 때였다. 예를 들어 Oval.resizeLeft()는 다른 원형 도형의 메서드와 비슷했다. 둘 다 원형 도형을 대상으로 한 것이기 때문이었다. 사각형의 모양을 가지는 직사각형, 헤더, 텍스트 블록에도 비슷한 코드가 있었다.

나는 아이디어를 하나 냈다.

아래와 같이 코드를 그룹화하여 중복을 제거할 수 있었다.

let Directions = {
  top(...) {
    // 5 unique lines of math
  },
  left(...) {
    // 5 unique lines of math
  },
  bottom(...) {
    // 5 unique lines of math
  },
  right(...) {
    // 5 unique lines of math
  },
};

let Shapes = {
  Oval(...) {
    // 5 unique lines of math
  },
  Rectangle(...) {
    // 5 unique lines of math
  },
}

그리고 동작할 코드를 구현했다

let {top, bottom, left, right} = Directions;

function createHandle(directions) {
  // 20 lines of code
}

let fourCorners = [
  createHandle([top, left]),
  createHandle([top, right]),
  createHandle([bottom, left]),
  createHandle([bottom, right]),
];
let fourSides = [
  createHandle([top]),
  createHandle([left]),
  createHandle([right]),
  createHandle([bottom]),
];
let twoSides = [
  createHandle([left]),
  createHandle([right]),
];

function createBox(shape, handles) {
  // 20 lines of code
}

let Rectangle = createBox(Shapes.Rectangle, fourCorners);
let Oval = createBox(Shapes.Oval, fourSides);
let Header = createBox(Shapes.Rectangle, twoSides);
let TextBox = createBox(Shapes.Rectangle, fourCorners);

코드는 절반으로 줄었고, 중복은 완전히 제거되었다. 매우 깨끗하게. 만일 특정 방향이나 모양에 대한 동작을 변경하고 싶다면 여러 곳의 코드를 변경하는 대신 한 부분만 변경하면 될 것이었다.

이미 밤이 늦었기 때문에(나는 이미 넋이 나갔다) 동료의 지저분한 코드를 풀어낸 것에 뿌듯해하며 리팩터링 한 코드를 마스터에 체크인하고 자러 갔다.

다음날 아침

... 일은 생각대로 되지 않았다.

내 상사는 나를 1:1 채팅에 초대하더니 내가 변경한 코드를 되돌려 줄 수 있냐고 물었다. 나는 경악했다. 기존의 코드는 지저분하고 내 코드는 깨끗한데!

나는 마지못해 승복했지만, 몇 년이 지난 후 그들이 옳았음을 깨닫게 되었다.

그것은 과정이다

"클린 코드"에 집착하고 중복을 제거하는 것은 많은 사람들이 겪는 과정이다. 우리는 자신의 코드에 자신이 없을 때, 측정할 수 있는 어떤 것을 통해 자부심과 전문가적인 자신감을 가지고자 하는 유혹에 빠진다. 엄격한 LINT규정, 네이밍, 스키마, 파일 구조, 중복제거와 같은 것들 말이다.

중복제거를 자동화할 수는 없지만 연습을 통해 더 쉽게 할 수 있게 된다. 매번 코드를 수정할 때마다 중복이 많은지 적은 지 쉽게 알 수 있다. 그 결과 중복을 제거하는 것은 코드에 대해 어떤 측정 가능한 목표를 개선한다는 느낌을 준다. 하지만 그것이 "나는 클린 한 코드를 작성하는 사람이야"라는 생각을 가지게 만들어 스스로를 망친다. 이런 생각은 스스로를 기만할 만큼 강력하다.

어떻게 추상화를 할지 알게 되면 그 능력에 대해 높이 평가하고 중복되는 코드를 볼 때마다 추상화하고자 하는 유혹에 빠진다. 몇 년 코딩을 해 보면 중복은 어디에나 보이게 되고, 추상화 능력은 새로운 초능력이 된다. 만일 누군가가 추상화가 미덕이라고 말한다면 쉽게 동의하게 될 것이다. 그리고 다른 사람들이 클린 한 코드를 숭배하지 않는지 판단하게 될 것이다.

지금의 나는 내 리펙토링이 두 가지 면에서 문제였음을 알게 되었다.

첫 번째, 나는 기존 코드를 작성한 동료와 이야기하지 않았다. 코드를 수정한 뒤 동료의 확인받지 않고 체크인했다. 비록 개선된 코드(더 이상 그렇게 생각하지 않지만)라고 하더라도 그렇게 한 것은 끔찍한 방식이었다. 건실한 엔지니어 팀은 끊임없이 신뢰를 쌓고 있다. 논의 없이 팀 동료의 코드를 변경하는 것은 코드를 기반으로 효율적으로 협업하는 팀의 능력에 큰 타격을 준다.

두 번째, 공짜는 없다. 내 코드는 변경 요구사항 반영과 중복제거를 맞바꾸었고 그건 별로 좋은 거래가 아니었다. 예를 들어 우리가 나중에 여러 가지 다른 모양의 도형과 핸들에 대해 특별한 상황이 필요할 수도 있다. 내가 만든 추상화 구조로는 그런 상황을 감당하기 위해 코드가 몇 배나 복잡해져야 하지만 기존의 '지저분한' 버전에서는 변경이 매우 쉬웠다.

당신이 '더러운'코드를 써야 한다고 말하고 있는 거냐고? 아니다. 나는 '깨끗하다' 또는 '더럽다'라고 이야기할 때 그게 무슨 의미인지 더 깊이 생각해 보라고 제안하고 있는 것이다. 반론하고 싶을 것이다. 그게 올바른 거냐고? 아름답냐고? 우아하냐고? 당신은 자신이 품질에 기반한 확실한 공학적 결과물을 내놓을 수 있는지 확신할 수 있는가? 사람들이 코드를 작성하고 수정하는 방식에 어떻게든 영향을 줄 수 있을까?

나는 그런 것들에 대해 깊이 생각해 보지 않았었다. 나는 내 코드가 어떻게 보이는지에 많은 신경을 썼다. 하지만 코드를 통해 동료들과 함께 발전할 수 있을지에 대해서는 생각하지 않았다.

코딩은 여행이다. 당신의 코드 첫 라인에서부터 현재에 이르기까지 얼마나 멀리 왔는지를 생각해 보라. 나는 처음에는 함수를 추출하거나 클래스를 리팩터링 하면서 코드를 단순하게 만드는 것이 즐거웠다고 생각한다. 만일 당신이 자신의 작업에 자부심을 느낀다면 코드를 클린 하게 하는 것은 큰 유혹이다. 일단 그렇게 해 보시라.

하지만 거기서 멈추면 안 된다. 클린 코드의 광신자가 되어서는 안 된다. 클린 코드는 목표가 아니다. 그것은 우리가 다루고 있는 시스템의 어마어마한 복잡성을 이해하려는 시도이다. 클린 코드는 당신이 변경하는 부분이 코드 근간에 어떤 영향을 줄지 확신할 수 없는 미지의 바다에서 지침이 필요한 경우 방어기제로 사용될 것이다.

클린 코드로 안내하라. 그리고 그냥 두어라.