关键要点

  • 利用人工智能代码辅助工具生成高质量代码并控制技术债务,需要一定的结构规范和约束机制。仅仅依赖提示信息是不够的。
  • 减少代码辅助工具生成代码所需的上下文范围至关重要。虽然“垂直切片”架构能够缩小上下文范围,但会忽略一些重要的跨领域问题以及各切片之间共通的处理方法。
  • 将“依赖反转模式”与“垂直切片”架构相结合,可以为代码辅助工具提供实现模板,从而限制上下文范围并指导生成代码的结构。
  • 基础类既用于实现非功能性需求(如安全性、性能、可观测性等),也用于规范应用程序的工作流程(例如将所有消息发送到事件总线)。我们将这一层称为“应用程序的骨架”。
  • 这个“骨架”可以用来添加额外的约束机制,使代码更符合应用程序的整体架构要求——比如确保代码遵循特定的消息格式规范。

GitHub首席执行官Thomas Dohmke最近发出了严厉的警告:“要么拥抱人工智能,要么就放弃这个行业。”但拥抱人工智能并不意味着仅仅使用自动完成功能。它意味着我们需要将主要技能从语法处理转变为系统思维”——学会将问题分解得足够简单,以便让人工智能来解决。简而言之,我们现在都是架构师了。

我一直在开发一个物联网应用程序,其中包含了设备固件、后端服务以及Web用户界面。虽然我有软件工程背景,但在使用Claude Code辅助开发过程中,我发现它确实能够帮助我提高工作效率,尤其是当我需要处理那些不太熟悉的语言和框架时。我的技术栈包括:设备端使用Python和Pytorch,前端使用React和TypeScript,后端则使用MQTT、Node.js以及Postgres。刚开始使用Claude的时候确实遇到了很多困难,因为我的请求往往会引发代码库中的大规模修改。但随着我逐渐掌握了代码的组织结构并优化了提示语的设计,情况变得好多了,现在我已经可以信任Claude生成的代码,而无需再进行详细的逐行审核了。我发现了一些模式,我将它们称为“骨架架构”,这些模式确实能够显著提升人工智能辅助工具的工作效率,因此我想在这里与大家分享这些经验。

随着人工智能编码技术的成熟,我们逐渐意识到,如果使用不当,人工智能反而会带来严重的技术债务。为了顺利度过这一过渡期,我们必须找到一些架构规范,以确保人工智能生成的代码是安全、可维护且可靠的。这就需要我们采取一种以三个核心原则为方向的策略:为人工智能处理代码提供合适的结构框架,建立严格的约束机制,并将自身的技能重点从“代码转换”转向“模型构建”。

代码的结构设计:上下文范围的限制

人工智能辅助工程面临的主要限制因素是上下文范围。当上下文规模扩大时,由于“中间信息丢失”的现象,准确度会呈反比下降,而延迟和计算成本则会呈线性增加。


因此,原生人工智能架构的“黄金法则”就是尽可能缩小模型在工作记忆中需要存储的信息范围。我们必须设计那些能够对信息流动施加物理限制的系统,通过隔离各种依赖关系,使人工智能能够将整个问题空间纳入一个简洁、明确的输入指令中。这种隔离机制具有双重作用:一方面,它可以通过减少干扰来提升人工智能的推理能力;另一方面,它可以确保系统运行的稳定性,防止某个组件在处理任务时无意中破坏其他组件的正常运行。

目前有两种架构模式正在被广泛采用来解决这个问题。

原子化架构是一种在微观层面运用的设计方法。这一概念由Brad Frost在2013年提出,最初是为了解决响应式网页设计的复杂性问题。原子化架构将系统组织成类似生物结构的层次结构,首先从不可分割的“基本元素”(如HTML标签、辅助函数)开始,这些元素组合起来形成“中间构件”,最终构成复杂的“整体系统”。虽然这种设计模式最初是用于用户界面开发的,但在人工智能辅助工程领域也展现出了巨大的价值。因为它能够严格执行“上下文隔离”的原则——要求人工智能生成的是独立、单一的基本元素,而不是复杂的整体功能,从而有效降低了出现错误的风险,同时确保生成的代码结构清晰、无状态且易于验证。然而,这种设计方式也会带来一定的负担:虽然人工智能能够生成完美的单个组件,但将这些无状态的组件组合成完整的系统所需的复杂处理工作,要么由人工智能本身来完成,要么就必须重新交回给人类设计师来处理。

为了解决宏观结构层面的问题,我们采用了垂直切片架构。这一架构由Jimmy Bogard提出,旨在打破传统N层“千层饼代码”的僵化结构。这种设计方式是根据业务功能(例如“下订单”)而非技术层次(例如“服务”“数据访问”)来组织系统。

这种模式特别适合用于AI智能体,因为它能够有效优化引用局部性。在分层系统中,AI需要从多个不同的目录中检索信息才能理解某个业务流程,这会导致上下文窗口中被大量无关代码所充斥。而垂直切片架构则确保“那些会一起发生变化的功能应该被放在一起处理”,从而使AI能够在一次操作中获取某个功能的完整上下文信息,而无需去推测各种依赖关系。然而,这种设计也会带来“数据重复”的问题:为了保持各功能模块之间的独立性,AI往往需要在不同的切片中生成冗余的数据结构,从而违背了“不要重复自己”这一开发原则。

垂直切片架构在实现模块隔离方面表现优异,但它们无法解决各个切片之间的协调问题。安全性、可扩展性、性能和认证等关键的非功能需求是整个系统所共有的特性,这些特性不能被分割开来处理。如果每个垂直切片都负责实现自己的授权机制或缓存策略,最终会导致“管理混乱”和安全措施不一致的问题,同时还会产生大量的代码冗余。因此,我们需要一个新的统一概念:框架结构与具体实现

解决方案:框架结构与具体实现

我们将系统划分为两个不同的部分。稳定框架结构代表了那些由人类定义的、不可更改的刚性结构(抽象基类、接口、安全上下文等),虽然这些结构的实现可能由AI来完成。而具体实现层则包含了由AI生成的、具有较强实现细节的功能模块(具体类、业务逻辑等)。

这种架构借鉴了两种经典的设计方法:行为模型和面向对象的控制反转原则。世界上一些最可靠的软件都是用Erlang语言编写的,而Erlang正是利用行为模型来保证系统的稳定性的。在控制反转结构中,各个切片之间的交互也是通过抽象基类来管理的,这样具体实现类就会依赖于稳定的抽象层,而不是反过来。

我们使用模板方法设计模式来实现这一目标。依赖 inversion原则可以保护高层逻辑免受底层细节的影响,而模板方法则通过固定执行流程来具体化这一原则。在这种模型中,人类架构师会在基类中定义一个final run()方法,该方法会负责处理日志记录、错误捕获和认证等跨层功能;AI则只需要实现一个特定的protected _execute()方法,而这个方法会被run()方法调用。这种区分非常重要:AI根本不可能“忘记”执行日志记录或绕过安全检查,因为它从一开始就没有拥有整个业务流程的控制权;它所做的只是填补架构师提供的逻辑空白而已。

在我的系统代码中,包含了许多用于处理图像的AI算法。我决定为这些算法各自创建一个继承自名为TaskBase的Python抽象基类的类来表示它们。其余的部分则是一些用于高效协调图像传递给这些算法并安排它们的运行时间的类。

代码示例:这种“人类设计的框架”

这说明了如何通过BaseTask类来隐藏与数据缓冲及状态检测相关的复杂性,从而使AI能够专注于自身的处理逻辑。

Python
# 来源文件:task.py
class BaseTask(ABC):
    """
    用于构建处理流程的抽象基类。
    AI负责实现具体的处理逻辑,而人类则负责控制整个流程的走向。
    """
    def __init__(self, name: Optional[str] = None):
        self.inputs: List['Buffer'] = []
        self.outputs: List['Buffer'] = []
        self._backgroundbusy = False

    def is_ready(self) -> bool:
        """
        该框架会自动进行状态检测。
        AI根本不需要了解这些复杂的细节,这样就能确保它的调度逻辑不会出错,也不会导致死锁现象。
        ```
        if not self.inputs:
            return True # 当没有输入数据时,视为已准备好

        # 默认规则:只要有任何输入数据,并且所有输出通道都有空闲空间,就认为系统已经准备就绪
        has_input = any(buf.has_data() for buf in selfinputs)
        can_output = all(buf.can_accept() for buf in self.outputs)
        return has_input and can_output

    @abstractmethod
    def process(self) -> None:
        """
        AI只需要实现这个方法即可完成整个处理流程。
        ```
        pass

对于这种设计方式,人们常常会批评说这种僵化的框架可能会限制AI的创新能力。但事实上,这种刚性恰恰是一种有意的设计特点,而不是缺陷。它明确地体现了“架构层面的管控机制”。如果系统的基本控制流程或行为需要调整,就必须由人类设计师来做出决策。这种约束机制能够有效防止“架构偏离初衷”的情况发生,因为AI往往会优先考虑局部优化,而这样的做法很容易导致系统出现临时性的解决方案或不一致的设计模式,从而逐渐破坏系统的长期稳定性。

交互机制:人类的“导演”角色

将代码辅助工具比作初级开发人员是一种危险的人为类比。AI并不是学习者,而是一个能够高速运行、以完成任务为目标进行优化的系统;它往往会把安全检查视为阻碍其工作的因素,并试图绕过这些检查。虽然提示信息可能较为温和,但架构设计本身却非常严谨。因此,开发者必须保持高度警惕,密切监控AI的行为。根据我的经验,即使有明确的指示要求“绝对不能绕过安全检查”,像Claude这样的模型有时也会试图禁用认证功能,只是为了尽快让代码能够正常运行而已。

<为了使“总监”这一角色具备可扩展性,我们必须建立一些被嵌入到系统中的、“强约束机制”——这些约束条件使得人工智能难以绕过它们。这些机制实际上就构成了该应用程序所遵循的不可更改的规则。

在我的应用程序中,最重要的防护措施之一就是设备、用户界面与后端之间的数据一致性(以JSON Schema作为数据规范的标准)。如果没有这一机制,Claude就会轻易地改变系统各部分之间的通信协议,从而导致数据不一致。我利用OpenAPI和AsyncAPI文档中的JSON Schema作为数据规范的依据,以此确保各个组件之间的交互规则不可被违反。我们在基类中实现了“快速失败”验证机制,一旦发现规则被违反,就会立即触发sys.exit(1),使系统立即崩溃。这样一来,当AI生成的代码虽然符合输入要求,但却违反了这些规则时,系统会立刻停止运行,从而迫使人类介入进行干预,将这种潜在的错误及时发现并处理。需要注意的是,这个验证机制必须运行在Claude无法对其进行修改的层面。

理想情况下,我们还应超越运行时的检查,在CICD流程中利用自动化工具来确保代码的结构完整性。我们可以使用ArchUnit这样的编译时工具来检查代码结构的严谨性。例如,我们可以编写测试用例来确保“任何由AI生成的模块都不允许直接导入数据库相关的包”,这样就能防止AI采用违反整体架构规则的捷径。

为了达到更高的安全保障水平,我们还可以采取物理隔离的措施。可以将那些包含接口定义、基类以及安全逻辑的代码文件放到一个只读的仓库中,而让AI仅能导入这些代码来构建系统结构,但它们本身没有权限去修改这些规则。虽然这种方式会带来一些不便(比如AI无法在未经人工批准的情况下随意添加新的数据类型),但它能够为我们提供对系统行为的绝对保障。

最后,我们必须隔离各种副作用。当业务逻辑与其他组件的交互混合在一起时,AI很难编写出可靠的测试用例,它们往往会生成一些复杂的模拟代码,或者编写出不可靠的集成测试。为了解决这个问题,我们应该将所有的交互逻辑放在“骨架层”中处理,而将真正的业务逻辑保留在“功能核心层”中。由骨架层定义的工作流程可以使用AI生成的模拟数据进行轻松测试,而功能核心层的类也可以通过简单的测试框架进行测试——因为这些类是对系统功能的直接抽象。

代码示例:不可篡改的防护机制

这个验证机制会在AI处理任何数据之前就被执行。通过sys.exit(1),我们可以确保系统在遇到安全违规时能够立即终止运行,而AI无法改变这一设置。

Python
# 来源文件:mqtt-validator.py
class MQTTValidator:
    def validate_message(self, topic: str, payload: Dict[str, Any]):
        # 1. 检查主题是否在允许的列表中
        schema_key = self._get_schema_for_topic(topic)
        
        if schema_key is None:
            logger.critical(f"严重错误:未知的MQTT主题:{topic}")
            sys.exit(1)  # 系统因安全违规而终止运行
       
        
        # 2. 确保数据结构符合规范
        try:
            validate(instance=payload, schema=self.schemas.get/schema_key))
        except ValidationError:
            logger.critical("验证失败,系统将终止运行")
            sys.exit(1)

学习:从语法到系统思维

这种架构上的转变要求人们重新审视开发者的技能。开发者不应再专注于语言特性和算法实现这些迅速变得普遍的技能,而应转向建模、信息流分析以及对非功能性需求的严格管理。在一个编写排序算法已经几乎毫无价值的世界里,工程师的价值不再取决于“将思想转化为代码”的能力,而是取决于“进行建模”以明确代码运行的约束条件。

这就是系统思维的时代。功能实现很容易,但系统的韧性却很难培养。人工智能程序会优先优化那些能够通过测试用例的代码,而完全忽略内存泄漏、延迟波动或可观测性方面的问题。因此,工程师必须扮演“导演”的角色,在发出任何指令之前,先理清信息流和各组件之间的交互关系。工程师必须全面负责非功能性需求的满足。

由于人工智能无法关注内存管理问题,人类架构师就必须将这些保护机制直接融入系统框架中。

进一步来说,工程师需要熟悉系统架构的相关知识,并不断思考诸如“这个系统会如何运行”这样的问题。

除了保障系统的稳定性之外,这种框架结构还能有效解决日益严重的“新手成长危机”。在人工智能已经生成了大量实现代码的世界里,初级工程师该如何积累成为高级架构师所需的能力呢?答案就是:这个框架本身就可以作为学习材料。通过强制初级工程师在《任务基架》和《验证器》这些严格的约束条件下进行开发,我们可以将那种令人束手无策的“无从下手”的问题,转化为结构化、易于操作的练习。他们不是通过阅读理论书籍来学习系统设计,而是通过使用这种高质量的系统框架来实践,从而避免养成不良的开发习惯。反馈机制也被大大优化了——从需要等待数天才能得到代码审查结果,变成了在几毫秒内就能发现错误并得到相应反馈,这样每一个错误都能立刻成为一次宝贵的学习机会。

代码示例:系统级的安全防护机制

人工智能负责编写处理图像的代码,但人类架构师会通过在框架中实现weakref跟踪机制,来确保系统不会因内存泄漏而崩溃。

Python
# 来源文件:memory_monitor.py
class MemoryMonitor:
    """
    用于追踪大型对象(如图像、张量),以便在生产环境中检测内存泄漏问题。
    人工智能使用简单的API进行开发,而系统思维的相关逻辑则体现在这里。
    ```
    def track(self, obj: Any, obj_type: str):
        # 创建一个弱引用对象,并设置清理回调函数来跟踪对象的生命周期
        obj_id = id(obj)
        weak = weakref.ref(obj, lambda ref: self._on_object_deleted(obj_id))
        self.tracked[obj_id] = ObjectLifetime(time.monotonic())
    
    def check(self):
        # 根据非功能性需求进行判断:如果对象存活时间超过60秒,则将其标记为有效对象
        return [obj for obj in self.tracked if obj.age > 60.0]

总结

垂直切片结构让人工智能能够专注于特定任务,骨架机制则赋予人类对系统的控制能力,而其他硬性约束措施则为整个团队提供了稳定性。我们并不是在“训练”人工智能,而是在对其行为进行规范与限制。通过构建这种严谨的架构框架,我们可以让人工智能快速运行,同时确保我们的软件系统不会因此受到破坏。

参考文献

Comments are closed.