Elixir 从零 · 第 27 课(展开目录)
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 2745 分钟
把真实文字洗干净
读 UTF-8 文件,统一换行,再把内容解析成可统计数据。
01 · 先看完整例子
把文件变成非空行
把代码放进 lib/trail_stats/parser.ex,再用临时文件测试。
Elixir
defmodule TrailStats.Parser do
@spec read(Path.t()) :: {:ok, [String.t()]} | {:error, term()}
def read(path) do
with {:ok, text} <- File.read(path),
true <- String.valid?(text) do
# 同时接受 Unix 与 Windows 换行
lines = String.split(text, ~r/\R/u, trim: true)
{:ok, lines}
else
false -> {:error, :invalid_utf8}
{:error, reason} -> {:error, reason}
end
end
end先对照结果
- 有效文件返回
{:ok, lines};不存在文件保留{:error, :enoent}等系统原因。
02 · 回头拆代码
从第一行往下读
File.read/1先建立文件边界。- UTF-8 检查通过后才调用字符串函数。
- 正则
\R接受常见换行,trim: true去掉空行。
03 · 认清新符号
符号不是暗号
String.valid?/1检查 binary 是否是有效 UTF-8 字符串。
String.split/3按换行切开,并可去掉空项。
{:error, reason}把文件或编码错误交给上层。
04 · 代码里的概念
把名字和意思对上
解析层
把外部原始数据变成项目内部约定的数据形状。
规范化
把多种等价输入整理成一种稳定形式,如统一换行。
05 · 再说清楚
这些写法为什么有用
真实文件可能有空行,也可能使用不同换行符。先把输入整理成稳定形状,后续统计才简单。
解析层负责从文件路径得到行 list。它不打印、不退出程序,也不计算最终统计。
保留 File 的错误原因,另把非法 UTF-8 转为项目自己的 :invalid_utf8。
06 · 自己改一次
合上答案,动手
为只有两行的临时文件写一个 Parser 测试。
练习起点
path = Path.join(System.tmp_dir!(), "trail_stats_test.txt")
File.write!(path, "长安\n春风\n")
assert TrailStats.Parser.read(path) == ____目标结果: 期待 {:ok, ["长安", "春风"]}。
卡住了,再看提示
成功 tuple 的第二项是两行字符串 list。
运行过以后,再看一种答案
一种答案
path = Path.join(System.tmp_dir!(), "trail_stats_test.txt")
File.write!(path, "长安
春风
")
# Parser 只返回清洗后的行
assert TrailStats.Parser.read(path) == {:ok, ["长安", "春风"]}想一想:Parser 为什么不在失败时直接 `IO.puts`?
打印属于命令边界。Parser 返回数据后,测试、网页或 CLI 都能选择自己的显示方式。
带走
记住三句
- 1
外部输入先验证,再解析。
- 2
解析层返回数据,不负责显示。
- 3
错误原因应能继续交给上层判断。
本课结束代码也跑过一次,就把这一课收好。