AI 写的代码,为什么总像"差不多对"?我用 opencode 把 commit 前审查做成三道门

AI 写的代码,为什么总像“差不多对”?我用 opencode 把 commit 前审查做成三道门 我用opencode做代码审查,注重agents.md的质量,聚焦多轮核心BUG输出质量,减少MR被驳回的风险 审查阶段不要使用可写 agent。 可写 agent 看到问题后很容易顺手修复,修复后的代码又变成新的审查对象。我要的是报告,不是未经确认的工作树变化。 AGENTS.md 不是越长越好。 我见过把抽象价值观、编码风格和所有历史事故都塞进去的规则文件,最后 AI 什么都读到了一点,却没有一条真正执行到位。规则应当短、具体、可验证。 不要只统计 AI 提了多少条意见。 我更看重高价值意见的比例、误报率、漏报案例和处理耗时。意见越多不等于审查越好。 我最近在 Go 项目里遇到过一个很典型的例子:我让 AI 把一个用 sync.Mutex 保护的缓存改成 sync.RWMutex,因为读请求远多于写请求。代码能编译,go test -race 也通过了,我正准备提交时又看了一遍 diff,才发现 AI 在 RLock() 之后没有为所有提前返回的分支补上 RUnlock()。原代码用 defer Unlock() 做了一次兜底,锁的种类变了,兜底结构却没有一起变。 这个问题不是函数体“完全写错”,而是局部正确、整体破坏。测试没有触发那个错误分支,竞态检测自然也抓不到。类似的情况我见过很多:错误被 _ 接住、context 半路丢失、goroutine 没有退出条件、异常路径没有补齐、依赖被悄悄换成团队没人维护的库。AI 生成代码的速度越快,diff 的影响半径就越容易被低估。 我现在把 commit 前的审查拆成三道门:机器门负责确定性检查,AI 自审负责挑出候选风险,人工 review 负责业务判断和最终签字。这篇文章记录我在 Go 项目里实践这套流程的方式,也会说明 opencode 适合放在哪一层、哪些事情仍然不能交给它。 蓝色 表示工具或事实,绿色 表示我的实践判断,红色 表示容易踩的坑,黄色 表示可以直接改写到自己项目里的模板。 一、先把 commit 当成一次小型发布 以前我把 git commit 当作保存进度,后来几次线上问题让我改了看法:commit 是把一组行为变化交给团队和环境的发布动作。即使还没有上线,它也已经影响了后续 review、合并、回滚和事故追踪。 ...

2026-08-29 · 4 min · 836 words · FunkyGod

AI 生成代码该怎么审:一套可以直接使用的实操框架

AI 生成代码该怎么审:一套可以直接使用的实操框架 我的核心要求:减少 BUG,减轻 Review 压力,避免生产环境出现不可逆的风险。不要怕对同一个任务开多个agent,重要的是让不同关注点能充分执行,人工做最后的审核兜底; 一、AI 写得越快,审查越不能只看“像不像对的” AI 一分钟可以生成几百行代码,几小时就能跑完一轮 PR 流程。真正决定质量的,已经不只是 AI 写得快不快,而是我们能不能在代码生成之后,用合适的方法把它审干净。我自己每天让 AI 生成大约一半的代码改动,但最后原样合进主干的,大概只有三成。剩下的代码往往不是“完全写错”,而是“看起来很像对”:错误被 _ 接住,权限校验在调用链里悄悄漏掉,并发场景只覆盖了 happy path,或者引入了一套团队没人熟悉的依赖。这些问题可能过得了 lint、单元测试和评审,却会在生产环境里变成真正的事故。 所以,我现在不把 AI 当成“替我批准代码的人”,而是把它放在风险筛查器的位置:先帮我快速找出值得追问的地方,再由作者、人工 Reviewer 和领域负责人作最终判断。这个定位也和公开实践比较一致:OWASP 把人工代码审查定义为对自动化安全测试的补充,重点放在业务逻辑、复杂安全实现和具体上下文上;Google 的评审指南则强调,测试本身也需要人来判断是否真的有效。可参考 OWASP Secure Code Review Cheat Sheet 和 Google 的代码评审实践。 二、先把审查输入准备好,AI 才不会对着空气猜 我以前最容易犯的错,是把一段代码直接丢给 AI,然后补一句“帮我仔细审查”。现在我会先准备一张很短的“审查输入卡”,至少包含下面五项: 目标:这次改动要解决什么问题?不解决什么问题? 不变量:哪些已有行为、权限边界、接口兼容性和数据约束绝对不能被破坏? 范围:本次 PR 改了哪些文件、调用链和配置?哪些内容只是必要上下文? 验证:新增或修改了哪些测试?正常路径、异常路径和回滚路径分别是什么? 风险:是否涉及登录、权限、支付、个人信息、文件上传、数据库迁移、加密或外部依赖? 日常 PR 我会让 AI 以 diff 为主,只补充相关接口、数据结构、配置和测试。OWASP 也把 diff-based review 作为 Pull Request 和日常开发的适用方式;新系统、重大版本、遗留系统接管或事故复盘,才更适合做全量的 baseline review。另一个有效做法是控制 PR 的大小:Google 建议一个变更尽量只做一件自洽的事,相关测试跟着代码一起提交,大型重构与功能修改分开。这样做的收益很直接:AI 看得少而准,人也更容易发现真正的行为变化。 ...

2026-08-26 · 2 min · 228 words · FunkyGod

AI 编程时代,我愿意用 Go 做后端

AI 编程时代,我愿意用 Go 做后端 AI 编程工具越来越会写代码,但真正把一个 AI 应用跑到线上,难点从来不只是"能不能生成一个接口"。请求超时怎么处理,模型服务失败后怎么重试,流式响应如何关闭,服务占多少内存,镜像怎样发布,出了问题能不能快速回滚,这些才是后端每天要面对的事情。 我越来越愿意把 Go 放在 AI 应用的后端位置:让 AI 负责加快实现,让 Go 负责把边界、资源和部署约束固定下来。这里说的不是把模型训练和推理强行改写成 Go,而是模型外围的 API、网关、任务和编排服务。蓝色文字表示语言和工程特性,绿色文字表示我的实践判断,红色文字表示容易被忽略的风险,黄色底色表示可以直接采用的做法。 一、Go 不会让 AI 更聪明,但会让错误更快暴露 我用 AI 写后端时,最常见的情况不是代码完全不能运行,而是它给出了一条顺利路径,却把失败路径一笔带过。比如调用大模型时,AI 很容易生成下面这种代码: func AskModel(prompt string) string { resp, _ := http.Get("http://model:8080/v1/chat") body, _ := io.ReadAll(resp.Body) return string(body) } 这段代码的问题很多:忽略了网络错误,没有关闭响应体,没有设置超时,也没有检查状态码。它可能在本地演示中返回结果,但一旦模型服务变慢,连接就可能长期占用;一旦服务返回 500,调用方拿到的可能仍然是一段错误页面。 Go 的显式语法会把这些问题推到台面上。error 必须被接收,类型转换必须写明,defer、context 和 goroutine 的生命周期也不会凭空消失。 这对 AI 生成代码形成了一种很有价值的约束:编译器先拦住低级问题,人再检查业务决策。 例如,我会要求模型先生成一个"责任完整的版本": var ErrModelUnavailable = errors.New("model service unavailable") func AskModel(ctx context.Context, client *http.Client, endpoint string, prompt string) ([]byte, error) { payload, err := json.Marshal(map[string]string{"prompt": prompt}) if err != nil { return nil, fmt.Errorf("encode model request: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("create model request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("call model: %w", err) } defer resp.Body.Close() if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { return nil, fmt.Errorf("model returned status %d: %w", resp.StatusCode, ErrModelUnavailable) } body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20)) if err != nil { return nil, fmt.Errorf("read model response: %w", err) } return body, nil } 这里不是说 Go 写起来没有重复代码,而是每个关键动作都落到具体的代码位置:编码、请求、响应、释放、超时、错误,全都看得见也改得动。 ...

2026-08-24 · 4 min · 666 words · FunkyGod

AI 一口气生成几百行代码,我们该怎么高效且正确地处理代码审核

AI 一口气生成几百行代码,我们该怎么高效且正确地处理代码审核 一、当 AI 能在一分钟里吐出一两百行代码时,真正的难点早已不是“写不出来”,而是“接得住、查得动、改得回、负责得起”。我自己一天常常让 AI 写十几轮,单次 50–300 行很常见,一周下来累计几千行并不夸张。这里面能直接合进主干的大概只有一半,剩下要么是风格不一致、要么是越权操作、要么是没覆盖到我期望的边界,真正被我留下来再加工的常常只有三分之一。速度确实上去了,但读、改、回滚这些事没有便宜半分。我需要同时做目标设计者、规则制定者和成果验收者:先把问题、边界和验收条件讲清楚,再让 AI 在明确的范围里多试几种写法、多跑几轮检查,最后由我看差异、看风险、看回滚路径,再决定是否合并。这一顺序不能反过来——如果我自己都不知道“完成”长什么样,再快的生成也只是把模糊需求放大成更难检查的代码。带着这个结论再读这个题目,会更容易看清它真正在问什么:不是“AI 写得太快怎么办”,而是“当写的成本被工具压平以后,程序员到底该把时间花在哪几个动作上,才能既不返工,也不背锅”。 二、过去半年我最大的感受是,AI 把“写一段代码”的价格压下去了,但“看懂一段代码”的价格几乎没变。一个函数能不能写出来,今天大多数时候已经不构成瓶颈;真正占用时间的,是它处在什么上下文、用了哪些约定、隐含了哪些假设、会不会影响别的模块。一个项目里如果突然多出几百行 AI 代码,常见问题不是“明显错误”,而是“看起来很像正确答案”。我整理了几类实际反复遇到的偏差:在 Go 项目里写出 Python 风格的命名(snake_case 夹杂驼峰、把 换成 ),在 React 项目里调用一年前版本的 清理写法,在 iOS 项目里把 写成一串 嵌套,在 SQL 里写出 ORM 不能识别的方言。这些都不算 Bug,跑得起来,过得了 lint,但放进项目里就是和团队既有代码打架。错误处理走的是另一套约定、用了项目里没人熟悉的依赖、或者把一份本该抽出来的逻辑写成了复制粘贴,也都同源。这并不是 AI 不努力,而是它默认按“训练里最常见的写法”回答,而不是按“这一个项目里最合理的写法”回答。读代码一旦要一边猜意图、一边查背景、一边补差异,效率马上塌掉。所以处理 AI 批量生成代码的第一个动作,不是立刻评审,而是先把这些代码放到一个能被人快速消化的上下文里:让它先和现有约定对齐,再让它进入人的视线。我自己的小习惯是:先跑一遍项目的 formatter / import-sort / gofmt-prettier / eslint --fix,把肉眼可见的风格差异抹掉,再开始看逻辑。看起来是小事,能省下 review 时一半的争论。 三、把 AI 当成“一小时能写完一周活”的同事是不够的,更准确的定位是:它负责在明确边界内把重复部分做出来,人负责在模糊地带做判断。越模糊的需求,越不该直接交给 AI;越清晰的接口,越适合让 AI 多试几种实现。具体到我自己:在把任务交给 AI 之前,我会先填一份简短的“任务说明模板”,哪怕只是几行清单: 目标:这次改动要解决什么,用一两句话讲清楚; 不能破坏的行为:列出已有的功能、API 兼容点、对外契约; 验收条件:包括成功路径、失败路径、边界输入、需要的日志或埋点; 范围:哪些文件/模块允许改动,哪些只能引用不能改; 测试:要求覆盖的关键 case(哪怕只是几条自然语言描述); 依赖:允许引入哪些新包,是否需要更新 lockfile、是否要走安全 review。 这份清单写在 issue、PR 描述、或者本地 prompt 模板里都行。我自己最常用的是把它贴在每次开新对话的第一段,让 AI 在生成前自己复述一遍——如果 AI 的复述和我理解的不一致,我先纠正它,再让它写代码。这一步看起来比直接生成慢一点,但它避免了我最怕的那种返工:代码已经写得很长,最后才发现我根本没想清楚要的是什么。一旦让 AI 开始写,我会刻意要“两三种实现思路”,而不是一个最终答案:每种思路分别有什么取舍、依赖、风险点,分别适合什么场景。AI 在这里负责的是“扩宽选择范围”,我负责的是“决定走哪条”。判断做完之后,AI 仍然很有用:写样板、补测试、翻译接口、改一处风格、批量重命名——这些是它擅长、我也乐意外包的部分。把“想清楚”和“写出来”分成两步,比把“想”也外包出去重要得多。 ...

2026-08-23 · 2 min · 228 words · FunkyGod

Go + Python 混合开发:两种语言的协同,我觉得很舒服

Go + Python 混合开发:两种语言的协同,我觉得很舒服 原文:Python 与 Go 混合开发 | 让 Golang 编程更丝滑 作者:原文信息未提供 来源:用户提供原文标题,发布平台及发布日期未详 很多团队并不是在 Go 和 Python 之间二选一,而是在同一个系统里同时使用它们:Python 负责模型、数据和快速试验,Go 负责服务、任务、网络和部署。 这种组合听起来很自然,真正做起来却经常出现另一种结果:两边各自开发都很顺,连在一起就变得不顺。调用延迟、数据转换、错误处理、版本发布和线上排障,很快会把“语言互补”变成“系统加倍复杂”。 原文从开发体验出发讨论 Python 与 Go 的混合使用。本文在这个基础上往前走一步:混合开发的核心不是把两种语言硬塞进一个进程,而是为变化速度不同的模块设计清晰、可观察、可演进的边界。 蓝色文字表示核心判断,绿色文字表示个人开发实践,红色文字表示风险提示,黄色底色表示可以直接执行的建议。 一、先纠正一个误区:混合开发不是“Python 慢、Go 快” 用“Python 负责灵活,Go 负责性能”概括两种语言,虽然容易理解,但不够准确。 Python 的价值不只在于写得快。它连接着成熟的数据处理、科学计算、机器学习和实验工具链。研究人员或业务工程师可以很快把数据读进来,换一个模型,调整参数,观察结果,再决定下一步怎么做。这个阶段最宝贵的是试错速度和生态覆盖面。 Go 的价值也不只是运行速度。它的静态类型、编译器、并发模型、标准库和独立二进制发布方式,更适合承载长期运行的服务和工具。一个网关、任务调度器、文件处理器或消息消费者,往往要面对超时、取消、限流、重试、优雅退出和故障恢复。这些问题的难点不在于写出第一版,而在于运行数月后仍然清楚、稳定、可维护。 所以,合理的分工不是“把慢的代码改成 Go”,而是看模块的主要矛盾是什么: 变化快、需要不断试验、依赖模型和数据生态的部分,优先考虑 Python; 需要长期运行、并发处理、跨平台发布和严格运维的部分,优先考虑 Go; 两者都能完成、但没有明确收益的部分,不要为了“混合”而混合。 语言边界应该服从业务边界。一个稳定的 Python 服务,通常比一个为了追求统一而重写的 Go 服务更有价值;一个需要高并发和强约束的 Go 组件,也不应该因为团队已经熟悉 Python 就继续堆补丁。 二、最常见的协同架构:Python 做变化,Go 做秩序 在 AI、数据和平台型系统中,可以把职责粗略分成四层。 1. Python:模型和数据层 Python 适合放置以下内容: 模型加载、推理和评测; 特征处理、数据清洗和离线任务; 提示词编排、实验脚本和策略验证; 需要频繁调整的业务规则; 对第三方 AI、科学计算或数据工具的适配。 这一层的代码变化频率通常较高。它不一定要承担所有外部流量,也不一定要直接管理复杂的重试和权限体系。 ...

2026-08-20 · 4 min · 656 words · FunkyGod

Go 语言在 AI 辅助编程时代被谷歌官方力挺:动态语言与 Go 并非对立,而是互补关系

Go 语言在 AI 辅助编程时代被谷歌官方力挺:动态语言与 Go 并非对立,而是互补关系 原文:谷歌官方力挺:为什么 Go 语言是 AI 辅助编程时代的“标准答案”? 作者:场长 来源:21CTO 公众号,发布于 2026 年 7 月 16 日 过去,程序员最关心的问题是:哪门语言写起来更快? 进入 AI 辅助编程时代后,这个问题正在变成:哪门语言更容易让 AI 写对,也更容易让人类看懂、验证、部署和维护? 这不是文字游戏,而是软件生产方式发生变化后的真实转向。AI 可以在几秒钟内生成数百行代码,却无法替人类承担全部架构责任。代码越容易生成,审查、测试、安全和运维的重要性就越高。 谷歌官方力挺 Go,核心并不是说 Python、JavaScript 或 TypeScript 已经过时,而是指出:Go 从诞生之初就偏向大型、长期运行的软件系统。在智能体持续生成代码的今天,这些原本服务于人类团队的设计原则,也恰好成为 AI 协作所需要的“确定性护栏”。 更准确的结论应该是:动态语言负责快速探索和连接生态,Go 负责把经过验证的能力变成稳定运行的服务、工具和基础设施。 一、AI 编程真正改变的,是软件开发循环 很多人提到 AI 编程,首先想到的是代码补全:输入函数名,AI 自动写出函数体;写一段注释,AI 生成一段实现。 这只是最早期、也最简单的用法。 真正的 AI 辅助编程,通常是一个持续循环: 实现 → 构建 → 测试 → 分析失败 → 修改 → 再次构建。 如果是能够自主操作终端和代码仓库的智能体,它还会继续执行:读取日志、搜索依赖、修改多个文件、运行集成测试、启动服务、调用接口,然后根据结果再次修复。 对人类开发者来说,几分钟的编译等待可能只是一次短暂的停顿。对一个需要运行几十轮的智能体来说,每一次等待都会累积成时间、算力和模型调用成本。 这会放大许多过去被忽略的工程摩擦: 构建速度慢:每次修改都要等待更久,智能体的有效迭代次数下降; 依赖关系复杂:环境安装失败后,智能体可能把大量上下文消耗在解决版本冲突上; 错误反馈太晚:问题到运行时才暴露,智能体可能已经在错误基础上写了更多代码; 代码风格不统一:人类和 AI 都要花时间理解不同写法,评审速度自然下降; 部署链路过长:代码虽然生成了,但服务无法稳定启动,任务仍然不能算完成; 生态变化太快:模型根据旧文档生成的代码,可能在今天已经不适用。 过去,这些问题可能每周出现几次。现在,智能体可能在一天内把同一个循环执行数百次。问题没有变,但问题出现的频率变了,成本结构也随之变化。 ...

2026-08-20 · 2 min · 417 words · FunkyGod

Go 项目结构之争:DDD 还是简洁分层?

Go 项目结构之争:DDD 还是简洁分层? Go 项目一开始通常很简单:几个接口、几张表、几个服务函数,按 handler、service、repository 分开就能跑起来。项目变大之后,团队又开始讨论 DDD、领域模型、聚合、应用层和基础设施层。 于是问题变成了:Go 项目到底应该采用 DDD,还是坚持简洁分层? 我的答案是:不要先选择目录结构,要先识别业务复杂度和变化边界。 简洁分层不是低级方案,DDD 也不是高级方案。它们只是解决不同问题的工具。 可以把项目想成一家餐馆。简洁分层像一家小店:前台接单,后厨做菜,仓库取料,路线短,出了问题也容易找到人。DDD 更像一家菜品、库存、会员和结算规则都很复杂的连锁餐厅:重点不是把岗位拆得越多越好,而是让“什么菜能做、什么时候能退单、库存怎么扣”这些规则有明确的负责人。 小店一开始就照搬连锁餐厅的总部制度,会被流程拖慢;连锁餐厅却不能永远把所有事情都交给一个大厨。Go 项目的结构选择,也是同一个道理。 一、争论表面是目录,本质是边界 很多团队讨论架构时,第一步就是贴目录树: internal/ ├── handler/ ├── service/ ├── repository/ └── model/ 或者: internal/ ├── domain/ ├── application/ ├── interfaces/ └── infrastructure/ 但目录本身不会自动带来好的架构。真正重要的是几个问题: 哪些代码属于同一个业务能力? 哪些规则必须始终成立? 哪些变化应该被隔离? 数据库、消息队列等技术细节,是否侵入了业务决策? 一个需求发生变化时,需要修改多少个包? 如果这些问题没有答案,换一套目录只是换了一种命名方式。 架构的价值不在于让目录看起来专业,而在于让变化停留在应该停留的地方。 先用一张图看两种结构的关注点:简洁分层按“技术职责”排队,DDD 更关心“业务规则”应该被谁拥有。 flowchart LR subgraph simple["简洁分层:按技术分组"] S1["请求"] --> S2["handler"] --> S3["service"] --> S4["repository"] --> S5[("数据库")] S3 -. "规则容易集中" .-> S6["变胖的 service"] end subgraph ddd["DDD:按业务边界组织"] D1["请求"] --> D2["应用用例"] --> D3["领域规则"] D2 --> D4["仓储接口"] D4 -. "具体实现" .-> D5["基础设施"] D5 --> D6[("数据库")] end 图中没有“谁更高级”的答案。左边像小餐馆的传菜路线,胜在短;右边像连锁餐厅的岗位边界,胜在业务规则变复杂时,能把变化关在领域边界里。 ...

2026-08-20 · 2 min · 426 words · FunkyGod

Harness Engineering:让 AI 可靠执行长任务的系统工程学

模型之外的那个「执行环境」,才是决定任务能否跑完的真正变量。

2026-06-02 · 2 min · 248 words · FunkyGod

我为什么推荐 new-api:从「能调用模型」到「能运营 AI 业务」的工程答案

很多团队做 AI 产品,第一阶段都很顺利: 调通一个模型; 写几个 API; 跑出一个 demo。 但到了第二阶段,问题会集中爆发: 客户要稳定,单一上游不稳定; 成本要可控,调用量上来后账就乱了; 用户要分层,个人、团队、管理员权限都不一样; 运营要可视,出了问题要知道是谁、哪个 key、哪个渠道、哪个模型; 商业要闭环,充值、订阅、额度、结算要打通。 这时你会发现,真正难的不是“会调模型”,而是“把模型能力做成一个能长期运营的系统”。 我这几天把 new-api 关键代码路径完整过了一遍,结论是:它的价值不在于“支持 40+ 上游”这句话本身,而在于它把 AI 网关最难的工程问题做成了一整套闭环。 这篇文章我不做功能清单,而是按“工程决策”的视角,讲我为什么推荐它。 典型请求链路 客户端 ↓ /v1/chat/completions 或 /v1/messages ↓ 认证 TokenAuth / UserAuth ↓ 限流 ModelRequestRateLimit ↓ 分发 Distribute ↓ Relay 进行请求校验、token 估算、预扣费 ↓ Adaptor 转换上游格式并请求 provider ↓ DoResponse 解析 usage 与流式结果 ↓ Settle / Refund 完成结算或退款 先说结论:new-api 适合什么团队? 如果你符合下面两条以上,我建议认真评估 new-api: ...

2026-05-23 · 3 min · 542 words · FunkyGod