Goodbye, Clean Code

I
Inkyu Oh

Front-End2025.11.21

overreacted 블로그 포스트 번역


늦은 저녁이었습니다.
제 동료가 일주일 동안 작성한 코드를 보게되었습니다. 저희는 그래픽 편집기 캔버스를 작업하고 있었고, 동료는 모서리의 작은 핸들을 드래그하여 직사각형과 타원 같은 도형의 크기를 조정하는 기능을 구현했습니다.
코드는 작동했습니다.
하지만 반복적이었습니다. 각 도형(예: 직사각형 또는 타원)은 서로 다른 핸들 세트를 가지고 있었고, 각 핸들을 다른 방향으로 드래그하면 도형의 위치와 크기가 다르게 영향을 받았습니다. 사용자가 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()는 다른 Oval 메서드들과 유사성이 있었습니다. 이는 모두 타원을 다루었기 때문입니다. Rectangle, Header, TextBlock 간에도 중복이 있었는데, 텍스트 블록이 직사각형이었기 때문입니다.
저는 아이디어가 떠올랐습니다.
코드를 다음과 같이 재구성하여 모든 중복을 제거할 수 있었습니다:
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);
코드는 전체 크기의 절반이었고, 중복은 완전히 사라졌습니다! 정말 깔끔했습니다. 특정 방향이나 도형의 동작을 변경하고 싶다면, 여러 곳의 메서드를 업데이트하는 대신 한 곳에서 할 수 있었습니다.
이미 밤이 늦었습니다(저는 빠져들었습니다). 저는 제 리팩토링을 master에 체크인하고 동료의 지저분한 코드를 정리한 것에 자부심을 느끼며 잠자리에 들었습니다.

다음 날 아침

… 예상과 달랐습니다.
제 상사가 저를 일대일 면담에 초대했고, 정중하게 제 변경 사항을 되돌려 달라고 요청했습니다. 저는 깜짝 놀랐습니다. 기존 코드는 엉망이었고, 제 코드는 깔끔했습니다!
저는 마지못해 따랐지만, 그들이 옳았다는 것을 깨닫는 데 몇 년이 걸렸습니다.

이것은 하나의 단계입니다

"깔끔한 코드"에 집착하고 중복을 제거하는 것은 저희 많은 사람들이 거쳐 가는 단계입니다. 우리가 코드에 대해 자신감이 없을 때, 측정 가능한 것에 우리의 자존감과 직업적 자부심을 결부시키고 싶은 유혹이 있습니다. 엄격한 lint 규칙 세트, 명명 스키마, 파일 구조, 중복 제거등.
중복 제거를 자동화할 수는 없지만, 연습하면 더 쉬워집니다. 매번 변경 후에 중복이 더 많은지 적은지 보통 알 수 있습니다. 결과적으로 중복을 제거하는 것은 코드에 대한 객관적인 메트릭을 개선하는 것처럼 느껴집니다. 더 나쁜 것은, 사람들의 정체성 감각을 흔듭니다: "저는 깔끔한 코드를 작성하는 사람입니다". 이것은 일종의 자기기만만큼이나 강력합니다.
일단 추상화를 만드는 방법을 배우면, 그 능력에 도취되어 반복적인 코드를 볼 때마다 공중에서 추상화를 끌어내는 것이 유혹적입니다. 몇 년의 코딩 후에, 우리는 반복을 어디서나 봅니다 — 그리고 추상화는 우리의 새로운 초능력입니다. 누군가 추상화가 미덕이라고 말하면, 우리는 그것을 받아들입니다. 그리고 우리는 "깔끔함"을 숭배하지 않는 다른 사람들을 판단하기 시작합니다.
저는 이제 제 "리팩토링"이 두 가지 방식으로 재앙이었다는 것을 봅니다:
  • 첫째, 저는 그것을 작성한 사람과 대화하지 않았습니다. 저는 코드를 다시 작성하고 그들의 의견 없이 체크인했습니다. 설령 그것이 개선이었다 하더라도(저는 더 이상 그렇게 생각하지 않습니다), 이것은 그것을 진행하는 끔찍한 방법입니다. 건강한 엔지니어링 팀은 지속적으로 신뢰를 구축합니다. 논의 없이 팀원의 코드를 다시 작성하는 것은 코드베이스에서 효과적으로 협업할 수 있는 능력에 큰 타격입니다.
  • 둘째, 무료인 것은 없습니다. 제 코드는 중복 감소를 위해 요구 사항을 변경할 수 있는 능력을 거래했고, 그것은 좋은 거래가 아니었습니다. 예를 들어, 나중에 다양한 도형의 다양한 핸들에 대해 많은 특수한 경우와 동작이 필요했습니다. 제 추상화는 그것을 제공하기 위해 여러 배 더 복잡해져야 했던 반면, 원래의 "지저분한" 버전에서는 그러한 변경이 쉬웠습니다.
제가 "지저분한" 코드를 작성해야 한다고 말하는 건가요? 아니요. 저는 "깔끔하다" 또는 "지저분하다"고 말할 때 무엇을 의미하는지 깊이 생각해 볼 것을 제안합니다. 혐오감을 느끼나요? 의로움? 아름다움? 우아함? 그러한 특성에 해당하는 구체적인 엔지니어링 결과를 명명할 수 있다는 것이 얼마나 확실한가요? 그것들이 정확히 코드가 작성되고 수정되는 방식에 어떻게 영향을 미치나요?
저는 확실히 그 어떤 것도 깊이 생각하지 않았습니다. 저는 코드가 어떻게 보이는지에 대해 많이 생각했습니다 — 하지만 그것이 끈기 있는 사람들이 모인 팀과 함께 어떻게 진화했는지에 대해서는 생각하지 않았습니다.
코딩은 여정입니다. 첫 번째 코드 줄에서 현재 위치까지 얼마나 멀리 왔는지 생각해 보세요. 함수를 추출하거나 클래스를 리팩토링하면 복잡한 코드가 간단해질 수 있다는 것을 처음 봤을 때 기쁨이었을 것 같습니다. 당신이 당신의 기술에 자부심을 느낀다면, 코드의 깔끔함을 추구하는 것에 대한 유혹이 있을것입니다. 한동안은 그렇게 하세요.
하지만 거기서 멈추지 마세요. Clean code 광신자가 되지 마세요. Clean code는 목표가 아닙니다. 그것은 우리가 다루고 있는 시스템의 엄청난 복잡성에 어느 정도 의미를 부여하려는 시도입니다. 변경이 코드베이스에 어떻게 영향을 미칠지 아직 확실하지 않지만 미지의 바다에서 지침이 필요할 때의 방어 메커니즘입니다.
Clean code가 당신을 안내하게 하세요. 그 다음 그것을 놓아주세요.

0
17

댓글

?

아직 댓글이 없습니다.

첫 번째 댓글을 작성해보세요!