Elixir 从零 · 第 29 课(展开目录)
01 · 先让代码说话02 · 先认六种值03 · 把值装起来04 · 从旧 list 生成新 list05 · 让形状对上06 · 用 case 做选择07 · 把文字安全变成整数08 · 拆开 &1 和管道09 · 把代码放进项目10 · 真假不只靠 true11 · 走进嵌套数据12 · 一字不一定一字节13 · 让函数按形状接活14 · 把一列数收成一个值15 · 给不同问题选不同路16 · 给 map 一张名片17 · 让项目自己验答案18 · 让成功和失败长得一样19 · 读写文件先管好路20 · 只算眼前需要的21 · 同一句话,不同做法22 · 先约好模块会做什么23 · 把数据形状写在门口24 · 测试不只看一条好路25 · 把项目磨成一个命令26 · 定下小项目边界27 · 把真实文字洗干净28 · 只开三扇门29 · 把约定钉在代码旁30 · 打包,也留下脚印31 · 照任务书交卷
ELIXIR · 作品 · LESSON 2950 分钟

把约定钉在代码旁

为项目补齐文档、typespec 与跨模块测试。

01 · 先看完整例子

为公开入口留一份可运行说明

完整例子同时声明 struct 类型、公开函数与可运行文档。

Elixir
defmodule TrailStats.Stats do
  @type t :: %__MODULE__{
          lines: non_neg_integer(),
          words: non_neg_integer(),
          chars: non_neg_integer()
        }
  defstruct [:lines, :words, :chars]
end

defmodule TrailStats do
  alias TrailStats.Stats
  @moduledoc "统计已经清洗的 UTF-8 文本行。"

  @doc """
  返回行、词和字符统计。

      iex> TrailStats.count(["长安 春风"]).words
      2
  """
  # 类型要与真实 struct 返回值一致
  @spec count([String.t()]) :: Stats.t()
  def count(lines) do
    %Stats{
      lines: length(lines),
      words: Enum.sum(for line <- lines, do: length(String.split(line))),
      chars: lines |> Enum.join() |> String.length()
    }
  end
end
先对照结果
  • 加入 doctest TrailStats 后,mix test 会验证示例结果 2。
02 · 回头拆代码

从第一行往下读

  1. 为 Stats 增加 @type t :: %__MODULE__{...}。
  2. 为所有公开函数写 spec,不给私有细节堆无用文案。
  3. 用临时文件测试 Parser 与 count 的完整成功路径。
03 · 认清新符号

符号不是暗号

@moduledoc

说明模块做什么和不做什么。

@doc

说明公开函数约定。

doctest Module

运行文档里的 IEx 示例。

04 · 代码里的概念

把名字和意思对上

01

集成测试

让多个模块一起工作,检查边界衔接是否正确。

02

文档示例

放在函数文档中的简短调用与结果,可由 doctest 自动验证。

05 · 再说清楚

这些写法为什么有用

项目走到这里,代码会运行,但约定还散在脑中。文档、类型与测试把它们留给明天的自己。

@moduledoc 说明模块责任,@doc 说明公开函数,@spec 说明输入输出。三者不要重复实现细节。

单元测试看一个模块;集成测试从临时文件走到统计结果。两层各守一段路。

06 · 自己改一次

合上答案,动手

为 Stats 写出三项非负整数类型。

练习起点
@type t :: %__MODULE__{
  lines: ____,
  words: ____,
  chars: ____
}

目标结果: 三个字段都写 non_neg_integer()。

卡住了,再看提示

计数不会是负数。

运行过以后,再看一种答案
一种答案
@type t :: %__MODULE__{
  # 三项都是从零开始的计数
  lines: non_neg_integer(),
  words: non_neg_integer(),
  chars: non_neg_integer()
}
想一想:文档是否应该逐行复述函数实现?

不该。文档重点是责任、输入输出和重要边界;实现可直接读源码。

带走

记住三句

  1. 1

    文档讲约定,不抄实现。

  2. 2

    类型与测试应覆盖真实返回分支。

  3. 3

    集成测试检查模块衔接。

本课结束代码也跑过一次,就把这一课收好。