2P by GN⁺ 1일전 | ★ favorite | 댓글 1개
  • 프로젝트는 바로 만들어 끝내는 흐름과 조사와 설계가 커지며 원래 문제를 놓치는 흐름으로 갈리기 쉬우며, 실제 진전에는 그냥 해보는 쪽이 더 앞설 때가 많음
  • Emacs용 fuzzy path 검색을 만들면서도 좋은 라이브러리의 부가 기능이 새 요구를 낳아 설계가 비대해졌고, 결국 필요하지 않은 앵커 기능 코드를 전부 버리며 YAGNI를 다시 확인하게 됨
  • 코드 diff에서는 줄 단위 비교가 함수나 타입 같은 상위 구조를 제대로 잡지 못하고, treesitter 기반 도구도 엔티티 매칭이 어긋나면 삭제와 추가를 길게 보여줘 읽기 어려워질 수 있음
  • 필요한 방향은 LLM 출력의 턴별 리뷰에 맞는 최소 범위 도구를 먼저 만드는 일이며, Rust용 엔티티 추출과 단순 매칭으로 시작해 상위 수준 변경 개요를 빠르게 보는 워크플로가 우선됨

과도한 고민과 범위 확장

  • 프로젝트는 바로 만들어 끝내는 흐름과, 선행 사례를 파고들다가 범위가 커져 정작 원래 문제를 해결하지 못하는 흐름으로 갈리기 쉬움
  • 주말에 만든 주방 선반은 커피를 마시며 설계를 정하고, 3D 프린트 행거를 몇 차례 수정한 뒤, 남은 자재와 페인트를 써서 주말 안에 완성됨
    • Ikea bin 행거용 CAD는 OnShape CAD로 공개돼 있음
    • 자재는 작업대에서 남은 것을 재사용했고, 모서리는 palm sander로 눈대중으로 다듬음
  • 이 선반은 정확히 주방에 맞는 물건을 만드는 일보다, 친구와 함께 목공을 즐기는 일이 주된 성공 기준이었고, 그 덕분에 세부 기준을 과하게 고민할 필요가 줄어듦
  • 반대로 구조적 diff 도구를 찾는 과정에서는 difftastic의 결과가 아쉬워 관련 도구와 워크플로를 4시간 동안 조사했지만, 결국 Emacs에서 쓸 더 나은 diff 워크플로라는 원래 기준으로 돌아가게 됨
  • 하드웨어 프로토타이핑 인터페이스, Clojure와 Rust를 섞은 언어, CAD용 언어 같은 오랜 관심사는 배경 조사와 작은 프로토타입에 수백 시간을 썼지만, 처음의 동기를 직접 해결하는 결과물로는 아직 이어지지 못함
  • 언어와 CAD 프로젝트에서는 Rust나 Clojure를 대체할지, 일부 문제만 다룰지, 학습용 놀이터면 충분할지, 상용 CAD를 바꿀지, 다른 사람에게도 유용해야 할지처럼 성공 기준이 흐릿함
  • 이런 질문을 검토하는 일은 가치가 있지만, 많은 것을 검토만 하기보다 실제로 많이 만들어보는 편이 더 낫다고 봄
  • 뒤늦게 보면 분명히 좋지 않은 결과물이 나오더라도, 그냥 해보는 쪽이 전체적으로는 더 앞서게 만듦

범위 확장의 보존 법칙

  • 무턱대고 만드는 시간에도 한계는 있고 균형이 필요하며, LLM agent로 코드를 많이 쓴 뒤 결국 전부 버린 경험이 다시 YAGNI를 떠올리게 만듦
  • Emacs에서 쓸 Finda 스타일의 전체 파일시스템 fuzzy path 검색을 만들고 싶었고, 예전에 같은 기능을 손코딩으로 만든 적이 있어 LLM을 감독하면 몇 시간 안에 끝낼 수 있다고 봄
  • 처음에는 계획용 대화에서 Nucleo를 추천받았고, 잘 설계되고 문서화돼 있어 smart caseUnicode normalization 기능을 얻기 위해 채택함
    • 예시로 쿼리 fooFoofoo를 모두 맞추지만, Foofoo를 맞추지 않음
    • cafecafé 처리도 같은 맥락으로 다뤄짐
  • 문제는 좋은 라이브러리 자체가 아니라, Nucleo가 앵커 기능도 지원한다는 점이었음
  • 파일 경로만 있는 코퍼스에서는 줄 시작 앵커가 쓸모없어 보여, 이를 path segment 기준 앵커로 해석하려고 함
    • 예시로 ^foo/root/foobar/는 맞추지만 /root/barfoo/는 맞추지 않게 하려 함
  • 이를 효율적으로 처리하려면 인덱스가 세그먼트 경계를 저장해야 하고, 각 세그먼트에 대해 쿼리를 빠르게 검사할 수 있어야 함
  • 여기에 ^foo/bar처럼 슬래시가 들어간 앵커 쿼리도 처리해야 했고, 세그먼트 단위 검사만으로는 /root/foo/bar/baz/ 같은 경로를 제대로 매칭하기 어려워짐
  • 이 설계를 두고 몇 시간을 더 보냈고, LLM과 아이디어를 주고받고, Nucleo 타입을 감싸는 코드를 만든 뒤, 코드가 지나치게 비대하고 마음에 들지 않아 결국 더 작은 래퍼를 직접 다시 작성함
  • 쉬고 난 뒤 Finda에서 앵커 기능이 필요했던 적이 떠오르지 않고, 경로 코퍼스에서는 쿼리 앞이나 뒤에 /를 붙여 대부분의 앵커 역할을 대신할 수 있다는 점을 깨닫게 됨
    • 파일명 끝에 대한 앵커만 예외로 남음
  • 결국 앵커 관련 코드는 전부 버렸고, LLM과 다른 사람들과의 논의 없이 처음부터 직접 썼을 때보다 여전히 이득이었는지는 확신하기 어려움
  • 프로그래밍 속도가 빨라질수록 그만큼 불필요한 기능, rabbit hole, 우회로도 함께 늘어나는 보존 법칙 같은 것이 있는 듯함

구조적 diffing

  • 코드에서 diff는 보통 파일 두 버전 사이의 줄 단위 변경 요약을 뜻하며, unified view에서는 추가와 삭제를 +, -로 표시함
  • 같은 diff는 좌우 비교 형태로도 렌더링할 수 있고, 변경이 복잡할수록 이런 형태가 읽기 더 쉬워질 수 있음
  • 줄 단위 diff의 문제는 함수나 타입 같은 상위 구조를 인식하지 못한다는 점이며, 중괄호가 어찌어찌 맞아떨어지면 서로 다른 함수에 속한 경우에도 표시가 생략될 수 있음
  • difftastictreesitter가 제공하는 concrete syntax tree를 이용해 이런 문제를 줄이려 하지만, 버전 간 엔티티 매칭이 항상 잘 되지는 않음
  • 직접 계기가 된 diff에서는 struct PendingClick이 양쪽에서 서로 대응되지 않고, 왼쪽에서는 삭제, 오른쪽에서는 추가된 것으로 표시됨
  • 왜 매칭에 실패하는지는 파고들지 않았지만, 전체 diff가 더 길어지더라도 PendingClickRequestPendingClick이 양쪽에서 대응되게 보는 편이 더 낫다고 판단함

구조적 diff 도구와 참고 자료

  • 가장 완성도 높고 신중하게 다듬어진 semantic diff 도구로는 semanticdiff.com을 꼽고 있음
    • 독일의 작은 회사가 제공하며, 무료 VSCode 플러그인과 GitHub PR diff를 보여주는 웹 앱이 있음
    • 다만 원하는 워크플로의 기반으로 삼을 수 있는 코드 라이브러리는 제공하지 않음
    • semanticdiff vs. difftastic 글에는 유용한 세부 사항이 많고, difftastic이 Python에서 의미 있는 들여쓰기 변화조차 보여주지 못하는 문제도 포함돼 있음
    • 저자 중 한 명은 HN 댓글에서 treesitter를 의미론 처리에 쓰다가 벗어났다고 밝히며, 문맥 의존 키워드와 lexer 동작 때문에 파싱이 실패해 async 같은 이름을 파라미터로 쓴 경우에도 도구가 멈출 수 있다고 적음
  • diffsitter는 treesitter 기반이며 MCP server를 포함함
    • GitHub star 수는 많지만 문서화는 그다지 잘돼 보이지 않았고, 동작 방식을 설명한 자료를 찾기 어려웠음
    • difftastic 위키에는 트리의 leaf에 대해 longest-common-subsequence를 수행한다고 적혀 있음
  • gumtree는 2014년의 연구·학술 배경에서 나온 도구임
    • Java가 필요해 Emacs에서 빨리 쓸 도구라는 개인 용도에는 맞지 않음
  • mergiraf는 Rust로 작성된 treesitter 기반 merge-driver임
    • architecture overview가 잘 정리돼 있고, 내부적으로 Gumtree 알고리듬을 사용함
    • 문서와 그림을 보면 세심하게 작성된 프로젝트라는 인상을 줌
    • semanticdiff.com 저자는 HN 댓글에서 GumTree가 결과를 빨리 내놓지만, 여러 후속 논문 개선안을 적용해도 항상 나쁜 매칭을 돌려주는 경우가 꽤 있었고, 결국 매핑 비용을 최소화하는 dijkstra 기반 접근으로 전환했다고 적음
  • weave는 Rust로 작성된 또 다른 treesitter 기반 merge-driver임
    • 화려한 랜딩 페이지, 많은 GitHub star, MCP server 등 전체 인상이 다소 과장돼 보임
    • 엔티티 추출 크레이트 sem을 살펴봄
    • 핵심 diff 코드는 괜찮지만 다소 장황하고, 엔티티 매칭은 greedy 알고리듬을 사용함
    • 데이터 모델은 파일 내부 이동을 감지하지 못하며, 그런 이동은 의미가 클 수 있음
    • 신뢰하려면 더 강한 언어 통합이 필요해 보이는 heuristic 기반 impact 분석도 많이 들어 있음
      • sem diff --verbose HEAD~4를 실행했을 때 실제로 바뀌지 않은 줄이 바뀐 것으로 표시되는 버그 출력도 만남
    • 80%쯤 끝난 가상적 유용 기능이 너무 많아 기반으로 쓰기에는 맞지 않았지만, 3개월 만에 이 정도를 만든 점은 높게 평가함
  • diffast는 2008년 학술 논문의 알고리듬을 바탕으로 AST의 tree edit-distance를 계산함
    • 전용 파서를 통해 Python, Java, Verilog, Fortran, C/C++를 지원함
    • example AST differences gallery가 잘 정리돼 있음
    • 정보를 tuple 형태로 내보내 datalog에 활용할 수 있음
  • autochrome는 Clojure 전용 diff 도구이며 dynamic programming을 사용함
    • 시각적 설명과 예시 walkthrough가 매우 좋음
  • Tristan Hume의 Designing a Tree Diff Algorithm Using Dynamic Programming and A*는 tree diff 알고리듬 설계 글로 참고 가치가 큼

원하는 워크플로와 최소 범위 계획

  • 주된 사용 사례는 LLM 출력의 턴별 리뷰이며, agent가 한 번에 1만 줄 넘는 코드를 마구 생성하게 두지 않음
  • agent에는 범위가 정해진 작업을 맡기고 몇 분 뒤 돌아와 전체 변경 개요를 본 다음, Emacs에서 직접 수정하거나 전부 버리고 다시 시도하거나, 아예 직접 다시 작성하고 싶어 함
  • 원하는 워크플로는 어떤 타입·함수·메서드가 추가·삭제·변경됐는지 상위 수준 개요를 먼저 보는 것임
  • 그 위에서 각 엔티티별 텍스트 diff를 빠르게 펼쳐 보고, 요약을 세부 diff로 자연스럽게 확장할 수 있어야 함
  • 또 변경을 다른 곳으로 이동하지 않고 바로 수정할 수 있어야 하며, diff 화면에서 file 화면으로 전환하지 않는 inline 편집을 원함
  • 지향점은 Magit의 변경 검토·staging 워크플로를 파일·줄 단위가 아니라 엔티티 단위로 옮기는 것임
  • 이번에 다시 떠올린 최소 범위 교훈에 맞춰, 먼저 Rust만 대상으로 treesitter 기반 엔티티 추출 프레임워크를 직접 급히 만들 계획임
  • 매칭은 우선 단순한 greedy 방식으로 시작하고, diff는 명령줄에 렌더링하려고 함
  • 이 정도가 특정 커밋에서 difftastic보다 나은 결과를 내면, 이후에는 Magit 같은 더 상호작용적인 Emacs 워크플로에 연결하려고 함
    • 가능하면 Magit 자체를 재사용할 가능성도 열어둠
    • 새 언어 지원은 필요할 때마다 추가하려고 함
    • 이후에는 단순 greedy 대신 점수 기반의 전역 매칭도 탐색할 수 있음
  • 충분히 만족스러우면 공개할 수도 있지만, GitHub star나 HN karma를 모으는 일은 목표가 아니며, 혼자 조용히 쓰는 도구로 남겨둘 수도 있음
  • 때로는 그저 선반 하나만 원할 때가 있다는 문장으로, 과한 확장 대신 필요한 것만 만드는 태도를 다시 묶어줌
Hacker News 의견들
  • 이건 PhD 연구의 가장 큰 어려움을 잘 보여준다고 봄
    흥미로운 주제를 잡고 관련 선행연구를 가능한 한 다 읽다 보면, 이미 내가 하려는 걸 해둔 것이 얼마나 많은지 깨닫게 되면서 scope creep가 심해지기 쉬움
    초반의 에너지와 설렘을 다 써버린 뒤에는, 남은 20~30%를 억지로 밀어붙여서 출판 가능한 상태까지 끌고 가야 함

    • 1일 차엔 기존 산업용 촉매를 새로운 용도에 적용해 필수 의약품 전구체 생산 비용을 낮춰보자고 시작함
      400일 차엔 만물 이론을 거의 다 설명한 뒤, 알려진 우주의 모든 힘을 매개하는 보편 입자를 검출하려고 라그랑주점 궤도 실험 장치를 만들려 듦
    • 그 느낌이 너무 생생하게 와닿음
      이걸 완화하려면 어떻게 해야 하는지 궁금함
    • 대부분의 박사과정이 이걸 겪는 이유는, PhD의 목적이 결국 normal science를 할 수 있음을 증명하는 데 있기 때문이라 봄
      실상은 시스템의 관측 가능성을 1%에서 1.001%로 올리는 식의 작업이고, 학계 커리어에 들어가기 위한 관문에 가까움
      그래서 정말 흥미롭거나, 대단히 새롭거나, 과학에 직접 적용 가능한 내용이 담긴 학위논문은 거의 못 봄
    • 게다가 애초에 PhD를 시작한 것에 대한 후회까지 점점 무거워지는 상태에서 이걸 버텨야 함
    • 선행연구를 가능한 한 전부 읽고 시작하는 건 확실히 잘못된 접근이라 봄
      실제로 그렇게 연구하는 사람은 거의 못 봤고, 보통은 논문 두세 편 정도를 읽고 거기서부터 쌓아 올리는 편이 맞음
      연구 문헌을 깊게 훑는 건 결과가 좀 나온 뒤, 글로 정리하기 시작할 때 하는 게 더 낫다고 봄
  • 더 나은 것이면 충분하다는 말이 계속 떠오름
    작은 개선도 시간이 지나면 누적되고, 처음부터 완벽한 새것은 없으니 완벽한 설계를 앉아서 짜내려는 태도는 오히려 역효과가 큼
    장애물이 곧 길이 된다는 말도 여기 잘 맞음

    • 완벽함이 충분히 좋은 것의 적이 되게 두지 말라는 말이 딱 맞음
      같이 일하던 동료는 코드 변경을 비판할 때도, 자기가 너무 사소한 걸 잡는다고 느끼면 "예전보단 낫다"라고 말했음
      덕분에 개선점은 짚으면서도, 사소한 흠이 남아 있어도 일단 진행해도 된다는 허락을 함께 줬고 이런 태도를 강하게 지지함
    • 이건 결국 완벽주의
      예전엔 완벽주의를 지나치게 높은 성취를 무리해서 추구하는 걸로만 생각했는데, 완벽하지 않으면 못 받아들여서 진전 없이 포기하는 것도 완벽주의일 수 있음
      큰 일을 미루는 procrastination도 같은 뿌리일 때가 많음
  • Rec Room의 CEO가 한 말이 좋았음
    팀들은 늘 프로젝트를 더 짧게 했으면 좋겠다고 하지, 출시를 더 미루고 더 복잡하게 만들고 더 다듬었으면 좋겠다고 말하는 경우는 거의 없다고 함
    모든 상황에 100% 맞진 않겠지만, 실수할 거라면 너무 크게 벌여 시간을 낭비하느니 작게 만들어 일찍 출시하는 쪽이 낫다고 봄

  • 인간은 본성상 비슷한 아이디어를 떠올리기 쉬워서, 모르고 프로젝트를 완성하면 결국 어느 정도는 재발명이 되기 쉽다고 봄
    반대로 먼저 조사하면 부분적으로는 이미 있던 것의 반복임을 깨닫고 김이 빠질 수 있음
    그래도 자기 학습을 위해 끝까지 만들어보는 일 자체가 가장 중요할 수 있음
    물론 새로운 학술 성과를 내야 하거나 고유한 프로젝트로 수익을 내야 할 때는 더 어렵지만, 그런 영역도 기존 것을 조금만 비틀어도 의외로 꽤 관대함

  • 지금 딱 사이드 프로젝트에서 이런 상황을 겪고 있음
    분야가 Information Retrieval이라 경험이 적고, 배울 수 있거나 통합할 수 있는 prior art가 당연히 많음
    그래서 이 글을 읽고 나니 우선 내 걸 만들면서, 막히거나 아이디어가 필요할 때만 선행 사례를 들여다보는 쪽으로 더 마음이 감
    한편 최근 나온 Clojure 다큐를 보면 Rich Hickey는 오히려 오랫동안 선행연구, 논문, 다른 언어를 깊게 파고든 뒤 작업한 것처럼 보였음
    하지만 그 사람도 그 전에 이미 다른 언어들을 만들어봤으니, 더 큰 그림은 결국 직접 만들어보며 배우는 데서 시작된 셈임
    너무 오래 고민만 하지 말고 일단 만들어보고, 실전에서 교훈을 쌓고 벽에 부딪힌 다음에 더 깊은 조사가 필요해지는 게 아닐까 싶음

  • 마감기한을 잡으면 scope creep 문제 대부분이 풀렸음
    체감상 game jam이나 프로그래밍 대회처럼 하드 데드라인이 있는 프로젝트는 끝내기 쉬운데, 끝이 열려 있는 프로젝트는 완주가 훨씬 어려움
    C++ 표준도 원하는 기능이 다 준비될 때까지 기다리지 않고 왜 3년마다 내보내는지와 비슷한 맥락으로 보임
    https://news.ycombinator.com/item?id=20428703

  • 글이 흥미롭긴 했지만, 저자의 생각이 좀 산만하게 흩어져 보였음

    • 여기서 핵심은 결국 scope creep라고 봄
    • 이건 특정한 주제를 날카롭게 파는 블로그 글이 아니라, 이 사람을 팔로우하는 독자에게 보내는 뉴스레터 업데이트에 가까움
  • scope creep에 압도된다고 말하는 사람치고는, 글 끝에 온갖 주제의 링크가 잔뜩 달려 있을 정도로 엄청 많은 일을 해내는 편으로 보임
    결국 배우고 이것저것 해보는 걸 진짜 좋아하는 타입 같고, rabbit hole로 빠지는 과정 자체가 머리를 즐겁게 자극하는 듯함

  • 혼자 만드는 입장에서 크게 도움 됐던 깨달음이 하나 있음
    필수 추상화처럼 보이는 것 대부분은 이름만 바꾼 scope creep였음
    새 기능마다 flag를 붙이다가 내 코드에서 패턴이 보여서 규칙을 하나 세움
    기능은 flag-off 동작에 대한 테스트가 없으면 배포하지 않기로 한 것임
    그러자 flag를 탈출구가 아니라 제품의 일부로 보게 됐고, 백로그에 있던 기능 셋은 그렇게 생각하기 시작하자 자연스럽게 사라졌음

  • 과도한 계획과 scope creep가 문제인 건 맞지만, 반대로 너무 즉흥 개발 쪽으로 흔들리는 것도 경계해야 함
    가장 성공적이었던 프로젝트 중 일부는, 실제로 돌아가는 소프트웨어를 만들기 전에 데이터를 모델링하면서 기능 대부분을 미리 계획하고 검토했던 경우였음
    그 단계에서는 무엇이 과한지 잘 모를 때가 많고, 나나 사용자가 원할 법한 기능을 빼면 나중에 코드 핵심을 크게 다시 설계하느라 시간을 많이 씀
    반대로 틀리면 프로젝트가 너무 커져서 scope creep라고 부르게 됨
    결국 이 판단은 도메인을 얼마나 잘 아느냐에 달려 있음
    도메인을 생각보다 덜 알면 재작업이 많아지고, 생각보다 더 잘 알면 사실 크게 나갈 수 있었는데 baby step으로 시간을 낭비하게 됨
    어느 쪽으로 가도 후회는 남아서, 결국 큰 판단의 문제라고 느껴짐

    • 이상적인 방법은 분석 단계에서 충분히 시간을 써서 머릿속에 올바른 맥락을 채워 넣되, 막상 만들 때는 과설계한 해법을 버리고 손이 가는 쪽으로 바로 구현할 준비를 하는 것이라 봄
      매몰비용 오류에 빠지면 안 되고, 박사과정 수준 주제를 몇 시간 조사했다고 해서 그걸 프로젝트에 꼭 써야 하는 건 아님
      지금 문제에 딱 맞지 않으면 과감히 버리는 편이 맞음
    • 너무 틀릴까 봐 걱정하지 말고, 일단 해본 뒤 필요하면 조정하면 됨