Elixir 从零 · 第 18 课(展开目录)
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 1830 分钟
让成功和失败长得一样
用 `{:ok, value}` 与 `{:error, reason}` 建立可组合的结果约定。
01 · 先看完整例子
安全地做除法
函数只返回稳定数据,外层负责写给人看的文字。
Elixir
defmodule Ratio do
# 零是明确的失败输入
def divide(_left, 0), do: {:error, :division_by_zero}
def divide(left, right), do: {:ok, left / right}
end
for input <- [{12, 3}, {12, 0}] do
case Ratio.divide(elem(input, 0), elem(input, 1)) do
{:ok, value} -> "结果:#{value}"
{:error, :division_by_zero} -> "不能除以零"
end
end先对照结果
- 得到
["结果:4.0", "不能除以零"]。
02 · 回头拆代码
从第一行往下读
- 最具体的零除数子句放在前面。
- 两条路都返回二元 tuple,第一项说明成功或失败。
case在边界处把内部原因翻译成人话。
03 · 认清新符号
符号不是暗号
{:ok, value}操作成功,第二项带回结果。
{:error, reason}操作失败,第二项说明原因。
with把多个遵守相同约定的步骤连起来。
04 · 代码里的概念
把名字和意思对上
结果约定
调用者与函数共同遵守的返回值形状。
错误原因
说明失败类别的稳定值,常用 atom 或带细节的 tuple。
05 · 再说清楚
这些写法为什么有用
函数会失败时,调用者最怕结果忽左忽右。统一形状比一句含糊说明更可靠。
成功返回 {:ok, value},失败返回 {:error, reason}。调用者可以用模式匹配处理两条路。
reason 应短而稳定,如 :division_by_zero。给人看的句子放在显示层,不要塞进每个底层函数。
设计选择
为什么这样写
为什么常返回 {:ok, value} 或 {ok, Value}?
预料之中的失败也写进返回值。调用者能用模式列出成功、失败和兜底分支。
真正意外、无法在本层处理的错误仍可以抛出,让监督边界接手。把所有异常都吞成 :error 会丢掉证据。
换一把尺子
从 Java、Python、JavaScript 看过来
Java
- 熟悉的起点
- checked/unchecked exception 或 Optional。
- BEAM 在意什么
- tagged result 把常见业务结果放在普通控制流中。
- 别带错直觉
- 不是每个错误都该 crash,也不是每个异常都该 catch。
Python
- 熟悉的起点
- 异常与
None。 - BEAM 在意什么
- tag 明说失败类别,避免只靠一个空值猜原因。
- 别带错直觉
- 边界处可以把第三方异常翻译成稳定 result。
JavaScript
- 熟悉的起点
- throw、rejected Promise 或 null。
- BEAM 在意什么
- tuple 不是 Promise;它是已经拿到手的数据契约。
- 别带错直觉
- 不要把课程里的 contract 误读成异步 Promise。
06 · 自己改一次
合上答案,动手
补全 positive/1,只接受正数。
练习起点
def positive(number) when number > 0, do: {____, number}
def positive(_), do: {____, ____}目标结果: 正数得到 {:ok, number},其他输入得到 {:error, :not_positive}。
卡住了,再看提示
成功标签是 :ok,失败标签是 :error。
运行过以后,再看一种答案
一种答案
def positive(number) when number > 0, do: {:ok, number}
# 原因使用稳定 atom
def positive(_), do: {:error, :not_positive}想一想:为什么不在失败时随便返回 `nil`?
nil 没说明失败原因,也容易与正常的“没有值”混淆;error tuple 更明确。
带走
记住三句
- 1
失败函数要公开稳定的返回形状。
- 2
atom 适合表示可匹配的错误类别。
- 3
展示文字与底层错误原因分开。
本课结束代码也跑过一次,就把这一课收好。