Elixir 错误
错误(或 异常 )用于代码中发生异常时.当试图将一个数字与原子相加,就可得到一个错误的例子:
iex> :foo + 1
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(:foo, 1)
一个运行时错误可有raise/1
引发:
iex> raise "oops"
** (RuntimeError) oops
其它错误可以由raise/2
引发,通过传送错误名称和一个关键词列表作为参数:
iex> raise ArgumentError, message: "invalid argument foo"
** (ArgumentError) invalid argument foo
你也可以通过在一个模块中使用defexception
结构来定义你自己的错误;这时你创造了一个与模块同名的错误.最常用的场景是定义一个带信息场的异常:
iex> defmodule MyError do
iex> defexception message: "default message"
iex> end
iex> raise MyError
** (MyError) default message
iex> raise MyError, message: "custom message"
** (MyError) custom message
错误可以被解救,通过try/rescue
结构:
iex> try do
...> raise "oops"
...> rescue
...> e in RuntimeError -> e
...> end
%RuntimeError{message: "oops"}
上述例子将运行时错误解救,并返回错误本身,然后将其打印到iex
中.
如果错误对你毫无用处,你可以不显示它:
iex> try do
...> raise "oops"
...> rescue
...> RuntimeError -> "Error!"
...> end
"Error!"
实际中,Elixir开发者很少用到try/rescue
结构.例如,当文件无法被打开时,许多语言会强制你解救这个错误.作为替代,Elixir中提供了File.read/1
函数,其会返回一个包含文件是否被成功打开的信息的元组.
iex> File.read "hello"
{:error, :enoent}
iex> File.write "hello", "world"
:ok
iex> File.read "hello"
{:ok, "world"}
这里没有try/rescue
.如果你想要处理打开文件时的不同输出,你可以简单地使用case
来进行模式匹配:
iex> case File.read "hello" do
...> {:ok, body} -> IO.puts "Success: #{body}"
...> {:error, reason} -> IO.puts "Error: #{reason}"
...> end
最终,打开文件时发生的错误是否为异常将由你的应用来决定.这就是Elixir为何不给File.read/1
和其它许多函数强加异常.而是留给开发者来选择最好的处理方式.
当你确信一个文件存在(缺失文件确实是错误的),你可以简单地使用File.read!/1
:
iex> File.read! "unknown"
** (File.Error) could not read file unknown: no such file or directory
(elixir) lib/file.ex:305: File.read!/1
标准库中的许多函数遵循对应的异常引发模式,而非返回匹配元组.函数foo
会返回{:ok, result}
或{:error, reason}
元组,而另一个函数(foo!
,同名但带有!
)虽然接受与foo
同样的参数,但遇到错误时会抛出异常.如果一切正常,foo!
会返回(没有被元组包裹的)结果.File
模块就是很好的例子.
在Elixir中,我们避免使用try/rescue
,因为我们不在控制流中使用错误.我们这样解释错误:它们是预留给意料外或异常的情形的.当你需要使用控制流结构时,应该使用抛出.下面我们将讲到.
更多建议: