3P by GN⁺ | ★ favorite | 댓글 1개
  • Riot Games는 기술 부채를 미래 개발자가 비용을 치르게 될 코드나 데이터로 보고, League of Legends 개발 사례를 통해 부채를 판단하는 공통 언어를 정리함
  • 평가 기준은 영향도, 수정 비용, 전염성 세 가지이며, 특히 전염성은 부채가 시간이 지나며 다른 시스템·데이터·개발 관행으로 퍼지는 정도를 가리킴
  • 부채 유형은 내부 구현에 갇힌 Local Debt, 두 시스템을 임시 연결한 MacGyver Debt, 깊은 가정이 구조에 박힌 Foundational Debt, 결함 위에 콘텐츠가 쌓인 Data Debt로 나뉨
  • Jarvan의 Cataclysm, std::string과 AString 병행, BlockBuilder의 Lua 사용, block parameter naming bug가 각 유형의 구체적 사례로 사용됨
  • 전염성이 낮은 부채는 오래 남겨도 괜찮을 수 있지만, 전염성이 높은 부채는 시간이 지날수록 수정 비용과 영향이 커져 조기에 확산 경로를 끊어야 함

기술 부채를 판단하는 세 가지 축

  • 기술 부채는 “미래 개발자가 비용을 치르게 될 코드나 데이터”임
  • 특정 부채를 지금 고칠지, 나중에 고칠지, 현실적으로 남겨둘지 판단하려면 공통 측정 기준이 필요함
  • Riot은 기술 부채를 세 축으로 평가함
    • impact: 플레이어와 개발자에게 미치는 영향
    • fix cost: 수정에 필요한 시간과 배포 위험
    • contagion: 그대로 둘 때 문제가 얼마나 퍼지는지
  • impact: 플레이어와 개발자에게 드러나는 비용

    • 플레이어에게는 버그, 누락된 기능, 예상 밖 동작으로 나타남
    • 개발자에게는 구현 지연, 워크플로 방해, 기억해야 할 불필요한 세부사항으로 누적됨
    • 여기서 개발자는 엔지니어뿐 아니라 디자이너, VFX 아티스트 등 게임 제작에 관여하는 직군을 포함함
    • 어떤 부채는 엔지니어의 새 코드 작성을 막고, 어떤 부채는 디자이너의 새 스크립트 작성이나 VFX 아티스트의 새 파티클 제작을 방해함
  • fix cost: 구현 시간과 배포 위험

    • 수정 비용에는 실제 개발 시간뿐 아니라 수정사항을 배포할 때 생기는 위험도 포함됨
    • 단일 함수의 단순 오류는 몇 분 만에 고칠 수 있지만, 게임 전체 코드 라인에 영향을 주는 깊은 가정은 수주 또는 수개월이 걸릴 수 있음
    • “잘못된” 시스템도 이미 좋은 게임을 만드는 도구로 쓰이고 있을 수 있어, 고치는 순간 기존 콘텐츠가 깨질 수 있음
    • 예를 들어 스크립팅 엔진의 오류 처리 방식이나 파티클 생성 시간 계산 방식을 바꾸면 140명 이상 챔피언의 500개 이상 스킬에 영향을 줄 수 있음
  • contagion: 시간이 지날수록 퍼지는 정도

    • contagion은 기술 부채를 그대로 둘 때 다른 시스템·데이터·개발 방식으로 얼마나 전파되는지를 뜻함
    • 전파는 문제가 있는 시스템과의 인터페이스, 그 위에 쌓인 데이터의 복사/붙여넣기, 새 기능 구현 방식의 변화로 발생함
    • 잘 격리된 부채는 나중에 고쳐도 지금 고치는 것과 비용 차이가 크지 않음
    • 전염성이 높은 부채는 시간이 지날수록 더 고치기 어려워지고, 더 많은 시스템이 핵심 타협에 감염될수록 영향도 함께 커짐

Local Debt: 내부만 지저분한 블랙박스

  • Local Debt는 고전적인 블랙박스 프로그래밍 모델과 비슷함
  • 외부에서 보면 시스템은 안정적으로 동작하지만, 내부 구현은 끔찍하거나 혼란스러울 수 있음
    • 예: 스킬, 네트워크 계층, 스크립트 엔진
  • 주변 시스템을 개발할 때 내부 부채를 의식할 필요가 없다면 전염성은 낮은 편임
  • 현실 세계 비유: 인간의 눈

    • 인간의 눈은 구조상 이미지를 거꾸로 받아들이고, 망막 신경은 각 눈의 중앙 근처에 맹점을 만듦
    • 뇌의 시각 중추가 데이터를 뒤집고 맹점을 채워 나머지 뇌가 “올바른” 이미지와 상호작용하게 함
    • 이 특이점은 눈과 시신경 시스템에 국소화되어 있고 다른 시스템이 쉽게 피할 수 있어 “충분히 좋은” 상태임
  • League 사례: Jarvan의 Cataclysm

    • Jarvan의 Cataclysm은 현재까지도 minion으로 만들어져 있음
    • 디자이너는 특정 위치나 위치 집합에 게임플레이 효과를 붙일 때 “invisible minion”을 생성하는 도구를 사용할 수 있음
    • RiotXypherous는 Reddit 댓글에서 여기서 말하는 “minion”을 설명함
    • 이런 게임 오브젝트는 스크립트 로직을 추적하고 실행하는 안정적이고 잘 이해된 방법임
    • Jarvan의 벽은 플레이어가 빠져나가지 못하게 하려면 정확히 24개의 minion이 필요함
    • 예전에는 12개였지만 플레이어가 가끔 벽 사이로 빠져나가 Riot Exgeniar가 24개로 늘림
    • 대안은 Cataclysm의 pathability를 제어하는 단일 논리 조각인 ring-terrain 구조이며, 로직을 정리하고 계산 비용을 약간 줄일 수 있음
  • Cataclysm의 평가

    • impact: 1/5
      • 벽이 minion으로 만들어졌다는 사실은 새 콘텐츠를 만드는 다른 개발자에게 거의 영향을 주지 않음
      • “Jarvan Ult Hitch”는 이 부채와 누락된 auto-attack 정의를 읽으려던 로딩 버그가 맞물린 결과였음
    • fix cost: 2/5
      • 현재는 새 코드 없이 합성 도형으로 커스텀 지오메트리를 만들 수 없음
      • ring 형태의 “area trigger”를 만들려면 ring 충돌 계산을 위한 전용 수학 코드가 필요함
      • Riot은 다른 목적을 위해 Constructive Solid Geometry를 탐색 중이며, 이것이 수정 비용을 크게 낮출 수 있음
    • contagion: 1/5
      • 기능 개발 시 Jarvan 벽 구현을 고려할 필요가 없어 잘 격리되어 있음
      • 전염 위험은 다른 디자이너가 이 구현을 새 챔피언에 복사/붙여넣기하는 경우이며, 실제로 간혹 발생했음
      • 구현 문제로서 Cataclysm의 잠재적 확산은 낮고 잘 이해되어 있음
  • Local Debt를 다루는 방식

    • Local Debt의 전형적인 특징은 낮은 contagion 점수임
    • impact가 fix cost보다 높으면 좋은 시민 역할을 하는 개발자가 오래 지나기 전에 고치는 경향이 있음
    • 정말 전염성이 없다면 필요한 만큼 오래 남겨도 안전함
    • 엔지니어의 완벽주의를 자극하지만 충분히 넓은 영향이 없는 Local Debt에 즉시 달려드는 것은 큰 실수 중 하나임
    • 변경 범위가 국소적이어서 수정 확인과 회귀 테스트가 보통 쉬움
    • 최근 수정된 예시에는 inhibitor 관련 버그, Janna’s Monsoon, Tear of the Goddess가 포함됨
      • 특정 상황에서 inhibitor가 챔피언을 0,0,0 좌표로 pathing하게 만들던 버그
      • Janna’s Monsoon이 spell shield를 무시하던 문제
      • Tear of the Goddess가 마나 없는 시전에 중첩되던 문제

MacGyver Debt: 두 시스템을 덕트 테이프로 붙인 상태

  • MacGyver Debt는 1980년대 중반 TV 쇼 MacGyver에서 이름을 따온 유형임
  • 기술 부채 맥락에서는 충돌하는 두 시스템이 코드베이스 전반의 인터페이스 지점에서 “덕트 테이프”로 붙어 있는 상태를 뜻함
  • 현실 세계 비유: Seattle

    • Seattle에는 과거 각자 자체 격자를 가진 두 개의 경쟁 정착지가 있었음
    • 두 정착지가 현대의 Emerald City로 성장하면서 약간 다른 격자가 합쳐졌고, 어색한 모양의 블록과 건물, 비효율적인 공간 사용이 생김
  • League 사례: std::string과 AString

    • League 코드베이스에는 C++ std::string과 Riot의 커스텀 AString 클래스가 함께 존재함
    • 둘 다 문자열을 저장, 수정, 전달하는 방법임
    • Riot은 std::string이 많은 “숨겨진” 메모리 할당과 성능 비용을 유발하고, 나쁜 코드를 쓰기 쉽게 만든다고 봄
    • AString은 신중한 메모리 관리를 염두에 두고 설계됨
    • 대체 전략은 두 시스템을 함께 두고 .c_str().Get()을 통해 서로 변환할 수 있게 하는 방식이었음
    • AString에는 사용성을 높이는 개선이 추가됐고, 엔지니어가 코드를 변경할 때 자율적으로 std::string을 교체하도록 권장됨
    • 이 방식으로 std::string은 천천히 단계적으로 사라지고, 두 시스템 사이의 “덕트 테이프” 인터페이스도 코드 정리와 함께 줄어듦
  • std::string vs AString의 평가

    • impact: 2/5
      • std::string에서 발생하던 고영향 할당 대부분은 profiling을 통해 이미 제거됨
      • 현재 주요 비용은 한 시스템에서 다른 시스템으로 변환할 때 드는 작은 정신적 전환 비용임
    • fix cost: 3/5
      • AString 전환은 단순 find-and-replace가 아님
      • AString에는 stack 메모리에 초기 할당하는 AStackString, static string 참조용 ARefString, heap 할당 기반 AString 같은 목적별 변형이 있음
      • 올바른 교체를 위해 사람이 각 지점을 직접 보고 판단해야 하며, 기존 시스템을 단계적으로 제거하는 과정은 길고 느릴 것임
    • contagion: -2/5
      • AString을 std::string보다 사용하기 쉽게 만들어 contagion을 유리한 방향으로 뒤집음
      • 엔지니어가 게임 코드 변경을 체크인할 때마다 AString이 더 퍼질 가능성이 생김
  • MacGyver Debt를 고치는 방식

    • MacGyver Debt의 큰 비용은 경계를 넘을 때 모드를 전환해야 하는 지적 비용인 경우가 많음
    • 버그나 기능이 “wrong” system에 있어서 막히면, 목표 지점을 “right” system으로 옮기는 작업은 대체로 직접적임
    • 새 시스템과 기존 시스템의 상대적 contagion이 핵심 지표임
    • 새 시스템 쪽이 더 전염되도록 균형을 뒤집으면 더 나은 시스템이 결국 이김
    • 전역적으로 더 나은 시스템이 로컬 수준에서도 더 매력적이게 만들어야 함
    • 시간에 쫓기는 엔지니어가 일상 작업 중 탐욕적 최적화를 하면서도 원하는 최종 상태 쪽을 선택하면 좋은 방향으로 가고 있는 것임
    • 다른 접근은 대규모 brute-force refactor이며, 시스템들이 얼마나 가깝게 매핑되는지에 따라 clever regex로 일부 또는 전체를 고칠 수도 있음

Foundational Debt: 깊은 가정이 전체 구조에 박힌 상태

  • Foundational Debt는 시스템 깊숙한 곳의 어떤 가정이 전체 동작 방식에 구워져 들어간 상태임
  • 시스템의 경험 많은 사용자는 이를 “그냥 그런 것”으로 보기 때문에 알아차리기 어려울 수 있음
  • 현실 세계 비유: United States Customary Units

    • 미국에서 자란 사람은 1마일이 5,280피트, 1쿼트가 2파인트, 1갤런이 4쿼트 같은 변환을 외우게 됨
    • 미국 정부는 metric 전환을 여러 번 고려했지만, 여전히 Système International을 공식 측정 시스템으로 채택하지 않은 7개국 중 하나임
    • 이 부채는 도로 표지판, 레시피, 초등학교, 사람의 머릿속에 구워져 있음
  • League 사례: BlockBuilder와 Lua

    • Riot이 다뤄온 큰 Foundational Debt 사례에는 Determinism in League of LegendsGame Data Server가 있음
    • League의 Lua scripting language 사용도 Foundational Debt 예시임
    • League 디자이너는 BlockBuilder라는 도구로 기능 블록을 이어 붙여 복잡한 동작을 만듦
    • 기능 블록에는 점 사이 거리 구하기, minion 생성, damage 처리, 다양한 script flow control이 포함됨
    • 디자이너가 고르는 연산 집합은 다양하지만 제한되어 있고, 각 연산의 매개변수도 제약됨
    • League of Legends 초기에는 블록과 매개변수를 데이터에 맞는 단순하고 제약된 형식으로 저장하지 않고, 강력하지만 이 목적에는 지나치게 복잡한 Lua 언어의 arrays와 tables로 저장하기로 결정함
    • 이후 약 10년의 게임 개발이 그 기반 위에서 진행됐고, Lua object 조작은 엔진에서 가장 흔한 작업 중 하나가 됨
  • BlockBuilder Lua의 평가

    • impact: 4/5
      • Lua와 해당 문제 공간의 불일치는 많은 비용을 유발함
      • BlockBuilder 로직의 각 frame마다 callstack이 약 6개의 marshalling stack frame으로 오염됨
      • marshalling 작업은 server CPU 사용량 측면에서 저렴하지 않음
      • script 변경 diff를 읽는 일이 불필요하게 어려움
      • 기능을 파악하기 위해 script file을 parsing/searching하려면 Lua 언어에 대한 꽤 깊은 이해가 필요함
    • fix cost: 4/5
      • Lua가 엔진에 깊게 박혀 있어 제거가 어려움
      • 현재 제안 중 하나는 Lua object처럼 동작하지만 내부적으로는 훨씬 단순한 struct인 wrapper class를 만들어 scripting 내부를 더 적합한 형태로 천천히 바꾸는 것임
      • 어떤 접근을 하든 신중하고 사려 깊게 진행해야 함
    • contagion: 4/5
      • 시스템이 scripting과 맞닿을 때마다 그 시스템은 Lua backend의 연산과 요구사항에 의해 형성됨
      • scripting은 LoL의 핵심 logic 단위임
      • Riot은 평균 약 3~4일마다 새 Building Block을 추가하며, 각 Building Block은 Lua object를 직접 조작함
      • Lua를 더 오래 대체하지 않을수록 Lua를 대체하기가 더 어려워짐
  • Foundational Debt를 줄이는 방식

    • Foundational Debt는 impact, fix cost, contagion 세 축 모두에서 높은 점수를 보이는 경향이 있음
    • 높은 fix cost는 불완전한 시스템을 계속 쓰게 만들며, 때로는 이것이 올바른 선택이기도 함
    • 하지만 높은 impact와 높은 contagion 때문에 심각한 Foundational Debt를 고치면 큰 보상이 따를 수 있음
    • Riot에서 관찰한 가장 흔한 수정 전략은 새 시스템을 기존 시스템 옆에 세우는 것임
    • 가능하면 기존 foundational debt를 MacGyver Debt로 바꾸고, conversion operation을 통해 새 시스템과 기존 시스템을 오가며 천천히 포팅함
    • 이 방식은 특정 영역에서 이점을 얻기 시작하면서 위험 노출을 제한함
    • 이런 전환이 불가능할 때는 compile time switch, 가능하다면 loading time switch를 만들어 새 시스템에 대한 신뢰를 쌓을 수 있음
    • compile time switch 방식은 GDS 전환에 사용 중임
    • loading time switch 방식은 Determinism에서 효과가 있었음

Data Debt: 결함 위에 대량 콘텐츠가 쌓인 상태

  • Data Debt는 다른 범주의 기술 부채 위에 많은 콘텐츠가 쌓일 때 발생함
  • 시작점은 scripting system의 버그, item에 적합하지 않은 file format, 서로 잘 맞지 않는 두 시스템 등일 수 있음
  • 그 코드 결함 위에 art, scripts, sounds 같은 대량의 콘텐츠가 만들어지면 초기 기술 부채를 고치는 일이 매우 위험해짐
  • 시간이 지나면 무엇이 깨질지 파악하기가 고통스러울 정도로 어려워짐
  • 현실 세계 비유: DNA

    • 유기체의 genome은 수백만 년 동안 mutation, transcription error, evolutionary pressure를 통해 천천히 쌓임
    • 어떤 복사 오류는 쓸모없지만 해롭지 않고, 어떤 오류는 해롭고, 어떤 오류는 강력한 이점을 줌
    • DNA 조각이 실제로 무엇을 하는지 알아내기는 매우 어려움
    • base pair가 무엇을 의미하는지, base pair 집합이 protein construction을 위한 amino acid로 어떻게 번역되는지는 완전히 이해하고 있음
    • DNA의 일부 non-encoding role에 대해서도 더 많이 이해하기 시작했음
    • 하지만 인간 genome의 30억 개 이상 base pair에는 아직 거의 이해하지 못한 부분이 많음
    • Radiolab의 CRISPR 에피소드는 최근 풀린 그런 퍼즐 하나를 다룸
  • League 사례: block parameter naming bug

    • League of Legends의 Data Debt는 원래는 사소한 수정이었을 일을 고된 작업으로 바꿀 때 가장 큰 영향을 줌
    • game engineer들은 게임 시스템 구현 방식에 대한 깊은 지식을 쌓고, 어떤 코드 변경이 어떤 데이터를 깨뜨릴지 예측하는 데 숙련됨
    • Data Debt는 LoL engine 변경 시 가장 중요한 고려사항 중 하나임
    • 몇 년 전 수정된 Data Debt 사례는 BlockBuilder scripting language의 block parameter 관련 버그였음
    • toy example에서 Owner의 armor를 변수와 상수로 증가시키려 하면 예상값은 변수 Delta 20과 상수 5를 더한 25 bonus armor임
    • 변수 이름이 parameter 이름과 같으면 예전에는 결과가 40이 되었음
    • 왜 45가 되지 않았는지는 글쓴이도 모른다고 함
  • 실제 수정 과정

    • Champions team 엔지니어 NoopMoney가 이 동작을 고치려 했을 때 실제 코드 수정은 4줄 삭제뿐이었음
    • 그러나 전염성이 높은 부채는 작은 변경에도 철저한 계획이 필요했음
    • LoL의 400,000줄 script 중 어느 곳의 numerical parameter라도 이 버그로 두 배가 되고 있을 수 있었음
    • 더 큰 문제는 게임이 그 잠재적으로 두 배가 된 값에 맞춰 balance와 tuning이 되어 있어, 해당 script들이 “correctly” 동작하고 있었다는 점임
    • NoopMoney는 예상치 못한 버그에 대비해 fix를 Live에서 toggle 가능하게 만들어야 했음
    • 어떤 script가 이 버그에 의존하는지 식별하기 위해 광범위한 regex searching과 QA sweep을 수행함
    • 최종적으로 수정으로 인한 문제는 비교적 작았고, 소수의 champion script만 변경이 필요했음
    • Data Debt 때문에 결과 예측은 어려웠음
  • Parameter Naming Bug의 평가

    • impact: 2/5
      • 발생 시 영향은 작았음
      • 전달된 값을 두 배로 만들고, 상수를 버릴 가능성이 있었음
      • 이를 알게 된 디자이너와 엔지니어가 기억해야 하는 또 하나의 쓸모없는 tribal knowledge가 됨
      • developer mindshare는 이렇게 낭비되기에는 가치 있는 자원임
    • fix cost: 2/5
      • 전체적으로 수정 자체는 직접적이었음
      • live feature toggle을 만들어 fix의 안전성에 대한 확신을 높일 수 있었음
      • 가장 비싼 부분은 테스트 타깃을 정하기 위해 문제 범위를 평가하는 초기 screening이었음
    • contagion: 4/5
      • 이 버그는 매우 논리적인 행동을 노렸다는 점이 불행했음
      • unit에 damage를 주려면 값을 “Damage”라는 변수에 저장하는 것이 완전히 논리적임
      • ApplyDamage block이 같은 이름의 parameter로 amount를 받으면서 버그가 트리거됨
      • 다른 사람이 비슷한 spell을 만들기 위해 해당 block을 copy/paste하면 버그가 더 퍼짐
  • Data Debt가 전염성이 높은 이유

    • Data Debt는 변경 영향을 평가하기 어렵게 만들기 때문에 일반적으로 수정 비용이 높게 평가됨
    • 더 우려되는 점은 data의 성질상 거의 항상 매우 전염성이 높다는 것임
    • 기존 data를 copy/paste해 새 data를 만드는 방식이 일반적으로 허용됨
    • 새 skillshot spell을 만들 때 Ezreal’s Mystic Shot에서 시작하면 시간을 크게 절약할 수 있고, 기존 data의 문제도 그 후손 data로 전파됨
    • data는 code review와 유사한 기술 검토를 거의 받지 않기 때문에, 나쁜 관행이 널리 알려져 있어도 확산을 알아차리고 멈추기 어려움
    • data의 문제를 고치려면 보통 눈과 뇌를 가진 사람이 직접 검증해야 하며, compiler와 formal logic만으로는 충분하지 않음
  • Data Debt를 고치는 두 접근

    • 첫 번째 접근은 do it right checkbox
      • data creator를 위해 기존 “broken” behavior와 새 “fixed” behavior 사이 toggle을 만드는 방식임
      • 이상적으로는 fixed version을 기본값으로 두고, old content가 broken version을 사용하게 함
      • 이후 MacGyver Debt처럼 느리고 꾸준한 replacement로 새 버전으로 옮길 수 있음
      • 단점은 editing UI에 점점 더 많은 불필요한 요소를 추가하는 영구 비용이 생긴다는 점임
    • 두 번째 접근은 just fix the damn thing
      • NoopMoney가 parameter naming bug에 사용한 방식임
      • 버그를 고친 뒤 의미 있게 영향을 받는 모든 data를 수리하려고 시도함
      • 덜 무섭게 만드는 기법에는 theoretical impact를 이해하기 위한 많은 grep과 regex searching, targeted testing, ship 후 더 나쁜 누락이 발견될 경우 기존 동작으로 되돌릴 toggle 준비가 있음
      • Determinism은 변경 전후 서버가 같은 결과를 생성하는지 확인할 수 있게 해 이런 변경 테스트에 많은 도움을 줌

정리: 전염성을 비용 평가에 포함해야 함

  • 기술 부채 측정 지표는 impact, fix cost, contagion
    • impact는 고객과 개발자에 대한 영향임
    • fix cost는 시간과 위험임
    • contagion은 문제의 전파 정도임
  • 대부분의 개발자는 impact와 fix cost를 정기적으로 고려하지만, contagion 논의는 상대적으로 드묾
  • contagion은 문제가 깊이 파고들어 점점 제거하기 어려워질 때 개발자의 최악의 적이 될 수 있음
  • 반대로 fix를 문제보다 더 전염성 있게 만들면 contagion을 무기로 바꿀 수 있음
  • League에서 본 대부분의 기술 부채는 네 범주 중 하나로 정리됨
    • Local Debt: 내부가 지저분한 black box 같은 부채
    • MacGyver Debt: conversion function으로 2개 이상의 시스템이 덕트 테이프처럼 붙어 있는 부채
    • Foundational Debt: 전체 구조가 불행한 가정 위에 만들어진 부채
    • Data Debt: 다른 유형의 부채 위에 막대한 data가 쌓여 수정이 위험하고 시간이 많이 드는 부채

댓글과 토론

Hacker News 의견들
  • 전염성은 인터페이스가 설계에서 가장 중요한 요소 중 하나이고 충분히 고민해야 하는 이유 그 자체임
    아름다운 인터페이스에 최적이 아닌 구현이 붙어 있으면 시간이 생겼을 때 쉽게 정리할 수 있지만, 그 반대는 거의 성립하지 않음

    • 동의하지만 제대로 설계하려면 보통 둘 중 하나가 빠져 있음: 설계할 시간, 그리고 오늘과 1년 뒤에 정확히 무엇을 해야 하는지에 대한 지식
      때로는 둘 다 없음
      이런 경우 작은 모듈과 단일 책임을 조합식으로 강제하면 전염이 너무 심해지는 걸 막을 수 있음. 미래 지식이나 시간이 많이 필요하지 않고, 매개변수로 여러 행동 변형을 제어하는 러시아 인형식의 넓은 표면적 인터페이스를 피하면 됨. 설정, 파싱, 행동 결정은 로직의 가장자리로 옮기고, 하위 모델 전체로 스며들게 두지 않는 편이 좋음
    • 동의하지만, 견고하고 미래에 강한 인터페이스를 설계하는 건 소프트웨어 개발에서 가장 어려운 문제 중 하나였음
      기술 부채를 무슨 수를 써서라도 피하겠다고 의식적으로 시작해도 제대로 하기가 어렵고, 단순한 기술적 자신감이나 아키텍처 비전 이상이 필요함. 실제로는 미래 예측의 영역에 들어감
    • 좋은 인터페이스를 드러내려면 좋은 내부 구현이 어떤 모습인지 이해해야 함
      멍청한 구현을 인터페이스에 암묵적으로 구워 넣으면 구현만 바꿔서는 고칠 수 없게 되기 쉬움. 떠오르는 예는 정렬과 페이지네이션 동작임. 주니어 개발자들과, 이제는 더 잘 알아야 할 많은 시니어들도 limit/offset류 매개변수를 쓰는 요청부터 시작하는 일이 잦고, 이는 끔찍한 성능 문제와 이상 동작으로 이어짐. 페이지네이션이 효율적으로 동작하는 방식과 성능 좋게 지원할 수 있는 정렬 옵션은 데이터 형태와 데이터 저장소 선택에 본질적으로 결합되어 있음. 낮은 계층에서 이 과정을 겪어보지 않은 사람은 구현을 먼저 충분히 파고들지 않는 한 상위 인터페이스를 제대로 만들 가능성이 낮음
    • 그래서 OCaml이나 Ada처럼 인터페이스를 매우 명시적으로 만드는 언어를 좋아함
      대부분의 경우 구현을 보고 싶지 않고, 제대로 문서화된 인터페이스만 보고 싶음. 인터페이스의 동작을 간단한 말로 설명할 수 없다면 뭔가 잘못된 것임
    • 역사에는 반례가 꽤 많아 보임
      QWERTY는 최적의 물리 인터페이스가 아닌 것으로 유명하고, 운전대도 비슷한 사례가 될 수 있음. 컴퓨터 쪽에서는 x86이 표면적으로 최적이 아닌 인터페이스의 대표 사례임
  • 이 글을 엔지니어링 매니저가 썼다는 게 꽤 놀라움
    같이 일해 본 매니저 중 우리 코드베이스를 이 정도 기술적 세부 수준으로 이야기할 수 있는 사람은 없었음. 예전에 엔지니어였던 사람들도 마찬가지였음
    다만 공정하게 말하면 내부 승진한 매니저가 없었고, 내부 사람들은 나를 포함해 엔지니어링을 그만두고 싶어 하지 않아서 외부에서 매니저를 뽑는 나쁜 습관이 있음

  • 내가 본 가장 흔한 부채 유형이 빠진 것 같음: 창업자 부채
    창업자들이 빠르고 가치 있는 기술을 내보내기 위해 만든 부채임. 쉬운 과실처럼 보였던 것이 전체 시스템의 기반이 되어버리는 경우
    많은 나라의 건국 문서도 이 범주에 들어감 lol(하지만 USA! USA! USA!는 아님)
    맥가이버 부채와 기반 부채가 가장 가깝지만, 둘 다 이 현상을 정확히 짚지는 못함

  • 기술적 관점에서 훌륭한 글임
    다만 이것은 “분류 체계”라기보다는 명명법에 더 가깝다고 봄. 의도적으로 포괄적이지도 않고 서로 배타적이지도 않기 때문인데, 내가 틀렸을 수도 있음. 각 항목의 물리적 예시는 특히 좋았고 생각할 거리를 줌
    늘 그렇듯 철학적으로 사소하게 걸리는 부분이 있음. 앞부분의 “세 축”은 전통적인 RoI의 수익과 투자에, 특정한 미래지향적·조건부 수익 하위 범주를 더한 것처럼 보임. 이 결정이 실제로는 잘 통했을 거라고 짐작하고, 비디오 게임 개발 관행이 절대적으로 과학적일 필요는 없지만, 철학적 확실성이 조금 더 있어도 나쁠 건 없음

  • 당시에도 논의됐음:
    A Taxonomy of Technical Debt - https://news.ycombinator.com/item?id=16810092 - 2018년 4월(댓글 113개)
    그리고 이것도 있음:
    A Taxonomy of Tech Debt (2018) - https://news.ycombinator.com/item?id=39782923 - 2024년 3월(댓글 1개)

  • “기술 부채를 미래 개발자가 비용을 치르게 될 코드나 데이터로 정의한다”는 설명은 내가 본 것 중 최고에 가까움
    모든 부채와 마찬가지로 부채를 질 때 즉각적인 필요와 미래 비용을 균형 잡는 임계값을 적용해야 함. 대부분의 사람은 개발자만이 아니라 즉각적인 필요는 과대평가하고 미래 비용은 과소평가한다고 느낌
    개인적으로는 어떤 종류의 부채든 거의 병적일 정도로 싫어함. 미래에 유용할 수 있는 것을 분리해내는 데 하루를 더 쓰기도 함. 맞을 때는 대략 50% 정도지만, 그런 작업을 할 때마다 습관이 강화되고 기본 작업 흐름도 더 빨라짐

  • 지금까지 “스타트업” 3곳에서 일했는데, 모두 정상에 가까운 급여를 줄 만큼 매출이 난 뒤에 합류했음
    가장 자주 본 건 여러 창업자가 자기가 떠올린 아이디어, 실제로 만들어진 것, 구현된 부분 중 실제로 동작하는 것을 흐릿하게 섞어 이해하고 있다는 점임

  • 이 글을 처음 읽은 뒤로 기술 부채를 설명할 때 전염성이라는 말을 써 왔고, 꽤 잘 들어맞음

  • “국소 부채”를 보통 상황에서 기술 부채라고 부를 수 있을지 잘 모르겠음
    현실적으로 어딘가에는 항상 지저분한 부분이 있고, 그것을 캡슐화해서 아무도 다치지 않게 숨겨두는 건 정상임. 요구사항이 바뀌지 않는 한 거의 바꿀 필요가 없고, 바뀌면 어떤 구현이든 수정해야 한다면 괜찮음
    예시의 24개 미니언 인스턴스가 단순히 우아하지 않은 정도가 아니라 실제 문제라면, “미니언”이 가장 단순한 기본 단위가 되었고 더 가벼운 무언가가 있을 수 있었다는 점에서 오히려 기반 부채에 가까워 보임

    • 글에서도 조금 다루지만, 비용은 실제로 작업해야 할 때 그것을 이해하는 인지 비용이고, 여기에 도구를 동일하게 유지하는 비용도 더하고 싶음
      개발자들이 오래되고 성숙해서 건드릴 필요가 없는 모듈까지 포함해 변경하도록 장려하면, 이것이 문제가 될 정도로 쌓이지 않게 하는 좋은 방법이 됨
  • 중요한 측면 하나는 단기 이익을 얻기 위해 의식적으로 기술 부채를 지는 경우임
    그러면 그 이익도 함께 저울질해야 할 또 다른 축이 됨

    • 실제 부채와 똑같음
      15년 뒤 자본이 생길 때까지 기다리지 않고 지금 새 건물을 지어 일을 끝내고 싶다면? 빚을 내면 됨
      부채는 도구지만 강력하고 위험한 도구임. 사용 중이라는 걸 인정하고 존중하지 않으면 다치게 됨. 아니면 당신에게서 수류탄을 넘겨받은 누군가가 다침. 실제 부채처럼
    • 이런 것을 “기술 부채” 대신 전술적 부채라고 부르는 걸 들어봤음
    • 보통은 속도 때문임