编程与软件工程journal.stuffwithstuff.com编辑精选
我写过的最难的程序——代码格式化器如何工作(2015)
The Hardest Program I've Ever Written – How a code formatter works (2015)
2015 — 2025,它一次次回到首页
黑点是上首页(≥ 50 分)的那次投稿,空心点是没冲上去的投稿,点越大分数越高。
导读
这是 Bob Nystrom 回顾自己编写 Dart 代码格式化器(dartfmt)的文章。他指出,格式化器表面上只是“读入字符串、写出字符串”,却因为要全局决定断行与缩进而变得极其困难:一处换行会改变后续缩进,进而影响其他换行,无法简单套用动态规划。文章详细拆解了这些权衡与实现难点。
为什么经久不衰:它揭示了“自动格式化”这一日常工具背后被低估的复杂度,是理解 dartfmt、Prettier 等格式化器设计取舍的经典入口,十年后仍被反复讨论。
HN 评论精选
推荐 Wadler 的代数 pretty printer 算法,优雅且实用。
Philip Wadler 有一篇开创性论文,用代数建模来实现 pretty printer。如今大多数语言都有该算法的实现,还有一些提升性能的新进展。这个算法非常优雅,用起来很愉快。Elixir 的格式化器用了它的变体,标准库里也有。我个人用它写过一个 GraphQL 查询格式化器,代码量很少但效果很强。如果你要做类似的事,一定要看看。
QuinnWilton查看英文原文
Philip Wadler has a seminal paper [0] on implementing pretty printers by modeling them using an algebra. There's implementations of the algorithm in most programming languages nowadays, and some state of the art advances that help with performance [1] It's a very elegant algorithm, and is very pleasant to work. Elixir's formatter uses a variant of it [2], and it appears in the standard library [3]. I've personally made use of it to write a code formatter for GraphQL queries, and it was a very small amount of code for some powerful results. Definitely take a look if you ever need to do something like this. [0] https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf [1] https://jyp.github.io/pdf/Prettiest.pdf [2] http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200 [3] https://hexdocs.pm/elixir/Inspect.Algebra.html
质疑 dartfmt 为何不能用动态规划,推测差异在于代码需要匹配兄弟节点缩进。
“最佳”是整个被格式化语句的属性:一个换行会改变语句其余部分的缩进,进而影响其他换行——抱歉,Knuth,这次没有动态规划。但 TeX 的断行算法也是如此:最佳是整个段落的属性,换行会改变段落其余缩进……可 TeX 用了动态规划。所以值得看看这个问题到底有何不同(为什么动态规划对 TeX 有效、对 dartfmt 无效)。也许 Knuth 选的“最佳”定义恰好适合动态规划;也许 TeX 算法也可能很贵,只是段落与代码语句的可行断点分布不同。补充:一个相关区别可能是,格式化代码时必须把缩进与同嵌套层的前一个兄弟节点匹配。
svat查看英文原文
> Note that “best” is a property of the entire statement being formatted. A line break changes the indentation of the remainder of the statement, which in turn affects which other line breaks are needed. Sorry, Knuth. No dynamic programming this time. For what it's worth, the same is true of TeX's line-breaking algorithm too: “best” is a property of the entire paragraph being formatted; a line break can change the indentation of the remainder of the paragraph (in many ways, but most obviously: discretionary hyphens with nontrivial pre- and post- texts), which in turn affects which other line breaks are needed. And yet TeX uses dynamic programming. So it would be interesting to see how this specific problem differs. (Why dynamic programming works for TeX but not for dartfmt.) Perhaps Knuth chose a definition of "best" that is amenable to dynamic programming (after all he had quite a bit of freedom; it doesn't take much to beat the naive first-fit/best-fit line-breaking algorithms as used in Word etc), or perhaps TeX's algorithm too can be expensive but in practice paragraphs just tend to have a different distribution of feasible breakpoints than code statements. Knuth and Plass's (very readable!) paper on Breaking Paragraphs into Lines is here: http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf Edit: Ah, I think one relevant difference is probably that when formatting code, one has to "match up" indentation with the previous corresponding sibling of the same nesting level, while with paragraphs the amount of required state is smaller: given a particular line break and indentation, the remainder of a paragraph can always be formatted in the same way, while a code formatter also has to "remember" all previous parent expressions' indentation. (This reason is different from what is directly implied by the quoted statement though…)
Prettier 选择简单规则加大量特例,而非穷举加权,认为不存在通用解法。
我在做 Prettier 之前读过这篇,说实话有点吓人 :) 我不确信穷举加权方法是个好主意:计算量极大,而且很难预测打印器会做什么。Prettier 反而基于一个简单想法:如果放不下一行,就断开最外层的父级。这条简单规则(用 Wadler IR 实现)高效且输出通常好看。难的是花约六个月全职,为人类习惯的特殊写法逐个加特例。这里面没有计算机科学层面的难题,只是大量特例;我不相信有通用解法,只能硬啃。最后最烦的是注释,它们无处不在,正确打印超级麻烦。总的来说并不真的难,只是工作量巨大。
vjeux查看英文原文
I read this before working on prettier and it was a bit scary to be honest :) I wasn’t convinced that the exhaustive approach with weigths was a good idea. It makes it computationally super intensive and hard to predict what the printer would do. Instead prettier runs on a simple idea: if something doesn’t fit in one line, break the outermost parent. This simple rule (implemented via the Wadler IR) is efficient to implement and makes the output generally look good. Then the hard part was to go through the very looong (took me ~6 months full time) process of adding special cases for every constructs that human write in a special way. There is nothing in there that’s hard at a computer science level, it’s just a lot of special cases. I don’t believe that there’s a general solution for it, you just need to slog through it. And the last annoying thing are comments, they can be everywhere and are super annoying to print correctly. Overall, it wasn’t really hard, but a lot of work.
自动格式化器无法理解开发者意图,许多大项目因此拒绝使用。
据我所知,没有格式化器能理解意图。比如我希望接收 9 个参数的 canvas.drawImage 调用把参数 2-5 放一行、6-9 放另一行,因为 2-5 是 sourceX/Y/Width/Height,6-9 是 destX/Y/Width/Height。这只是自动格式化器无法理解意图的上百个例子之一。再比如我常对齐数字和宽高,或 if 块该单行还是换行、要不要括号——总有例外能让代码更易读,而自动格式化器不懂这些例外。这就是为什么据我所知许多大项目不用自动格式化器,比如 Firefox、Safari、Chrome;有些地方自动格式化会让代码可读性差得多。甚至 Flutter 团队也不用。
greggman3查看英文原文
AFAIK no formatting can understand intent. Example. I want my canvas.drawImage calls that take 9 arguments to be formatted with arguments 2-5 on one line, and 6-9 on another line because 2-5 are sourceX, sourceY, sourceWidth, sourceHeight, and 6-9 are destX, destY, destWidth, destHeight. That's just one example of 100s where an auto formatter can not figure out intent. Another example, I often align numbers and width and height // convert from screen coords to clip space clipX = mouseX / screenWidth * 2 - 1 clipY = mouseY / screenHeight * -2 + 1 Another example would be how to format an if block. Depending on your style guide if (cond) single-statement might be ok. Or maybe you want them on different lines. Or maybe you require brackets. Or maybe you don't. But there will almost ways be some place in the code where an exception to your style guide makes the code more readable. Auto formatters don't understand those exceptions. This is why AFAIK many major projects do NOT use an auto-formatter. Firefox, Safari, Chrome for example. Because there are places where an auto-formatter will make the code far less readable. They'll give the advice that you are free to run an auto-formatter on your own changes but adding an automated one is a no-go. Even the Flutter team does not use an auto-formatter https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#formatting
历次讨论
同类还值得读
开源应用架构
The Architecture of Open Source Applications
《开源软件架构》(AOSA)是一本在线免费书,邀请知名开源项目的作者亲自剖析其内部设计,涵盖 SQLAlchemy、LLVM、Audacity、Apache、Bash 等系统。
编程与软件工程上首页 9 次 · 2011–2024aosabook.org编辑精选编程语言简史:简短、不完整且多半错误
A Brief, Incomplete, and Mostly Wrong History of Programming Languages
一篇以戏谑、故意不准确的方式回顾编程语言史的博客文章,用夸张轶事和讽刺定义串起 Lisp、C、C++、Java、JavaScript、PHP 等语言。
编程与软件工程上首页 8 次 · 2009–2021james-iry.blogspot.comSQLite 是如何测试的
How SQLite is tested
SQLite 官方文档页,系统介绍 SQLite 的测试体系:多种测试方法(TCL 测试、模糊测试、回归测试、静态分析等)和海量测试用例,展示了这个小型开源项目如何靠极致测试获得…
编程与软件工程上首页 8 次 · 2009–2025sqlite.org编辑精选高级编程语言(2009)
Advanced programming languages (2009)
Matt Might 的经典文章,按用途推荐“高级编程语言”:如果追求就业就选主流语言,如果追求表达力、学术研究或创业,则推荐 Haskell、OCaml、Scala 等函数式语…
编程与软件工程上首页 8 次 · 2009–2022matt.might.net我的 Forth 与栈机器经历
Yosefk - My history with Forth & stack machines
作者以亲身经历回顾了使用 Forth 语言和栈式机器的历程:既赞叹其简洁优雅、代码量小、可自举的优点,也坦率指出栈操作在实际编程中的痛苦,以及 Forth 社区文化中的问题。
编程与软件工程上首页 8 次 · 2010–2024yosefk.com编辑精选