HarmonyOS ArkTS 倒计时组件实战:当AI建议用策略模式时,我选择了质疑

23 阅读8分钟

HarmonyOS ArkTS 倒计时组件实战:设计模式反思篇 - 当AI建议用策略模式时,我选择了质疑

最近在重构一个 HarmonyOS 倒计时组件,遇到了一个复杂的条件分支。我让AI帮忙优化,它建议使用策略模式。但我质疑了这个建议,最终找到了更简洁实用的方案。想和大家分享一下这个过程,特别是在AI时代,如何保持独立思考,识别过度设计。

📋 背景:一个复杂的条件分支

项目中有一个 decideWhatToShow() 方法,需要根据多种条件决定显示的时间格式:

decideWhatToShow() {
  /*倒计时分支较多,注意保留注释减小维护难度*/
  // 不开启自定义时:hh:mm:ss 或 D天hh小时
  // 开启自定义时:根据样式类型显示不同格式
  
  if (this.remainingTime == 0) {
    return
  }
  
  const above48h = (this.totalTime > 60 * 60 * 48) && this.allow48ToDay
  let key = ''
  
  if (!this.originalEnableCustomStyle) {
    key = above48h ? 'D天hh小时' : 'hh:1mm:2ss'
  } else {
    this.customStyleEnabled = true
    if (this.activeCustomStyleType === 0) {
      key = 'hh:1mm:2ss'
      this.shouldHideHour && (key = key.replace('hh:', ''))
      this.shouldShowMS && (key = key + '.ms')
    } else if (this.activeCustomStyleType === 1) {
      key = 'D天hh小时'
    } else if (this.activeCustomStyleType === 2) {
      key = 'ss'
      this.shouldShowMS && (key = key + '.ms')
    } else if (this.activeCustomStyleType === 3) {
      key = '自定义文本'
    } else {
      this.customStyleEnabled = false
      if (above48h) {
        key = 'D天hh小时'
      } else {
        key = 'hh:1mm:2ss'
        this.shouldHideHour && (key = key.replace('hh:', ''))
        this.shouldShowMS && (key = key + '.ms')
      }
    }
  }
  
  this.assignFlags(key)
}

这段代码的问题很明显:

  • 60+ 行的 if-else 嵌套,看着就头疼
  • 逻辑分散,维护起来很麻烦
  • 有重复代码(格式构建逻辑出现多次)
  • 连注释都在说"分支较多,注意保留注释减小维护难度"

看到这段代码,我的第一反应就是:这必须重构! 但我一时想不出特别好的方案,于是让AI帮忙看看。

🤖 AI的建议:策略模式

AI分析后,建议使用策略模式。它说这是处理多种算法/策略的经典设计模式,正好符合这个场景,并且给出了详细的实现方案:

AI的方案设计

AI设计了一个基于 Record 数据驱动的策略模式:

// 策略上下文
interface FormatStrategyContext {
  remainingTime: number
  totalTime: number
  allow48ToDay: boolean
  shouldHideHour: boolean
  shouldShowMS: boolean
  activeCustomStyleType?: number
  originalEnableCustomStyle: boolean
}

// 策略结果
interface FormatStrategyResult {
  key: string
  customStyleEnabled: boolean
}

// 策略函数类型
type FormatStrategy = (context: FormatStrategyContext) => FormatStrategyResult

// 策略 Record
const formatStrategies: Record<string, FormatStrategy> = {
  'default-above48h': (context) => ({
    key: 'D天hh小时',
    customStyleEnabled: false
  }),
  'default-below48h': (context) => ({
    key: buildBaseTimeFormat(context.shouldHideHour, context.shouldShowMS),
    customStyleEnabled: false
  }),
  'custom-type-0': (context) => ({
    key: buildBaseTimeFormat(context.shouldHideHour, context.shouldShowMS),
    customStyleEnabled: true
  }),
  // ... 8个策略函数
}

// 策略选择和执行
function selectStrategyKey(context: FormatStrategyContext): string { /* ... */ }
function executeFormatStrategy(context: FormatStrategyContext): FormatStrategyResult { /* ... */ }

// 重构后的主方法
decideWhatToShow() {
  if (this.remainingTime == 0) return
  
  const context: FormatStrategyContext = {
    remainingTime: this.remainingTime,
    totalTime: this.totalTime,
    // ... 构建上下文
  }
  
  const result = executeFormatStrategy(context)
  this.customStyleEnabled = result.customStyleEnabled
  this.assignFlags(result.key)
}

AI的方案看起来确实很"优雅":

  1. ✅ 消除了复杂的 if-else 分支,主方法从 60+ 行减少到 20 行
  2. ✅ 使用了经典的设计模式
  3. ✅ 符合函数式风格,用 Record 存储函数
  4. ✅ 可扩展性强,新增策略只需在 Record 中添加

但当我仔细看代码统计时,发现了问题:

  • 增加了约 160 行代码(类型定义 + 策略函数 + 选择逻辑)
  • 主方法从 60 行减少到 20 行
  • 总代码量从 60 行增加到 180 行

❓ 我的质疑:这是优化吗?

看到这个数据,我开始质疑AI的建议:

"增加了100多行代码,功能一样,性能差不多,这真的是优化吗?"

说实话,这种"为了优雅而增加代码"的做法,我在同事的代码中也经常看到。大家似乎都认为"用了设计模式 = 代码质量高",但真的是这样吗?

数据对比分析

先看看数据对比:

指标原始实现AI的策略模式变化
代码行数60行180行+120行
函数数量1个5个+4个
类型定义0个3个+3个
性能基准相同无变化
功能完整完整无变化

仔细分析一下:

  1. 代码量大幅增加:从 60 行增加到 180 行,增加了 200%
  2. 功能完全一样:没有任何新功能
  3. 性能没有提升:甚至可能因为函数调用有微小的开销
  4. 复杂度增加:需要理解策略模式、上下文、选择逻辑等概念

这真的是优化吗?还是为了"优雅"而过度设计?

我意识到,这种"为了用设计模式而用设计模式"的做法,在团队代码中太常见了。大家似乎都认为"用了设计模式 = 代码质量高",但忽略了代码简洁性的价值。

🤔 深入分析:AI的建议真的适合吗?

AI 用了“类/上下文/选择器”这套偏 OOP 的惯性写法,是否为当前简单场景付出了过高的复杂度成本?

我开始分析策略模式真正适用的场景:

  1. 策略数量多且会持续增长(>10个策略)
  2. 每个策略逻辑复杂(有大量独立逻辑)
  3. 需要频繁扩展新策略
  4. 策略需要独立测试

再看看当前场景:

  1. 策略数量固定:只有 8 种格式,且基本固定
  2. 策略逻辑简单:大部分只是返回不同的字符串
  3. 扩展需求不明确:未来是否会频繁扩展未知
  4. 测试需求一般:逻辑简单,测试成本低

AI的建议虽然"优雅",但把策略模式和面向对象实现强绑定:

  • ❌ 违反了 YAGNI 原则(You Aren't Gonna Need It)
  • ❌ 违反了 KISS 原则(Keep It Simple, Stupid)
  • ❌ 这是过度设计(Over-engineering)

这让我想到,在团队代码评审中,我经常看到类似的过度设计。大家似乎都认为"用了设计模式 = 代码质量高",但忽略了实际场景和代码简洁性的重要性。

🔄 寻找更好的方案

认识到AI的策略模式建议是过度设计后,我开始寻找更合适的方案。AI又建议了一个轻量级的优化:

// 只提取重复的格式构建逻辑
function buildTimeFormatKey(shouldHideHour: boolean, shouldShowMS: boolean): string {
  let key = 'hh:1mm:2ss'
  if (shouldHideHour) {
    key = key.replace('hh:', '')
  }
  if (shouldShowMS) {
    key = key + '.ms'
  }
  return key
}

decideWhatToShow() {
  // ... 原有逻辑,但使用 buildTimeFormatKey 消除重复
  key = buildTimeFormatKey(this.shouldHideHour ?? false, this.shouldShowMS ?? false)
}

这个方案:

  • ✅ 只增加 10 行代码
  • ✅ 消除了重复逻辑
  • ✅ 保持了原有结构

✨ 最终方案:场景映射

经过思考,我找到了一个更简洁的方案,用 Record 直接映射场景到格式:

decideWhatToShow() {
  if (this.remainingTime == 0) {
    return //到时保持最终样式
  }
  
  const above48h = (this.totalTime > 60 * 60 * 48) && this.allow48ToDay
  this.customStyleEnabled = this.originalEnableCustomStyle &&
    this.activeCustomStyleType != null && this.activeCustomStyleType !== -1
  
  const occasionKey = this.originalEnableCustomStyle
    ? ('启用自定义' + (this.activeCustomStyleType ?? -1))
    : '禁用自定义'
  
  // 提取重复的分钟格式构建逻辑
  const minuteForm = (this.shouldHideHour ? '1mm:2ss' : 'hh:1mm:2ss') + (this.shouldShowMS ? '.ms' : '')
  
  const occasions: Record<string, string> = {
    '禁用自定义': above48h ? 'D天hh小时' : 'hh:1mm:2ss',
    '启用自定义0': minuteForm,
    '启用自定义1': 'D天hh小时',
    '启用自定义2': this.shouldShowMS ? 'ss.ms' : 'ss',
    '启用自定义3': '自定义文本',
    '启用自定义-1': above48h ? 'D天hh小时' : minuteForm,
  }
  
  this.assignFlags(occasions[occasionKey] ?? occasions['禁用自定义'])
}

怎么看待AI的设计模式

AI 的写法更贴近 GoF 式“上下文+策略对象+选择器”的传统策略模式;

我去掉了 OOP 仪式,但意图一致。

根据最初的GoF模式描述来狭义理解的话,不完全吻合最初描述就不算xx模式,需要给出新的命名,但是我们更应该关注本质的思想,因此大可使用广义的解释,认为我的方法同时具备:

  • 策略模式(广义):不同 key/occasion 选择不同格式生成策略(数据式/函数式实现依然是策略模式)。
  • 状态模式(类比):不同 occasionKey 也像不同状态触发不同输出。
  • 工厂模式(轻量):根据条件“生产”格式字符串,属于轻量工厂。
  • 表驱动编程:完全基于查找表完成分派与输出。

为什么这个方案更好?

1. 代码量对比
方案代码行数增加量
原始实现60行基准
策略模式180行+120行
轻量级优化70行+10行
场景映射25行-35行
2. 设计思路/可读性/维护性对比
维度OOP 策略模式场景映射
设计思路上下文+策略选择器+执行器,函数/类层层分派字符串场景键直接映射格式,条件内联在值里
可读性需要理解上下文、选择、执行的调用链一眼看到“场景 → 格式”映射
维护性改格式需改策略函数,新增场景需加函数+选择逻辑改格式直接改表值,新增场景加一行表项

💡 精简结论与经验

结论

  • 当前场景不需要重型/OOP 策略模式,轻量 Record/表驱动策略即可。
  • 模式要看意图,不必绑死在 GoF/OOP 形式。

方案对比

维度原始OOP 策略场景映射
代码行数6018025
可读性很高
可维护性
可扩展性很高
复杂度
推荐度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

工程经验

  • 简洁性优先:别为“优雅”牺牲体量和维护成本。
  • 数据驱动要简单:映射表 > 过度抽象的函数分派。
  • 识别过度设计:代码量增长>50%,功能/性能无收益,要警惕。
  • 工程权衡:代码量 vs 维护性,简洁 vs 扩展,当前 vs 未来。
  • AI 时代的独立思考:AI建议需经工程判断,不盲从。

实用检查表

  • 这次“优化”代码量涨了多少?功能/性能有提升吗?
  • 是否为了用设计模式而用设计模式?
  • 映射表/数据驱动是否能更简单地解决?
  • 场景是否真的需要重型架构?未来扩展是否明确?

参考资料: