首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >当 AI 写完的代码总是留坑:我如何把 iOS 项目的 AI 工作流从"写完就上线"改成了"六道质量门"

当 AI 写完的代码总是留坑:我如何把 iOS 项目的 AI 工作流从"写完就上线"改成了"六道质量门"

原创
作者头像
莫空9081
发布2026-07-09 16:22:10
发布2026-07-09 16:22:10
110
举报
文章被收录于专栏:iOS 备忘录iOS 备忘录

当 AI 写完的代码总是留坑:我如何把 iOS 项目的 AI 工作流从"写完就上线"改成了"六道质量门"


几个版本连着出了事。

先是首页新增了一个挂件,需要支持多语言。需求本身不大,流程也和之前一样:AI 分析需求、生成方案文档、我确认后执行、功能验证通过、AI 自检最小改动和影响范围、提交上线。结果上线后收到反馈:首页多语言切换失效了。排查发现,AI 用 Category 扩展了一个基础类来做便利方法,而这个 Category 的行为影响了首页已有的语言刷新机制。

没过多久又出事。一个网络请求模块,我让 AI 修了一个"同时请求会 stop 第二个"的逻辑问题,改完后那个场景好了,但另一个完全不相干的页面里,回调不再执行了——因为那个页面依赖的是"即使被 cancel 也会收到 completion"的旧行为,而新逻辑悄悄改了这条隐性契约。

两件事放到一起看,我开始回过味来:问题不是 AI 写错了一行代码,而是整个流程在设计阶段就没让 AI 看到这些风险


原来的流程,问题出在哪?

我之前的 AI 工作流是这样的:

代码语言:txt
复制
需求 → AI 分析文档 → 我确认方案 → AI 执行 → 功能验证 → AI 检查最小改动和影响范围 → 提交

看起来每一步都有,比"直接让 AI 写代码"已经成熟不少。但两件事暴露了同一个结构性问题:"影响范围检查"发生得太晚。代码已经改完了,你再让 AI 看 diff 做解释,它只会基于当前改动做正向推理,不会站在全项目调用关系、运行时行为、历史 bug、边界场景的角度做反证。

而 Objective-C 老项目恰恰是最容易被这种流程漏掉问题的场景。OC 的很多影响不体现在显式调用上——Category 覆盖方法、runtime swizzling、KVO 回调、block 持有关系、单例状态污染、多语言资源的隐式刷新链路、请求 cancel 后的回调契约——这些东西都不容易通过常规代码搜索发现,但一旦改动,影响就是全局的。

我那两个线上 bug 本质上是同一个问题:AI 在"实现者"的位置上做得很好,但缺乏"变更审计员"的视角。它能完成当前需求,但做不到主动发现"这段代码在历史上被哪些地方依赖、哪些副作用没有写在需求文档里、哪些旧逻辑靠隐式行为活着"。


两个事故的技术根因复盘

1. Category 为什么能影响首页多语言切换?

多语言切换在 iOS 里不是靠改一个配置就能辐射全局那么简单。它依赖通知刷新、页面重建、资源重新读取这一整套链路。但如果某个 UI 组件在初始化时把翻译好的字符串缓存下来了,它就不会在语言切换时自动刷新。

AI 接到"新增挂件多语言"这个需求时,它看到的上下文是挂件本身——它不知道首页原有的多语言刷新依赖什么链路,不知道 Category 新增的方法会不会覆盖已有行为,不知道 associated object 存的状态会不会和宿主对象的生命周期不一致。

在 OC 老项目里,Category 的坑是系统性的:新增同名方法会在运行时产生不可预测的覆盖行为、localizedStringsetText: 这类基础方法的全局修改会影响所有页面、便利方法被多个模块共用后改动面远超预期。

所以根因不是那几行 Category 代码写错了,而是流程里没有"涉及 Category 的需求必须先做影响面审计"这个硬门槛。

2. 请求 stop 为什么会导致回调丢失?

这个问题的本质是"请求生命周期契约不清楚"。

stop 到底表示取消当前请求、取消队列里的请求、取消同类请求,还是取消某个 owner 下的全部请求?取消后是否必须回调?回调是 success、failure、cancel,还是 completion?多个请求并发时,如何保证不误伤?

我原来的请求封装里,"同时请求会 stop 第二个"这个行为本身就很粗糙——它用一个共享状态来控制多个请求的生命周期。AI 改这个逻辑时,它看到的是"让第二个不被 stop",但它没看到"某些旧调用方依赖 cancel 也会走 completion 来关闭 loading 和恢复按钮状态"。改完后,第二个请求确实不被 stop 了,但某个旧调用方在 cancel 路径上的状态恢复逻辑断了。

根因同样是流程问题:改请求封装这种公共能力时,没有先定义契约,没有扫描所有调用方,没有确认每个调用方在 success/failure/cancel 三条路径上的行为。


关键转折:从"最小改动"到"最小风险改动"

复盘完这两件事,我发现一个更深层的问题:我之前让 AI 检查"是否是最小、最优改动",这个问题本身就问错了。

"最小改动"是用代码行数衡量的——三行改完,看起来很美。但在 OC 老项目里,三行改动一个公共 Category,可能比三十行业务代码的风险大一百倍。改一个 request manager 的 stop 逻辑,代码可能很少,但所有调用方的行为都可能被改变。

我把这个问题换成了:

请从"最小风险改动"的角度审查本次 diff,而不是只看代码行数。

具体来说,AI 必须回答:

  • 本次改动是否修改了公共能力、全局状态、基础类、Category、请求管理器?
  • 如果是,有没有局部替代方案?
  • 所有调用方有哪些?每个调用方的行为是否改变?
  • 哪些旧逻辑依赖原行为?
  • 有没有测试覆盖这些旧逻辑?如果没有,是否应该缩小改动范围?

这个转变很小,但效果很大。它迫使 AI 不再满足于"我改了三行,看起来没问题",而是要去追溯"这三行改了之后,谁的行为会变"。


重构后的工作流:六道质量门

我不需要推翻原来的 AI 工作流,但必须在关键节点加硬门槛。新的流程是:

代码语言:txt
复制
需求 → 风险分类 → 代码地图 → 方案+禁止项 → 测试点 → 实现 → 证据审查

每一道门都有明确的输入和输出。

第一道门:变更分类

在 AI 写任何代码之前,先让它判断这个需求属于什么风险等级。我定义了几类高风险标签:

  • 涉及 Category / runtime / swizzling
  • 涉及多语言、主题、暗黑模式等全局 UI
  • 涉及网络请求的取消、重试、队列、并发
  • 涉及缓存、单例、全局状态
  • 涉及通知、KVO、delegate、block 回调
  • 涉及首页、登录、支付等核心路径
  • 涉及老代码、无测试代码、多人改动过的模块

只要命中一个,就必须走高风险流程——不允许只做 happy path 验证,必须输出影响面和回归点。

这一步的输出格式是:

  • 风险等级:低 / 中 / 高
  • 命中的风险类型
  • 必须阅读的相关文件
  • 必须搜索的关键词
  • 必须补充的测试场景
  • 不允许采用的实现方式

第二道门:代码地图

AI 漏问题的很大一部分原因是上下文不够。你不能只把当前文件扔给它,要让它主动告诉你还需要看什么。

比如多语言需求,我要求 AI 先搜索哪些关键词、找到哪些文件,然后输出一份"代码地图":

  • 多语言入口在哪
  • 首页语言切换的刷新链路是什么
  • 挂件在什么时机设置文案
  • 是否存在字符串缓存
  • 是否存在 Category 覆盖系统或业务类方法
  • 哪些页面共用这套逻辑
  • 本次改动最可能影响哪些旧功能

网络请求同理:请求从创建到完成的完整生命周期、stop/cancel 的语义、completion 是否保证一定执行、多个并发请求如何区分、哪些调用方依赖 completion 做 UI 状态恢复——这些必须在写代码之前摸清楚。

第三道门:方案必须带禁止项

以前我只要 AI 给一个方案。现在我要求它必须先列出什么不能做、为什么。

比如 Category 场景,AI 必须明确说明:

  • 推荐方案和理由
  • 不推荐方案和理由
  • 是否使用 Category
  • 如果使用,是否会新增同名方法、是否影响全局行为
  • 动态切换语言时如何刷新
  • 如何避免字符串缓存

另外我也定了一条硬规则:业务需求默认不允许通过 Category 改变已有类的核心行为。Category 只能新增带前缀的工具方法,不能覆盖原方法,不能隐式修改全局行为,不能承担复杂状态管理。需要全局拦截的情况必须单独评审。

第四道门:先写测试点

方案确认后,AI 必须先输出测试点或手工验证清单,然后再写实现。这样做的目的是把验证思路和实现思路剥离开——AI 在"实现模式"下会倾向于为当前方案找证据,但在"测试设计模式"下会更客观地找边界。

第五道门:实现(这是原来流程的核心,现在变成其中一环)

实施代码、跑测试和核心回归。这一步和之前一样,但它前后都被质量门包围。

第六道门:证据型 diff 审查

这是替代原来"检查是否最小改动"的那一步。现在 AI 必须给出证据,不能泛泛而谈:

  • 修改了哪些文件,每个文件为什么必须改
  • 是否涉及公共方法、Category、单例、通知、请求管理器
  • 每个被修改方法的所有调用方
  • 每个调用方行为是否改变
  • 新增或修改了哪些测试
  • 哪些历史问题被覆盖
  • 哪些风险没有被测试覆盖
  • 如果要进一步缩小风险,可以怎么改
  • 是否建议提交(必须基于证据给出理由)
  • 如果建议提交,commit message 是什么

关键要求是"列出所有调用方"和"给出证据"。AI 如果不能列出调用方,就不能声称影响范围可控。


从事故中长出来的 Checklist

另一个重要改变是:每个线上遗漏都不只是"记住了下次注意",而是变成了一条固定的检查项。

我已经积累了两套 Checklist,每次让 AI 改代码时直接贴给它:

多语言变更检查项

  • 文案是否通过统一入口获取
  • 是否把翻译后的字符串缓存到 model、单例、静态变量里
  • 语言切换后当前页面是否刷新
  • 语言切换后返回首页是否刷新
  • 语言切换后新挂件、旧挂件是否都刷新
  • 不同语言文案长度是否影响布局
  • 是否使用 Category 修改 UILabel、NSBundle、NSString 等基础类
  • Category 方法是否可能和已有方法重名
  • 是否存在 method swizzling
  • 是否影响首页其他组件

请求生命周期检查项

  • completion 是否保证只执行一次
  • cancel 是否有明确的 completion 或 cancel callback
  • 多个请求并发时是否有独立 request id
  • 新请求是否会误取消旧请求、旧请求是否会误取消新请求
  • UI loading 是否在所有分支关闭
  • 失败、取消、超时、重复请求是否都覆盖
  • 是否有调用方依赖 failure 回调做状态恢复
  • 是否有调用方依赖 success/failure 二选一,但没有处理 cancel
  • 修改底层请求逻辑是否扫描了所有调用方
  • 是否有测试覆盖连续请求、取消后回调、旧请求晚返回等场景

给 AI 固定的 OC 项目规则

Objective-C 和 Swift 不一样,很多能力是动态的,AI 在没有约束的情况下很容易低估影响面。我现在把下面这几条作为每次对话的前置约束:

Category 规则

  • 业务需求中不要优先用 Category
  • Category 方法必须加前缀,避免冲突
  • 不要在 Category 中覆盖已有方法
  • 不要在 Category 中做隐式状态管理
  • 不要在 Category 中悄悄注册通知或改变生命周期
  • 不要用 Category 改 NSBundle、NSString、UILabel、UIViewController 等基础类行为
  • 如果 Category 影响多语言、主题、字体等全局能力,必须列出所有受影响页面

网络请求规则

  • 请求完成必须有明确的状态枚举(success / failure / cancelled / superseded)
  • completion 默认必须执行一次,除非调用方显式声明不关心
  • cancel 不等于无声失败,必须可被观察
  • 并发请求用 request id 或 token 区分
  • "只保留最新请求"的逻辑在业务层做,不要藏在底层 stop 里
  • 不让一个请求对象的 stop 影响另一个请求
  • 不用共享 bool 控制多个请求的生命周期
  • 所有 loading、按钮状态、刷新状态在 success / failure / cancel 三条路径上全部收口

多语言规则

  • UI 不长期保存翻译后的字符串,保存 key,切换语言时重新 resolve
  • 语言切换后有统一通知或刷新机制
  • 首页、Tab、挂件、弹窗、空态、错误提示全部覆盖
  • 新增文案必须验证至少两种语言
  • 新增挂件必须验证:语言切换前后、首页重进、冷启动、后台返回
  • 不让局部组件绕过统一多语言入口

用少量自动化测试锁住已知风险

OC 项目测试基础薄弱是常态,但这不意味着什么都不做。我的策略是不追求覆盖率,只追出过事故的路径

三个最优先的测试方向:

1. 多语言切换测试

不是为了测每一个文案,而是测"语言切换后会重新取值"这个规则本身:

代码语言:objc
复制
- (void)testHomeWidgetTitleUpdatesAfterLanguageChanged {
    [LanguageManager setLanguage:@"en"];
    HomeWidgetView *view = [[HomeWidgetView alloc] init];
    [view refreshLocalizedText];
    NSString *englishTitle = view.titleLabel.text;

    [LanguageManager setLanguage:@"zh-Hans"];
    [[NSNotificationCenter defaultCenter] postNotificationName:LanguageDidChangeNotification object:nil];
    [view refreshLocalizedText];
    NSString *chineseTitle = view.titleLabel.text;

    XCTAssertNotEqualObjects(englishTitle, chineseTitle);
}

2. 请求 cancel 回调测试

确认 cancel 后会回调且只回调一次:

代码语言:objc
复制
- (void)testCancelRequestStillCallsCompletionOnce {
    XCTestExpectation *expectation = [self expectationWithDescription:@"completion"];
    __block NSInteger callbackCount = 0;

    RequestToken *token = [client startRequestWithCompletion:^(Response *r, NSError *e, RequestState state) {
        callbackCount += 1;
        XCTAssertEqual(state, RequestStateCancelled);
        [expectation fulfill];
    }];
    [token cancel];

    [self waitForExpectationsWithTimeout:1 handler:nil];
    XCTAssertEqual(callbackCount, 1);
}

3. 连续请求不互相干扰测试

覆盖"取消第一个不能误伤第二个":

代码语言:objc
复制
- (void)testSecondRequestNotStopped {
    XCTestExpectation *expectation = [self expectationWithDescription:@"second"];
    RequestToken *first = [client startWithCompletion:^(...) {}];
    RequestToken *second = [client startWithCompletion:^(Response *r, NSError *e, RequestState state) {
        XCTAssertNotEqual(state, RequestStateCancelled);
        [expectation fulfill];
    }];
    [first cancel];
    [self waitForExpectationsWithTimeout:2 handler:nil];
}

三组测试加起来不到一百行代码,但它们锁定了所有线上出过事故的路径。每次改涉及多语言或请求的代码时,先跑这三组——能过不一定说明没问题,但过不了就一定有问题。


现在的状态

这套流程跑了几个迭代之后,最直观的变化不是"再没出过 bug"——那不可能——而是每次出问题后,我有了一套标准动作:不是在心里记一笔"下次注意",而是把它拆成一条 checklist 检查项、一组测试用例、或者一条 AI 约束规则。

AI 没有变聪明,但流程变了。以前它是实现者,现在它是风险审计员。以前我在上线前问它"这个改动最小吗",现在我问它"这个改动的风险最小吗"。以前线上出了事我只能复盘加内耗,现在出了事我能把它变成一条规则锁进系统里。

如果你也用 AI 做 iOS 开发,尤其是 OC 老项目,我建议你把下面三件事排在所有优化前面:

  1. 建一份 AI_REVIEW.md,把 Category、多语言、请求生命周期这三类风险写成固定规则,每次对话先贴给它
  2. 给出过事故的路径补最小回归测试——不需要全覆盖,只覆盖已经痛过的点。出过事的地方必须从"人记着"变成"机器记着"
  3. 把"最小改动"改成"最小风险改动"——改一行公共代码比改五十行业务代码更需要审查,在 OC 项目里尤其如此

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 当 AI 写完的代码总是留坑:我如何把 iOS 项目的 AI 工作流从"写完就上线"改成了"六道质量门"
    • 原来的流程,问题出在哪?
    • 两个事故的技术根因复盘
      • 1. Category 为什么能影响首页多语言切换?
      • 2. 请求 stop 为什么会导致回调丢失?
    • 关键转折:从"最小改动"到"最小风险改动"
    • 重构后的工作流:六道质量门
      • 第一道门:变更分类
      • 第二道门:代码地图
      • 第三道门:方案必须带禁止项
      • 第四道门:先写测试点
      • 第五道门:实现(这是原来流程的核心,现在变成其中一环)
      • 第六道门:证据型 diff 审查
    • 从事故中长出来的 Checklist
      • 多语言变更检查项
      • 请求生命周期检查项
    • 给 AI 固定的 OC 项目规则
      • Category 规则
      • 网络请求规则
      • 多语言规则
    • 用少量自动化测试锁住已知风险
      • 1. 多语言切换测试
      • 2. 请求 cancel 回调测试
      • 3. 连续请求不互相干扰测试
    • 现在的状态
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档