这篇文章就像 Ryan Dahl 的 JavaScript Containers 那样发人深思。尽管我对编程也只是初窥门径,期待十年之后编程会是怎样。
Programming(n. 名词)——the work of writing instructions for computers to perform particular tasks.
https://yoyo-code.com/programming-breakthroughs-we-need/
https://news.ycombinator.com/item?id=32495133
我觉得我们需要一些突破来彻底改变我们开发软件的方式。当我谈论"突破"时,我指的是巨大突破。类似于"结构化编程"那样,完全改变了我们如何思考编程。这里是一些观察和想法。
写胶水代码和模板是一种浪费
我写的大多数代码并没有做什么有趣的事情,他们要么是模板要么是胶水,用于链接各个子系统。感觉好像这种代码在过去已经写过很多,在未来依旧会写很多次。所以,我为什么要再写一遍?
好吧,问题其实是代码足够不同,以至于不能直接使用现成的代码,我必须修改它们已适合我的用法。而且这会以整个软件生命周期的形式带来很多额外的负担。现在我不能只是使用代码,我还必须写出来、测试好、自动化、部署……
更确切地说,对于我关注的大多数 Web 项目,我有着十分相似的 yaml
文件用于 CI(持续集成), Dockerfile
,一些用于压缩图片的脚本,一些脚本用于迁移,一些模板用于路由、授权,一些语言/框架的安装,像
package.json
或 Cargo.toml
,部署配置,第三方集成等等。
然后,通常最大的问题是大量的 CRUD 模板,这些模板在每个项目中看起来非常相似,但在重要的细节上却有所不同。这是 Web 项目的典型情况,但其他类型的软件也有类似的情况。
为什么不使用框架?
一些项目试图把模板打包进一个单一框架中,但这种方法并非总是有效,因为这样会引入引入泛型机制和复杂性,经常隐藏你关心的重要细节。因为这些框架需要经过大量定制,这种框架经常引入自己的胶水和模板代码。
好像这里还有一层抽象未曾探索。
Something where generics, interfaces and higher order functions are too static and low level primitives.
很难确定这里的重复模式是什么以及如何利用它。我想用一种有效的方法来做一些特定的事情,而不是一般的框架来做所有的事情。
一个很好的提示是最近的 GitHub Copilot 开发。它可以轻松地生成许多具有我所描述的"相同但不同"属性的常见样板代码。然而,我认为我们需要更强大的东西,这就把我带到了下一个话题。
修改代码通常行不通
我觉得我们编程的方式并不理想。改变程序是我们在工作最常做的事情,这甚至是我们工作存在的原因。还有,大多数编程人员的时间花在了阅读或计划如何改变代码上。
不仅如此——我们所做的很多活动只是为了避免过早地更改代码或降低更改的风险。这也是为什么我们阅读那么多代码,我们需要很好地理解这些代码以便正确地修改它们。这也是为什么我们整理代码、代码审查以及自动化测试。
程序不是文本
问题是——如果我们想做的大多数工作关于改变现有代码,那么为什么系统在默认情况下没有专门用于改变的优化。我们编写的代码却针对读取源文本及其存储进行了优化。
我们花费了无数的时间来修改语法使其正确、确认缩进级别、选取制表符还是空格,或者确定在文件结构中放置代码的位置。但是这一切都感觉毫无意义。这些都是文本的属性,但是文本只是一个用来操作程序的抽象模型的工具。
程序是一种模型
我把这种模型想象成关系型数据库——你有一些像 structs
, fields
,
functions
, arguments
和关系的表格。当你这样思考的时候,很明显,使用文本源代码来操作这个模型是非常低效的方法。它非常容易出错,并且需要大量额外的处理。
我第一次思考它是重构 Zebra 时,将 C2Rust 的输出转换为安全 Rust。我喜欢用 IntelliJ,因为它能自动化编辑代码,但是对于这个(重构 Zebra)来说还不够。我需要某种更强大的工具。
IntelliJ 能够帮助你做这样的事情,像"extract parameter"或"inline function",我想做这样的事——
for each function that touches this global variable, I want to extract it to a parameter that is
&mut
if it needs to be mutated or&
if it's immutable and move the global into main function as a local variable - and also, do it for all globals in this file.
或者更好,我想确定一个目标,像"想让这个函数不传入这个参数",并让系统决定如何改变程序以实现目标。我能够想象一个系统,它能将小的改变合并成大的,用一些 AI 魔法来组合并实现该种特定目标。
我们需要一个语言吗?
注意,我想要的任何东西都和编程语言无关。我甚至不关心文本如何,文件位于哪里,如何导入,参数顺序是怎样,哪里放括号或括号是否存在。这些都不重要,我们又何必费心呢?为什么不直接切入主题?
还要注意,将重构作为模型上的查询编写实际上并不那么困难。我能够想象我将用短短几行写成像那样的 SQL 查询语句。另一方面,在 IntelliJ 或 VSCode 上写一个自动重构系统听起来像是一个终生难题,而且这个问题无法解决。
为什么?因为我们对待程序像对待文字,所以我们从文字哪里继承了很多复杂度。我们需要考虑导入、格式化、文件系统、类型接口、宏和其他内容。所有这些偶然出现的复杂度,都和输入文本如何映射到程序模型有关。
如果我们转向关注构建合适的模型,我们便能够为了方便编辑而优化它,文本就变成模型的一种视图。如果文本只是,那么就无所谓它是如何写就的。让所有人根据自己需要定制它。我不在乎你是在新的一行加上开括号,我甚至不想在乎。
据我所知,这正是 Dion 项目所探索的。我很期待结果是什么。
测试及正确性
测试与这一切都相关。这里是一句大胆的声明:
软件测试不起作用
不管我们怎么努力,结果都糟透了。编写测试非常耗时,通常无法应变。它很容易应用到具体软件实现中,这使得改变变得困难。测试无法测试我们关心的东西,并且测试过程涉及大量工具和步骤。 唯一比测试更糟糕的就是不测试,但测试也好不到哪去。
编程人员甚至无法就一些基本的事情达成一致,比如什么时候测试、如何测试、单元测试是否有用,或者 TDD 是否是构建软件的唯一正确方法。这是一个很好的暗示,说明我们还没有找到正确的方法。我认为我们需要一个突破,这样才能永远结束这场毫无意义的辩论。
每当有人指出单元/集成/e2e 测试的问题时,总会有一大群人回应说:"你就是做得不对。"关于 TDD 也是如此——关于 TDD 的每句抱怨都会有"如果用正确的方式完成"类似的评论。如果真的是这样的话,测试就真的需要如此多经验和谨慎的工作使得"做得正确"。但是这样值得吗?我们为什么不寻找一些更好的方式测试呢?
我想要更简单的测试
我们有一些很有希望的想法。一些例子,比如强类型系统、模糊测试、快照测试和 sanitizers(不知道当前语境下的意思)。这些似乎都在正确的轨道上,因为它们允许我们通过一个工具来覆盖整个测试维度。
我所想念的是一些通用的机制,使测试超级便宜和有效。这就是为什么我不认为单元测试或任何类型的手工编写测试的系统是正确的。这些方法是有效的,也是有意义的,但它们从根本上就没有扩展性。本周,我回顾了一段包含 90 行测试代码的代码,用于测试一行代码和 2 个测试用例。这可不怎么有效。
你的愿景是怎样?
在上述所有观点中都有一条共同的线索。所有重大突破都需要观点的转变。结构化编程完全改变了我们写代码的方式。我觉得我们需要这个。结构化编程改变了我们看待程序结构的方式。在这里,转变与程序随时间的变化有关。
整个敏捷革命将我们推向这个方向——我们的过程是基于反馈和快速迭代的。我们需要不断地改变事物,用它们做实验。这就是为什么我们需要工具,让我们可以改变所有的时间。我们有一些——我们有 Git,数据库迁移,Terraform,CI 系统,云… 但我们的许多实践仍然阻碍我们。我们受到变化规模的限制。
如果你想象上述所有突破加在一起,你会看到什么样的世界?我看到了一个我们可以快速开发程序的世界,变化的需求比敏捷宣言更受欢迎,编程比以前更加基于变化。
重构甚至不需要作为一个单独的概念或活动存在——编程本身就是这样。我们每天定期更换整个程序。迁移到新数据库?支付提供商?前端框架?您可以这样做,永远不用为它设计接口抽象。你不需要用户界面模型和原型——你只需要编写实际的程序,因为它就是这么简单。如果用户不喜欢它,您可以轻松地完全重组它。
更进一步说,如果一种编程语言的整个概念都不再有意义了,那该怎么办?你只需要有程序模型,如何呈现它并不重要。也许语义并不重要——您可以编写自定义规则作为模型的查询。如果"编程语言"只是一组表和查询,您可以根据自己的需要选择这些表和查询,那该怎么办?
结论(Closing thoughts)
这个愿景会成真吗?我不知道… 也许吧。但我觉得我们已经在朝这个方向发展了。这不会是一夜之间的革命,即使是结构化编程也不会。这些想法中有许多还没有被充分探索,有些甚至不可能实现,谁知道呢。我得说,测试是其中最成熟的。尽管它仍然远远落后于我想要的,我认为大部分的积木都在那里。
对于一些想法,我甚至不知道从何说起。我可以想象基于模型的编程非常容易,但是处理样板/粘合问题似乎更加困难。我们需要什么共同协议吗?或者我们只是用人工智能生成样板?我们有一些测试和使用人工智能生成代码,直到测试通过?如果我们使用基于模型的编程,那么粘合代码看起来会是什么样子呢?还是那个问题吗?
让我们看看这个愿望清单是否会在未来几十年内实现。