그럭 브레인 개발자(2022)
(grugbrain.dev)- 복잡성은 개발에서 가장 위험한 요소임
- 진정한 효율성은 "80/20 솔루션" 등 복잡성을 피하는 실용주의적 접근에서 나옴
- 테스트와 리팩터링에 대해 균형 있고 유연한 자세를 유지하는 것이 중요함
- 도구 활용 및 읽기 쉽고 관리하기 쉬운 코드 작성 습관 채택을 강조함
- 과도한 추상화와 트렌드를 경계하며, 단순성을 추구하는 태도를 추천함
서론
- 이 글은 오랜 기간 소프트웨어를 개발하며 경험에서 배운 점을 정리한 그럭 브레인 개발자의 생각 모음임
- 그럭 브레인 개발자는 스스로 똑똑하지 않다고 생각하지만, 오랜 시간동안 프로그래밍을 하며 많은 것을 배움
- 다른 사람들이 실수에서 배우기를 바라는 마음으로 쉽고 웃긴 방식으로 깨달음을 공유함
- 복잡성이야말로 개발 인생의 최대의 적임
- 복잡성은 코드베이스에 몰래 침투하여, 처음엔 이해하기 쉽던 코드도 점차 수정이 불가능한 지경에 이르게 만듦
복잡성의 악령 다루기
- 복잡성은 보이지 않는 영처럼 소리 없이 스며들며, 프로젝트 매니저 및 비그럭 개발자들이 잘 인식하지 못하는 경우가 많음
- 복잡성을 막는 최고의 방법은 "아니오"라고 말하는 것임
- "이 기능을 만들지 않겠다"
- "이 추상화를 도입하지 않겠다"
- 물론, 커리어 면에서는 "예"를 외치는 것이 더 이득일 수 있지만 그럭 브레인 개발자는 스스로에게 정직한 선택을 중시함
- 조건에 따라 타협(“ok”)도 필요하며, 이런 경우 80/20 솔루션(Pareto 법칙 적용)으로 문제를 단순하게 해결하는 방식을 선호함
- 프로젝트 매니저에게 모든 것을 말하지 않고 실제로는 80/20 방식으로 해내는 것도 현명한 전략임
코드 구조와 추상화
- 코드의 적절한 단위(컷포인트)는 시간이 지나면서 자연스럽게 드러남, 그래서 초반의 추상화는 피하는 것이 좋음
- 좋은 컷포인트는 시스템 나머지와의 인터페이스가 좁은 것이 이상적임
- 조기 추상화 시도는 실패하기 쉽고, 경험 많은 개발자는 코드의 형태가 어느 정도 자리를 잡은 뒤에 천천히 구조화를 시도함
- 경험이 적거나 “빅브레인”인 개발자들은 프로젝트 초기에 지나친 추상화를 시도하며, 유지보수의 부담을 남겨둠
테스트 전략
- 테스트에 대한 집착과 균형이 중요함
- 프로토타이핑 후, 코드가 어느 정도 고정된 뒤에 테스트를 짜는 것을 선호함
- 유닛 테스트는 초기에 활용하지만, 실제로는 중간 단계(통합 테스트)가 가장 큰 효과를 보임
- 엔드 투 엔드 테스트도 필요하지만, 너무 많으면 유지보수 불가 상황이 오므로 꼭 필요한 경로만 소수로 유지
- 버그 리포트 시에는 반드시 재현 테스트 추가 후 버그를 고침
프로세스, 애자일, 리팩터링
- 애자일은 그럭 개발자에게 나쁘지 않으며, 최악도 아니지만, "애자일 샤먼"에게 과도한 기대는 위험
- 프로토타이핑, 도구, 좋은 동료가 실제로 더 중요한 성공 요소
- 리팩터링도 좋은 습관이지만, 크고 무리한 리팩터링은 위험함
- 복잡한 추상화를 무리하게 도입하는 것이 오히려 프로젝트 실패를 초래함
유지보수, 완벽주의와 겸손
- 기존 시스템을 이유 없이 뜯어고치는 것은 위험하며, “왜 있는지 모르는 구조”를 무작정 없애는 것은 좋지 않은 습관임
- 완벽한 코드를 꿈꾸는 이상주의는 현실적으로 대부분 문제를 야기함
- 경험이 쌓일수록 “동작하는 코드는 존중”해야 함을 몸소 느낌
도구와 생산성
- 좋은 개발 도구(IDE 코드완성, 디버거 등)는 생산성을 크게 높여주며, 깊게 파악하는 것이 중요함
- 타입 시스템의 실제 가치는 “자동 완성”과 실수 방지에 있음을 강조하고, 과도한 추상화와 제네릭은 오히려 위험함
코드 스타일과 반복
- 더 읽기 쉽고 디버깅하기 쉬운 코드를 위해 조건식을 여러 줄로 쪼개는 식의 스타일 권장
- DRY(Don’t Repeat Yourself) 원칙은 존중하지만, 반복 코드를 무리하게 없애는 것보단 균형이 중요한 점을 강조함
- 단순 반복이 복잡한 DRY 구현보다 나은 상황도 많음
소프트웨어 설계 원칙
- SoC(관점 분리) 원칙보다 행동 지역성을 선호, "해당 동작을 하는 코드가 그 객체에 있어야 유지보수가 쉬움"을 주장
- 콜백/클로저, 타입 시스템, 제네릭, 추상화 등은 소량만 적절히 사용할 것을 경고함
- 클로저의 남용은 자바스크립트에서 "콜백 지옥"을 만들 수도 있음
로깅, 운영
- 로깅은 매우 중요한데, 주요 분기마다 남기고, 클라우드 환경에서는 요청 ID 등으로 추적 가능하게 구성함
- 동적 로그 레벨, 사용자별 로그를 활용할 수 있으면, 운영 중의 문제 추적에 큰 도움이 됨
동시성, 최적화
- 동시성은 최대한 단순한 모델(상태 없는 웹 요청, 분리된 워커 큐 등)만 신뢰
- 최적화는 실제 성능 프로파일 데이터를 확보한 후에만 실제로 수행하기를 권장
- 네트워크 I/O 등 숨겨진 비용에 주의해야 하며, 단순히 CPU 복잡도만 보는 것은 위험함
API 설계
- 좋은 API는 사용하기 쉬워야 하며, 너무 복잡한 설계나 추상화는 개발자 경험을 해침
- "사용 케이스에 맞는 단순한 API"와 "복잡한 케이스도 구현 가능한 계층적 API" 구조를 권장함
파서 개발
- 재귀하강 파서는 학계에서 저평가되지만, 실제 생산 코드에 가장 적합하고 이해하기 쉬운 방법임
- 대부분의 파서 개발 경험상, 툴로 생성한 파서는 결과물이 너무 복잡해 문제 해결에 오히려 마이너스임
- 추천 도서로 "Crafting Interpreters"를 최고로 꼽으며 실무적인 조언을 많이 담고 있음
프론트엔드와 유행
- 모던 프론트엔드(React, SPA, GraphQL 등)는 오히려 복잡성 악령을 추가로 불러오며, 불필요한 경우가 많음
- Grug 본인은 htmx, hyperscript 같은 단순한 도구를 통해 복잡성을 줄이는 방식을 선호함
- 프론트엔드에서 끊임없이 새로운 시도가 이뤄지고 있지만, 기존 아이디어의 반복이 많음에 유의할 필요가 있음
심리적 요소, 임포스터 신드롬
- 대부분 개발자들은 “내가 뭘 하는지 모른다”고 느낄 때가 많으며, FOLD(Fear Of Looking Dumb) 현상에서 자유로워질 필요가 있음
- 선임 개발자가 “이건 나도 어렵다, 너무 복잡하다”고 공개적으로 말하면, 주니어 개발자도 부담을 내려놓을 수 있음
- 임포스터 신드롬은 흔한 감정이며, 충분히 배워가며 성장할 수 있음을 격려함
결론
- 프로그래밍에서 복잡성은 항상 경계해야 하며, 단순함 유지는 성공적 개발의 핵심임
- 경험, 도구의 효과적 활용, 겸손, 실제로 동작하는 코드의 존중이 장기적으로 효율적이고 가치 있는 개발로 이어짐
- "복잡성 매우, 매우 나쁨"—이 문장을 항상 기억해야 함
Hacker News 의견
- 나는 좋은 디버거의 가치를 돌로도 환산할 수 없을 만큼 높게 여김, 실제로 더 대단함을 느낌. 작은 스타트업이든 유명 빅테크 팀이든, 팀에서 나만 디버거를 쓰는 경우가 많았음. 실제 많은 사람들이 여전히 print 문으로 디버깅하는 현실을 봄. 내 워크플로를 동료들에게 알려주려 해도 반응이 없음. 시스템을 이해하기에 가장 좋은 출발점은 바로 디버거라는 데 동의함. 테스트 중 흥미로운 코드 라인에서 중단하고 스택을 보는 게 머릿속으로 코드를 따라가는 것보다 훨씬 쉬움. 디버거 쓰는 법을 익혀두면 진짜 소소한 초능력 얻는 것임. 가능하다면 꼭 한번 적용해보길 추천함
- 나는 진짜 디버거를 쓰고 싶지만, 대기업에서만 일해온 입장에선 현실적으로 불가능했던 상황임. 마이크로서비스 메쉬 아키텍처에선 로컬에서 뭘 돌릴 수가 없고, 테스트 환경에서도 스텝 디버거를 붙일 수 없도록 세팅되는 경우가 대부분임. 그래서 print 디버깅이 유일하게 가능한 선택지임. 심지어 로그 시스템에까지 문제가 생기거나 프로그램이 로그를 출력하기 전에 그냥 크래시된다면 print조차 쓸 수 없는 상황임
- 이 주제에 대해 수년 전 좋은 토론이 있었음. Brian Kernighan과 Rob Pike의 명언이 있는데, 둘 다 어린 개발자는 아님. "우리는 stack trace나 변수 값 몇 개를 확인하는 정도 외의 목적으로는 디버거를 쓰지 않음. 복잡한 자료구조와 제어 흐름 때문에 디테일에 갇히게 쉬움. 직접 프로그램을 머릿속으로 더 고민하고, 중간중간 print로 출력과 self-checking 코드 넣는 것이 더 생산적임. print 넣는 것이 디버거로 스텝 단위로 들어가는 것보다 훨씬 빠름. 또, print 코드는 프로그램에 남아 있고, 디버깅 세션은 사라짐." 나도 이 의견에 동의함. 대부분의 개발 과정에서 print-가설-실행 루프가 훨씬 빠른 문제 해결 제공함. 코드를 머릿속으로 "실행해보는" 게 아니고, 코드 흐름에 대해 이미 작동 모델이 있어서 print가 잘못된 출력을 보여주면 대개의 경우 빠르게 실상을 직감함. 관련 링크: The unreasonable effectiveness of print debugging
- 리눅스 계열에서 printf 디버깅이 늘 보편적이었던 이유가, GUI 기반 디버거를 신뢰할 수 없는 환경 때문임. 리눅스의 GUI는 종종 불안정해서 믿을 수가 없음. 내게도 디버거를 제대로 쓰기 시작한 시점이, (1) 윈도우에서 GUI가 잘 되지만 CLI가 종종 깨질 때였고, (2) print 디버깅 코드가 실수로 버전에 반영돼서 문제 일으킨 경험을 여러 번 한 뒤였음. 그다음엔 CLI 디버거로 여러 모험을 했었고, Junit+디버거(이클립스 등 IDE 기반)로 실험적 코드를 바로 써보며 테스트로 남기는 식의 프로세스가 Python REPL만큼 편리함을 느낌. 단, 디버거를 환경에 맞게 세팅하는 초기 투자가 필요하긴 함
- 내 코드에서는 디버거 쓰기가 쉽고 진짜 좋아함. 그런데 디버거가 내가 작성한 코드보다, 라이브러리나 프레임워크 내부로 깊게 들어가면 나도 바로 길을 잃고 싫어짐. 이런 프레임워크/라이브러리는 수십만 시간을 들여 만들어진 것이기 때문에 내 수준에서는 이해의 범위를 바로 넘어감
- 교수님(Carson) 혹시 이 글 보신다면, 진심으로 감사 인사 전하고 싶음. 대학 때 HTMX를 왜 배우는 지, 왜 그렇게 열정적이셨는지 이해 못하다가, 몇 년 뒤에 제대로 깨달음. HTML over the wire가 진짜 전부임. Staff Ruby on Rails Engineer로 일하며 Hotwire에서도 교수님의 작업을 여러 번 봤고, 가끔 GitHub나 Hacker News에서 활동하시는 걸 보면 정말 놀라움. 늘 프로그래밍 커뮤니티의 빛 같은 존재임. 깊은 존경과 감사를 전함
- 여기서 울컥하는 건 나뿐만이 아님, 감동임
- HTMX가 그냥 밈 아니었나? Poe’s Law 때문에 진지한 건지 헷갈림
- 이 글에는 진짜 명언이 많지만, 나는 마이크로서비스 얘기가 제일 좋았음: "grug는 큰 뇌가 시스템을 제대로 분해하기 힘든데, 굳이 네트워크 호출까지 추가하는 이유를 모름"
- 어떤 사람들은 시스템을 파트로 쪼개는 방법을 API로 만드는 것밖에 모름. API로 노출되지 않으면 그저 이해 불가하고 재사용도 안되는 불투명 코드라고 생각함
- 여러 이유로 마이크로서비스가 실용적인 경우가 있어서 쓰이는 점이 아쉽기도 함
- 나는 사소한 웹앱 하나에 다섯 개의 폼만 있어도, 두 명짜리 소규모 dev팀이 이를 “마이크로서비스” 구조(데이터베이스 공유, API 관리, 큐를 통한 배치 작업, 이메일 알림, 자체 Observability 플랫폼 추가 등)로 복잡하게 만드는 걸 계속 봄. 그리고 결국은 평범한 폼도 SPA로 만들어서 ‘더 쉽기 때문’이라고 함. 이제는 “아키텍처”와 “패턴”이 쓸모없는 개발자들의 일거리 창출용임을 이해함. 만약 그런 게 없다면 “샌드위치 한 조각이라도 줄 테니 자바스크립트 쓸게요”라는 푯말 들고 거리에 있을 사람들임
- 내 음모론 하나 말하자면, 마이크로서비스 패턴을 클라우드 벤더들이 밀어서 이런 결과가 나왔다고 생각함. - K8S 같은 오케스트레이터 없이는 실행도 못하게 만들고, 이걸로 관리형 클라우드 팔기 쉬워짐 - 더 많은 네트워크 트래픽/CPU 사용으로 과금 더 나옴 - 대규모 상태 공유가 힘들어서 관리형 데이터베이스/이벤트 큐 필요하게 만듦 - 로컬 실행 어려워져서 개발 환경까지 클라우드 비용으로 이어짐 - 클라우드 고유 방식에 종속돼 벗어나기 힘들어짐. 예전에 클라우드가 IT 비용을 절약해준다고 광고했는데 완전 웃김. 이미 2000년대부터 그게 허상임을 알았고, 결국 전부 더 비싸지는 결과뿐임
- "복잡함 vs 티라노사우르스 대 면대면, grug는 복잡함보다 티라노사우르스 택함: 적어도 티라노사우르스는 눈에 보이니까"라는 문장을 일주일에 한 번은 떠올릴 만큼 인상 깊음
- 인용문: "넘어지면서도 Leyster는 삽을 놓지 않고 있었다. 당황 속에서 그 사실을 잊은 것. 그래서 필사적으로 삽을 들고 새끼 티라노의 다리에 휘둘렀다...". 티라노사우르스와의 극한 생존 싸움을 생생하게 묘사한 장면임. 결국 동료 Tamara가 용맹하게 창으로 티라노 얼굴 한가운데를 찔러서 위기를 극복한 순간. 전투와 긴장감, 그리고 침묵의 장면이 인상적임
- grug는 분명 ‘보이지 않는’ 티라노사우르스와 싸워본 적 없음. 나는 지금도 보이지 않는 티라노사우르스와 1:1 대결 중임, 진짜 고생임
- 이 아티클에서 감탄할 점은, 작성자가 ‘더 복잡한 것’을 할 수 있지만 경험적으로 그 길을 선택하지 않는다는 점임. 물론 추상화나 복잡함이 필요한 때/장소가 있지만, grug 철학은 그런 것 자체에 본질적 가치가 없다는 것을 말함. 이 부분이 정말 일리 있다는 생각임. AI도 일관되고 데이터에 기반한 코드에서 더 효과적임을 느낌
- 복잡함과 추상화를 사용할 때는, 그로 인해 코드가 전보다 이해하기 쉬워질 때임. ‘이해를 위해 특별 강좌가 추가로 필요하지 않을 때’라는 전제를 꼭 기억함. (상황에 따라 다름)
- "모든 것은 가능한 한 단순하게 만들어야 한다, 그러나 너무 단순하게는 말고"
- 이 글이 2022년 글이라니 믿기 힘듦. 이미 10년 전에 읽고 ‘고전’으로 알고 있었던 느낌임
- 이 에세이가 소프트웨어를 만드는 데 있어 내가 제일 좋아하는 글임. 스타일도 매력적이고(누군가는 거부감 느낄 수도 있겠지만), 본질은 늘 유효함
- "슬프지만 사실임: '예스' 배우고, 실패하면 다른 grug 탓하는 법 배우기, 최고의 커리어 전략"이라는 코드조각이 현실임. 나도 처음에는 회사에서 단순히 기술팀의 소통 문제가 원인이라고 착각했지만, (grug처럼) 실제로 그렇다는 걸 시간 지나며 배움
- 지금까지 본 visitor pattern 설명 중 이 아티클 내용이 최고임
- 나는 전형적인 OO 코드베이스에서 일하지 않아서 visitor pattern이 뭔지 제대로 몰랐는데, "Crafting Interpreters"라는 인터프리터/VM 만드는 책을 추천하고 싶음. 그 책에서는 visitor pattern을 실제로 어떤 식으로 쓰는지 나옴. 직접 읽어보며 복잡성의 이유를 이해하려 애썼지만, 결국 tagged union으로 대체했었음. 아마 내가 OO에 약한 건지도 모르지만, grug 아티클의 요지도 이와 같음. 굳이 복잡함과 간접성을 자처할 필요가 없을 때는 더 직관적인 방법이 있다는 점
- 나는 네이밍에 민감한 편인데, visitor pattern이라는 이름은 너무 모호해서 불만임. 실제로 Visitor라는 이름으로 만든 적 없음. 예를 들어 문법 트리(AST) 실습이라면 Visitor 대신 AstWalker, AstItem::dispatch(AstWalker), AstWalker::process(AstItem)처럼 구체적 네이밍이 훨씬 의미 있음. visitor란 “방문한다”는 게 너무 추상적이고 무의미함. 상황에 따라 다른 게 맞고, 그냥 주석으로 ‘visitor pattern’이라고 명시하면 인식에 문제없음. 과거 두 개의 오브젝트 트리를 맞춰서 데이터를 비교/임포트할 일이 있었을 때, AbstractImporter라는 이름을 썼던 경험이 있음. 더 구체적인 이름, 과정, 역할이 명확했음. 전형적 visitor pattern과는 다름
- 실제로 찾아보니 “Bad”라는 평이 있었음. ㅋㅋ
- 관련 글 공유함. 다른 사람의 의견이나 추가 글 있음?<br/><i>The Grug Brained Developer (2022)</i> - https://news.ycombinator.com/item?id=38076886 - 2023년 10월 (192개 댓글)<br/><i>The Grug Brained Developer</i> - https://news.ycombinator.com/item?id=31840331 - 2022년 6월 (374개 댓글)