5P by GN⁺ | ★ favorite | 댓글 1개
  • 오래 프로그래밍한 “작은 뇌” 개발자의 경험담을 통해, 소프트웨어 개발의 가장 큰 적은 복잡성이며 이를 줄이는 태도가 실무의 중심이어야 함을 강조함
  • 복잡성을 막는 기본 무기는 “no”라고 말하기이고, 타협이 필요할 때는 80/20 해법·프로토타입·작은 리팩터링·늦은 추상화로 현실적인 해법을 찾음
  • 테스트는 코드가 어느 정도 굳은 뒤 통합 테스트를 중심에 두고, 단위 테스트와 소수의 엔드투엔드 테스트를 보조로 쓰며, 버그는 회귀 테스트로 먼저 재현한 뒤 고침
  • 도구, 타입 시스템, 로깅, 디버거, 단순한 API는 개발자의 기억과 추론 부담을 줄여주지만, 과도한 제네릭·콜백·마이크로서비스·프런트엔드 프레임워크는 복잡성을 키울 수 있음
  • 기존 코드와 조직 프로세스에는 겸손하게 접근하고, 이해하지 못한 코드를 함부로 제거하지 않으며, “너무 복잡하다”고 말할 수 있는 문화가 필요함

복잡성은 개발자의 영원한 적

  • 소프트웨어 개발에서 가장 위험한 적은 복잡성
    • 복잡성은 코드베이스에 서서히 들어와, 한 곳을 바꾸면 관련 없어 보이는 곳까지 깨지게 만듦
    • 그럭은 이를 보이지 않는 “악마”에 비유하며, 개발자가 직접 보거나 쉽게 때려잡을 수 없는 존재로 다룸
  • 복잡성은 선의의 개발자나 프로젝트 매니저를 통해서도 들어올 수 있음
    • 기능, 추상화, 프로세스가 늘어날수록 코드는 이해하기 어려워짐
    • 그럭 자신도 때로는 복잡성을 들여온 장본인이었다고 인정함

“no”와 “ok”의 사용법

  • 복잡성을 막는 가장 강한 무기는 “no” 임
    • 필요 없는 기능을 만들지 않음
    • 필요 없는 추상화를 만들지 않음
  • 다만 “no”는 좋은 엔지니어링 조언이어도, 경력상 항상 유리하지는 않음
    • “yes”는 더 많은 보상이나 관리직으로 이어질 수 있음
    • 그래도 개발자로서 자신에게 진실하려면 “no”가 중요함
  • 타협이 필요할 때는 “ok” 라고 답한 뒤 80/20 해법을 찾음
    • 80/20 solution은 20%의 코드로 80%의 요구를 만족시키는 접근임
    • 모든 장식적 기능을 갖추지는 못해도 대부분의 가치를 전달하고 복잡성을 억제할 수 있음
    • 때로는 프로젝트 매니저에게 세부 구현 방식을 모두 설명하지 않고 단순한 방식으로 해결하는 편이 낫다고 봄

코드 구조화와 리팩터링

  • 프로젝트 초기에 애플리케이션을 너무 빨리 나누지 말아야 함
    • 초반에는 시스템의 모양이 아직 불분명하고, 무엇을 만드는지도 완전히 이해되지 않은 상태임
    • 시간이 지나면 좋은 절단점(cut point) 이 드러남
  • 좋은 절단점은 나머지 시스템과의 인터페이스가 좁음
    • 적은 수의 함수나 추상화로 내부 복잡성을 감춤
    • 그럭은 이를 복잡성 악마를 수정 안에 가두는 것에 비유함
  • 큰 뇌 개발자가 프로젝트 초기에 많은 추상화를 만들려 할 때는 피해를 줄이는 방식이 필요함
    • UML 다이어그램처럼 코드에 직접 피해를 주지 않는 산출물로 돌릴 수 있음
    • “내일 동작하는 데모”를 요구하면 실제로 작동하는 코드와 현실을 빨리 보게 됨
    • 이 데모 접근을 프로토타입이라고 부를 수 있음
  • 리팩터링은 프로젝트 후반에 코드가 굳은 뒤 유용함
    • 큰 리팩터링일수록 실패할 가능성이 커짐
    • 시스템이 계속 동작하는 상태를 유지하며 작은 단계로 진행하는 편을 선호함
    • 엔드투엔드 테스트는 리팩터링 중 생명줄이 되지만, 깨졌을 때 원인을 이해하기 어려울 수 있음
  • 과도한 추상화는 리팩터링 실패와 시스템 실패로 이어질 수 있음
    • J2EE는 과도한 추상화의 예로 등장함
    • OSGi 도입은 복잡성을 줄이려던 시도였지만 오히려 더 강한 복잡성을 만들었고, 여러 인년의 재작업이 필요했다고 함

테스트 전략

  • 테스트는 많은 시간을 구해주지만, “무조건 테스트 먼저”에는 회의적임
    • 아직 도메인을 이해하지 못한 상태에서는 무엇을 테스트할지 알기 어려움
    • 그럭은 프로토타입 이후 코드가 굳기 시작할 때 대부분의 테스트를 쓰는 편을 선호함
  • 테스트를 나중에 쓰더라도 규율이 필요함
    • “내 기계에서는 동작한다”는 이유로 테스트를 건너뛰면 안 됨
    • 다른 기계나 미래의 같은 기계에서 동작한다는 보장이 없음
  • 단위 테스트, 통합 테스트, 엔드투엔드 테스트의 역할을 구분함
    • unit tests는 프로젝트 초반에 도움이 되지만 구현 변경에 따라 잘 깨지고 리팩터링을 어렵게 만들 수 있음
    • end to end 테스트는 전체 시스템 동작을 보여주지만, 깨졌을 때 원인을 파악하기 어렵고 자주 깨지면 무시될 수 있음
    • integration tests는 시스템 정확성을 확인할 만큼 높고, 디버거로 원인을 볼 만큼 낮아 “스위트 스팟”에 가까움
  • 테스트 구성의 이상적인 형태는 다음과 같음
    • 초반에는 일부 단위 테스트
    • 절단점이 생기고 시스템이 안정될수록 강한 통합 테스트
    • 가장 흔한 UI 기능과 중요한 엣지 케이스만 담은 작고 잘 관리되는 엔드투엔드 테스트
  • 모킹은 드물게만 사용하고, 필요할 때도 시스템 경계 같은 큰 단위에서만 쓰는 편을 선호함
  • 버그를 고칠 때는 예외적으로 먼저 회귀 테스트로 버그를 재현한 뒤 수정함

프로세스, 애자일, 기존 코드에 대한 태도

  • 애자일은 최악은 아니지만 좋다고만 보기도 어려움
    • 개발 조직화 방식으로 어느 정도 괜찮을 수 있음
    • 실패할 때마다 “애자일을 제대로 하지 않았다”고 말하는 애자일 전문가를 경계함
  • 성공에는 프로토타입, 도구, 좋은 개발자 채용이 더 중요함
    • 애자일 프로세스는 도움이 될 수 있지만, 너무 진지하게 받아들이면 해가 될 수 있음
    • no silver club, 즉 모든 소프트웨어 문제를 해결하는 은탄환은 없음
  • Chesterton’s Fence는 기존 코드 제거에 대한 경고로 쓰임
    • 어떤 울타리의 용도를 모르면 먼저 이해해야 하며, 바로 치우면 안 된다는 내용임
    • 못생긴 코드라도 오늘 동작하고 있다면 존중해야 함
    • 특히 큰 시스템일수록 먼저 이해한 뒤 개선해야 함
  • 테스트는 왜 어떤 “울타리”가 존재하는지 알려주는 힌트가 될 수 있음

도구와 타입 시스템

  • 도구는 개발자가 직접 기억하고 추론해야 할 부담을 줄여줌
    • 새 환경에 들어가면 주변 도구를 배우는 데 시간을 쓰는 편이 생산성을 높일 수 있음
    • 문서가 없으면 다른 개발자에게 물어보며 파악해야 할 수 있음
  • IDE의 코드 완성은 API를 모두 기억하지 않아도 되게 해줌
    • Java 프로그래밍은 코드 완성 없이는 거의 불가능하다고 표현함
  • 좋은 디버거는 매우 중요함
    • 조건부 중단점, 표현식 평가, 스택 탐색 같은 기능을 깊이 배워야 함
    • 새 개발자에게 디버거 학습은 대학 수업보다 컴퓨터에 대해 더 많이 가르칠 수 있다고 봄
  • 타입 시스템의 가장 큰 가치는 “점(.)을 눌렀을 때 할 수 있는 일이 뜨는 것”임
    • 타입 정확성도 좋지만, 그럭에게는 도구 지원과 코드 완성이 더 큰 가치임
    • 과도한 타입 추상화나 제네릭은 실제 업무 코드를 어렵게 만들 수 있음
    • 제네릭은 주로 컨테이너 클래스에 제한해 쓰는 편을 선호함

표현식, DRY, 관심사 분리

  • 짧은 코드보다 디버깅하기 쉬운 코드를 선호함
    • 복잡한 조건식을 한 줄에 쓰기보다 중간 변수로 나누면 각 표현식의 결과와 의미를 보기 쉬움
    • 줄 수가 늘어나더라도 조건문의 이해와 디버깅이 쉬워짐
  • DRY는 좋은 조언이지만 균형이 필요함
    • 단순하고 명확한 중복 코드는 복잡한 콜백, 클로저, 객체 모델보다 나을 수 있음
    • 중복을 제거하려다 복잡성이 커지면 손해가 될 수 있음
  • Separation of Concern에 대해서는 더 비판적임
    • 웹 개발의 전형적 예는 CSS, HTML, JavaScript 분리임
    • 그럭은 대안으로 locality of behavior를 선호함
    • 동작하는 대상 가까이에 관련 코드를 두면, 그 대상을 볼 때 무엇을 하는지 바로 알 수 있음
  • 클로저는 컬렉션 작업 추상화 같은 올바른 용도에는 유용함
    • 하지만 소금, 타입 시스템, 제네릭처럼 조금만 써도 충분하며 과하면 해로움
    • JavaScript의 “callback hell”은 클로저 남용의 예로 나옴

로깅, 동시성, 최적화

  • 로깅은 특히 클라우드 배포 환경에서 매우 중요함
    • 주요 논리 분기마다 로그를 남김
    • 요청이 여러 머신에 걸치면 모든 로그에 request ID를 넣어 묶을 수 있게 함
    • 가능하면 로그 레벨을 동적으로 조절함
    • 가능하면 사용자별 로그 레벨도 조절함
  • Java 로깅 라이브러리는 복잡할 수 있지만, 로깅 인프라에 제대로 투자하면 나중에 큰 보상이 돌아옴
  • 동시성은 두려워해야 할 대상임
    • 가능한 한 상태 없는 웹 요청 핸들러와 독립적인 원격 작업 큐처럼 단순한 모델을 사용함
    • optimistic concurrency는 웹 영역에서 잘 동작한다고 봄
    • thread local variable은 주로 프레임워크 코드를 쓸 때 가끔 사용함
    • Java의 ConcurrentHashMap 같은 동시성 자료구조도 여전히 조심해서 써야 함
  • 최적화는 실제 성능 프로파일이 있을 때 시작해야 함
    • “premature optimization is the root of all evil”에 동의함
    • 실제 병목은 예상과 다를 수 있음
    • CPU만 보지 말아야 하며, 네트워크 호출은 수백만 CPU 사이클에 해당할 수 있으므로 가능한 줄여야 함

API, 파싱, 방문자 패턴

  • 좋은 API는 개발자가 많이 생각하지 않게 만듦
    • 나쁜 API는 구현 내부나 도메인 모델 중심으로 설계되거나, 지나치게 추상적일 때 생김
    • 단순한 경우에는 단순한 API를 제공하고, 복잡한 경우에는 더 복잡한 API를 제공하는 계층화를 선호함
  • 객체지향 API는 기능을 대상 객체에 붙이는 편이 낫다고 봄
    • Java에서 리스트를 필터링하려고 Stream으로 바꾸고 다시 List로 수집해야 하는 흐름을 나쁜 예로 듦
    • 흔한 작업인 filter()는 list에 있고 list를 반환해야 한다고 봄
  • 파서는 recursive descent가 재미있고 아름다운 방식임
    • 파서 생성기는 이해하기 어렵고 디버깅하기 어렵다고 비판함
    • 실제 프로덕션 파서는 거의 항상 재귀 하강 방식이라고 주장함
  • Bob Nystrom의 Crafting Interpreters를 추천함
    • 온라인에서 무료로 볼 수 있지만 책 구매도 추천함
    • 다만 visitor pattern은 함정이라고 표현함
  • Visitor pattern에 대한 평가는 짧게 “bad”임

프런트엔드, 마이크로서비스, 유행

  • 마이크로서비스는 “시스템을 제대로 나누는 가장 어려운 문제에 네트워크 호출까지 추가한다”는 점에서 위험함
  • 프런트엔드 개발에서는 복잡성이 특히 강함
    • 단순한 폼 저장이나 브로슈어 사이트에도 SPA 라이브러리, GraphQL JSON API, HTTP 백엔드가 사용되는 상황을 비판함
    • 프런트엔드와 백엔드를 나누면 복잡성의 거처가 두 개 생긴다고 표현함
  • 그럭은 복잡성을 낮추기 위해 htmxhyperscript를 만들었다고 밝힘
    • 단순한 HTML을 유지하고 JavaScript를 많이 쓰지 않는 방향을 선호함
    • React는 취업과 특정 종류의 애플리케이션에는 더 좋을 수 있다고 인정함
  • 개발에는 유행이 많고, 특히 프런트엔드에서 그렇다고 봄
    • 백엔드는 더 지루해졌고 많은 나쁜 아이디어가 이미 시도되었다고 표현함
    • 혁명적 새 접근에는 소금을 한 알 치듯 조심스럽게 접근하라고 함
    • 많은 아이디어는 이미 한 번쯤 시도되었고, 재활용된 나쁜 아이디어가 시간을 낭비할 수 있음

두려움과 가면 증후군

  • 시니어 개발자가 공개적으로 “이건 너무 복잡하다”고 말하는 것은 좋음
    • 개발자들은 Fear Of Looking Dumb(FOLD) 때문에 이해하지 못한다고 말하기 어려워함
    • 시니어가 먼저 인정하면 주니어도 복잡함과 이해 부족을 말할 수 있음
  • FOLD는 특히 젊은 개발자에게 복잡성이 힘을 얻는 주요 원천임
    • 유머 감각과 과거 실패 사례를 기억하는 태도가 도움이 된다고 봄
  • 가면 증후군도 개발에서 흔함
    • 그럭은 자신이 모든 것을 지배하는 느낌과 무엇을 하는지 모르는 느낌 사이에 있다고 표현함
    • htmx와 _hyperscript의 오픈소스 성과가 있어도 여전히 실수와 실패를 두려워한다고 함
  • 모두가 가면 증후군을 느낀다면 아무도 진짜 가짜는 아니라는 식으로 받아들이는 편이 낫다고 봄

추천 읽을거리와 결론

댓글과 토론

Hacker News 의견들
  • Carson 교수님이 댓글을 보고 계시다면, 그동안 해주신 모든 기여에 진심으로 감사드리고 싶음
    대학에서 왜 HTMX를 배우는지, 왜 그렇게 신나 하셨는지 당시엔 몰랐지만 몇 년이 지나니 이제 이해됨. HTML over the wire가 전부였음
    Staff Ruby on Rails Engineer로 일하면서 Hotwire에서 교수님의 작업을 봤고, Hacker News에 가끔 등장하거나 GitHub에서 Hotwire 개발자들과 대화하는 모습을 보는 것도 멋졌음. 프로그래밍 커뮤니티의 빛 같은 존재로 크게 존경받고 감사받고 있음

  • “좋은 디버거는 반짝이는 돌만큼 값지고, 사실 그보다 더 값짐”이라는 말에 공감함
    작은 스타트업과 ‘엘리트’ 빅테크 팀을 모두 겪었지만, 팀에서 디버거를 쓰는 사람이 거의 항상 나뿐이었음. 현실 세계, 적어도 웹 기술 쪽에서는 대부분 print 문으로 디버깅하는 듯함
    테스트 실행 중 흥미로운 코드 줄에서 멈추고 거기까지 온 호출 스택을 보는 것이, 머릿속으로 코드를 순방향 실행해보는 것보다 훨씬 쉬움. 젊은 grug라면 이 기술은 작은 초능력이니, 가능하다면 자기 코드베이스에서 동작하게 만드는 데 시간을 들일 만함

    • 몇 년 전 이 주제로 좋은 논의가 있었고, 최상단 댓글은 Brian Kernighan과 Rob Pike의 인용을 공유했음. 둘 다 젊은 grug라고 부르긴 어려운 사람들임
      그들은 스택 추적이나 변수 한두 개 값을 보는 정도를 넘어서는 디버거 사용을 별로 하지 않는다고 했고, 복잡한 자료구조와 제어 흐름의 세부에 빠지기 쉬우며, 핵심 지점에 출력문과 자기검증 코드를 넣는 편이 더 생산적이라고 봤음.
      나도 대체로 동의함. 내가 하는 거의 모든 일에서는 가설-로그-실행 반복이 훨씬 빠르게 답에 도달하게 해줌. 머릿속으로 코드를 실행하려는 게 아니라, 이미 코드가 어떻게 도는지에 대한 작업 모델이 있고, 그 모델이 맞다면 어떤 출력이 나와야 하는지 알며, 잘못된 출력으로 실제 상황을 빨리 직감할 수 있음
      [0] The unreasonable effectiveness of print debugging (349 points, 354 comments) April 2021 https://news.ycombinator.com/item?id=26925570
    • 진짜 디버거를 쓰고 싶지만, 대기업에서만 일해온 입장에서는 선택지가 아니었음
      마이크로서비스 메시 구조에서는 로컬에서 뭔가를 제대로 실행하기도 어렵고, 테스트 환경도 단계 실행 디버거를 붙일 수 있게 설정돼 있지 않은 경우가 많음. 결국 print 디버깅뿐이고, 로깅 시스템 자체가 문제이거나 로그를 flush하기 전에 프로그램이 죽으면 그것조차 못 씀
    • 코드베이스에 들어가면 가장 먼저 하는 일 중 하나가, 당장 문제를 고치는 중이 아니어도 프로그램을 디버거로 빠르게 실행할 수 있는 IDE/편집기 환경을 마련하는 것임
      얼마 안 가 꼭 필요해짐. 그런데 협업 중에도 디버거 사용법 자체를 모르는 사람들을 보고 당황했음. “거기에 중단점 걸고”, “이제 함수 안으로 들어가서 변수 상태를 봐요”, “그건 넘어가요”라고 해도 매번 멍한 표정이 돌아옴
    • 나도 디버거를 거의 쓰지 않는 쪽에 가까움
      엄밀히 말하면 print 문을 넣었다 지우는 게 아니라, 계속 남겨둘 로깅 코드를 넣음. 주요 인터페이스라면 보통 INFO 수준에서 함수 진입/종료와 매개변수 값을 기록하는 것부터 시작하고, 시스템을 쓰면서 더 살펴봐야 할 곳이 보이면 더 자세한 로그를 추가함
      로그 형식에도 꽤 공을 들임. 분산 시스템에서 일할 때 각 로그 줄의 접두사를 정확히 맞추는 게 매우 유용했음. 노드 ID, pid, 타임스탬프를 모두 고정 폭으로 넣어 클러스터 전체 로그를 내려받아 정렬하면, 여러 노드의 동작이 한 파일에 interleave된 형태로 보였음
    • 내 코드에 디버거를 쓰는 건 쉽고 좋아함
      하지만 디버거가 내가 쓰는 라이브러리나 프레임워크 깊숙이 들어가는 순간 길을 잃고 싫어짐. 그 프레임워크나 라이브러리는 수만 인시의 작업이 쌓인 물건이라, 내 수준을 훌쩍 넘어선 느낌이 듦
  • 여기 보석 같은 문장이 많지만, 마이크로서비스에 대한 이 문장이 제일 좋음: “grug는 왜 똑똑한 사람들이 시스템을 올바르게 나누는 가장 어려운 문제를 가져와서 거기에 네트워크 호출까지 넣는지 궁금해함”

    • 이걸 1~2명짜리 작은 개발팀에 계속 설명하려고 함
      폼이 다섯 개쯤 있는 사소한 웹앱을 기꺼이 “마이크로서비스”로 쪼개고, 같은 데이터베이스를 공유하게 만들고, API 관리 계층과 대량(메가바이트) 배치 작업용 큐, 이메일 알림 시스템, 직접 만든 관측성 플랫폼까지 붙인 다음, “그게 더 쉽다”며 단순한 웹 폼을 SPA로 바꿈
      이제 “아키텍처”와 “패턴”이 쓸모없는 개발자들을 위한 일자리 창출 프로그램이라는 걸 이해함. 이게 아니면 길거리에서 “샌드위치 주면 JavaScript 작성함” 팻말을 들고 있을 듯함
    • 어떤 사람들은 시스템을 더 작은 부분으로 나누는 다른 방법을 모르는 것 같음
      그런 사람들에게는 API 호출로 노출되지 않으면 이해하거나 재사용할 수 없는 불투명한 코드 덩어리일 뿐임
    • 네트워크 경계는 대부분의 언어 모듈 시스템에는 없는 분해 도구를 줌
      여러 패키지가 내부적으로 협력하되 나머지 코드베이스에는 작은 API만 노출할 수 있음. 네트워크라는 사실이 모듈들로 하여금 콜백이나 동작이 아니라 데이터만 주고받게 만들고, 인터페이스를 하위 호환 방식으로 발전시키도록 압박함. 그래서 서로 다른 모듈을 다른 시점에 “핫 리로드”해도 폭발하지 않게 만들 수 있음
      실제 네트워크 홉 없이도 대부분 얻을 수 있을 것 같지만, 진지한 시도는 아직 못 봤음
    • 마이크로서비스는 클라우드 회사들이 사람들에게 이런 애플리케이션을 만들게 하려고 밀어붙인 패턴이라는 음모론을 갖고 있음
      K8S 같은 오케스트레이터 없이는 실행할 수 없고 설치·운영이 어려워 관리형 클라우드 판매에 유리함. 네트워크 대역폭과 CPU를 더 쓰게 하고, 둘 다 과금 대상임
      애플리케이션 내부에서 복잡하거나 큰 상태를 공유·유지하기 어렵게 만들어, 관리형 데이터베이스나 이벤트 큐 서비스를 대체재로 쓰게 함. 모놀리스라면 큐나 채널을 쓰면 되지만, 마이크로서비스에서는 Kafka 같은 거대한 짐승을 원하게 됨
      로컬 실행도 어려워져 클라우드 개발 환경이 필요하고, 개발·테스트 환경을 여러 개 둬야 할 수도 있음. 특정 클라우드의 네트워킹 방식 같은 특성에 의존하게 되어 클라우드 종속도 커짐
      클라우드가 IT 비용을 줄여준다고 팔리던 시절을 기억하는지 모르겠음. 웃기는 일이었고, 2000년대부터 헛소리라는 걸 알았으며 결국 모든 비용을 더 키울 거라고 봤음
    • 이건 주로 팀 사이에서 시스템을 나누기 위한 것이라고 봄
      그 방식이 관리하기 더 쉬워서이며, 기술적 결정이라기보다 개발 조직 방식에 가까움. 대안이 모노레포라면, 개인적으로는 그쪽이 더 나쁘다고 봄
  • “복잡성과 티라노사우루스와의 1:1 중 고르라면 grug는 티라노사우루스를 고름. 적어도 grug는 티라노사우루스를 볼 수 있음”
    이 문장을 적어도 일주일에 한 번은 떠올림

    • grug는 보이지 않는 티라노사우루스를 상대해본 적이 없는 게 분명함
      이 grug는 투명 티라노사우루스와 1:1 중이고, 저주받았음
    • 그걸 읽고 제3의 눈이 열린 느낌이었음. 진짜 영감을 줬음
  • 이 글의 가치 중 하나는, 더 정교하고 복잡한 일을 할 수 있는 사람이 경험상 일부러 피하려 한다는 데 있음
    정교함과 더 높은 추상화가 필요한 때와 장소는 분명 있음. 하지만 grug 철학은 그런 일을 하는 것 자체에 본질적 가치가 있는 건 아니라고 말하고, 꽤 타당한 조언으로 보임
    AI 보조도 일관되고 평범하며 데이터 중심적인 코드에서 더 효과적이라는 점을 봤음. 상황에 따라 다를 수 있음

    • 이건 벨 커브 밈에 딱 맞음
      초보 개발자는 단순한 코드를 쓰고, 중급 개발자는 복잡한 코드를 쓰며, 전문가 개발자는 다시 단순한 코드를 씀
    • 정교함과 추상화를 쓸 때와 장소는, 그게 먼저 특별 강의를 들어야 이해되는 방식이 아니라 실제로 코드 이해를 쉽게 만들 때임
      어떤 강의를 당연히 들어봤다고 전제할 수 있는지는 상황마다 다름
    • “모든 것은 가능한 한 단순하게 만들어야 하지만, 그보다 더 단순하게 만들면 안 된다”
  • 현대 소프트웨어 개발의 아이러니 중 하나는, “결국 시간을 아껴줄 것”이라고 생각해 복잡성을 도입한다는 점임
    때로는 맞고 실제로 시간을 아끼지만, 항상 그런 것도 아니고 자주 그런 것도 아닐 수 있음
    DRY는 때때로 성급한 추상화로 이어짐. “이 패턴이 다른 곳에서도 쓰일 테니 공통 부분을 뽑아내야지”라고 생각하는 순간 복잡성 악마가 들어옴
    가능한 많은 버그를 컴파일 시점에 잡고 싶어 하지만, 그러려면 컴파일러가 우리가 실제로 하려는 일을 더 많이 알아야 하고, 이해력을 갉아먹는 복잡한 타입을 만들게 됨
    보일러플레이트를 피하려고 복잡한 매크로나 DSL을 만들지만, 새는 추상화의 법칙 때문에 실제 구현을 알아야 하는 순간 머리가 터짐
    이 모든 예시는 때로 좋은 생각이라는 점이 어렵다. 언제 복잡성을 도입해야 단순화가 되는지 판단할 수 있는 것이 좋은 소프트웨어 엔지니어의 표시라고 봄

    • 경험적으로는 DRY보다 SPoT(single point of truth, 단일 진실 공급원)가 더 나은 격언이었음
      비즈니스 로직은 이상적으로 한 곳에 정의돼야 하지만, 다른 것들은 필요하면 중복해도 되고 그 자체가 나쁜 건 아님
      DRY를 조절하기 위해 “3의 법칙”도 강조함. 복사/붙여넣기 코드가 세 번까지는 괜찮고, 그 이후에 추상화를 생각해볼 만함. 물론 모든 경우에 맞는 경험칙은 없고, 그 감각은 가르치기 어려움
    • 평균적으로 대부분의 코드는 아직 DRY가 충분하지 않다고 믿지만, 개인 프로젝트에서는 최근 “애플리케이션은 없고 화면만 있다”는 원칙을 세웠음. 흥미롭게도 이 블로그 작성자가 만든 것으로 보이는 HTMX를 쓰고 있음
      보통 Flask 같은 Sinatra류 프레임워크로 웹앱을 만들며, URL 패턴에 응답하는 함수를 작성함. 하나의 “화면”은 함께 동작하는 함수 하나 이상과 관련 HTML 템플릿으로 이뤄질 수 있음
      애플리케이션이 데이터베이스 연결, 파일 위치, HTML 헤더·푸터 같은 것을 설정한다면, 그 외에는 화면들 사이 결합이 거의 없음. 새 화면이 필요하면 기존 화면을 복사해 고치거나, LLM에게 화면이나 엔드포인트를 만들게 할 수 있고, 결과가 나쁘면 다시 만들면 됨
      예전 직장에서 ML 학습 세트를 만드는 Themis라는 프레임워크를 만들고 있었는데, 마이크로서비스, React, Docker 등을 사용했음. 실제 요구사항은 새 작업을 계속 추가하고, 작업마다 단순하지만 매우 최적화된 화면을 빨리 만드는 것이었음. 2만 번 판단해야 하는데 한 번씩 클릭하는 것도 힘든데, 네 번씩 클릭하면 8만 번이 되어 포기하게 됨
      당시 구조에서는 JAXB 애플리케이션의 API 엔드포인트와 모놀리식 React 앱의 컴포넌트를 작성하고, TypeScript·Docker·javac가 20분 동안 도는 걸 기다려야 했음. 운 좋으면 부팅되고 아니면 처음부터 다시였음
      Themis 비판을 쓰고 새 작업의 빠른 개발을 목표로 Nemesis를 설계했지만, 예전 직장에서는 선택되지 않은 길이었음. 그래도 Nemesis와 나는 그 이후로 수백만 개 작업 인스턴스를 처리해오고 있음
    • 노트북에 음양 스티커가 붙어 있는데, 음에는 DRY, 양에는 YAGNI라고 적어뒀음
    • DRY는 그렇게 위험하지 않음
      한 곳에서만 쓰는 헬퍼를 뽑아내라는 뜻이 아님. 로직이 한 함수·클래스·파일 등에 잔뜩 들어 있어도 복사된 게 아니라면 여전히 DRY임
      성급한 추상화는 실제로 존재함. CS 수업이 대체로 그렇게 하라고 가르치는 것도 도움이 안 됨. 신입에게 MySQL 데이터베이스를 주면 가장 먼저 MySQL을 추상화하려 할 수도 있음
    • grug가 일을 끝내는 기계를 만드는 데 100시간을 쓰기도 하지만, 수작업은 1시간이면 끝날 때가 있음. 또는 기계 만드는 데 1시간을 쓰고 그 멍청한 기계를 고치느라 100시간을 쓰기도 함
      복잡하든 단순하든 결과가 가치를 더하지 않으면 중요하지 않음. 먼저 덜어내는 것보다 더 많은 가치를 더하는 데 집중하고, 복잡성은 그다음에 걱정하면 됨
  • 좋아하는 LLM 활용법 중 하나는 이 에세이를 넣고, grug-brained developer 페르소나로 지금 내가 다루는 이슈에 대해 코멘트해달라고 하는 것임. 스트레스 해소에 좋음

    • LLM에 아직 익숙하지 않지만 멋져 보임. 이 에세이를 “먹인다”는 건 어떻게 하는 건지 궁금함
      프롬프트를 “이 에세이의 Grug Brained Developer처럼 행동해줘” 같은 식으로 시작하면 되는 건지 궁금함
  • “복잡성 매우 나쁨”은 정말 맞는 말임
    소프트웨어 엔지니어링을 해온 모든 해 동안, 이 생각은 모든 상황에서 꾸준히 참으로 드러난 몇 안 되는 원칙 중 하나였음. 어떤 문제는 본질적으로 복잡하지만, 그럴 때조차 시간을 들여 가장 단순한 해결책에 도달하는 편이 훨씬 낫다
    내 가장 효과적인 작업들은 이전 접근을 의심하고 과감하게 단순화한 뒤에 나왔음. 잠재적 유연성을 조금 잃을 수 있지만, 대부분은 필요하다고 생각한 만큼의 유연성이 실제로 필요하지 않았음
    예를 들어, 꽤 괜찮고 에이전트적으로 동작하는 LLM이 생긴 뒤로는 깨지기 쉽고 디버깅이 어려운 과도하게 복잡한 TypeScript 타입을 피하고, 명세 같은 코드를 작성한 뒤 LLM에게 그 기반의 코드를 정적으로 생성하게 함
    프로젝트의 ESLint 의존성은 버전 업데이트 후 계속 깨졌고, 많은 규칙이 오탐을 피할 만큼 정교하지 않았으며 TypeScript와 VSCode에서 제대로 유지하는 것도 복잡했음. Biome.js로 바꾸니 더 단순하고 충분히 효과적이었지만, 최근엔 버그가 생기고 있음. 그래도 린팅은 있으면 좋은 것이지 과도한 시간을 들여 돌봐야 할 대상은 아니라는 걸 깨달아 빌드 도구 체인에서 제거했고, VSCode에서도 항상 켜둘 필요가 없어졌음. 가끔 Biome을 실행해 코드 스타일과 포맷만 확인하면 됨
    프로젝트용 사용자 정의 데이터 마이그레이션 도구를 만들면서, 순방향 마이그레이션은 필요하지만 역방향 마이그레이션은 구현할 시간과 복잡성을 들일 가치가 없다고 봤음. 데이터가 있는 데이터베이스를 롤백해야 하면 백업을 복원하면 되고, 데이터가 없거나 프로덕션 DB가 아니면 버전이 붙은 초기화 스크립트로 깨끗한 상태에서 시작하면 됨

    • 앞의 두 예시는 다른 도구를 써서 복잡성을 숨긴 것 아닌가 싶음
      세 번째가 어떻게 더 단순한지도 잘 모르겠음. 수학적인 머리로는 전단사 공간을 쉽게 만들 수 있음. 다른 수단으로 역방향 마이그레이션을 흉내 내는 쪽이 더 어려울 수도 있음. 물론 세부에 따라 달라서 일반 규칙은 아님
    • 많은 사람이 복잡성을 말하지만, 복잡성이 무슨 뜻인지는 거의 말하지 않음
      똑똑한 Rich는 complect가 서로 묶는 것이라고 했고, 거기엔 동의함. Rich는 복잡성이 나쁘다고 했지만, 그건 동의하지 않음. 무언가를 묶는 건 필요함. 서로 연결되지 않으면 문제를 풀 수 없음
  • 이 글이 2022년 글이라는 게 믿기지 않음
    10년 전에 읽었고 그때 이미 고전이었다고 자신 있게 말했을 것 같음

    • 나도 같음. 아마 더 이른 작업에서 영감을 받은 것 같음
  • 슬프지만 맞음: “yes”를 배운 다음 실패하면 다른 grug 탓으로 돌리는 법을 배우는 것이 이상적인 커리어 조언임
    처음 기업 세계에 들어갔을 때는 이게 사실이 아니고, 기술팀의 의사소통이 부족할 뿐이라고 생각했음. 내가 틀렸고, grug가 맞았음