轨道
/
Elixir
Elixir
/
练习
/
RPN 计算器
RPN 计算器

RPN 计算器

学习练习

简介

错误

错误总会发生。在 Elixir 里,人们常说“让它崩溃”,但有时为了履行软件约定,我们需要把函数调用从错误中恢复到一个已知的良好状态。在某些语言中,错误被用作一种控制流的方法,但在 Elixir 中并不鼓励这种做法。通常只凭名字就能认出哪些函数可能会抛出错误:会抛出错误的函数,名字末尾都带!。与之相对的是返回{:ok, value}或:error的函数。看看下面这几个库函数的例子:

Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError

Try/Rescue

Elixir 提供了try .. rescue这个结构,用来从错误中恢复。

try do
  raise RuntimeError, "error"
rescue
  e in RuntimeError -> :error
end

我们来逐行看看这个结构:

  • 第 1 行,用try声明这个代码块。
  • 第 2 行,把可能出错的函数调用放在这里,本例中我们调用的是raise/2。
  • 第 4 行,在rescue段中,我们对所抛出错误的 模块 名称做模式匹配
    • 在->的左侧:
      • e与错误结构体匹配。
      • in是关键字。
      • RuntimeError是我们想要捕获的错误。
        • 如果想捕获所有错误,可以用_代替模块名,或者干脆省略in关键字。
    • 在右侧:
      • 错误匹配时要执行的指令。

错误结构体

用这种方式捕获的错误(有时也叫“异常”)都是结构体。 在 Elixir 中,捕获错误是很少见的做法。 通常会把捕获到的错误记入日志,或发送到外部监控服务,然后再重新抛出。 这意味着我们通常不关心具体错误结构体的内部结构。

在异常概念中,你会进一步了解错误结构体,包括如何定义自己的自定义错误。

说明

在 Instruments of Texas 工作期间,你接到的任务是开发一个用 Elixir 编写的实验性逆波兰表示法 [RPN] 计算器。你的团队遇到了一个问题:某些操作会抛出错误,导致进程崩溃。你接到的任务是编写一个函数来包装操作函数,以便用符合 Elixir 习惯的代码更优雅地处理这些错误。

1. 提醒团队

实现calculate!/2函数,让它调用操作函数,并把栈作为唯一的实参。操作函数定义在别处,但你知道它要么成功完成,要么抛出一个错误。

stack = []
operation = fn _ -> :ok end
RPNCalculator.calculate!(stack, operation)
# => :ok

stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate!(stack, operation)
# => ** (ArgumentError) An error occurred

以!结尾的函数名是在提醒程序员,这个函数可能会抛出错误

2. 包装错误

在做更多调研时,你发现很多函数用原子和元组来表示成功或失败。用这种策略实现calculate/2。

stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate(stack, operation)
# => {:ok, "operation completed"}

stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate(stack, operation)
# => :error

3. 传递消息

有些错误包含重要信息,你的同事需要这些信息来确保系统正常运行。实现calculate_verbose/2来传递错误消息。这个错误是一个结构体,其中有一个:message字段。

stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:ok, "operation completed"}

stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:error, "An error occurred"}
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Elixir Exercism

准备好开始 RPN 计算器 了吗?

注册 Exercism,借助 58 个概念168 个练习 和真人导师指导,学习并掌握 Elixir,全部免费。