Elixir/Phoenix 开发助手。运行并解读 mix test、credo、dialyzer、format。生成符合 OTP 规范的模块(Contexts、Schemas、GenServers 等)。调试编译错误,协助 Ecto 迁移与查询。适用于编写模块、修复测试、重构代码及理解 OTP 模式。
Elixir Dev. 运行混合命令是一项面向实际任务的技能,主要用于请参看参考文献/ mix- commands. md 以获取完整命令的参考文献;Test. 解释失败 : 1. (MatchError) - 模式匹配失败;
它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。使用时应结合输入条件选择合适的执行方式,核对必要参数、依赖环境与输出内容,并按原始要求处理异常情况。从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;
若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。该技能适合用于一次性任务,也可以接入自动化工作流,与其他技能或上层代理配合完成更完整的业务链路;在组合使用时,应明确每一步的输入输出关系,并避免不同步骤之间出现参数冲突。
完整命令参考请见 references/mix-commands.md。
# 运行全部测试 mix test # 指定文件或行号 mix test test/my_app/accounts_test.exs:42 # 按标签筛选 mix test --only integration # 仅重跑失败用例(需在前一次运行中启用 --failed 标志) mix test --failed # 启用覆盖率统计 mix test --cover
解读测试失败信息:
** (MatchError) —— 模式匹配失败;检查返回值结构是否符合预期。** (Ecto.NoResultsError) —— 对不存在的 ID 调用了 Repo.get!;改用 Repo.get 或补充种子数据(seed data)。** (DBConnection.OwnershipError) —— 缺少 async: true 配置或未正确设置 sandbox。no function clause matching —— 函数参数元数(arity)错误,或传入了意外类型的参数。mix credo --strict mix credo suggest --format json mix credo explain MyApp.Module # 解释指定模块中的问题
常见 Credo 修复方式:
Credo.Check.Readability.ModuleDoc —— 添加 @moduledoc 注释。Credo.Check.Refactor.CyclomaticComplexity —— 提取辅助函数以降低圈复杂度。Credo.Check.Design.TagTODO —— 处理或移除 TODO 注释。mix dialyzer mix dialyzer --format short
常见 Dialyzer 警告含义:
The pattern can never match —— 不可达代码(dead code),或模式中类型不匹配。Function has no local return —— 所有执行路径均会崩溃;检查内部调用逻辑。The call will never return —— 调用了总会抛出异常的函数。@spec 类型签名;万不得已时可使用 @dialyzer {:nowarn_function, func: arity} 抑制警告。mix format mix format --check-formatted # CI 模式 —— 若存在未格式化代码则退出码为 1
所有公开函数必须包含 @moduledoc、@doc 和 @spec。
defmodule MyApp.Notifications do
@moduledoc """
管理通知投递与用户偏好设置。
"""
import Ecto.Query
alias MyApp.Repo
alias MyApp.Notifications.Notification
@doc "按时间倒序列出指定用户的全部通知。"
@spec list_notifications(String.t(), keyword()) :: [Notification.t()]
def list_notifications(user_id, opts \\ []) do
limit = Keyword.get(opts, :limit, 50)
Notification
|> where(user_id: ^user_id)
|> order_by(desc: :inserted_at)
|> limit(^limit)
|> Repo.all()
end
end
defmodule MyApp.Notifications.Notification do
@moduledoc """
推送/邮件/SMS 通知的数据结构定义。
"""
use Ecto.Schema
import Ecto.Changeset
@type t :: %__MODULE__{}
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@timestamps_opts [type: :utc_datetime_usec]
schema "notifications" do
field :channel, Ecto.Enum, values: [:push, :email, :sms]
field :title, :string
field :body, :string
field :delivered_at, :utc_datetime_usec
field :user_id, :binary_id
timestamps()
end
@required ~w(channel title body user_id)a
@doc false
def changeset(notification, attrs) do
notification
|> cast(attrs, @required ++ [:delivered_at])
|> validate_required(@required)
|> validate_length(:title, max: 255)
end
end
GenServer、Supervisor、Agent、Task 等典型 OTP 模式的详细说明请参阅 references/otp-patterns.md。
| 模式 | 适用场景 |
|---|---|
| GenServer | 需要维护状态并支持同步/异步调用的进程(如缓存、限流器、连接池) |
| Agent | 仅需简单封装状态,无复杂业务逻辑 |
| Task | 一次性异步任务,可选择“即发即弃”或等待结果 |
| Task.Supervisor | 受监督的“即发即弃”任务 |
| Supervisor | 管理子进程生命周期(启动、重启策略等) |
| Registry | 基于名称或键进行进程查找 |
| DynamicSupervisor | 运行时动态启动子进程 |
defmodule MyApp.RateLimiter do
@moduledoc "令牌桶限流器。"
use GenServer
# 客户端 API
def start_link(opts) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
@spec check_rate(String.t()) :: :ok | {:error, :rate_limited}
def check_rate(key), do: GenServer.call(__MODULE__, {:check, key})
# 服务端回调
@impl true
def init(opts) do
{:ok, %{limit: Keyword.get(opts, :limit, 100), window_ms: 60_000, buckets: %{}}}
end
@impl true
def handle_call({:check, key}, _from, state) do
now = System.monotonic_time(:millisecond)
{count, state} = increment(state, key, now)
if count <= state.limit, do: {:reply, :ok, state}, else: {:reply, {:error, :rate_limited}, state}
end
defp increment(state, key, now) do
# 实现细节
end
end
| 错误信息 | 原因 | 修复方法 |
|---|---|---|
module X is not available |
依赖缺失或模块名拼写错误 | 检查 mix.exs 中的 deps 配置,并核对模块名 |
undefined function X/N |
未导入(import)或未别名引用(alias)该函数所在模块 | 添加 import、alias,或使用完整模块路径调用 |
(CompileError) redefining module |
模块名重复定义 | 重命名其中一个模块 |
protocol not implemented |
缺少对应协议(protocol)的实现 | 为你的 struct 添加 defimpl |
cannot use ^x outside of match |
Pin 操作符(^)使用位置错误 |
将其移至模式匹配上下文中(如 case、函数头、receive 等) |
def list(filters) do
Enum.reduce(filters, base_query(), fn
{:status, val}, q -> where(q, [r], r.status == ^val)
{:since, dt}, q -> where(q, [r], r.inserted_at >= ^dt)
{:search, term}, q -> where(q, [r], ilike(r.name, ^"%#{term}%"))
_, q -> q
end)
|> Repo.all()
end
# 查询时预加载(单次查询 + JOIN) from(p in Post, join: a in assoc(p, :author), preload: [author: a]) # 分离式预加载(两次查询) Post |> Repo.all() |> Repo.preload(:author) # 嵌套预加载 Repo.preload(posts, [comments: :author])
from(o in Order,
where: o.tenant_id == ^tenant_id,
group_by: o.status,
select: {o.status, count(o.id), sum(o.amount)}
)
|> Repo.all()
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket, items: [], loading: true)}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
MyApp.Items.delete_item!(id)
{:noreply, assign(socket, items: MyApp.Items.list_items())}
end
@impl true
def render(assigns) do
~H"""
<%= item.name %>
"""
end
end
# 在 mount 中订阅
def mount(_, _, socket) do
if connected?(socket), do: Phoenix.PubSub.subscribe(MyApp.PubSub, "items")
{:ok, assign(socket, items: list_items())}
end
# 在 Context 中广播事件
def create_item(attrs) do
with {:ok, item} <- %Item{} |> Item.changeset(attrs) |> Repo.insert() do
Phoenix.PubSub.broadcast(MyApp.PubSub, "items", {:item_created, item})
{:ok, item}
end
end
# 在 LiveView 中处理消息
def handle_info({:item_created, item}, socket) do
{:noreply, update(socket, :items, &[item | &1])}
end
相关专题
热门下载
相关下载
精品课程
共6课时 | 54.7万人学习
共89课时 | 134.1万人学习
共49课时 | 82.5万人学习
最新文章