안녕, 클린 코드(2020)
(overreacted.io)- 그래픽 편집기 리사이즈 코드를 중복 제거 중심으로 재작성했다가 되돌리게 된 경험을 통해, 클린 코드 집착이 실제 변경 용이성을 해칠 수 있음을 보여줌
- 기존 구현은 Rectangle, Oval, Header, TextBlock의 핸들별 수식이 반복됐고, 새 구현은 방향과 도형을 분리해 조합하는 방식으로 중복을 제거하려 했음
- 리팩터링 직후에는 코드 크기가 절반으로 줄고 변경 지점도 한곳에 모였지만, 이후 도형별·핸들별 특수 동작이 필요해지면 추상화가 오히려 발목을 잡을 수 있었음
- 기술적 판단보다 더 큰 문제는 원래 코드를 쓴 동료와 상의하지 않고 master에 바로 체크인한 점이며, 팀의 신뢰 구축을 해칠 수 있음
- “깨끗함”은 목표가 아니라 복잡한 시스템을 다루기 위한 도구일 뿐이며, 코드의 모양보다 팀이 함께 변화에 대응할 수 있는지가 더 중요함
그래픽 편집기 리사이즈 코드의 반복
- 그래픽 편집기 캔버스에는 사각형과 타원 같은 도형을 가장자리의 작은 핸들로 리사이즈하는 코드가 있었음
- 구현은 동작했지만, 도형과 핸들 방향마다 위치와 크기를 계산하는 수식이 반복됐음
Rectangle은resizeTopLeft,resizeTopRight,resizeBottomLeft,resizeBottomRight같은 메서드를 가짐Oval은resizeLeft,resizeRight,resizeTop,resizeBottom같은 메서드를 가짐Header와TextBlock도 각자 리사이즈 메서드를 가짐
- 사용자가 Shift를 누르면 리사이즈 중 비율 유지도 처리해야 했고, 그만큼 수식이 더 많아졌음
중복 제거를 위한 추상화
- 반복은 두 축으로 보였음
- 같은 방향의 핸들끼리 반복됨:
Oval.resizeLeft()와Header.resizeLeft()는 모두 왼쪽 핸들을 드래그하는 동작임 - 같은 도형 안의 메서드끼리 반복됨:
Oval의 여러 메서드는 모두 타원을 다룸
- 같은 방향의 핸들끼리 반복됨:
- 새 구조는
Directions와Shapes로 코드를 나눠 방향별 동작과 도형별 동작을 조합하려 했음Directions에는top,left,bottom,right를 둠Shapes에는Oval,Rectangle을 둠createHandle,createBox로 핸들과 도형을 조합함
- 이 방식에서는
fourCorners,fourSides,twoSides같은 핸들 구성을 만들고, 이를 사용해Rectangle,Oval,Header,TextBlock를 생성함
리팩터링 직후의 착시
- 리팩터링 후 전체 코드 크기는 절반으로 줄었고 중복은 사라진 것처럼 보였음
- 특정 방향이나 도형의 동작을 바꿀 때 여러 메서드 대신 한곳만 수정하면 된다고 여겼음
- 밤늦게 변경을 master에 체크인했고, 동료의 지저분한 코드를 깔끔하게 풀어냈다고 생각했음
다음 날 드러난 문제
- 다음 날 상사는 변경을 되돌리라고 요청했고, 당시에는 기존 코드보다 새 코드가 더 깨끗하다고 믿어 납득하기 어려웠음
- 시간이 지나고 보니 그 판단은 두 가지 이유에서 맞았음
- 원래 코드를 쓴 사람과 이야기하지 않은 채 코드를 다시 작성해 체크인했음
- 중복 감소를 얻는 대신 요구사항 변경 용이성을 희생했음
- 건강한 엔지니어링 팀은 계속 신뢰를 쌓아야 하며, 동료 코드의 대규모 재작성은 협업에 큰 타격이 될 수 있음
잘못된 추상화의 비용
- 이후 여러 도형의 여러 핸들에 대해 많은 특수 케이스와 동작이 필요해졌음
- 새 추상화 구조에서 이런 요구를 수용하려면 코드가 몇 배 더 복잡해질 수 있었음
- 반대로 원래의 “지저분한” 구조는 각 메서드가 분리돼 있어 이런 변경을 적용하기 쉬운 편이었음
- 코드 중복 제거는 무료가 아니며, 어떤 능력을 얻는 대신 다른 능력을 잃을 수 있음
“깨끗함”을 다시 생각하기
- “깨끗한 코드”와 중복 제거에 집착하는 시기는 많은 개발자가 겪을 수 있음
- 자신이 작성한 코드에 자신감이 없을 때, 엄격한 린트 규칙, 네이밍 규칙, 파일 구조, 중복 없음처럼 측정 가능한 것에 자부심을 붙이기 쉬움
- 추상화를 만들 줄 알게 되면 반복되는 코드를 볼 때마다 추상화가 미덕이라고 믿고 적용하고 싶어질 수 있음
- “깨끗함”이나 “더러움”이라는 감각이 실제로 어떤 엔지니어링 결과와 연결되는지 깊게 생각해야 함
- 코드가 어떻게 보이는지뿐 아니라, 팀의 사람들과 함께 코드가 어떻게 변화하고 수정되는지가 중요함
클린 코드를 놓아야 할 때
- 클린 코드는 목표가 아니라, 복잡한 시스템을 이해하기 위한 시도임
- 코드베이스에서 변경이 어떤 영향을 줄지 확신이 없을 때 방향을 잡아 주는 방어 기제가 될 수 있음
- 처음에는 함수 추출이나 클래스 리팩터링으로 복잡한 코드를 단순하게 만드는 경험이 즐거울 수 있음
- 다만 거기서 멈춰 “클린 코드 광신도”가 되어서는 안 됨
- 클린 코드가 길잡이가 되게 하되, 어느 시점에는 그것을 놓아야 함
댓글과 토론
Hacker News 의견들
-
코드 중복이 때로는 괜찮지만, 그렇다고 클린 코드가 나쁘다는 증거는 아님
리팩터링에 조금 과하게 들어간 것 같고, “반복되는 수학 10줄”을 함수로 빼는 정도였다면 더 깔끔했을 것임
그 10줄을 함수로 만들지 않은 팀원도 잘한 건 아니어서 나라면 PR을 거절했겠지만, 직접 다시 쓰지는 않았을 것임
남의 PR을 다시 써버리는 건 토론 여지를 닫아버리는 빠른 모욕이고, 코드가 그렇게 작성된 맥락을 모를 때도 많음
코딩 표준이 없으면 PR은 리뷰하고 거절해야지, “클린 코드는 중요하지 않다”는 식으로 커밋하게 두면 몇 달 안에 아무도 일하고 싶지 않은 코드베이스가 됨
여기서 배울 건 클린 코드가 아니라 소통과 배려임- 남의 동작하는 코드를 직접 다시 쓰는 건 작성자를 모욕하는 것일 뿐 아니라, 유지보수자가 따로 있는 코드를 바꾸느라 시간을 낭비하는 비전문적 행동이기도 함
원래 작성자가 낮은 품질의 코드가 실제로 시간을 어떻게 낭비하는지 배울 기회도 사라짐
클린 코드 접근이 더 낫더라도, 여기서 핵심은 코드가 아니라 쓸모 있는 피드백 없이 자기 시간을 낭비했다는 데 있음
최선은 원래 작성자가 기준을 높이고, 본인은 실제로 손봐야 할 다른 일을 하는 것이었음. 즉 정답은 코드 리뷰였음 - 교훈은 “맥락별 절충을 고려하지 않고 규칙을 과잉 적용하지 말자”에 가깝고, 이건 배우는 데 시간이 걸리는 교훈임
- 20년 넘게 보니 “반복되는 10줄을 함수로 안 뺐으니 형편없다”는 생각은 대체로 코드베이스에서 엉뚱한 것에 집중하는 사람들이 자주 가짐
이런 사람들은 실제 문제를 풀기보다 문제가 아닌 것을 풀면서 미래의 문제까지 만드는 경우가 많음
첫 구현에서는 그런 10줄 정도의 아주 작은 코드를 추상화하지 않는 편이 맞음. 정확히 그 일만 하는 코드는 나중에 고치기 쉽고, 검증되지 않은 이득을 위해 10줄을 추상화하는 건 의미가 작음
Clean Code는 전혀 중요하지 않고, 그와 비슷한 규칙이나 “원칙” 대부분도 중요하지 않음. 오히려 코드베이스에 시간이 지나며 벌집 같은 구조를 쌓아놓고 정당화하려는 사람들이 붙잡는 것들임
결과적으로 과도하게 추상화되고, 바꾸기 어렵고, 느리며, 이해하기 힘든 코드가 되기 쉽다
원칙이 필요하다면 프로그램이 실제로 하는 일인 데이터 변환을 명확하고 쉽게 바꿀 수 있게 표현하는 것만 보면 됨
SOLID 원칙 대부분은 데이터 변환이 무엇이고 어떻게 일어나는지 보는 데 도움이 안 되거나 오히려 방해하므로, 실제 프로그램을 이해하는 데는 대체로 쓸모가 적음 - 원래 글의 표현이 좋지 않다고 봄
제안된 리팩터링을 보면 그 10줄은 사실 동일하지 않고 비슷했을 뿐임
사각형의 좌상단 모서리를 크기 조절하는 공식과 타원의 우하단을 바꾸는 공식은 서로 다름. 겉으로는 비슷하지만 어떤 곳은+대신-가 들어가는 식임
이 경우 복잡성은 기하 자체에 들어 있고, 이를 “추상화”해도 지저분함을 다른 곳으로 옮길 뿐 근본적으로 사라지지 않음
특수한 수 체계 같은 영리한 수학적 추상화가 있을 수는 있겠지만, 언어에 내장돼 있지 않다면 기본 정수로 특수 연산을 구현하는 비용이 얻는 이득보다 클 가능성이 높음
- 남의 동작하는 코드를 직접 다시 쓰는 건 작성자를 모욕하는 것일 뿐 아니라, 유지보수자가 따로 있는 코드를 바꾸느라 시간을 낭비하는 비전문적 행동이기도 함
-
“클린 코드”는 리브랜딩이 필요함
25년 넘는 경력 동안 디자인 패턴, 추상화, 중복 제거 등에 대한 끝없는 반발을 계속 봐왔음
늘 나오는 이유는 추상화가 코드를 더 복잡하게 만든다, 클린 코드를 쓸 시간이 없다, 전부 취향과 의견일 뿐이라는 것임
하지만 클린 코드의 목적은 요구사항이 바뀔 때 코드를 더 단순하고 유지보수하기 쉽게 만드는 데 있음
소프트웨어의 가치는 시간이 지나며 바뀔 수 있다는 데 있음. 그렇지 않다면 구현과 유지보수가 훨씬 쉽고 싼 고정 회로를 쓰면 됨
리팩터링이 이 목표를 달성하지 못했고, 상사가 미래 요구사항 변화에 더 유지보수하기 어려워졌다고 설득력 있게 말할 수 있다면 그 코드는 “클린”하지 않았던 것임
원 작성자와 논의하지 않은 건 클린 코드 범위 밖의 절차와 예절 문제임. 무례하게 독단적으로 움직였다고 해서 클린 코드의 가치가 사라지는 건 아님- 반발은 지나치게 절대적인 처방에 대한 것이라고 봄
유명한 책 Clean Code는 “중복 없음”을 잘 설계된 시스템의 조건으로 제시하고, 중복을 추가 작업·추가 위험·불필요한 복잡성의 주적이라고 함
이 정의에 따르면 중복 제거가 단순화와 동의어가 되는데, 그건 틀렸음
중복 제거는 의존성 도입임. 그 의존성이 문제를 잘 모델링하면 좋은 추상화이자 단순화가 될 수 있지만, 아니면 추상화의 탈을 쓴 압축일 뿐임
참고로 이 인용은 Clean Code에 등장하는 Kent Beck의 Simple Design 언급임 - “클린 코드”는 두 가지 뜻으로 쓰임
하나는 어떤 코드가 좋다는 주관적 느낌이고, 다른 하나는 책 Clean Code와 관련 지침들을 따르는 것임
점점 더 많은 사람들이 이 둘이 상당 부분 다르다고 느끼고 있음. 책의 지침 일부를 따르면 오히려 전혀 깨끗하지 않은 코드가 나오기도 함
이상적인 세상에서는 모두가 클린 코드를 쓰고 싶어 하겠지만, Clean Code가 그 목표를 달성하는 좋은 방법이라고 느끼는 사람은 줄어드는 중임
특히 과도한 추상화와 중복 제거는 많은 사람에게 지저분한 코드의 원천으로 여겨지게 됨 - 업계에서 배운 건, 가장 깔끔하게 작성한 코드도 제품 요구사항과 만나면 오래 버티지 못한다는 것임
때로는 기술 부채가 제품 요구사항 자체에 인코딩돼 있어서, 리팩터링이나 모범 사례로 고칠 수 없고 고통을 줄이는 정도만 가능함 - “진짜 스코틀랜드인은 아니다”식 논리처럼 들림
클린 코드의 목적과 실제 실천 결과는 크게 다름. 사람들은 요구사항을 눈앞에 두고도 코드의 미래 상태를 대체로 예측하지 못하기 때문임
클린 코드는 “만약에?”라는 끔찍한 질문을 부르고, 이건 출시를 끝없이 미루는 판도라의 상자가 되기 쉽다
거의 항상 아는 것에 맞춰 코딩하고, 도메인 경험이 있는 사람이 아키텍처를 이끄는 편이 낫다 - 대안 목록을 들자면, 우리가 만드는 추상화 대부분은 나쁘고, 좋다고 생각하는 것들조차 대개 그렇음
좋은 추상화는 상당수가 이미 라이브러리에 들어 있음. 물론 안타깝게도 항상 그렇지는 않음
나쁜 추상화는 시간을 잡아먹고, 코드는 팀의 다른 사람과 소통하는 일이기도 하므로 내가 생각하는 방식보다 팀원들이 소통을 어떻게 받아들이는지가 훨씬 중요함
- 반발은 지나치게 절대적인 처방에 대한 것이라고 봄
-
동료가 복사·붙여넣기로 많은 코드를 작성했고, 당신은 커밋 후 리팩터링했음
동료는 상사에게 불평했고 상사는 당신을 혼냈으며, 다음에는 코드베이스에 지저분함을 남겨두겠다고 결론낸 셈임
여기서 배울 교훈은 있지만, 그게 복사·붙여넣기가 함수 작성보다 낫다는 뜻은 아님
리팩터링은 원 작성자를 태그한 별도 변경으로 올려 리뷰받았어야 함
다만 동료가 직접 이야기하지 않고 상사에게 올렸고, 상사가 되돌리라고 한 건 꽤 나쁜 신호임- 나라면 동료에게 Slack 개인 메시지로 “이 함수들마다 수학 10줄이 반복되는데, 별도 함수로 빼는 게 낫지 않을까요?”라고 물었을 것임
가능한 답은 “아니요, 이유는…”이거나 드물게 “맞네요, 내일 할게요” 정도임
소란 없이 모두가 문제를 피하고, 누군가는 배울 수도 있음 - 게으른 복사·붙여넣기라면 반복에 대해 함수를 쓰는, 즉 추상화하는 쪽이 DRY 관점에서 더 나음
하지만 글쓴이는 중요한 맥락을 덧붙였음. 요구사항 변경 가능성을 중복 감소와 맞바꿨고, 나중에 도형별 핸들마다 많은 특수 사례와 동작이 필요해졌다는 것임
이는 우발적 DRY와 본질적 DRY의 차이를 보여줌
라이브러리 A와 B가 서로 일관돼 보여서 코드가 같아지는 경우가 많지만, 실제로는 우연일 수 있음
lib A를 감싼 래퍼와 lib B를 감싼 래퍼가 같은 코드를 담고 있어도 서로 완전히 다른 것을 감싸고 있다면, 이를 추상화하면 결과가 나빠짐
“15개”도 마찬가지임. “모든 목록은 항상 15개를 보여야 한다”면 15를 추상화하는 게 맞지만, “홈페이지 상위 15곡”과 “노래 아래 댓글 기본 15개”가 각각이라면 그 15를 묶는 건 더 나쁜 선택임 - 가장 큰 위험 신호는 동료의 제출물을 아무 소통 없이 바꾼 것임
두 번째 위험 신호는 리팩터링 자체도 좋은 리팩터링이 아니라는 점임. 모듈성이 부족하고 여전히 결합돼 있으며, 중복만 조금 줄였을 뿐임 - 원 작성자를 태그하는 것도 문제를 부를 수 있음
“네가 쓴 코드가 별로라서 내가 다시 썼어. 불만 있어?”처럼 들릴 수 있음
가장 나은 해법은 “늦은 리뷰” 형태로 설명을 요청하고, 해결책을 제안하며, 원 작성자가 그냥 무시하지 않기를 바라는 정도일 것임 - 글 끝에는 이후 코드베이스 변경에서 도형별 핸들을 특수 처리해야 했고, 리팩터링된 구조에서는 어렵지만 반복 코드에서는 쉬웠다는 중요한 대목이 있었음
핵심은 코드가 미래에 바뀐다는 점을 인식하는 것임. 요구사항도 바뀌고 관련 코드도 바뀜
그 변경 능력을 제한하는 추상화를 만들면 미래의 내가 코드베이스를 적응시키는 능력을 해치게 됨
특히 많은 시간과 노력을 들인 추상화에는 감정적으로 집착해서, 변화를 우아한 해법에 끼워 맞추느라 시간을 더 낭비하기도 함
KISS가 맞음
- 나라면 동료에게 Slack 개인 메시지로 “이 함수들마다 수학 10줄이 반복되는데, 별도 함수로 빼는 게 낫지 않을까요?”라고 물었을 것임
-
이 사례는 “아무도 잘난 척하는 사람을 좋아하지 않는다” 문제처럼 보임
특히 주니어 개발자가 일이 자기와 컴퓨터 사이에만 있는 게 아니라 자기, 컴퓨터, 그리고 다른 사람 사이에 있다는 걸 아직 배우지 못했을 때 자주 생김
더 깔끔한 버전은 원 작성자와 실제 대화를 나누고 더 나은 방법에 합의했다면 받아들여졌을 가능성이 큼
글 제목은 “잘 가라, 클린 코드”보다 팀에서 일하는 법을 배웠다가 더 맞아 보임- “아무도 잘난 척하는 사람을 좋아하지 않는다”는 표현이 개발 환경을 잘 잡아냄
더 복잡한 코드는 위협으로 받아들여졌고, 관리 체인을 통해 눌러야 할 경쟁자로 여겨졌을 수 있음
주니어라면 이 말이 맥락 의존적이라는 걸 알아야 함. 많은 직장에서는 단순성이 유연성보다 중요하고, 혼란스러운 언어 기능을 피하며, 키보드 앞에서 오래 보내는 방식과 그런 언어가 선호됨
반면 평생 학습, 소프트웨어 장인정신, 프로세스 성찰을 중시하는 철학은 이런 환경과 완전히 충돌함
더 나아지려는 시도는 성장하지 않는 사람들에게 인기가 없음
둘 다 유효한 삶의 선택임. 소프트웨어는 7시간 일하고 집에 가기 편한 곳이기도 하고, 상상력과 지성만이 한계인 탐험의 세계이기도 함
새로운 것을 발견하고 싶다면 미지의 것을 두려워하는 사람들과 수십 년을 보내는 일을 조심해야 함
더 짧게 말하면, 동료들과 인센티브가 어긋나 있다면 떠나는 걸 고려해야 함 - 제목은 괜찮다고 봄
이 글은 오래 사랑받을 만한 이유가 있고, 같은 함정에 빠지는 많은 개발자에게 눈을 뜨게 해줬음
혼자 일하더라도 깔끔한 추상화를 과하게 설계했다가 미래에 그 추상화에 물릴 수 있음 - 이런 문제는 보통 주니어보다 권력에 취한 시니어가 더 자주 저지름
- “아무도 잘난 척하는 사람을 좋아하지 않는다”는 표현이 개발 환경을 잘 잡아냄
-
문제는 변경이 리뷰 없이 바로 main 브랜치에 커밋된다는 데 있음
모든 코드 변경에는 짝 프로그래밍이든 전통적인 PR이든 동료 리뷰가 있어야 이런 식의 확대를 막을 수 있음
상사는 나쁜 상사였음. 꾸짖을 게 아니라 좋은 프로세스가 빠져 있다는 걸 알아차렸어야 함
코드 리뷰 문화가 있는 팀이라면 의욕이 앞선 신입이 이런 시도를 해도 동료가 “고맙지만 이건 아니야” 또는 “좋은 생각인데 이렇게 해보자”라고 말했을 것임
별개로, 좋은 이유 없이 코드를 리팩터링하면 안 된다고 생각함. 나쁜 코드라도 거의 수정할 일이 없는 코드일 수 있음
리팩터링하기 가장 좋은 때는 동작을 바꿔야 하는데 기존 구조가 너무 어렵게 만든다는 걸 깨달았을 때임
안타깝게도 여기서는 잘못된 교훈을 배운 것 같음- 병합 후 코드 리뷰도 완전히 괜찮고, 어떤 면에서는 더 나음
-
왜 다들 그가 대신 뭘 했어야 하는지만 말하는지 모르겠음. 이건 충분히 정당한 교훈임
금융에서는 어느 정도 비슷하지만 완전히 같지는 않은 상품을 계속 다룸. 주식 옵션과 외환 옵션은 둘 다 옵션이고 행사가가 있으며 Black-Scholes의 어떤 버전으로 가격을 매기지만, 실제로는 엄청나게 다름
새로 합류한 사람이 많은 동작을 공통화하고 싶어 하는 유혹은 크고, 우리는 종종 원글 상사의 입장에서 젊은 개발자에게 진정하고 분리해두라고 말하게 됨
클린 코드와 작별하자는 게 아니라, 과도한 추상화를 피해야 실제로 클린 코드를 유지할 수 있다는 뜻임- 금융 예시는 맞지만 여기서는 기하를 다루고 있음
일반적인 핸들이 하는 일은 아핀 변환, 즉 이동·회전·스케일링·반사이고, 균일 스케일링이면 닮음 변환임. 이는 어떤 도형에도 동작하는 것이 보장됨
진짜 문제는 충분히 알지 못하는 것을 추상화하면 안 된다는 데 있음. 특히 미래에 어떤 가능성이 생길지 모르면 더 그렇다
예를 들어 타원이 원과 타원뿐이라고 생각하면, 첫 “진짜” oval이 등장했을 때 꽤 놀라게 됨 - 글의 요지는 이해하지만, 제목은 실제로 지저분한 코드를 정당화하는 핑계처럼 쉽게 읽힐 수 있음
글쓴이가 지저분한 코드와 “비클린” 코드를 적극적으로 구분하지 않으니 다른 사람들이 구분하려 드는 것임 - 여기 있는 모두가 몇 줄의 의사코드 외에는 맥락도 없으면서 당시 팀원들과 글쓴이보다 더 잘 안다고 느끼는 게 꽤 드러남
DRY는 가치가 있지만 추상화에는 비용이 따름. 항상 절충임
- 금융 예시는 맞지만 여기서는 기하를 다루고 있음
-
Haskell 같은 언어를 좋아하는 이유 중 하나는 추상화 극대주의가 코딩 수준이 아니라 언어 수준에 자리 잡고 있기 때문임
Applicative가 무엇을 해야 하는지에 대해서는 모두 동의함. 도전은 아이디어에서 코드로 가는 파이프라인의 더 오른쪽, 즉 문제에 맞는 Applicative를 찾는 쪽으로 이동함
대부분의 객체지향 디자인 패턴은 이런 환경에서 자연스럽고 잘 정의된 대응물을 갖고, 프로젝트별 추상화를 줄이는 데 도움이 됨
초보자에게는 언어를 배우는 초기 비용이 크고, 다른 Haskell 코드를 열었더니 처음 보는 컴파일러 확장이 12개쯤 있어서 이해해야 하는 일이 자주 생김
그래도 그 컴파일러 확장은 이후 모든 프로젝트에 가져갈 수 있으니 순이익이라고 봄
전문가에게는 cats가 RAM을 얼마나 먹을지 감을 잡으려면 박사학위가 필요하다는 단점이 있음- Applicative와 Monad가 무엇을 하는지 아는 이유는 수학적 법칙이 있기 때문임
어떤 코드가 Applicative나 Monad를 쓰면 그 법칙 덕분에 동작을 알 수 있음
객체지향 디자인 패턴에는 그런 법칙이 없음. 느슨하게 정의된 개념이나 패턴이라 개인 해석과 커스터마이징에 열려 있지만, 정확히 무엇이고 어떤 동작이나 출력을 기대해야 하는지 말해주는 구체적 법칙은 없음 - Haskell 사용자로서 언어의 추상화 능력이 매우 좋다는 데 동의함
다만 블로그 글의 예시는 클린 코드 경로를 따라가다 미래의 자신 발등을 찍는 일이 정말 많았다고 말할 수밖에 없음
나중에 다시 이해하려고 추상화를 공부해야 한다면 그 추상화는 유익하지 않음 - 원글은 팀이 전반적으로 배척하지 않는 한 객체지향 원칙을 더 쓰는 편이 나았을 것임. 그런 팀이라면 나라면 다른 곳에서 일할 것 같음
객체지향이 가장 자연스럽게 맞는 곳은 GUI 위젯과 편집기 구현임
객체지향의 다른 규칙을 통합하는 규칙은 묻지 말고 시켜라임
이 예시라면 핸들은 알려진 방식으로 공유되고, 도형별 제어점이 있을 것임. 박스 핸들이 바뀌면updateControlPoints(changedHandle)을 호출하고, 제어점이 바뀌면updateBoxHandles(changedControlPoint)를 호출할 수 있음
다른 깔끔한 분리도 가능하겠지만 나라면 그렇게 할 것임 - Applicative가 무엇을 해야 하는지는 동의할 수도 있음
하지만 어떤 파싱 라이브러리를 쓸지,Data.Text.Strict와Data.Text.Lazy중 무엇을 쓸지, 포인트프리 스타일을 얼마나 쓸지,.와>>>나 다른 조합자 중 무엇을 쓸지, 어떤 언어 확장을 쓸지는 합의가 잘 안 됨
Haskell은 좋아하지만 다른 언어와 비슷한 문제를 겪음. 사람들은 일을 하는 최선의 방식에 정말로 합의하지 못함 - 한 단계 더 나아가 아핀 타입을 쓰는 Rust를 쓰면 됨
Haskell 타입 시스템과 예측 가능한 성능을 얻을 수 있음
- Applicative와 Monad가 무엇을 하는지 아는 이유는 수학적 법칙이 있기 때문임
-
반복되는 수학 부분만 별도 함수로 옮기고, 크기 조절 함수에서 호출하면 됐음
크기 조절 함수 자체가 반복돼 보였던 건 인터페이스를 이루고 있었기 때문이고, 그래서 그걸 없애는 리팩터링은 잘못이었음
하지만 그 함수들이 수행한 수학은 코드를 정리하기에 완전히 타당한 대상임- 맞음. 명백한 방향처럼 보이고 보통 나도 그렇게 함
결과적으로 작고 순수한 함수가 잔뜩 생기지만, 테스트와 리팩터링이 쉬워서 문제가 되지 않음 - 특히 리팩터링이라면 팀과 일할 때는 보통 단계적으로 처리하는 편이 좋음
같은 문제를 푸는 방식과 패턴은 많지만, 다른 사람들이 쓰는 기존 인터페이스를 뜯어내는 건 좋은 출발이 아님
추상화의 장점은 나머지를 해치지 않고 내부를 더 유연하게 바꿀 수 있다는 데 있음
좋은 첫 단계는 말한 것처럼 아래쪽에 더 유연한 수학 함수를 만들고 나머지는 그대로 두는 것임
그다음 팀과 인터페이스가 명확한 파사드를 제공하면서 구현은 추상화되도록 어떻게 소통할지 더 깊게 이야기할 수 있음
- 맞음. 명백한 방향처럼 보이고 보통 나도 그렇게 함
-
여기서 묘사한 건 나쁜 추상화처럼 느껴짐
진짜 문제는 사람들이 두세 가지 예시만 보고 설계하면서, 미래 사용을 수용할 만큼 충분히 일반적인지 확인하지 않는 데 있음- 특히 좋은 접근은 공통 부분을 아주 단순한 함수들로만 추상화하는 것임
아래에서 위로 추상화해야지, 위에서 아래로 추상화하면 안 됨
모든 일이 한곳에서 일어나고2^n조합을 처리하는 단일 지점을 만들 필요는 없음
그래도 반복을 줄이고 한 지점에서 바꿀 수 있으며, 필요할 때 맞춤 변경 능력도 유지할 수 있음 - 현실의 문제는 누구도 자신이 나쁜 추상화를 쓰고 있다는 걸 작성 당시에는 모른다는 점임. 알았다면 쓰지 않았을 것임
그래서 “나쁜 추상화를 피하라”거나 “미래 사용을 수용하게 하라”는 말만으로는 부족함
일반적으로 누구도 미래를 예측할 수 없음
추상화는 옳다고 느껴질 때조차 극도로 조심해서 써야 함. 극단적인 회의론자가 되는 편이 낫다 - “클린” 버전의 문제는 완전히 서로 결합돼 있다는 데 있음
수학 코드에서 하나를 건드리면 모든 것에 영향을 줌
어느 시점에는 일부 중복 코드를 남기고, 코드 구조의 단서나 주석으로 유사성을 표시하며, 시간이 지나며 핵심 차이를 주석으로 강조하는 편이 더 쉬워짐
이런 종류의 코드에 대한 클린 코드 접근은 변하는 요구사항을 잘 다루지 못함
결국 호출 위치에 따라 여러 코드 경로가 들어간 헬퍼 함수 뭉치가 되기 쉽다
때로는 이를 추상화할 수 있는데, 이미 눈치챈 사람도 있겠지만 앞 문장은 객체지향 다형성을 설명한 것임
하지만 정확한 요구사항을 미리 어떻게 알 수 있겠음? 이렇게 이른 단계에서 “클린 코드”를 시도하면 나쁜 추상화나 과설계로 끝날 뿐임
올바른 접근은 아무것도 하지 않고, 가능한 문제를 메모한 뒤, 문제 도메인에 대해 더 많이 알 때까지 기다리는 것임 - 변경을 통해 패턴을 발견하는 방식을 점점 좋아하게 됐음. 새 기능과 버그 수정에서 패턴을 찾는 것임
원래 예시처럼 약간 중복됐지만 쉽게 이해되는 코드는 크게 걱정하지 않음
하지만 같은 이슈를 세 곳에서 고치거나 새 기능 코드를 세 곳에 추가해야 한다면 걱정함
그때 바꾸거나 추가해야 하는 코드를 어떻게든 공통 위치로 밀어 넣을 수 있는지 고민하기 시작함 - 맞는 말임
사람들은 디자인 패턴이나 DRY를 읽고, 배우는 중이라 대체로 잘못 적용한 뒤, 그 책임을 원전 탓으로 돌리곤 함
- 특히 좋은 접근은 공통 부분을 아주 단순한 함수들로만 추상화하는 것임
-
Rob Pike는 Go가 그런 모습이 된 이유 중 하나로 “작은 의존성보다 작은 복사가 낫다”고 말한 적이 있음
프로그래밍을 더 할수록 그 뜻을 더 이해하게 됨
반복 자체는 문제가 아님. 중요한 건 지금 읽고 있고 어쩌면 수정할 단위를 파악하는 것임
DRY는 종종 또 다른 추상화를 추가한다는 뜻이고, 그 추상화가 충분히 직교적이지 않으면 이제 하나가 아니라 두 단위를 고려해야 하므로 상황을 더 나쁘게 만든 것임