Elixir 从零 · 第 28 课(展开目录)
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 2845 分钟
只开三扇门
设计小而稳的公开 API,把分词细节藏进私有函数。
01 · 先看完整例子
让公开入口只接收行
本项目约定空白分隔词,字符数不包含换行。
Elixir
defmodule TrailStats.Stats do
@enforce_keys [:lines, :words, :chars]
defstruct [:lines, :words, :chars]
end
defmodule TrailStats do
alias TrailStats.Stats
# 对外只公开 count/1
def count(lines) do
%Stats{
lines: length(lines),
words: Enum.reduce(lines, 0, &(&2 + word_count(&1))),
chars: lines |> Enum.join() |> String.length()
}
end
defp word_count(line), do: line |> String.split() |> length()
end先对照结果
TrailStats.count(["长安 春风", "又一程"])返回行 2、词 3、字符 8 的 struct。
02 · 回头拆代码
从第一行往下读
- struct 固定三项输出,避免 map 键随手变化。
count/1只接内部数据,不关心路径。- 分词规则藏在
word_count/1,以后可单独替换。
03 · 认清新符号
符号不是暗号
def定义公开函数。
defp定义只供当前模块使用的私有函数。
%Stats{}返回固定字段的统计结果。
04 · 代码里的概念
把名字和意思对上
公开 API
模块外可以依赖的函数和数据形状。
实现细节
模块内部可修改而不影响调用者的步骤,通常放在私有函数。
05 · 再说清楚
这些写法为什么有用
模块对外开的门越多,未来越难改。项目只公开调用者真正需要的函数。
统计模块接收已经清洗的行,不再读文件。字符、词、行的规则写在同一处。
内部 helper 使用 defp。调用者看见稳定结果,不需要知道中间 list 怎样生成。
06 · 自己改一次
合上答案,动手
补全空输入测试。
练习起点
assert TrailStats.count([]) == %TrailStats.Stats{
lines: ____,
words: ____,
chars: ____
}目标结果: 三项都为 0。
卡住了,再看提示
空输入没有行、词和字符。
运行过以后,再看一种答案
一种答案
# 空输入是公开 API 的重要边界
assert TrailStats.count([]) == %TrailStats.Stats{
lines: 0,
words: 0,
chars: 0
}想一想:为什么 `word_count/1` 使用 `defp`?
它只是 count/1 的实现细节,调用者不需要依赖它。
带走
记住三句
- 1
公开 API 要小而明确。
- 2
模块之间传稳定数据,不传隐含状态。
- 3
实现细节用私有函数收好。
本课结束代码也跑过一次,就把这一课收好。