风格化编程:如何将AI生成的“氛围代码”转化为可维护的工程资产

本文分享了作者如何通过对比原始与重构后的AI生成代码,提炼出一套编码风格指南(AGENTS.md),以提升AI辅助开发代码的可读性和可维护性,涉及Ruby on Rails实践、配置管理和代码组织模式。

以风格进行氛围编码.md — 火星人编年史,Evil Martians团队博客

如今,创始人、设计师、产品经理、市场营销人员——他们具备不同程度的技术背景,但并非工程师——都在大量使用代码生成工具。这感觉很自由,也令人上瘾。但现实检验始于当一位工程师打开你的代码仓库,并(尽管他们尽了最大努力让代码工作)不得不扔掉你“氛围编码”出来的一切,从头重写。如果你想避免这种情况,这篇文章就是为你准备的。这就是我们如何将“氛围代码清理”转变为一项可复用资产的过程,它将火星人工程师的精髓封装进一个小文件:AGENTS.md。

AGENTS.md 是火星人工程师的精华浓缩。在你的下一个AI辅助项目中试试吧!预约通话

Cloud Cards 故事

一些背景故事。在思考更智能的方式来推广旧金山Ruby大会时,Victoria Melnikova 想出了一个主意:让现有参会者基于他们的照片生成带有卡通角色的有趣个人邀请函。

接下来我记得的是:消耗着深夜额度,用 Ruby on Rails、Avo、RubyLLM 和 Nano Banana 构建了这个应用。生成的“云卡片”有时会拼错大会名称,但它们看起来很可爱,并且与我们的品牌形象匹配得几乎过于完美。

茶歇时间:用 Bolt.new 构建 sfruby.com

我尝试将应用部署到 Railway,遇到了一些问题,并与 Vladimir Dementyev 分享了代码仓库和项目的访问权限(方便的是,他是我在 Evil Martians 的同事,也是世界上最杰出的 Web 工程师之一)。

Vladimir 的反馈通常不适合胆小的人,但这次尤为极端:他专注其中,从头重写了整个应用。

当我再次查看这个应用时,它让我想起阅读代码可以是一种享受。这感觉像是奢侈——而且确实是,考虑到投入了多少技能和专注力。

不知不觉中,我有了一个新的瘾好:“生成”看起来像 Vladimir 写的代码。极其优雅且不言自明。

问题:不是每个人都有一位 Vladimir

有一个问题:没有一位世界级工程师会永久待命,花费他们的生命来重写我氛围编码的应用。

我们不能总拥有好东西!

我们确实拥有的是同一个应用的两个版本:原始的 Cloud Cards 和重构后的版本。于是问题变成了:我们能利用它们来教一个模型模仿 Vladimir 的风格吗?

值得一试。

我们的 3 步流程

以下是我们使用的流程:

  1. 让一个思考模型比较两个版本,并生成一份解释关键差异和 Vladimir “风格”的文档。将其视为未来工作的设计文档:在构建下一个应用时,我们可以将其交给模型,这样结果会更接近 Vladimir 自己写的代码。

  2. 将那个大文件 style.md 总结成一个更短、更聚焦的文件 AGENTS.md

    • AGENTS.md 重述了应用背景和用例
    • 它还将关键的风格和结构决策编码为规则和模式
  3. 再次使用思考模型来发现任何技术上的不一致之处并改进文档质量。人工通读也很重要。

这就是最终产生的文件:AGENTS.md。

接下来,我在构建一个新项目时使用了 AGENTS.md —— 这次是一个关于文本编辑和 AnyCable 的实验。几天后,Vladimir 审查了新的代码库。他分享了直接、详细的反馈,但这一次,他说他可以在这个代码基础上工作,而不是将其扔掉。

这算是成功了吗?至少,这是一个有希望的开始!我们已经进步到另一种反馈类别,并且第一次,代码也让我感觉像是能读懂的。

AGENTS.md 内部

免责声明:需要明确的是,这些并非“官方”的 Evil Martians Rails 指南。它们本就不完美,是通过一个非常简单的流程,从单一的重构案例中产生的。但这是一个好的开始,并且已经具有实用性。

那么,AGENTS.md 里到底有什么?出乎意料地,相当多。以下是其中一些规则。

使用领域语言

命名时,使用你在自然语言中会使用的相同“普通”词汇,而不是通用的技术术语。结果是代码不需要图例:名称本身已经不言自明。

  • 差:User, Image
  • 好:Participant, Cloud, Invitation

对状态使用枚举

使用 enum 来表示状态,以获得免费的谓词方法、带感叹号的方法和作用域:

1
2
3
4
5
6
7
8
class Cloud < ApplicationRecord
  enum :state, %w[uploaded analyzing analyzed generating generated failed].index_by(&:itself)
end

# 用法:
cloud.uploaded?          # 谓词方法
cloud.generating!        # 感叹号方法(更新 + 保存)
Cloud.generated.count    # 作用域

将代码从模型中提取到命名空间类中

避免肥胖模型的一个简单组织技巧:让模型专注于数据、关联关系、验证和简单逻辑。将复杂行为移到命名空间类中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Cloud < ApplicationRecord
  # 模型: 只包含数据和简单方法
  def ready_to_generate?
    analyzed?
  end
end

# app/models/cloud/card_generator.rb
class Cloud::CardGenerator
  private attr_reader :cloud, :api_key

  def initialize(cloud, api_key: GeminiConfig.api_key)
    @cloud = cloud
    @api_key = api_key
  end

  def generate
    # 复杂的 API 逻辑放在这里,返回 IO 对象
  end
end

何时提取:

  • 任何超过约 15 行的方法
  • 任何调用外部 API 的方法
  • 任何复杂的计算
  • 任何在多个地方重复使用的代码

使用 anyway_config 进行类型安全配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# config/configs/application_config.rb
class ApplicationConfig < Anyway::Config
  class << self
    delegate_missing_to :instance

    private

    def instance
      @instance ||= new
    end
  end
end

# config/configs/gemini_config.rb
class GeminiConfig < ApplicationConfig
  attr_config :api_key
end

api_key = GeminiConfig.api_key

为何?

  • 类型安全(加载时验证)
  • 单例风格访问
  • 组织在 config/configs/ 目录下
  • 易于使用辅助方法扩展(ssl?, configured? 等)
  • 环境特定(开发、测试、生产)

绝不要直接使用 Rails credentials 或 ENV。

倾向使用(和避免)的模式

文件中鼓励的其他示例:

  • 表单对象
  • 查询对象
  • ViewComponent

文件中建议避免的几件事:

  • 服务对象
  • 结果对象

这些并非普遍真理,只是这种特定风格和代码库的规则。文件的其余部分会更详细地阐述。

手动编辑

我们还在 AGENTS.md 文件中发现了一些幻觉内容,所以我们手动清理了它们。一个例子是它错误地得出结论认为 ActiveJob::Continuable 需要 bundlebun gem。

将清理工作转化为资产

我们将一项非常昂贵且不可扩展的服务——“Vladimir 清理我的氛围代码”——转变成了一个可重用的资产,它让我生成的代码更易于管理。

我们接下来的计划:

  • 在其他氛围编码项目上尝试这个 AGENTS.md(Vladimir 风味)。
  • 在更多“氛围清理”案例上,使用相同的流程扩展它(或创建新的指南)。
  • 继续尝试更好的代码生成风格指南和护栏格式。
  • 公开分享结果并持续学习。

你觉得这个小实验怎么样?如果你决定尝试类似的事情——或者已经在做了——请分享你的成果!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计