拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI代码评审实践:Gemini如何帮我重构一个Go写的DevOps CLI工具

AI代码评审实践:Gemini如何帮我重构一个Go写的DevOps CLI工具

做 DevOps 做了快八年,大部分时间其实耗在环境切换、重复部署和排障上,真正写业务逻辑的时间少得可怜。前阵子我用 Go 写了一个叫 wydevops 的统一命令行工具,试图把日常的部署、日志追踪、环境信息查询全部收拢成一个命令集合。写完第一阶段,正巧在 VS Code 里装了 Gemini Code Assist,脑子里冒出来一个想法:与其自己反复看代码,不如让 Gemini 站在第三方视角,给这个项目做一次完整分析和评价。于是就有了这篇文章——记录"AI 评审一个真实运维工具"的完整过程、踩过的坑,以及把评价落地成修复方案的具体做法。

如果你也在做一个中大型 CLI 工具或内部 DevOps 平台,或者只是好奇 AI 到底能不能干代码评审这件事,这篇内容应该能给你一些参考。

1. 为什么让 Gemini 当"项目评审员":动机、边界和评审输入

1.1 wydevops 是做什么的

先交代一下背景。我的日常工作是维护一小片不算小的基础设施,包括开发、测试、预发布、生产四套环境,再加上几套临时环境,命令多到根本记不住。以前我靠一长串 shell alias 和备忘录活着,直到发现某天误在预发布环境跑了生产脚本,才下定决心搞一个工具来兜住这些操作。

wydevops 的核心设计其实很简单:把"环境信息""执行命令""部署服务""查看日志""检查状态"这些高频操作封装成一个带子命令的 CLI。技术选型上我用了 Go,一个原因是编译成单文件往服务器上一丢就能跑,另一个原因是 goroutine 在处理多节点并发采集时比较省心。

工具的整体结构分成了几块:最上层是 cobra 管理的命令行入口,中间是 environment(环境上下文)、executor(执行器)、deploy(部署)、observe(观测)、config(配置)几个业务模块,外围还预留了一个 plugin 接口,方便团队同学按自己的流程扩展脚本。第一版功能算不上惊艳,但对内部工具来说,已经比一堆 shell 脚本规整多了。

1.2 我给 Gemini 设计的"评审任务书"

AI 评审的效果,一半取决于工具本身,另一半取决于你怎么把任务交代清楚。我第一次把项目丢给 Gemini 的时候,只写了一句"帮我看看这个项目有没有问题",结果得到的基本是套话,什么"代码结构清晰""思路很好"之类的,压根没有可用信息。

后来我重新设计了提示词,把任务拆成四个要素:角色(资深 DevOps 工程师 / 代码评审专家)、评价对象(项目说明 + 目录结构 + 按模块分批提供的核心代码)、评价维度(架构合理性、健壮性、可维护性、扩展性)、输出要求(先给总体判断,再给分项评价,最后必须列改进建议清单)。

这样改造之后,Gemini 的输出质量立刻上了一个台阶。它会给结论,也会给依据——虽然有些依据是从代码风格、命名这类表面特征推导出来的,但对一次"预评审"来说已经足够了。

1.3 分段喂代码的策略:上下文窗口的现实约束

这里有一个很现实的问题:Gemini 的上下文窗口是有限的,wydevops 第一阶段的代码量虽然不算大,但把全部代码一次性塞进去仍然不现实。我的做法是把项目分成三批喂:第一批是 CLI 入口、配置加载和环境上下文模块;第二批是执行器和部署模块;第三批是观测模块和插件接口。

每喂一批,我都要求 Gemini 结合前面已经给过的背景信息做增量评价,最后再让它汇总成一份完整报告。这个过程有点像把一个大项目拆成几次小规模评审会议,虽然麻烦一点,但每次评审的深度明显比一次性看完要好。

提示:喂代码时尽量带上"这个文件在项目里扮演什么角色"一句话说明。AI 在没有背景的情况下,很容易把工具函数误判成核心业务逻辑,从而给出偏掉的建议。

2. Gemini 的第一轮评价:架构亮点确实被它看出来了

2.1 "命令-执行-状态"三层结构得到正反馈

Gemini 第一轮看完 CLI 入口、配置加载和环境上下文模块后,给了一个让我意外的评价:它认为 wydevops 的分层是合理的,"命令行解析层不直接接触具体执行逻辑,而是通过 context 对象把环境信息传递给底层执行器"这一点,符合它见过的多数成熟 CLI 项目的组织方式。

它举了个具体例子:我的 environment 模块里有一个 Environment 结构体,里面保存了环境名、API 地址、密钥引用、跳板机信息等字段,而 executor 不会自己猜测环境信息,只认 context 里带过来的值。Gemini 认为这种"环境上下文显式传递"的设计,比很多工具里到处读全局变量的做法安全得多——因为多环境场景下最容易出事故的,就是命令在错误的上下文里执行。

这个评价其实点中了我当时的一个设计初衷:我不希望任何子命令"猜"自己要连哪个环境,所有环境信息都必须在命令调用时显式传入。Gemini 从一个相对中立的代码阅读者角度确认了这一点,算是一颗定心丸。

2.2 部署与观测的功能覆盖:至少没有漏掉关键环节

第二轮评审聚焦在 deploy 和 observe 模块。Gemini 的评价集中在几个点上:一是 deploy 支持预检查(检查目标服务器磁盘、服务健康状态),二是在部署前会生成变更摘要,三是 observe 模块能够统一采集多台服务器的日志流。

它没有停留在"功能多"这类赞美上,而是指出这些设计解决的是"部署这个动作本身的不可回退风险"和"多节点日志查找效率"两个真实痛点。作为长期写运维工具的人,我知道很多内部工具体积比 wydevops 大得多,却连"部署前生成变更摘要"都没做,导致出问题后人肉比对版本号。Gemini 能识别出"哪些功能是凑数的、哪些是解决问题的",说明分段喂代码时带上模块职责说明,确实有效。

2.3 把评价转成可执行清单

第一轮评审结束后,我没有让结论停在聊天记录里,而是手动整理了一张表格,把 Gemini 反馈里值得落地的部分提取出来,作为下一阶段迭代的候选清单。评价摘要与我的处理方式如下:

Gemini 的评价维度具体反馈内容我的处理方法
架构分层命令层与执行层解耦,环境上下文显式传递保留现状,补单元测试锁定行为
功能覆盖部署预检查、变更摘要、日志聚合是有价值的特性继续深化,增加变更摘要 diff 详情
健壮性风险大量直接 panic / os.Exit,错误信息不完整列入下一迭代重点修复项
配置管理存在硬编码地址与 Key,环境切换靠改代码引入配置分层与环境覆盖机制
可观测性日志输出用 fmt.Println,无结构化、无审计字段切换日志库,统一输出格式
幂等性deploy 缺少状态判断,重复执行可能二次推进设计部署状态锁,用锁文件加状态机解决

把 AI 的评价转成表格,不是为了显得自己很严谨,而是因为 AI 的输出天然是"成段成篇"的,直接照着改容易漏项。表格化之后,每一条都能对应到具体代码文件和改动方案,执行起来清晰很多。

3. 被点破的问题:一个工具从"能跑"到"好用"的距离

3.1 暴力错误处理:panic 一时爽,排障火葬场

Gemini 在健壮性评价里毫不留情地指出了我的错误处理问题。wydevops 第一版的代码里,确实有相当多的 panic 和直接 os.Exit(1),典型场景是配置加载失败、目标服务器 SSH 连接超时、日志文件读取异常。当时我心里想的可能是"反正内部工具,挂了就挂了,重新跑一遍就行",但从代码评审的角度看,这是非常不职业的做法。

问题不只在于"不优雅",而在于排障信息几乎为零。举个真实例子:有一次 deploy 命令在预检查阶段直接 panic,只抛了一个字符串和堆栈地址,连哪个环节失败、哪台机器异常都看不出来,最后我只能手动加日志重新跑一遍,才定位到是其中一台服务器的磁盘路径写错了。

Gemini 建议的错误处理方案很标准:所有可能出错的操作统一返回 error,在调用链路上层层包装上下文信息,最后在 CLI 入口统一处理并输出对应退出码。这个建议不新鲜,但经 AI 那么一刺激,我才意识到自己第一版写得太随意了。

3.2 配置硬编码与环境切换的隐患

另一个被重点点名的问题是配置硬编码。wydevops 刚写完时,我为了图省事,直接把各环境的 API 地址、SSH 用户名、密钥路径写在了一个 config.go 文件里,切换环境依赖修改源码后重新编译。对这种做法,Gemini 给了一个非常尖锐的评价:这种设计会让环境切换依赖"发布新二进制",既容易改错,又没法审计谁在什么时候改过什么。

它说得没错。后来团队里另一位同学想用 wydevops 连一套新的临时环境,只能跑过来找我改代码。而且一旦改错了,生产环境配置就可能被一起带进错误状态。这是一个内部工具做大之后必然会撞上的问题——早期图省事,后期都是债。

当时我采用的修复思路很常规:引入配置分层,默认配置放内置,环境级配置放在 ~/.wydevops/config.xxx.yaml,命令执行时通过 --env 参数选择加载哪一层。Gemini 在复评时对这个改动给了正面评价,认为它把"环境信息"从代码里彻底剥离了出去。

3.3 日志体系缺失:输出一时多,审计两行泪

wydevops 第一版日志基本就是 fmt.Println 大集合,什么东西都往标准输出里打。这样做有两个非常直接的后果:第一,日志没法按级别过滤,debug 信息、普通流程、错误信息都混在一起;第二,日志没有结构化和固定字段,没法被日志采集系统(比如 Loki 或 ELK)直接消费。

Gemini 评价的时候,特别强调了"审计字段"的重要性。它建议至少在每个关键操作里记录操作者、目标环境、执行命令、开始时间、结束时间、结果状态六个字段。我当时觉得内部工具做审计有点小题大做,直到有一次需要排查"谁在凌晨对预发布环境执行了重启操作",翻遍命令行历史和 shell 日志都没找到源头,才意识到审计字段不是给别人用的,恰恰是先给犯错时的自己用的。

修复方案是引入 slog 结构化日志,关键操作统一打 JSON 格式日志,包含上述审计字段。改完之后,排查问题的效率提升非常明显。

3.4 缺少幂等性:重复执行部署的连锁反应

这个坑是 Gemini 用静态分析直接看出来的:deploy 命令没有做人任何状态判断,重复执行时会把同样的部署动作再推进一次。对单机小服务来说后果可控,但对依赖顺序执行的发布流程来说,重复推进可能导致后续步骤操作了一个已经不存在的中间版本,出现难以排查的脏状态。

我没实际踩过这个坑,原因是运气好,但 Gemini 说得有道理。它建议引入"部署状态锁",用一个锁文件标记当前环境是否处于部署中,锁存在时直接拒绝重复执行;同时记录上次部署的目标版本 hash,如果目标版本与当前一致,就询问用户是否跳过。

这套建议后来成了 wydevops 最强的一个改进点。详细做法我放在下一章统一讲。

3.5 CLI 输出不够机器友好

最后一个问题是 CLI 输出格式。当时的 wydevops,人读没问题,但一旦想用脚本定时执行、把结果喂给其他系统,基本是噩梦。Gemini 建议所有子命令都支持 --format json 参数,输出统一结构的 JSON,方便下游消费。

说实话,这个点我自己也早有想法,只是一直没排上优先级。Gemini 评审之后,我把它的位置提前了,因为"机器可读"对内部工具来说几乎是刚需——只有输出是结构化的,才能被其他平台对接,否则工具就只是一堆手工命令的集合。

4. 落地修复:把 Gemini 的评价变成可验证的工程改进

4.1 错误与退出码规范化

首先要解决的是错误处理和退出码问题。我按 Gemini 的建议做了一个统一约定:底层函数一律返回 error,错误信息使用 Go 的 fmt.Errorf 加 %w 包装链路,最外层 CLI 框架根据错误类型映射退出码,比如配置错误对应 2、部署失败对应 3、超时对应 4,未知错误对应 1。

代码层面大概是这种感觉:

func deployService(ctx context.Context, env *environment.Environment, svc string) error { if err := preCheck(ctx, env); err != nil { return fmt.Errorf("deploy service %s in env %s failed: %w", svc, env.Name, err) } // ... 实际部署逻辑 }

这样改造后,每个错误都携带了"在哪个环境、部署哪个服务、卡在哪一步"的信息,排障时基本一眼就能定位。退出码规范化则是为了让上层调度系统在自动化调用时,能根据退出码做不同的重试策略。

4.2 配置分层与环境覆盖

配置改造的核心是引入分层加载。具体来说就是三层:内置默认配置 → 用户级配置文件 → 命令行临时参数。优先级从低到高,后面的覆盖前面的,这样既保证了没有配置文件也能跑起来,又允许用户针对特殊环境做临时覆盖。

我在环境上下文里增加了一个 config 对象,每次执行命令前都会重新加载配置,避免长时间运行导致配置缓存过期。Gemini 在复评时提醒了我一个细节:环境名最好做白名单校验,否则用户误敲了一个不存在的环境名,可能默认加载到生产配置。这个提醒确实及时,后来我加了环境名校验逻辑,未知环境名直接拒绝执行。

4.3 结构化日志与审计字段落地

日志改造没有技术难度,难的是所有人形成习惯。我在 executor 层做了一个统一封装,所有关键操作都经过 trackOperation 函数,自动带上操作者、目标环境、命令名、开始时间、结束时间、状态等字段。

type auditFields struct { Operator string `json:"operator"` Env string `json:"env"` Command string `json:"command"` Started string `json:"started_at"` Finished string `json:"finished_at"` Status string `json:"status"` }

接入结构化日志后,最直接的一个收益是:我可以写一条极简查询语句,把所有失败操作按环境和操作者聚合出来,做月度排障分析。这在 fmt.Println 时代是根本做不到的。

4.4 幂等部署的状态锁设计

部署幂等性这块,我按 Gemini 的框架做了两个机制的结合:状态锁 + 目标版本校验。

锁文件存放在每个环境对应的状态目录下,文件名为 deploy.lock。命令启动时先检查锁文件是否存在,存在就读取其中的时间戳和操作者信息,拒绝启动并提示谁在什么时候发起的部署;不存在则创建锁文件,部署结束后释放。防止并发部署的前提是:所有部署都通过 wydevops 执行,如果还有人手动跑脚本,这个锁就是摆设。

目标版本校验则依赖版本清单。部署前读取当前环境正在运行的版本号,如果与目标一致,默认跳过部署,除非用户显式加 --force。这样一来,重复执行同样的部署命令变得非常安全。

4.5 机器可读输出与自动化集成

输出改造我放在了最后,因为想先保证逻辑正确再谈展示。现在所有子命令都支持 --format json,输出结构包含命令名、状态、耗时、关键结果字段。这样 wydevops 可以很方便地被集成进 CI 流水线或内部告警系统。

配合代码里预留的 plugin 接口,团队同学也能基于 JSON 输出做自己的巡检脚本。从"能用"到"好用"的转变,很大程度上就是这个输出格式带来的。

4.6 回归验证与复评

修复完成后,我做了一轮针对性的回归验证:模拟三个环境并发执行部署、重复执行相同部署验证幂等、人为制造 SSH 超时验证错误信息与退出码、批量执行命令验证 JSON 输出格式稳定性。验证过程发现的一个小问题是:锁文件在高并发下存在竞态条件,同一毫秒内两个进程可能同时创建成功。后来改成用 flock 系统调用加锁,问题消失。

回归通过之后,我又把第二轮改进后的代码分段喂给 Gemini 做了一次复评。这一轮它的语气明显不同,评价从"发现问题"变成"确认改进方向正确",同时指出遗留的问题:例如测试覆盖率偏低、部署状态锁缺少异常恢复机制、插件接口缺少文档和示例。这些都是合理的后续迭代方向,我把它排进了下一阶段计划。

5. 对"AI 评审"的清醒认识:它看得懂的,和它看不见的

5.1 AI 评审不是银弹:它擅长"检查表",不擅长"业务判断"

经过这轮完整的 AI 评审体验,我的感受是:Gemini 这类模型在代码评审上的优势,接近于一个"极强的静态检查器加半个资深开发经验库"。它能迅速发现硬编码、错误处理缺失、输出不规范、幂等性风险这类通用问题,因为它见过的优质代码模式足够多,可以总结出非常接近主流工程实践的建议。

但它看不见的东西也很明显:它对我所在团队的发布流程、历史故障、人和人之间的协作习惯一无所知。比如我们的特殊变更窗口、某些服务器的历史遗留问题,这些 context 不在代码里,AI 无从评判。所以它的评价上限是"好的通用工程实践",而不一定是"我们团队现在最该改的东西"。

5.2 提示词工程决定评审质量

这次让我体会最深的一点是:同样一个项目,让 Gemini 评价和让 Gemini 认真评价,输出质量完全是两回事。我的提示词模板最终固定为四要素:

  • 角色定义:你是一位资深的 DevOps 平台开发工程师,做过多个 CLI 运维工具。
  • 评审标准:请从架构合理性、健壮性、可维护性、可扩展性四个维度打分,单维度满分十分。
  • 上下文信息:这是项目背景说明 + 本次评审的模块职责 + 代码片段。
  • 输出要求:先给出总体结论,再列问题清单,每个问题必须附改进建议和优先级。

其中"输出要求"最容易被忽略。如果你不要求它列改进建议和优先级,它就可能走温和路线,最后给你一段"整体不错,注意错误处理和日志"的废话。加了优先级之后,它反倒会认真权衡哪些问题值得先改。

5.3 上下文窗口限制下,分段喂代码的正确姿势

上下文窗口的限制是硬约束,不能靠提示词绕过去,只能靠喂代码的策略。我的经验是:按模块边界分段,每段都带上"这段代码在项目里的作用"说明;喂下一段之前,先要求 Gemini 基于前面的内容更新整体印象,而不是让它每次从零开始。

如果项目特别大,连单个模块都塞不下,那就把代码里最容易产生风险的函数抽出来单独评审,比如错误处理逻辑、状态变更逻辑、并发采集逻辑。针对这些重点函数喂完整实现,效果远好于把一大片代码剪断后再喂。

5.4 把 AI 评审当"预审",再到真人评审

我最推荐的使用方式是:把 AI 评审当作正式 Code Review 之前的"预审"。以前我提 Merge Request 的时候,总觉得心里没底,不知道有没有低级错误没发现。现在我会先让 Gemini 过一遍,把明显的通用问题修掉,再把 AI 报告和改动一起交给团队里的真人评审。

这样做有几个好处:真人评审不再需要花时间在"这个函数没有错误处理""这里配置写死了"这类基础问题上,可以直接聚焦业务正确性和架构演进方向;同时因为我提前处理过一轮,评审效率明显提升,评审意见的含金量也高了很多。

注意:涉及敏感信息、密钥、生产环境的代码,绝对不要随意交给外部 AI 服务评审。我的做法是先做脱敏,把密钥引用替换成占位符、把真实服务器地址改成示例地址,再提交给 AI。商用工具一般会在服务条款里说明数据用途,自用的第三方接口更要警惕数据流向。

5.5 AI 评审结果也要人工甄别:它有时会"自信地错"

最后提醒一句:Gemini 给出的评价里,会有少数它"自信地错"的地方。比如它曾经建议我把某个 goroutine 并发采集改成串行执行,理由是"避免资源竞争",但实际上那段代码已经用 sync.WaitGroup 和 channel 做了安全同步,不存在竞争问题。如果我不加判断直接照做,反而会把并发性能搞退化。

所以对待 AI 评审结论的正确姿势是:每条建议都问一句"它看到了什么才这么说"。AI 的分析往往是从代码特征推导的,如果它看到的依据和实际情况不符,那这条建议就不成立。把它当成一个水平不错但偶尔会开脑洞的同事,而不是权威,Batch 的使用体验会好很多。

结尾:这轮评审带给我的实际变化

这轮评审给我最大的变化,不是 wydevops 这个工具的代码质量变好多少,而是我养成了"提交评审之前先让 AI 过一遍"的习惯。因为 AI 不需要面子,敢直接说"这里有硬编码、那里缺错误处理",而人类同事在指出这些问题时,多多少少会留余地。对一个内部工具来说,这种没有社交压力的评审,反而更容易把问题暴露干净。

如果让我再走一遍这个过程,我会在一开始就给 Gemini 提供更完整的上下文,包括项目的演进历史、团队使用场景和已知痛点,而不是只丢代码。上下文越完整,AI 的评价就越贴近"能落地",而不是停留在"通用实践"。下次你再拿到一堆粗略的 AI 评审建议时,不妨先自己过一遍,把能确定落地的条目挑出来,再拿不准的部分回问 AI 让它给出具体代码方案——这个来回多次的交互过程,才是 AI 评审真正产生价值的地方。

返回列表