A Lannister Always Pays His Technical Debts
The author sorts technical debt into three buckets: aesthetic (import order, naming — annoys you but doesn't touch users or velocity, and tooling can fix it), deferrable (contained, to be crossed off methodically inside normal sprint work), and toxic (a half-finished thing that turns into a workaround magnet, with every new feature built on top of it). Missing tests and missing docs are called out as toxic debt: without an integration suite you won't dare swing a sledgehammer at the codebase, and without READMEs and runbooks every bug repro starts with reverse-engineering the system at real dollar cost. The related advice: TODO comments mean you will never do it, so turn them into actual cards on the board, and tag debt stories so the ratio of features to debt stays visible. The debt column will never be empty — just don't let it run away.
A tale of two rewrites
Jamie Zawinski is kind of a tech legend. He came up with the name “Mozilla”, invented that whole thing where you can send HTML in emails, and more. In his harrowing work diary of how the Mosaic web browser came to be, Jamie described the burnout rodeo that was the development of what was later going to become Netscape (the top disclaimer has its own history — ignore it):
I slept at work again last night; two and a half hours curled up in a quilt underneath my desk, from 11am to 1:30pm or so. That was when I woke up with a start, realizing that I was late for a meeting we were scheduled to have to argue about colormaps and dithering, and how we should deal with all the nefarious 8-bit color management issues. But it was no big deal, we just had the meeting later. It’s hard for someone to hold it against you when you miss a meeting because you’ve been at work so long that you’ve passed out from exhaustion.
两个重写的故事
Jamie Zawinski 算是技术圈传奇人物。Mozilla 这个名字是他起的,他还发明了在邮件里直接发送 HTML 的那套玩法,等等。在他记录 Mosaic 浏览器如何诞生的那本揪心工作日记里,Jamie 描述了后来演变成 Netscape 的那段开发经历——简直是一场拿命换进度的 burnout 牛仔竞技(开头那段免责声明另有一段历史,这里忽略):
昨晚我又睡在公司;从上午 11 点到下午 1:30 左右,我裹着被子蜷在办公桌下睡了两小时半。醒来时我一激灵,想起我们本来要开个会,争论 colormap 和 dithering,以及怎么处理那些烦人的 8-bit 颜色管理问题。不过也没啥大不了,会后来才开。你因为工作太久累到昏过去而错过会议,别人也很难怪你。
Netscape’s wild ride is well-depicted in the dramatized Discovery miniseries Valley of the Boom, and the company eventually collapsed with the death march rewrite of what seemed to be just seriously unmaintainable code. It was the subject of one of the more famous articles by ex-Microsoft engineer-turned-entrepreneur Joel Spolsky - Things You Should Never Do.
There have been big, successful rewrites. Twitter moved away from Ruby-on-Rails to JVM over a decade ago but the first, year-long full rewrite effort completely failed. Following architecture by fiat from the top, the engineering team said nothing, speaking out only days before the launch. The whole thing would crash out of the gate, they claimed, so Twitter had to go back to the drawing board and rewrite again.
Netscape 的疯狂旅程在 Discovery 的剧情迷你剧《Valley of the Boom》里有生动刻画。这家公司最终随着一次死亡行军式的重写而崩塌——要重写的,似乎就是严重无法维护的代码。前微软工程师、后来创业的 Joel Spolsky 在名文《Things You Should Never Do》里专门写过这件事。
大型重写也有成功案例。十多年前,Twitter 从 Ruby-on-Rails 迁到了 JVM,但第一次长达一整年的完整重写彻底失败了。当时高层强行拍板架构,工程团队一声不吭,直到发布前几天才开口。他们断言,这套东西一上线就会崩,于是 Twitter 只能推翻重来,再写一遍。
All of this is to say that you should probably never let your system rot so badly until a code rewrite is even in the cards. It never just happens. Your code doesn’t just become unmaintainable overnight. It gets there by the constant cutting of corners, hard-coding things, and crop-dusting your work with long-forgotten //FIXME comments. Fix who?
所有这些无非说明:系统不该烂到非重写不可的地步。代码不会突然变得无法维护。它是一路偷工减料、到处硬编码、再留下一堆早就被遗忘的 //FIXME 注释给糟蹋成那样的。Fix who?——修谁?
We used to call it technical debt - a term that is now being frowned upon. The concept of “technical debt” got popular around the time when we were getting obsessed with “proh-cess” and Agile, as we got tired of death march projects, arbitrary deadlines, and general lack of structure and visibility into our work. Every software project felt like a tour — you came up for air and then went back into the 💩 for months.
Agile meant that the stakeholders could be present in our planning meetings. We had to explain to them - somehow - that it took time to upgrade the web framework from v1 to v5 because no one has been using v1 for years, and in general, it slowed everyone down. Since we didn’t know how to explain this to a non-coder, someone came up with the condescending term “technical debt” — “those spreadsheet monkeys wouldn’t understand what we do here!”
While the term has most likely run its course as a manipulative verbal device, it is absolutely the right term to use amongst ourselves to reason about risks and to properly triage them.
我们过去管它叫 technical debt(技术债)——这个词如今越来越不受待见。技术债这个概念流行起来的时候,正好是我们迷上“proh-cess”(流程)和 Agile 的时候。我们受够了死亡行军项目、拍脑袋的截止日期,以及工作普遍缺乏结构和透明度。每个软件项目都像一次巡演——浮出水面喘口气,然后又在 💩 里泡几个月。
Agile 意味着 stakeholder 可以出现在我们的计划会上。我们得想办法向他们解释:把 web framework 从 v1 升级到 v5 需要时间,因为没人用好多年的 v1 了,而且它拖慢了所有人。既然不知道该怎么跟非程序员讲清楚,有人就发明了“技术债”这个居高临下的说法——“那些做表格的猴子哪懂我们在这儿干什么!”
虽然作为操控话术,这个词大概已经过时了,但在我们自己人之间,用它来推理风险、给风险分级,绝对合适。
The three type of technical debt
The word “debt” has negative connotations for sure, but just like with actual monetary debt, it’s never great but not always horrible. To mutilate the famous saying - you have to spend code to make code. I would categorize technical debt into three types — Aesthetic, Deferrable, and Toxic. A mark of a good engineer is knowing when to create technical debt, what kind of debt, and when to repay it.
三种技术债
“债”这个词当然带负面含义,但和真正的金钱债务一样,它从来都不算好事,却也不总是坏事。把那句名言篡改一下——你得先花代码,才能挣代码。我把技术债分成三类:审美债、可延期债和有毒债。一个好工程师的标志,就是知道什么时候该制造技术债、制造哪种债,以及什么时候该还。
Aesthetic debt
This is the kind of stuff that triggers your OCD but does not really affect your users or your velocity in any way. Maybe the import sort order is an eyesore, or maybe there is a naming convention that is grinding your gears. It’s something that can be addressed with relatively low effort when you are good and ready, in many cases with proper automated code analysis and tools.
审美债
这类债会触发你的强迫症,但并不真正影响用户,也不影响开发速度。也许是 import 排序顺序看着难受,也许是某个命名规范让你抓狂。等你准备好了,用相对低的成本就能解决,很多时候靠自动化代码分析和工具就行。
Deferrable debt
Deferrable debt is what should be refactored at some point, but it’s fairly contained and will not be a problem in the immediate future. The kind of debt that you need to minimize by methodically crossing it off your list, and as long as it seeps through into your sprint work, you can probably avoid a scenario where it all gets out of control.
Sometimes this sort of thing is really contained - a lone hacky file, written in the Mesozoic Era by a sleep-deprived Jamie Zawinski because someone was breathing down his neck. No one really understands what the code does, but it’s been humming along for the last 7 years, so why take your chances by waking the sleeping dragons? Slap the Safety Pig on it, claim a victory, and go shake down a vending machine.
可延期债
可延期债是那种迟早该重构,但范围相当可控、短期内不会出问题的债。你需要有条不紊地把它从清单上划掉,只要它能渗透进每个 sprint 的工作里,大概就能避免彻底失控。
有时这类债真的很局部——一个孤零零的 hacky 文件,中生代的时候由睡眠不足的 Jamie Zawinski 写出来,因为当时有人在他背后催命。没人真正明白这段代码在干什么,但它已经稳定跑了 7 年,何必冒险唤醒睡龙?给它贴上 Safety Pig,宣布胜利,然后去摇自动售货机吧。
Toxic debt
This is the kind of debt that needs to be addressed before it’s too late. How do you identify “toxic” debt? It’s that thing that you did half-way and now it’s become a workaround magnet. “We have to do it like this now until we fix it - someday”. The workarounds then become the foundation of new features, creating new and exciting debugging side quests. The future work required grows bigger with every new feature and a line of code. This is the toxic debt.
有毒债
这类债必须趁早处理,晚了就来不及。怎么识别“有毒”债?就是那种你当初只做了一半,现在变成了 workaround 磁铁的东西。“在修好之前,我们只能先这样——总有一天会修的。”然后这些 workaround 成了新功能的地基,制造出一个个刺激的新 debug 支线任务。每加一个新功能、每写一行代码,未来要还的债就更大。这就是有毒债。
Lack of tests is toxic debt
Not having automated tests, or insufficient testing of critical paths, is tech debt in its own right. The more untested code you are adding, the more miserable your life is going to get over time. Tests are important to fight the debt itself. It’s much easier to take a sledgehammer to your codebase when a solid integration test suite’s got your back. We don’t like it, it’s upfront work that slows us down, but at some point after your Minimal Viable Prototype starts running away from you, you need to switch into Test Mode and tie it all down — before things get really nasty.
缺测试是有毒债
没有自动化测试,或者关键路径测试不足,本身就是一种技术债。你加入的未测试代码越多,日子就会越过越惨。测试是抗击债务本身的重要手段。当有一套扎实的集成测试兜底时,拿大锤改造代码库会容易得多。我们不喜欢它,前期投入会拖慢速度,但当你的 Minimal Viable Prototype 开始失控到一定程度,就得切换到 Test Mode,把一切固定住——赶在事情变得真正糟糕之前。
Lack of documentation is toxic debt
I am not talking about a War & Peace sized manual or detailed and severely out of date architecture diagrams in your Google Docs. Just a set of critical READMEs and runbooks on how to start the system locally and perform basic tasks. What variables and secrets do I need? What else do I need installed? If there is a bug report, how do I configure my local environment to reproduce it, and so on.
The time taken to reverse-engineer a system every time has an actual dollar value attached to it, plus the opportunity cost of not doing useful work.
缺文档也是有毒债
我说的不是《战争与和平》那么厚的手册,也不是 Google Docs 里那些详细却严重过时的架构图。只需要一套关键的 README 和 runbook,说明怎么在本地启动系统、怎么完成基本任务:需要哪些变量和 secret?还需要装什么?有人报了 bug,我该怎么配置本地环境来复现?诸如此类。
每次都要重新逆向理解系统,这背后有实打实的金钱成本,再加上没做有用工作的机会成本。
Put. It. In. A. Card.
I love creating TODOs. They are easy to add without breaking the flow, and they are configured in my IDE to be bright and loud. It’s a TODO — I will do it someday. During the Annual TODO Week, obviously. Let’s be frank — marking items as “TODO” is saying to yourself that you should really do this thing, but probably never will.
This is relevant because TODO items can represent any level of technical debt described above, and so you should really make these actual stories on your Kanban/Agile boards.
把它放进卡片里。
我很喜欢建 TODO。它们加起来方便,不打断心流,而且在我的 IDE 里被配置得又亮又吵。这是 TODO——我总有一天会做的。当然是等到一年一度的 TODO 周。说白了,把事项标成“TODO”,就是在告诉自己:这事真该做,但多半永远不会做。
这很重要,因为 TODO 可以代表上面任何级别的技术债,所以你真该把它们变成 Kanban/Agile 板上的实际 story。
Mark technical debt as such
You should be able to easily scan your “debt stories” and figure out which ones have payment due. This can be either a tag in your issue-tracking system or a column in your Kanban-style board like Trello. An approach like this will let you gauge better the ratio of new feature stories vs the growing technical debt. Your debt column will never be empty — that goal is as futile as Zero Inbox, but it should never grow out of control either.
// TODO: conclusion
Further reading
The Big Rewrite, revisited
Five steps for managing legacy code
给技术债打上标记
你应该能轻松扫一遍“债务 story”,看出哪些已经到还款日了。可以在 issue-tracking 系统里加标签,也可以在 Trello 这样的 Kanban 板上开一列。这样能更好地衡量新功能 story 与不断增长的技术债之间的比例。你的债务列永远不会空——这个目标跟 Zero Inbox 一样徒劳,但它也绝不该失控。
// TODO: conclusion
延伸阅读
《The Big Rewrite, revisited》
《Five steps for managing legacy code》