今天我重读了一次《重构》,说说我觉得能在AI中用到的几个地方
所谓重构是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入错误的概率。本质上说,重构就是在代码写好之后改进他的设计。
今天外面下着大雨,在家没事儿的时候想起,上周在 MonoRepo 项目中遇到代码的霰弹式修改问题。
顺着这个问题,我又试着回想起更多关于《重构》里提及的“代码的坏味道”以及具体的重构方式。但,除了霰弹式修改之外,我已经想不起来更多了。
心里想着,这本书我之前就已经看过,闲暇时就再翻一遍,加深下印象也挺好。
经过3小时的努力,我终于在天黑之前读完了。
由于这本书里的案例都是基于 Java 的,所以在读的时候,我一直在思考。
“哪些内容是在 Agentic Coding 时能用到的?不仅仅是 Java,在 Node、Python中也能用到的是哪些?”
最后,读完之后又经过一小时整理,我整理出5个大的方向。分别是类的定义、类之间如何传递数据、函数的定义、简化条件表达式、简化函数的调用。
类的定义
如果一个类中的函数或属性与另一个类的交流比与自身所在类更频繁,就应该考虑将其搬移。
简单来讲就是,如果一个属性或者方法,频繁地被另一个类使用,当前所在的类不怎么用,那就应该移过去。
类之间如何传递数据
这一部分主要包含,数据类的设计和状态码的代替两个部分。数据类在 Node、Python 中基本不会使用,那就暂时不讲了,着重讲讲关于状态码的部分。
Replace Type Code with SubClasses 和 Replace Type Code with State/Strategy
当代码里用到 switch 或者 if-then-else 来根据某个值判断处理逻辑的时候,应该将类型和特定的行为封装到一个 SubClass 或者 State/Strategy。
这里需要特别注意下,在 Node、Python 中的 dict 是可以天然支持这种方式的,所以简单的逻辑处理完全可以使用dict,只有复杂的逻辑需要保证好读懂才需要使用这个。
const dispatch = (type: string, num1: number, num2: number): number => { const map: Record<string, (a: number, b: number) => number> = { "a": (a, b) => a + b, "b": (a, b) => a + b - 3, "c": (a, b) => (a + b) / 2, } return map[type](num1, num2) }(不过,对 Agent 来说,写代码的工作量稍微大点也无所谓,这个也是可以直接作为 rule 加入的。)
函数的定义
当代码包含一大串计算时,需要通过引入解释性临时变量(Introduce Explaining Variable)让代码变得可读。同样,如果代码中的处理逻辑特别多,也需要通过提炼函数(Extract Method)让主干代码保持整洁可读。
简化条件表达式
当代码的条件表达式嵌套层级太深,可以用 Guard Clauses。这个是我觉得最需要为 Agent 配置 rule 的地方。
简化函数的调用
Separate Query From Modifier - 尽量不要有副作用的函数,让读和写分开。
Replace Constructor With Factory Method - 使用工厂模式来进行复杂的对象初始化(即除了初始化对象,还可能关联其他的对象方法调用)
Replace Error Code with Exception - 让错误显式抛出,而不是返回一个错误码(这样子很容易通过堆栈知道问题出在哪儿)
这本书是 2003 年出版,里面很多内容已经变成现在的主流 Java 开发规范。很多模式很熟悉,认为就应该是这样写,却不知道为什么。在书中,作者不仅告诉你要这样写,更给出例子来对比改动前后的差异,非常直观地告诉你为什么要遵守这个模式。
重读一遍,我才意识到上周遇到的霰弹式修改,本质上就是职责放错了地方。而重构要做的,正是把这些散落的职责重新归位。
更让我感慨的是,这些原则从 Java 到 Node、Python,再到今天的 AI 辅助编程,软件工程仍然有效。即使这是我第三次重读,依然觉得受益匪浅,好东西还真是经久不衰。
