用途
本技能是一份 Elixir/Phoenix 反模式目录,用于识别和规避会损害应用健壮性与可维护性的写法。核心目录包含 8 个关键反模式,主题分布为:错误处理 3 个、架构 2 个、性能 2 个、测试 1 个。
建议在以下场景加载:编写 Elixir 模块与函数;使用 Phoenix Framework(控制器、LiveView);构建 Ecto schema 与数据库查询;实现 BEAM 并发(Task、GenServer);使用标记元组处理错误;使用 ExUnit 编写测试。
主要能力(8 个核心反模式)
错误处理
-
用标记元组返回结果:应返回 {:ok, value} | {:error, reason},而不是返回 nil 或抛出异常。用 @spec 在函数签名中记录错误情形,模式匹配会强制调用方显式处理。
-
不要为业务错误使用 raise:例如不要写
Repo.get(User, id) || raise "User not found",而应返回 {:error, :not_found}。 -
不要在 with 的失败操作中使用 =:
with {:ok, user} <- fetch_user(id), validated = validate(user), ...中=不会检查 {:error, _},应改为{:ok, validated} <- validate(user)。
架构与边界
-
不要把业务逻辑放在 LiveView:校验与数据操作应委托给 context(如 Accounts.create_user/1),LiveView 只处理 UI 与事件分发。好处是 context 无需 Phoenix 即可测试,逻辑可复用。
-
不要在 with 中串联超过 4 步:过多的职责应聚合为内聚函数,例如分为 validate_and_fetch/1、process_business_rules/1、persist_and_notify/1 三段。
数据与性能
-
不要在循环中查询(N+1):应使用
User |> Repo.all() |> Repo.preload(:posts),将 100 个用户场景从 101 次查询降为 2 次。资料给出的影响对比为:100 用户下 N+1 约 10 秒,preload 约 5ms。若需要复杂过滤(如 WHERE posts.status = 'published'),应使用 join + preload,具体写法参见 EXTENDED.md。 -
不要在缺少索引的列上频繁查询:在迁移中为常查询列创建索引,例如
create unique_index(:users, [:email]),避免在百万行级数据上全表扫描。
测试
- 不要编写没有断言的测试:每个测试都应断言预期行为,例如
assert {:ok, user} = UserService.create_user(%{name: "Juan"})并进一步断言user.name == "Juan"。
使用方式
资料提供的使用方式为:在相应开发场景中加载本技能作为参考,按其中 ❌ BAD / ✅ CORRECT 代码示例对照修改。风格约束可配合 mix format 与 Credo。更多模式与深入示例参考 EXTENDED.md(资料提到其中包含 40 多个反模式,具体内容暂未提供)。
快速对照表
| 场景 | 反模式 | 正确模式 |
|---|---|---|
| 错误处理 | raise "Not found" | {:error, :not_found} |
| 数据缺失 | 返回 nil | {:error, :not_found} |
| 业务逻辑 | 放在 LiveView | 放在 context 模块 |
| 关联数据 | Enum.map + Repo.get | Repo.preload |
| with 链 | validated = fn() | {:ok, validated} <- fn() |
| 频繁查询 | 无索引 | create index(:table, [:column]) |
| 测试 | 无断言 | assert 预期行为 |
| 复杂逻辑 | 6 步以上 with | 聚合为 3 个函数 |
适用场景
Elixir 模块与函数编写、Phoenix 控制器与 LiveView 开发、Ecto schema 与查询构建、BEAM 并发(Task、GenServer)、标记元组错误处理、ExUnit 测试编写。
限制
资料未说明该技能的安装方式、运行环境要求、授权与收费信息,暂未提供。EXTENDED.md 中 40 多个模式的具体清单与示例内容,资料仅提及存在而未展开。