AI写埋点代码,35%覆盖率坑惨运营

五月中旬,公司要给一个C端鸿蒙应用上一个新功能:付费转化漏斗分析。运营要看用户从"详情页→加入对比→点击分享→付费弹窗→付费成功"这五步的真实流失情况。

按理说,埋点嘛,我在行。Web 端埋过三四年,鸿蒙端也不是第一次。但那天我脑子一抽,想着"试试 AI 写埋点提效",于是打开了 Cursor。

我跟它说:

"帮我在鸿蒙 ArkTS 项目里给详情页加一套埋点,覆盖五个事件:详情页曝光、加入对比、点击分享、付费弹窗展示、付费成功。要求:解耦、不影响主流程、有完整注释。"

三分钟后,它给了我 200 行代码。

我看完第一反应是:牛逼。结构清晰、注释完整、错误处理也写了,比我手写还规范。

我直接合到了主干。

一个月后,运营找上门了

"斌哥,付费漏斗对不上------曝光数 1.2 万,但加入对比只有 800,分享 200,付费弹窗 1500,付费成功 3。"

我盯着这组数字,愣了十秒。

"等等,曝光 1.2 万,付费弹窗 1500?这说明很多用户没经过'加入对比'直接看到付费弹窗了?这漏斗逻辑根本不通。"

运营也懵了:"我也不知道为什么,反正你埋的数对不上。"

我打开埋点后台,逐条比对------五个核心事件,实际只有"详情页曝光"和"付费成功"两个有数据,"加入对比"、"点击分享"、"付费弹窗展示"全都没埋上。

那一刻我后背有点凉。AI 写的 200 行代码,看着那么漂亮,关键事件漏埋了 60%。

排查:AI 写的代码到底缺了什么

我重新看了一遍 AI 生成的代码。它给我写了一个 EventTracker 类,每个事件都封装了 try-catch,逻辑是这样的:

typescript 复制代码
// AI 生成的埋点(简化版)
export class EventTracker {
  static track(eventName: string, params?: Record<string, any>) {
    try {
      const payload = {
        event: eventName,
        timestamp: Date.now(),
        params: params || {},
        sessionId: AppStorage.get('sessionId')
      }
      // 上报到埋点服务
      HttpClient.post('/track', payload).catch(e => {
        console.warn('track failed', e)
      })
    } catch (e) {
      console.error('tracker error', e)
    }
  }
}

// 详情页曝光埋点(AI 写的)
aboutToAppear() {
  EventTracker.track('detail_view', { itemId: this.itemId })
}

// 加入对比埋点(AI 写的)
handleAddCompare() {
  // ...业务逻辑
  EventTracker.track('add_compare', { itemId: this.itemId })
}

单看每段代码都对------调用了 track 方法,参数传得也对。但为什么没数据?

我开始逐事件排查。第一个线索在"加入对比"事件

我的"加入对比"按钮的完整链路是这样的:

typescript 复制代码
// 真实链路(业务代码)
async handleAddCompare() {
  // 1. 校验用户是否登录
  const user = await this.checkLogin()
  if (!user) {
    // 2. 未登录跳登录页
    router.pushUrl({ url: 'pages/Login' })
    return  // <-- 关键:这里 return 了
  }
  
  // 3. 已登录,加入对比
  await this.compareService.add(this.itemId)
  
  // 4. 弹个 toast
  promptAction.showToast({ message: '已加入对比' })
  
  // 5. 埋点
  EventTracker.track('add_compare', { itemId: this.itemId })
}

看到了吗?AI 写埋点的时候,它只看到了"埋点在最后一行"这个理想路径。但真实业务里:

  • 用户未登录 → 走分支 1-2,return 之前没埋点
  • compareService.add 失败 → 走 catch 分支,没埋点
  • 组件被卸载(用户快速返回)→ add 还没 await 完,组件就没了,没埋点

而 AI 写的代码,把 track('add_compare') 放在了"所有可能路径都成功"的假设下。

我顺藤摸瓜,把五个事件全排查了一遍。结果:

事件 实际触发场景数 AI 埋点覆盖 漏埋场景
详情页曝光 1 1 0
加入对比 4(未登录/成功/失败/卸载) 1 3
点击分享 5(弹窗打开/选择平台/分享成功/取消/失败) 1 4
付费弹窗展示 3(正常/重试/被拦截) 1 2
付费成功 1 1 0

总触发场景 14 个,AI 埋点覆盖 5 个,覆盖率 35.7%。

这就是为什么运营拿到的是 1.2 万 / 800 / 200 的离谱数据。

为什么 AI 会犯这个错

事后我想了很久,AI 写埋点为什么会系统性漏埋。

根本原因是:AI 写代码的逻辑是"从入口到出口"线性扫描,它默认所有代码路径都是"直走到底"的。但埋点的本质不是"写代码",是"覆盖所有可能路径"。

具体来说,AI 漏掉的场景有这么几类:

1. 异步路径中的提前 return

typescript 复制代码
async handleXxx() {
  const result = await this.check()  // 可能 reject
  if (!result.ok) return  // 提前 return,AI 不会在这里埋点
  // ...主流程
  this.track()  // AI 只会在这里埋
}

2. 错误分支的 catch

typescript 复制代码
try {
  await this.doSomething()
  this.trackSuccess()
} catch (e) {
  this.handleError()
  // AI 不会在这里埋 trackFail
}

3. 组件生命周期中的提前卸载

typescript 复制代码
aboutToDisappear() {
  // 用户在异步操作完成前返回了
  // AI 完全忽略这个时机
}

4. 用户主动取消的分支

typescript 复制代码
handleShare() {
  this.dialog.open()
  this.dialog.onDismiss((action) => {
    if (action === 'cancel') return  // 用户取消,AI 不会埋 trackCancel
    this.trackShare(action)
  })
}

5. 重复触发时的去重逻辑

typescript 复制代码
// 按钮防抖逻辑,AI 不知道这是为了去重,会重复埋
private lastClickTime = 0
handleClick() {
  if (Date.now() - this.lastClickTime < 500) return
  this.lastClickTime = Date.now()
  this.track()
}

这五类场景,AI 一个都没考虑。它给你的是"理想路径的完美埋点",不是"真实路径的完整埋点"。

我的修复方案:把 AI 代码全删了,重写 35 行

最后我把 AI 生成的 200 行代码全删了。改回了我自己写的 35 行"丑但全"的埋点:

typescript 复制代码
// 手写版埋点:覆盖率第一,可读性其次
export class EventTracker {
  // 通用打点:所有可能路径都埋
  static async trackWithGuard<T>(
    eventName: string,
    fn: () => Promise<T>,
    errorEvent: string,
    params?: Record<string, any>
  ): Promise<T | null> {
    const startTime = Date.now()
    try {
      const result = await fn()
      // 主路径成功埋点
      HttpClient.post('/track', {
        event: eventName,
        status: 'success',
        duration: Date.now() - startTime,
        ...params
      }).catch(() => {})  // 埋点失败不影响主流程
      return result
    } catch (e) {
      // 错误分支埋点
      HttpClient.post('/track', {
        event: errorEvent,
        status: 'error',
        error: String(e),
        ...params
      }).catch(() => {})
      return null
    }
  }
}

// 业务侧使用(以"加入对比"为例)
async handleAddCompare() {
  const result = await EventTracker.trackWithGuard(
    'add_compare',  // 成功事件
    'add_compare_fail',  // 失败事件
    async () => {
      const user = await this.checkLogin()
      if (!user) {
        router.pushUrl({ url: 'pages/Login' })
        return null  // 未登录不算"加入对比"成功
      }
      await this.compareService.add(this.itemId)
      promptAction.showToast({ message: '已加入对比' })
      return true
    },
    { itemId: this.itemId }  // 公共参数
  )
  
  if (result === null) {
    // 这里不用再埋点,trackWithGuard 内部已经处理
    return
  }
}

这套写法看着不如 AI 给的"漂亮",但它的优势是:

  • 强制把成功/失败/取消三类路径都埋上 ,通过 try-catchreturn null 强制业务方走完整流程
  • 埋点失败不影响主流程 ,因为 .catch(() => {}) 吞掉了网络错误
  • 统一参数注入 ,避免漏传 itemId 之类的上下文

我把这套改完,五个事件全部重新埋了一遍。两周后运营拉数据,14 个触发场景全部覆盖,覆盖率 100%。

反思:AI 适合写什么,不适合写什么

这次踩坑之后,我重新整理了一下 AI 在前端代码里的适用边界:

AI 适合写的:

  • 工具函数(纯函数、输入输出明确)
  • 组件样板(CRUD 列表、表单弹窗)
  • 类型定义(TypeScript interface)
  • 单测代码(AI 写测试用例反而很准)

AI 不适合写的:

  • 埋点(覆盖率敏感) ← 本次的教训
  • 状态管理(异步分支多)
  • 错误处理(容易漏分支)
  • 安全相关代码(鉴权、加密)
  • 性能关键路径(容易写出"正确但慢"的代码)

判断标准其实就一句话:如果这段代码的"完整覆盖"比"优雅"更重要,就别让 AI 写。

埋点就是典型的"覆盖率即生命"的代码------漏埋一个事件,运营拉数据就少一块,可能影响一个产品决策。

而 AI 给你的是"看起来完整"的代码,反而比"明显不完整"的代码更危险,因为你会本能地信任它。

写在最后

我后来跟同事复盘这次经历,他说"那以后埋点就全手写呗"。我倒觉得不是这么绝对------AI 不是不能写埋点,是要换一种用法

我现在的工作流是:

  1. AI 先帮我生成"理想路径"的埋点代码(主流程)
  2. 我自己手动补全所有边缘场景(提前 return、catch、卸载、取消)
  3. 跑一个两周的影子期,用真实数据比对覆盖率
  4. 漏埋的就补上

AI 在第一步提效明显(以前写 200 行埋点要半天,现在 5 分钟),但第二步第三步必须人来。

这次复盘之后,我把该应用所有埋点代码都过了一遍------果然,AI 写的另外三个模块也漏埋了若干个边缘场景。改了之后,运营那边的数据终于对得上了。

如果你也在用 AI 写埋点,建议第一周就拉真实数据比对覆盖率,别等一个月才发现问题。

关于作者:斌哥,10+ 年软件开发老兵,软件设计师、注册人工智能工程师、agent 工程师,日常折腾鸿蒙 ArkTS 北向开发和 Web 前端,最近在用 AI 写一些自己的小项目。偶尔在 CSDN 分享鸿蒙和 AI 方向的文章。雷达鸭(华为应用市场可下载的鸿蒙版)就是用这套 AI 协作工作流做的真实产品。
本文遵循 MIT 协议,转载请注明出处。

相关推荐
Junerver3 天前
把 DevEco Code 的 HarmonyOS 开发能力装进口袋——harmonyos-dev-skill
harmonyos
程序猿追4 天前
那个右下角的小数字怎么“卡”住我打字——我用 HarmonyOS 自己写了一个字数限制输入框
pytorch·华为·harmonyos
古德new4 天前
鸿蒙PC使用electron迁移:Joplin Electron 桌面适配全记录
华为·electron·harmonyos
世人万千丶4 天前
桌面便签小应用 - HarmonyOS ArkUI 开发实战-TextArea与Flex布局-PC版本
华为·harmonyos·鸿蒙·鸿蒙系统
慧海灵舟4 天前
AGenUI 鸿蒙端实战踩坑录:从 Column 布局消失到异步组件宽度为 0
华为·harmonyos
yuegu7774 天前
HarmonyOS应用<节气通>开发第33篇:状态管理实战
华为·harmonyos
YM52e4 天前
买菜计算器小应用 - HarmonyOS ArkUI 开发实战-PC版本
学习·华为·harmonyos·鸿蒙·鸿蒙系统
阿捏利4 天前
系列总览-鸿蒙科普系列完全指南
华为·harmonyos
小雨下雨的雨4 天前
HarmonyOS ArkUI训练营入门-组件掌握系列-Animation 动画效果实现-PC版本
学习·华为·harmonyos·鸿蒙