可枚举重构技能Skill enumerable-refactor

这个技能专注于重构 Ruby 代码,通过使用 Enumerable 方法替代手动迭代和空变量初始化,识别并重构常见的反模式。它旨在提高代码的简洁性、表达性和性能,适用于 Ruby 3.3+ 版本。关键词:Ruby, Enumerable, 代码重构, 迭代优化, 反模式, 集合处理。

后端开发 0 次安装 0 次浏览 更新于 3/19/2026

name: enumerable-refactor description: 这个技能应在用户请求重构 Ruby 代码以使用 Enumerable 方法替代手动迭代和空变量初始化时使用。当审查初始化空数组、哈希、字符串或计数器,然后通过 .each 循环填充的 Ruby 代码时使用。针对 Ruby 3.3+。当用户提及 “enumerable”、“refactor each”、“ruby iteration”,或在审查 Ruby 代码以进行惯用的集合处理时使用。

可枚举重构

概述

这个技能识别并重构最常见的 Ruby 反模式:初始化一个空变量并用 .each 填充它。几乎每次你看到这个模式,都有一个专门构建的 Enumerable 方法,它更简洁、表达性更强,并且通常性能更好。

核心反模式

# 任何时候你看到这种形状,它几乎肯定可以重构:
result = []        # 或 {}, 0, "", Set.new, false, nil
something.each do |item|
  # ... 构建 result ...
end
result

何时使用此技能

应用当:

  • 审查或重构使用 .each 构建返回值的 Ruby 代码
  • 用户要求使 Ruby 代码更符合习惯用法
  • 用户提及 “enumerable”、“refactoring” 或 “each loop”
  • 在代码审查中发现空变量然后each的模式

不要应用当:

  • .each 循环纯粹用于副作用(发送邮件、写入数据库、打印)
  • 循环有复杂的控制流(retry、异常处理)确实抵抗函数式表达
  • 命令式版本对于特定情况确实更清晰

工作流程

1. 找到反模式

搜索代码库中的迹象:

# 搜索空数组初始化后跟 .each
Grep for: "= []\s*$" in *.rb 文件
Grep for: "= {}\s*$" in *.rb 文件
Grep for: "= 0\s*$" in *.rb 文件
Grep for: "= \"\"\s*$" in *.rb 文件
Grep for: "= Set.new" in *.rb 文件
Grep for: "= false\s*$" in *.rb 文件

然后检查这些初始化是否在几行内后跟 .each

加载参考以获取完整模式目录:

读取 references/patterns.md

2. 分类每个实例

对于每个找到的反模式,识别 .each 循环在做什么:

循环行为 替换为
将每个元素转换为新数组 map
转换 + 扁平化嵌套数组 flat_map
转换 + 移除 nil filter_map
保留匹配条件的元素 select / filter
移除匹配条件的元素 reject
从集合构建哈希 to_h { |x| [k, v] }
用条件/复杂逻辑构建哈希 each_with_object({})
按键分组元素 group_by
分成两组(true/false) partition
求和数字 sum
计数匹配 count
构建频率哈希 tally
找到第一个匹配 find / detect
按属性找到最小/最大 min_by / max_by
设置布尔标志 any? / all? / none?
连接字符串 joinmap { ... }.join
累积单个不可变值 reduce / inject

3. 重构

对于每个实例,应用替换。显示前后。验证行为被保留。

关键规则:

  • 对于可变累加器(哈希、数组、集合),使用 each_with_object,而不是 inject
  • 只对不可变累加(数字、冻结字符串)使用 inject/reduce
  • 优先使用 filter_map 而不是 .select.map.map.compact(单次遍历)
  • 优先使用 tally 而不是 Hash.new(0) + .each 用于频率计数
  • 对于简单的键值映射,优先使用 to_h { |x| [k, v] } 而不是 each_with_object({})
  • 优先使用 partition 而不是两个独立的 select/reject 调用
  • 优先使用 sort_by 而不是带比较块的 sort

4. 检查链式机会

在单个重构后,寻找可以简化的方法链:

# 两次遍历 -> 一次遍历
users.select(&:active?).map(&:email)
# 变为
users.filter_map { |u| u.email if u.active? }

# 不必要的中间数组
departments.map(&:employees).flatten
# 变为
departments.flat_map(&:employees)

# 两次独立迭代用于最小和最大
lowest = temps.min
highest = temps.max
# 变为
lowest, highest = temps.minmax

5. 验证

  • 确保重构后的代码产生相同结果
  • 如果有测试,运行测试
  • 检查细微差异(例如,filter_map 也移除 false 值,不仅仅是 nil

重要注意事项

  1. filter_map 也移除 false:如果转换可以合法返回 false,请使用 .map.compact 代替。

  2. inject 哈希错误:切勿使用 inject 构建哈希 — Hash#[]= 返回值,而不是哈希。使用 each_with_object 代替。

  3. 惰性评估:对于非常大的集合,你只需要前 N 个结果,建议在链前使用 .lazy

  4. Rails 扩展:在 Rails 代码库中,也建议在适当情况下使用 index_byindex_withpluckexcludingsole

  5. 副作用与 .each 可以:不是每个 .each 都是反模式。如果循环体执行副作用并且不构建返回值,.each 是正确的选择。