Published on

《软件设计的哲学》给AI时代程序员的启示

Authors
  • avatar
    Name
    Ethan
    Twitter

在每年看书的份额里面,我会固定一部分,留作本职工作相关的领域。自从AI代替了编码之后,手写代码量趋近于零。一直在思考一个问题,这种新形态下的程序员,在编程领域,该看什么书呢?

目前我还不能很好的总结出一个普适的规律,但拿起一本书,凭直觉,翻看一下目录和介绍以及前面几页,大致就能得知这本书适不适合AI时代。

《软件设计的哲学》就是本适合AI时代程序员阅读的书。

输入prompt,看着coding agent在疯狂的输出代码,此时的程序员,应该思考些什么呢?这本书给我的答案是:降低复杂性。

复杂性的本质是什么?使人难于理解和修改系统的所有因素即是复杂性。

但是修改不是已经交给AI了吗? 对于AI来说,修改代码仍有难和易的区别吗?AI既然都可以照着你的需要做出修改,那难理解的代码,又有什么影响呢?

复杂性有2个层面:实现的复杂性和认知的复杂性。

一个程序可以实现的复杂性很高,但是认知的复杂性很低。最典型的是操作系统,实现复杂性不可谓不高。但是它的接口和抽象设计得好,使用者,包括系统开发者,不需要理解内部细节,就能使用和调接口做开发,这就做到很低的认知复杂性。

反过来,一个实现未必复杂,比如就几百行代码,但充满了各种隐藏状态,不符合直觉的抽象,难于跟踪的数据流,没有注释,导致你要修改它时,必须得读懂每一行代码,以及询问每一个参与的程序员背后的缘由,这就是很高的认知复杂性。

实现的复杂性,决定机器需要处理多少问题。  认知的复杂性,决定人需要理解多少问题。AI帮我们解决了实现的复杂性,以及部分的认知复杂性,剩下的,仍得有程序员,确切的说,由人和AI来共同把控和承担。

战术性编程和战略性编程,是《软件设计的哲学》中提出的两种概念。 区别在于前者面向更快的完成任务,让某些东西能运作起来工作;而后者能认识到可以工作的代码是不够的,而以投资的心态,努力降低系统的复杂性。

AI最擅长的恰恰是战术性编程,它可以用极快的速度完成任务,受限于上下文和隐性知识的关系,AI编程基本没有什么战略可言。它不能判断这个业务规则本来是否合理;它也不能判断这套架构半年后是否还能支撑业务;它不确定哪些历史包袱的代码可以忽略或必须得留意,诸如此类。

即使在战术性编程的范畴,AI虽然可以理解最难的代码,但并不意味着AI理解起来没有成本。AI的认知也是有边界的。比如遇到复杂性高的代码,AI前后两次分析得出的结论可能不一致,去验证这些结论是否可靠也需要更高的成本。

书中解释了很多降低复杂度的方法,刷新我认知的一点是,模块应该设计的深一点。

图片

如图左边的矩形,顶部的那条边,是系统暴露出的接口,它越短代表接口越简单,矩形的深度,则是系统内部的实现,越是深的矩形,说明它隐藏了越多的复杂度。从而让使用者只需要面对整体复杂性的一小部分即可。 反例是右边的矩形, 它的顶部边长很长,说明接口理解起来比较难,而整个矩形比较浅,隐藏的信息不多,对于整体复杂度的降低没有起到很大的作用。

我认为Python里面最经典的一个深模块的例子是:

    response = requests.get(url)

接口基本只需一个url参数, 而模块内的深度,对外隐藏着TLS握手, HTTP 请求编码与解析,重定向的处理,内容解压等等。

反例是:

    def get_user_name(user):

几乎没有隐藏复杂性,也没有提供额外能力.

这个准则无论在手写时代还是在AI时代都适用。 想想看, 当AI输出一个上千行代码的深类时,我们只需要扫一眼类名和少许几个简单的接口,即可掌握整个类模块的用途,不需要去看那一千行代码是怎么写的。这就做到了实现复杂性高,认知的复杂性低。

之所以说这一点颠覆了以前的认知,是因为我以前认为,一个类或者模块,大到一定程度,比如上千行,就应该去重构和拆分一下,让它由更小的模块组合而成。如图所示,a可以重构成b或c或d 3种。只有b模式有成效。

图片

这个观点在手写时代是有一定道理的。因为手写时我们必须得考虑实现的复杂性,更小的模块组合,对于我们实现上的理解,是更有帮助的。而现在不需要考虑实现复杂性时,一个模块的几千行,无非就是多了一点token。只要保证它暴露的接口足够的简洁,我们就可以用这一点点的认知成本,去四两拨千斤的get到这整个模块。

深模块的模式,自然而然的引出来注释的问题。思考注释怎么写之前,得牢记一个点,我们的目的是降低复杂性。写注释,是降低复杂性的重要手段。在AI时代甚至可以说,是最重要的手段。因为我们很少再看代码本身了,看注释成了理解系统的首要路径。

以前我们会讨论优秀代码是否需要注释的问题,这个问题在AI时代失去了意义。在深模块里,写上一段抽象良好的顶部注释,已成为了一个认知地图,是人机沟通的一个重要渠道。

那什么是抽象良好的注释呢?抽象是代码的一个简化视图。代码太详尽了,代码之所以是代码,是因为编译器和操作系统,需要这样详尽的操作系列去遵照执行。但对于人理解代码,不需要那么的详尽,我们只需要在更高一级的抽象层次上明白代码的意图即可。

class TokenBucketRateLimiter:
    """
    Thread-safe rate limiter based on the token bucket algorithm.

    Guarantees fixed-capacity traffic shaping with smooth burst control.
    Calls to consume tokens are non-blocking by default and return immediately
    with availability status.

    Key Assumptions:
    - Time accuracy depends on the system clock.
    - State is maintained in-memory and does not persist across restarts.
    """

所以抽象良好的注释,简单的标准是无需阅读代码,即可获得模块的必要信息。这些信息有一部分是通过"蒸馏"代码得出,这部分AI可以代劳。另一部分,还隐藏在开发者脑中,或者其他模块当中,也或者是团队的共识当中。怎样书写提炼这些信息,将成为AI时代优秀程序员与普通程序员的分水岭。

软件设计的哲学》书中还举了很多降低复杂度的途径,在此只挑了我比较有共鸣的两点。AI时代,程序员需要关注的是,如何创造更少复杂性的系统。未来也属于那些能够驾驭复杂性的人。