elixir-antipatterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseElixir Anti-Patterns
Elixir 反模式
Critical anti-patterns that compromise robustness and maintainability in Elixir/Phoenix applications.
Complement with:andmix formatfor style enforcementCredo
Extended reference: Seefor 40+ patterns and deep-dive examplesEXTENDED.md
这些会影响Elixir/Phoenix应用健壮性和可维护性的关键反模式。
配套工具:使用和mix format进行代码风格检查Credo
扩展参考:查看获取40+种模式及深度示例EXTENDED.md
When to Use
适用场景
Topics: Error handling (3 patterns) • Architecture (2 patterns) • Performance (2 patterns) • Testing (1 pattern)
Load this skill when:
- Writing Elixir modules and functions
- Working with Phoenix Framework (Controllers, LiveView)
- Building Ecto schemas and database queries
- Implementing BEAM concurrency (Task, GenServer)
- Handling errors with tagged tuples
- Writing tests with ExUnit
主题:错误处理(3种模式)• 架构(2种模式)• 性能(2种模式)• 测试(1种模式)
在以下场景使用本技能:
- 编写Elixir模块和函数
- 使用Phoenix Framework(Controllers、LiveView)开发
- 构建Ecto schema和数据库查询
- 实现BEAM并发(Task、GenServer)
- 使用标记元组处理错误
- 使用ExUnit编写测试
Critical Patterns
核心模式
Quick reference to the 8 core patterns this skill enforces:
- Tagged Tuples: Return instead of
{:ok, value} | {:error, reason}or exceptionsnil - Explicit @spec: Document error cases in function signatures
- Context Separation: Business logic in contexts, not LiveView
- Preload Associations: Use to avoid N+1 queries
Repo.preload/2 - with Arrow Binding: Use for all failable operations in
<-with - Database Indexes: Index frequently queried columns
- Test Assertions: Every test must assert expected behavior
- Cohesive Functions: Group chains >4 steps into functions
with
Seesection below for detailed ❌ BAD / ✅ CORRECT code examples.## Anti-Patterns
本技能涵盖的8种核心模式速查:
- 标记元组:返回而非
{:ok, value} | {:error, reason}或异常nil - 显式@spec:在函数签名中记录错误情况
- 上下文分离:业务逻辑放在上下文模块,而非LiveView
- 预加载关联:使用避免N+1查询
Repo.preload/2 - with箭头绑定:在中对所有可能失败的操作使用
with<- - 数据库索引:为频繁查询的列添加索引
- 测试断言:每个测试必须断言预期行为
- 内聚函数:将超过4步的链封装为函数
with
查看下方「## 反模式」章节获取详细的❌ 错误示例 / ✅ 正确示例。
Code Examples
代码示例
Example 1: Error Handling with Tagged Tuples
示例1:使用标记元组处理错误
elixir
undefinedelixir
undefined✅ CORRECT - Errors as values, explicit in @spec
✅ 正确 - 错误作为值,在@spec中显式声明
defmodule UserService do
@spec fetch_user(String.t()) :: {:ok, User.t()} | {:error, :not_found}
def fetch_user(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
end
defmodule UserService do
@spec fetch_user(String.t()) :: {:ok, User.t()} | {:error, :not_found}
def fetch_user(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
end
❌ BAD - Exceptions for business errors
❌ 错误 - 对业务错误使用异常
def fetch_user(id) do
Repo.get(User, id) || raise "User not found"
end
undefineddef fetch_user(id) do
Repo.get(User, id) || raise "User not found"
end
undefinedExample 2: Phoenix LiveView with Context Separation
示例2:Phoenix LiveView的上下文分离
Architecture Layers:
User Request → LiveView (UI only) → Context (business logic) → Schema/Repo (data)
↓ ↓ ↓
handle_event() Accounts.create_user() Repo.insert()elixir
undefined架构分层:
用户请求 → LiveView(仅处理UI)→ 上下文(业务逻辑)→ Schema/Repo(数据层)
↓ ↓ ↓
handle_event() Accounts.create_user() Repo.insert()elixir
undefined✅ CORRECT - Thin LiveView, logic in context
✅ 正确 - 轻量LiveView,逻辑委托给上下文
defmodule MyAppWeb.UserLive.Index do
use MyAppWeb, :live_view
def handle_event("create", params, socket) do
case Accounts.create_user(params) do
{:ok, user} -> {:noreply, redirect(socket, to: ~p"/users/#{user}")}
{:error, changeset} -> {:noreply, assign(socket, changeset: changeset)}
end
end
end
defmodule MyAppWeb.UserLive.Index do
use MyAppWeb, :live_view
def handle_event("create", params, socket) do
case Accounts.create_user(params) do
{:ok, user} -> {:noreply, redirect(socket, to: ~p"/users/#{user}")}
{:error, changeset} -> {:noreply, assign(socket, changeset: changeset)}
end
end
end
❌ BAD - Business logic in LiveView
❌ 错误 - 业务逻辑放在LiveView中
def handle_event("create", %{"user" => params}, socket) do
if String.length(params["name"]) < 3 do
{:noreply, put_flash(socket, :error, "Too short")}
else
case Repo.insert(User.changeset(%User{}, params)) do
{:ok, user} -> send_email(user); redirect(socket)
end
end
end
undefineddef handle_event("create", %{"user" => params}, socket) do
if String.length(params["name"]) < 3 do
{:noreply, put_flash(socket, :error, "Too short")}
else
case Repo.insert(User.changeset(%User{}, params)) do
{:ok, user} -> send_email(user); redirect(socket)
end
end
end
undefinedExample 3: Ecto N+1 Query Optimization
示例3:Ecto N+1查询优化
elixir
undefinedelixir
undefined✅ CORRECT - Preload associations (2 queries total)
✅ 正确 - 预加载关联(共2次查询)
users = User |> Repo.all() |> Repo.preload(:posts)
Enum.map(users, fn user -> process(user, user.posts) end)
users = User |> Repo.all() |> Repo.preload(:posts)
Enum.map(users, fn user -> process(user, user.posts) end)
Note: For complex filtering (e.g., WHERE posts.status = 'published'),
注意:对于复杂过滤(如WHERE posts.status = 'published'),
use join + preload in the query itself. See EXTENDED.md for advanced patterns.
需在查询中同时使用join + preload。查看EXTENDED.md获取进阶模式。
❌ BAD - Query in loop (101 queries for 100 users)
❌ 错误 - 循环内查询(100个用户会产生101次查询)
users = Repo.all(User)
Enum.map(users, fn user ->
posts = Repo.all(from p in Post, where: p.user_id == ^user.id)
{user, posts}
end)
---users = Repo.all(User)
Enum.map(users, fn user ->
posts = Repo.all(from p in Post, where: p.user_id == ^user.id)
{user, posts}
end)
---Anti-Patterns
反模式
Error Management
错误管理
Don't: Use raise
for Business Errors
raise不要:对业务错误使用raise
raiseelixir
undefinedelixir
undefined❌ BAD
❌ 错误
def fetch_user(id) do
Repo.get(User, id) || raise "User not found"
end
def fetch_user(id) do
Repo.get(User, id) || raise "User not found"
end
✅ CORRECT
✅ 正确
@spec fetch_user(String.t()) :: {:ok, User.t()} | {:error, :not_found}
def fetch_user(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
**Why**: `@spec` documents errors, pattern matching forces explicit handling.
---@spec fetch_user(String.t()) :: {:ok, User.t()} | {:error, :not_found}
def fetch_user(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
**原因**:`@spec`会记录错误类型,模式匹配强制显式处理错误。
---Don't: Return nil
for Errors
nil不要:用nil
表示错误
nilelixir
undefinedelixir
undefined❌ BAD - No context on failure
❌ 错误 - 失败时无上下文信息
def find_user(email), do: Repo.get_by(User, email: email)
def find_user(email), do: Repo.get_by(User, email: email)
✅ CORRECT - Explicit error reason
✅ 正确 - 显式错误原因
@spec find_user(String.t()) :: {:ok, User.t()} | {:error, :not_found}
def find_user(email) do
case Repo.get_by(User, email: email) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
---@spec find_user(String.t()) :: {:ok, User.t()} | {:error, :not_found}
def find_user(email) do
case Repo.get_by(User, email: email) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
---Don't: Use =
Inside with
for Failable Operations
=with不要:在with
中对可能失败的操作使用=
with=elixir
undefinedelixir
undefined❌ BAD - Validate errors silenced
❌ 错误 - 验证错误被静默
with {:ok, user} <- fetch_user(id),
validated = validate(user), # ← Doesn't check for {:error, _}
{:ok, saved} <- save(validated) do
{:ok, saved}
end
with {:ok, user} <- fetch_user(id),
validated = validate(user), # ← 未检查{:error, _}情况
{:ok, saved} <- save(validated) do
{:ok, saved}
end
✅ CORRECT - All operations use <-
✅ 正确 - 所有操作使用<-
with {:ok, user} <- fetch_user(id),
{:ok, validated} <- validate(user),
{:ok, saved} <- save(validated) do
{:ok, saved}
end
---with {:ok, user} <- fetch_user(id),
{:ok, validated} <- validate(user),
{:ok, saved} <- save(validated) do
{:ok, saved}
end
---Architecture & Boundaries
架构与边界
Don't: Put Business Logic in LiveView
不要:将业务逻辑放在LiveView中
elixir
undefinedelixir
undefined❌ BAD - Validation in view
❌ 错误 - 验证逻辑在视图中
def handle_event("create", %{"user" => params}, socket) do
if String.length(params["name"]) < 3 do
{:noreply, put_flash(socket, :error, "Too short")}
else
case Repo.insert(User.changeset(%User{}, params)) do
{:ok, user} -> redirect(socket)
end
end
end
def handle_event("create", %{"user" => params}, socket) do
if String.length(params["name"]) < 3 do
{:noreply, put_flash(socket, :error, "Too short")}
else
case Repo.insert(User.changeset(%User{}, params)) do
{:ok, user} -> redirect(socket)
end
end
end
✅ CORRECT - Delegate to context
✅ 正确 - 委托给上下文模块
def handle_event("create", params, socket) do
case Accounts.create_user(params) do
{:ok, user} -> {:noreply, redirect(socket, to: ~p"/users/#{user}")}
{:error, changeset} -> {:noreply, assign(socket, changeset: changeset)}
end
end
**Why**: Contexts testable without Phoenix, logic reusable.
---def handle_event("create", params, socket) do
case Accounts.create_user(params) do
{:ok, user} -> {:noreply, redirect(socket, to: ~p"/users/#{user}")}
{:error, changeset} -> {:noreply, assign(socket, changeset: changeset)}
end
end
**原因**:上下文模块无需Phoenix即可测试,逻辑可复用。
---Don't: Chain More Than 4 Steps in with
with不要:with
链超过4步
withelixir
undefinedelixir
undefined❌ BAD - Too many responsibilities
❌ 错误 - 职责过多
with {:ok, a} <- step1(),
{:ok, b} <- step2(a),
{:ok, c} <- step3(b),
{:ok, d} <- step4(c),
{:ok, e} <- step5(d) do
{:ok, e}
end
with {:ok, a} <- step1(),
{:ok, b} <- step2(a),
{:ok, c} <- step3(b),
{:ok, d} <- step4(c),
{:ok, e} <- step5(d) do
{:ok, e}
end
✅ CORRECT - Group into cohesive functions
✅ 正确 - 封装为内聚函数
with {:ok, validated} <- validate_and_fetch(id),
{:ok, processed} <- process_business_rules(validated),
{:ok, result} <- persist_and_notify(processed) do
{:ok, result}
end
---with {:ok, validated} <- validate_and_fetch(id),
{:ok, processed} <- process_business_rules(validated),
{:ok, result} <- persist_and_notify(processed) do
{:ok, result}
end
---Data & Performance
数据与性能
Don't: Query Inside Loops (N+1)
不要:循环内查询(N+1问题)
elixir
undefinedelixir
undefined❌ BAD - 101 queries for 100 users
❌ 错误 - 100个用户产生101次查询
users = Repo.all(User)
Enum.map(users, fn user ->
posts = Repo.all(from p in Post, where: p.user_id == ^user.id)
end)
users = Repo.all(User)
Enum.map(users, fn user ->
posts = Repo.all(from p in Post, where: p.user_id == ^user.id)
end)
✅ CORRECT - 2 queries total
✅ 正确 - 共2次查询
User |> Repo.all() |> Repo.preload(:posts)
**Impact**: 100 users with N+1 = 10 seconds vs 5ms with preload.
---User |> Repo.all() |> Repo.preload(:posts)
**影响**:N+1查询处理100个用户需10秒,预加载仅需5毫秒。
---Don't: Query Without Indexes
不要:无索引查询
elixir
undefinedelixir
undefined❌ BAD - No index on frequently queried column
❌ 错误 - 频繁查询的列无索引
Migration:
迁移文件:
create table(:users) do
add :email, :string
end
create table(:users) do
add :email, :string
end
✅ CORRECT - Add index
✅ 正确 - 添加索引
create table(:users) do
add :email, :string
end
create unique_index(:users, [:email])
**Why**: Full table scan on 1M+ rows vs instant index lookup.
---create table(:users) do
add :email, :string
end
create unique_index(:users, [:email])
**原因**:百万级数据下全表扫描 vs 索引即时查找。
---Testing
测试
Don't: Write Tests Without Assertions
不要:编写无断言的测试
elixir
undefinedelixir
undefined❌ BAD - What's being tested?
❌ 错误 - 测试目标不明确
test "creates user" do
UserService.create_user(%{name: "Juan"})
end
test "creates user" do
UserService.create_user(%{name: "Juan"})
end
✅ CORRECT - Assert expected behavior
✅ 正确 - 断言预期行为
test "creates user successfully" do
assert {:ok, user} = UserService.create_user(%{name: "Juan"})
assert user.name == "Juan"
end
---test "creates user successfully" do
assert {:ok, user} = UserService.create_user(%{name: "Juan"})
assert user.name == "Juan"
end
---Quick Reference
速查表
| Situation | Anti-Pattern | Correct Pattern |
|---|---|---|
| Error handling | | |
| Missing data | Return | |
| Business logic | In LiveView | In context modules |
| Associations | | |
| with chains | | |
| Frequent queries | No index | |
| Testing | No assertions | |
| Complex logic | 6+ step | Group into 3 functions |
| 场景 | 反模式 | 正确模式 |
|---|---|---|
| 错误处理 | | |
| 数据缺失 | 返回 | |
| 业务逻辑 | 放在LiveView中 | 放在上下文模块 |
| 关联查询 | | |
| with链 | | |
| 频繁查询 | 无索引 | |
| 测试 | 无断言 | |
| 复杂逻辑 | 6步以上 | 封装为3个内聚函数 |
Resources
参考资源
- Elixir Style Guide
- Phoenix Contexts
- Ecto Query Performance
- ExUnit Best Practices
- Extended patterns: See for 40+ anti-patterns
EXTENDED.md
- Elixir 风格指南
- Phoenix 上下文
- Ecto 查询性能
- ExUnit 最佳实践
- 扩展模式:查看获取40+种反模式
EXTENDED.md