重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
elixir-antipatterns by gentleman-programming/gentleman-skills
npx skills add https://github.com/gentleman-programming/gentleman-skills --skill elixir-antipatterns影响 Elixir/Phoenix 应用程序健壮性和可维护性的关键反模式。
补充工具:使用
mix format和Credo进行风格检查
扩展参考:查看EXTENDED.md获取 40 多个模式和深入示例
主题:错误处理(3 个模式) • 架构(2 个模式) • 性能(2 个模式) • 测试(1 个模式)
在以下情况下加载此技能:
本技能强制的 8 个核心模式快速参考:
{:ok, value} | {:error, reason} 而不是 nil 或异常广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
Repo.preload/2 避免 N+1 查询with 中对所有可能失败的操作使用 <-with 链分组到函数中查看下面的
## 反模式部分获取详细的 ❌ 错误 / ✅ 正确代码示例。
# ✅ 正确 - 错误作为值,在 @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
# ❌ 错误 - 对业务错误使用异常
def fetch_user(id) do
Repo.get(User, id) || raise "User not found"
end
架构分层:
用户请求 → LiveView(仅 UI) → 上下文(业务逻辑) → 模式/仓库(数据)
↓ ↓ ↓
handle_event() Accounts.create_user() Repo.insert()
# ✅ 正确 - 轻量级 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
# ❌ 错误 - 业务逻辑在 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
# ✅ 正确 - 预加载关联(总共 2 次查询)
users = User |> Repo.all() |> Repo.preload(:posts)
Enum.map(users, fn user -> process(user, user.posts) end)
# 注意:对于复杂过滤(例如,WHERE posts.status = 'published'),
# 在查询本身中使用 join + preload。查看 EXTENDED.md 获取高级模式。
# ❌ 错误 - 在循环中查询(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)
raise# ❌ 错误
def fetch_user(id) do
Repo.get(User, id) || raise "User not found"
end
# ✅ 正确
@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 记录错误,模式匹配强制显式处理。
nil# ❌ 错误 - 失败时没有上下文
def find_user(email), do: Repo.get_by(User, email: email)
# ✅ 正确 - 显式错误原因
@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
with 内部对可能失败的操作使用 =# ❌ 错误 - 验证错误被静默处理
with {:ok, user} <- fetch_user(id),
validated = validate(user), # ← 不检查 {:error, _}
{: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
# ❌ 错误 - 在视图中进行验证
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", 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 即可测试,逻辑可重用。
with 中链接超过 4 个步骤# ❌ 错误 - 职责过多
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, validated} <- validate_and_fetch(id),
{:ok, processed} <- process_business_rules(validated),
{:ok, result} <- persist_and_notify(processed) do
{:ok, result}
end
# ❌ 错误 - 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)
# ✅ 正确 - 总共 2 次查询
User |> Repo.all() |> Repo.preload(:posts)
影响:100 个用户使用 N+1 = 10 秒 vs 使用预加载 5 毫秒。
# ❌ 错误 - 频繁查询的列没有索引
# 迁移:
create table(:users) do
add :email, :string
end
# ✅ 正确 - 添加索引
create table(:users) do
add :email, :string
end
create unique_index(:users, [:email])
原因:在 100 万+ 行上进行全表扫描 vs 即时索引查找。
# ❌ 错误 - 在测试什么?
test "creates user" do
UserService.create_user(%{name: "Juan"})
end
# ✅ 正确 - 断言预期行为
test "creates user successfully" do
assert {:ok, user} = UserService.create_user(%{name: "Juan"})
assert user.name == "Juan"
end
| 场景 | 反模式 | 正确模式 |
|---|---|---|
| 错误处理 | raise "Not found" | {:error, :not_found} |
| 数据缺失 | 返回 nil | {:error, :not_found} |
| 业务逻辑 | 在 LiveView 中 | 在上下文模块中 |
| 关联关系 | Enum.map + Repo.get | Repo.preload |
| with 链 | validated = fn() | {:ok, validated} <- fn() |
| 频繁查询 | 无索引 | create index(:table, [:column]) |
| 测试 | 无断言 | assert 预期行为 |
| 复杂逻辑 | 6+ 步 with | 分组到 3 个函数中 |
EXTENDED.md 获取 40 多个反模式每周安装次数
47
代码仓库
GitHub 星标数
362
首次出现
2026年1月25日
安全审计
安装于
opencode42
gemini-cli34
codex34
github-copilot33
cursor33
kimi-cli30
Critical anti-patterns that compromise robustness and maintainability in Elixir/Phoenix applications.
Complement with :
mix formatandCredofor style enforcement
Extended reference : SeeEXTENDED.mdfor 40+ patterns and deep-dive examples
Topics: Error handling (3 patterns) • Architecture (2 patterns) • Performance (2 patterns) • Testing (1 pattern)
Load this skill when:
Quick reference to the 8 core patterns this skill enforces:
{:ok, value} | {:error, reason} instead of nil or exceptionsRepo.preload/2 to avoid N+1 queries<- for all failable operations in withwith chains >4 steps into functionsSee
## Anti-Patternssection below for detailed ❌ BAD / ✅ CORRECT code examples.
# ✅ CORRECT - Errors as values, explicit in @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
# ❌ BAD - Exceptions for business errors
def fetch_user(id) do
Repo.get(User, id) || raise "User not found"
end
Architecture Layers:
User Request → LiveView (UI only) → Context (business logic) → Schema/Repo (data)
↓ ↓ ↓
handle_event() Accounts.create_user() Repo.insert()
# ✅ CORRECT - Thin LiveView, logic in context
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
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
# ✅ CORRECT - Preload associations (2 queries total)
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'),
# use join + preload in the query itself. See EXTENDED.md for advanced patterns.
# ❌ BAD - Query in loop (101 queries for 100 users)
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)
raise for Business Errors# ❌ BAD
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.
nil for Errors# ❌ BAD - No context on failure
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
= Inside with for Failable Operations# ❌ 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
# ✅ CORRECT - All operations use <-
with {:ok, user} <- fetch_user(id),
{:ok, validated} <- validate(user),
{:ok, saved} <- save(validated) do
{:ok, saved}
end
# ❌ 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
# ✅ 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.
with# ❌ 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
# ✅ 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
# ❌ BAD - 101 queries for 100 users
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
User |> Repo.all() |> Repo.preload(:posts)
Impact : 100 users with N+1 = 10 seconds vs 5ms with preload.
# ❌ BAD - No index on frequently queried column
# Migration:
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.
# ❌ BAD - What's being tested?
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
| Situation | Anti-Pattern | Correct Pattern |
|---|---|---|
| Error handling | raise "Not found" | {:error, :not_found} |
| Missing data | Return nil | {:error, :not_found} |
| Business logic | In LiveView | In context modules |
| Associations | Enum.map + Repo.get |
EXTENDED.md for 40+ anti-patternsWeekly Installs
47
Repository
GitHub Stars
362
First Seen
Jan 25, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode42
gemini-cli34
codex34
github-copilot33
cursor33
kimi-cli30
Next.js 15+ 最佳实践指南:文件约定、RSC边界、异步模式与性能优化
1,400 周安装
Repo.preload |
| with chains | validated = fn() | {:ok, validated} <- fn() |
| Frequent queries | No index | create index(:table, [:column]) |
| Testing | No assertions | assert expected behavior |
| Complex logic | 6+ step with | Group into 3 functions |