simplifying-ruby-code
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSimplifying Ruby Code
简化 Ruby 代码
Prefer plain data (Hash, Array, Struct, Data) and pure functions over classes
that add no state or behavior. Separate decisions from effects: a decision
takes data and returns data; an effect does I/O (database, network, files,
time). Keep them in separate methods or modules so decisions test without
mocks.
When the user asks for a review, report findings and edit nothing unless
they ask. Cite patterns by number ("Pattern 1") or by anti-pattern name.
优先使用普通数据(Hash、Array、Struct、Data)和纯函数,而不是那些未增加任何状态或行为的类。将决策与副作用分离:决策接收数据并返回数据;副作用执行 I/O(数据库、网络、文件、时间)。将二者放在不同的方法或 module 中,使决策无需 mocks 即可测试。
当用户要求审查时,只报告发现的问题,除非用户明确要求,否则不要编辑代码。通过编号(“Pattern 1”)或反模式名称引用相关模式。
Patterns
模式
-
Command objects to module functions. A class whose only public method is,
call,perform, orrun, holding nothing beyond its constructor arguments, becomes a direct call or a module function (executebecomesUserCreator.new(params).call). Keep the object when it holds state across calls, runs a multi-step algorithm worth naming, or must be serialized, as a background job is.User.create(params) -
Value objects to Data, Struct, or Hash. A class that only stores attributes, with hand-written, readers, or
initializeand no behavior or validation:==Use When Hash Transient data, varying keys, JSON in or out Struct Fixed attributes, mutation acceptable Data Fixed attributes, immutable (check >= 3.2)ruby -vCustom class Validation, invariants, or real domain behavior -
Class-method-only classes to modules. A class never instantiated, defining onlymethods, becomes a module with
self..module_function -
Deep inheritance to composition. More than 2 levels counting only the project's own classes (framework and stdlib bases such asor
ApplicationRecorddo not count), or an abstract base with a single subclass.StandardError -
Missing Ruby protocols. Callers unpack a collection-like or value-like class by hand instead of it implementingwith
each,Enumerable,to_hwith<=>, orComparableandhash.eql? -
Mixed decisions and effects. Tests need heavy mocks, or a method computes and writes in one body. Extract the computation into a pure function and leave a thin method for the I/O.
-
Command object 转换为 module function。 如果一个类唯一的公开方法是、
call、perform或run,且除构造参数外不保存任何内容,则应改为直接调用或 module function(execute变为UserCreator.new(params).call)。当对象需要在多次调用之间保存状态、运行值得命名且包含多个步骤的算法,或必须像 background job 一样支持序列化时,应保留该对象。User.create(params) -
Value object 转换为 Data、Struct 或 Hash。 对于只存储属性、没有行为或验证,并手写、readers 或
initialize的类:==使用方式 适用场景 Hash 临时数据、键可变、输入或输出 JSON Struct 固定属性,允许变更 Data 固定属性,不可变(检查 >= 3.2)ruby -vCustom class 验证、不变量或真正的领域行为 -
仅含类方法的类转换为 module。 从不实例化且只定义方法的类,改为使用带有
self.的 module。module_function -
深层继承转换为组合。 继承层级超过 2 层时,仅计算项目自身的类;framework 和 stdlib 基类(如或
ApplicationRecord)不计入。抽象基类仅有一个子类时也应进行转换。StandardError -
缺少 Ruby protocol。 调用方手动解包 collection-like 或 value-like class,而该类没有通过实现并包含
each、实现Enumerable、通过to_h并包含<=>,或实现Comparable和hash来支持相应 protocol。eql? -
决策与副作用混杂。 测试需要大量 mocks,或一个方法在同一函数体中同时执行计算和写入时,应提取计算逻辑并放入纯函数,同时保留一个执行 I/O 的精简方法。
Rails anti-patterns
Rails 反模式
- Service objects: a single-method, stateless service belongs in a model method or module function (Pattern 1). Keep complex orchestration as a service but separate decisions from effects (Pattern 6). Jobs are legitimate objects because they serialize.
- Models: keep ActiveRecord models as classes; move business calculations into pure functions.
- Concerns: a concern shares behavior across models; flag one used as a dumping ground for a single model.
- Helpers: view formatting only; business rules go in a module.
- Reinvented Rails: custom DSLs or base classes that duplicate scopes,
validations, callbacks, enums, or .
ActiveModel
- Service objects: 单方法、无状态的 service 应放入 model method 或 module function(Pattern 1)。复杂的 orchestration 可以保留为 service,但应将决策与副作用分离(Pattern 6)。Job 是合理的对象,因为它们需要序列化。
- Models: ActiveRecord model 应保留为类;将业务计算移入纯函数。
- Concerns: Concern 用于在多个 model 之间共享行为;如果某个 concern 只是某个 model 的“垃圾场”,应将其标记出来。
- Helpers: 仅用于视图格式化;业务规则应放入 module。
- Reinvented Rails: 自定义 DSL 或基类重复实现了 scopes、validations、callbacks、enums 或 的功能。
ActiveModel
Plain Ruby anti-patterns
普通 Ruby 反模式
- Template methods: a base class with hook methods where a block would do.
- Builders: for objects that keyword arguments or build directly.
Data.define - Wrappers: classes that wrap Array, Hash, Set, or another stdlib type and forward most calls.
- Metaprogramming: ,
method_missing, ordefine_methodDSLs where plain methods work.instance_eval - Dependencies: a gem pulled in for a few lines of stdlib code.
In a gem, every public constant is API that other projects may call.
- Template methods: 基类包含 hook methods,而使用 block 本可完成相同目的。
- Builders: 为可直接通过 keyword arguments 或 构建的对象创建 builder。
Data.define - Wrappers: 类包装 Array、Hash、Set 或其他 stdlib 类型,并转发其中大多数调用。
- Metaprogramming: 使用 、
method_missing或define_method编写 DSL,而普通方法即可完成。instance_eval - Dependencies: 仅为几行 stdlib 代码引入一个 gem。
在 gem 中,每个 public constant 都是其他项目可能调用的 API。
Before removing or inlining a class
删除或内联类之前
- Run for each class.
bash <skill-dir>/scripts/find-callers.sh <Constant> <project> - Exit 0: account for every and
referencehit, and decide for eachrequirehit whether the name it builds can be the class; that takes reading the code, so it stays with you.dynamic - Exit 1: no caller found. Exit 2: fix the argument. Exit 3: install the named tool. Exit 4: report the grep error.
- A controller is also reached from by its snake_case resource name, which the script does not search; read that file.
config/routes.rb - In a review, drop or downgrade a finding whose callers you cannot account for.
- 对每个类运行 。
bash <skill-dir>/scripts/find-callers.sh <Constant> <project> - Exit 0:处理每一条 和
reference命中;对于每一条require命中,判断其构建的名称是否可能指向该类。这需要阅读代码,因此必须由你亲自判断。dynamic - Exit 1:未找到调用方。Exit 2:修正参数。Exit 3:安装指定工具。Exit 4:报告 grep 错误。
- Controller 还可以通过 中的 snake_case resource name 访问,而脚本不会搜索该文件;请读取该文件。
config/routes.rb - 在审查中,如果无法确认某个发现的全部调用方,应删除或降低该发现的严重程度。
Review findings
审查发现
Read review findings when reporting a
review: the grouping, the fields of each finding, and the closing order.
报告审查结果时,阅读审查发现:其中说明了分组方式、每条发现的字段以及收尾顺序。
Refactoring
重构
Get a green test baseline first. Apply one pattern at a time and run the
suite after each; on failure, fix or revert that step before continuing.
Update tests that mocked the removed layer to call the pure function or the
direct API instead.
先建立测试全绿基线。每次只应用一个模式,并在之后运行测试套件;如果失败,应先修复或回退该步骤,再继续。更新那些 mock 已删除层的测试,使其改为调用纯函数或直接 API。