编程与软件工程number-none.com编辑精选
John Carmack 谈内联代码
John Carmack on Inlined Code
2014 — 2024,它一次次回到首页
黑点是上首页(≥ 50 分)的那次投稿,空心点是没冲上去的投稿,点越大分数越高。
导读
这是 John Carmack 关于代码内联与函数拆分的心得笔记:他认为只被调用一次、且会修改状态的函数,拆出去反而降低可读性、更容易引入 bug,很多情况下直接内联在主流程中更好。文中还包含他关于延迟与响应性的实践反思。
为什么经久不衰:它挑战了“函数越短越好”的流行教条,提醒人们代码组织应服务于可读性和正确性,这一争论至今仍在持续。
HN 评论精选
提出折中方案:用形似函数定义的代码块就地组织中间步骤,兼顾可读性与局部性。
我年纪越大,代码就越趋向于函数式和单静态赋值风格。我常遇到这种情况:一个大函数由许多清晰可分的小步骤组成,每步只在一个地方被调用。拆成小函数有利有弊——大函数变成一串描述性函数名更易读,但要理解中间步骤就得跳来跳去看各函数内部。我的折中方案是用类似函数定义的控制块把中间步骤就地组织起来。
corysama查看英文原文
The older I get, the more my code (mostly C++ and Python) has been moving towards mostly-functional, mostly-single static assignment (let assignments). Lately, I've noticed a pattern emerging that I think John is referring to in the second part. The situation is that often a large function will be composed of many smaller, clearly separable steps that involve temporary, intermediate results. These are clear candidates to be broken out into smaller functions. But, a conflict arises from the fact that they would each only be invoked at exactly one location. So, moving the tiny bits of code away from their only invocation point has mixed results on the readability of the larger function. It becomes more readable because it is composed of only short, descriptive function names, but less readable because deeper understanding of the intermediate steps requires disjointly bouncing around the code looking for the internals of the smaller functions. The compromise I have often found is to reformat the intermediate steps in the form of control blocks that resemble a function definitions. The pseudocode below is not a great example because, to keep it brief, the control flow is so simple that it could have been just a chain of method calls on anonymous return values. AwesomenessT largerFunction(Foo1 foo1, Foo2 foo2) { // state the purpose of step1 ResultT1 result1; // inline ResultT1 step1(Foo1 foo) { Bar bar = barFromFoo1(foo); Baz baz = bar.makeBaz(); result1 = baz.awesome(); // return baz.awesome(); } // bar and baz no longer require consideration // state the purpose of step2 ResultT2 result2; // inline ResultT2 step2(Foo2 foo) { Bar bar = barFromFoo2(foo); // second bar's lifetime does not overlap with the 1st result2 = bar.awesome(); // return bar.awesome(); } return result1.howAwesome(result2); } I make a point to call out out that the temp objects are scope-blocked to the minimum necessary lifetimes primarily because doing so reduces the amount of mental register space required for my brain to understand the larger function. When I see that the first bar and baz go out of existence just a few lines after they come into existence, I know I can discard them from short term memory when parsing the rest of the function. I don't get confused by the second bar. And, I don't have to check the correctness of the whole function with regards to each intermediate value.
批评机械执行“函数不能太长”规范的做法,认为盲目拆分函数反而损害可读性。
John 的观察——只被调用一次且修改状态的函数其实更难读、更易出 bug——与许多公司编码规范中对函数大小的武断上限相悖。于是工程师们把 250 行的大函数无谓地拆成五个 50 行的小函数,看似合规,但未必更可读,甚至更易出 bug:LittleFunction4() 改了 LittleFunction5() 依赖的状态变量吗?谁知道呢,只能去翻源码。
ryandrake查看英文原文
John's observation, that having state-mutating functions that get called only once is actually less readable and more bug-prone, flies in the face of many companies' coding style guidelines that place an arbitrary upper limit on the size of functions. So you have engineers needlessly refactoring 250-line BigFunction() into: BigFunction() { LittleFunction1(); // 50 lines LittleFunction2(); // 50 lines LittleFunction3(); // 50 lines LittleFunction4(); // 50 lines LittleFunction5(); // 50 lines } Which complies with the ill-advised policy, but is often not any more readable, and could be more bug prone. Does LittleFunction4() change some state variable that LittleFunction5() depends on? Who knows, better go find the source and check it. Yuck!
提醒这些观点依赖具体语言和场景,不能一概而论。
要记住这些观点高度依赖于语言和应用场景。在其他语言、开发环境(IDE、静态分析器)或领域(非性能关键)中,优先级会不同。总体上代码越少越好,可测试的函数优于复制粘贴的内联代码,可读性和线程安全都很重要。不过很难反驳 C++ 和游戏编程大神本人。另外注意,这些想法写于 2007 年而非 2014 年。
lazyjones查看英文原文
One has to keep in mind that these thoughts are very language- and application-dependent. In some other languages, programming environments (IDE, static analyzers) or even fields (i.e. less performance-critical) priorities change. Generally, less code is better. Testable functions are better than copy/pasted inline code. Readability is important. Thread-safety is important (if your functions mutate some global state, good luck with that, whether inlined or not...). But it's also hard to argue against the C++ and game programming god himself. As stated in the beginning of the article, these thoughts were from 2007 (not 2014).
从业余和教学视角支持大主函数风格,批评填空式编程教育让学生失去全局视野。
我不是职业程序员,但我的代码这些年也稳步走向“大主函数”风格。原因之一是我用了更有表达力的语言和结构(Python、NumPy),可以在一处放下更多内容。做游戏编程时,我绝对更喜欢主循环直接触及游戏各组件,而不是把一切委托给神秘的 entity.update() 之类的函数——所有逻辑按顺序展开,往上滚动就能看到这一帧已发生的事。我尤其不喜欢现在“填空式”的编程教学,学生很少有机会理解整体。
tgb查看英文原文
I'm not a professional programmer and I rarely work with large code bases. So the fact that my code has drifted steadily over the years towards the large-main-function I thought was a factor of several things, first being my general amateurism. I still think that, but there are definitely other reasons too: I now use more expressive languages (Python instead of C) and more expressive idioms within those languages (list comprehensions instead of while loops) and more expressive structures/libraries (NumPy instead of lists of structures), so I can afford to put more in one spot. I also write smaller but more numerous programs. But there are very real advantages. I learned through game programming and still do some for fun and I absolutely prefer having a main loop that puts its fingers into all the components of the game than to have a main loop which delegates everything to mysterious entity.update()-style functions. The lack of architecture allows me to structure the logic of the game more clearly for exactly the reasons Carmack outlines. Everything is sequenced - what has already happened in the frame can be seen by scrolling up a bit instead of digging through a half-dozen files. But the real win here is for the beginner programmer. I strongly dislike the trend these days towards programming education being done in a "fill in the blanks" manner where the student takes an existing framework and writes a number of functions. The problem is that the student rarely has any idea what the framework is doing. I would rather not have beginners write games by make on_draw(), on_tick(), etc. functions but much rather have them write a for loop and have to call gfx_library_init() at program start and gfx_library_swap_buffers() at the end of a frame. That way they can say "The program starts here and steps through these lines and then exits here" versus having magic frameworks do the work for them. There is plenty of magic done these days behind the scenes for any beginner, but it is too much to have a completely opaque flow-control.
历次讨论
同类还值得读
开源应用架构
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编辑精选