轨道
/
Julia
Julia
/
练习
/
工厂传感器
工厂传感器

工厂传感器

学习练习

简介

程序员一般都力求写出完美的软件,但往往做不到。

事情总会出乎意料地出错,而我们需要有能力应对。

有些语言设计者认为,首要任务是尽快检测到错误,然后终止执行,并给出一条有助于调试的提示信息。

数据科学类的语言往往采取更细腻的做法。有些错误非常严重,必须立即终止;但很多时候,更好的做法是把问题标记出来留待以后处理,然后继续执行。

我们在空值概念中看到,Julia 为有问题的值提供了各种占位符:missing、NaN 和 Inf。在具体情况下,这些做法是否比直接终止程序更好,要由程序员自己判断。

关于命名的一点说明:在展开细节之前先讲清楚,Julia 文档基本把 “error” 和 “exception” 这两个词当作可以互换使用。下文的用法可能同样前后不一致。

标准错误类型

学到这里,你一定已经见过很多 Julia 给出的错误信息了。例如:

julia> Int(3.14)
ERROR: InexactError: Int64(3.14)

把浮点数转换为整数会损失精度,所以会得到 InexactError。

InexactError 是一个类型,是 Julia 内置的若干标准类型之一(目前有 25 种)。它们全都是 Exception 的子类型:

julia> supertype(InexactError)
Exception

throw()

有些标准错误类型,你可能也会想在自己的代码里抛出。

和所有具体类型一样,这些错误也有构造函数。它们接受的实参各不相同,所以要使用哪一种,请查阅文档。

julia> DomainError(42, "out of range")
DomainError(42, "out of range")

要使用这个错误,把构造函数包在 throw() 函数里:

julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range

error()

如果只想简单粗暴地处理,error() 函数会很方便。它接受一个字符串(或字符串的各个组成部分)作为实参:

julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong

自定义错误

创建新的错误类型原则上非常简单。只要再加一个 Exception 的子类型即可:

julia> struct MyError <: Exception end

julia> throw(MyError)
ERROR: MyError

断言

断言的基本想法是:“这个判定应该是真的,如果是假的就大声报错。” 它的价值主要体现在调试期间,因为生产代码不应该让断言失败。

我们在类型概念中看到,可以添加类型断言,比如用来检查函数的返回值类型。

julia> 42::Number
42

julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String

更一般地说,@assert 宏可以测试任何求值为布尔值的表达式:

julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd

try...catch

有些错误必然是致命的,但很多时候我们希望程序能优雅地恢复。

默认情况下,错误会立即终止当前函数,并把错误(连同任何提示信息)传给调用它的函数。

这样一路沿着调用栈向上传递,直到顶层代码带着错误信息终止。

在任何一层,都可以用 try...catch 块拦截错误并尝试处理它。

julia> n = -1;
julia> try
           log_n = log(n)
       catch problem
           if problem isa DomainError # number out of range
               # See next section for more on @warn and @info
               @warn "you may have supplied a negative real number: $n"
               @info "trying with complex argument"
               log_n = log(Complex(n))  # fallback calculation

           elseif problem isa MethodError # no idea what n is
               @error "please supply a valid argument"
 
           else
              rethrow() # the error could be anything else
           end
      end
┌ Warning: you may have supplied a negative real number: -1
└ @ Main REPL[3]:5
[ Info: trying with complex argument
0.0 + 3.141592653589793im  # success

在上面的例子里,log(n) 要求 n 要么是正实数,要么是任意的复数。try ... catch 会捕获负实数带来的问题,并返回用数学记号表示的正确答案 iπ。

比如,如果你传入的是字符串实参,那就无法恢复,只能请用户改正。

作为最后的兜底,我们加了 rethrow(),用来处理既不是 DomainError 也不是 MethodError 的情况。

***注意:***有时候 try...catch 确实是你需要的,但请避免过度使用。如果能改用 if...else 块,它的性能会比捕获异常好得多。

日志记录

注意,前面讨论的 error() 函数,不要和 @error 宏混淆。

这个函数会生成一个异常,除非被捕获,否则它会沿着调用栈向上传递。

@error 宏和它的 @debug、@info、@warn 同类一样,属于 Logging 模块,目的是生成提示信息而不改变程序的执行流程。

默认情况下,输出会送到终端(按严重程度用颜色区分),不过在真实的应用程序里还有很多其他可能。

julia> @warn "Something looks not quite right"
┌ Warning: Something looks not quite right
└ @ Main REPL[55]:1

julia> @error "Panic!"
┌ Error: Panic!
└ @ Main REPL[56]:1

另请参见前面 try...catch 一节的示例。

说明

Elena 是一家报纸工厂的新任质量经理。 她刚加入公司,便决定检查工厂里的一些流程,看看有哪些地方可以改进。 她发现技术人员要手工完成大量质量检查。她看到了自动化的好机会,于是请身为自由开发者的你开发一套软件,用来监控部分机器。

1. 检查房间的湿度

你的第一个任务是写一套软件,监控生产车间的湿度。公司软件上已经连接了一个传感器,它会定期返回车间的湿度百分比。

你需要在软件中实现一个函数,当湿度百分比过高时抛出错误。 如果湿度在可接受范围内,就添加一条 Info 日志。 这个函数应命名为humiditycheck,并以湿度百分比作为实参。

如果百分比超过 70%,你应该以 ErrorException 中止(具体消息内容不重要,但必须包含测得的湿度数值)。 否则,添加一条 Info 日志,消息为"humidity level check passed: h%",其中h是湿度百分比。

julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%

2. 检查过热

Elena 对你的第一个任务非常满意,于是请你负责监控机器的温度。在和技术人员 Greg 聊天时,你得知如果机器的温度超过 500°C,技术人员就会开始担心过热。

机器上装有一个测量其内部温度的传感器。你要知道,这个传感器非常敏感,经常损坏。这种情况下,技术人员就得更换它。

你的任务是实现一个函数temperaturecheck,它接受温度作为实参:一切正常时添加一条日志,传感器损坏或机器开始过热时抛出错误。考虑到你之后需要根据错误类型做出不同的反应,你需要一种机制来区分这两类错误。

  • 如果传感器损坏,温度会是nothing。这种情况下,你应该以ArgumentError中止(消息内容不重要)。
  • 传感器正常工作时,如果温度超过 500°C,你应该抛出包含测得温度的DomainError。
  • 否则一切正常,添加一条 Info 日志,消息为"temperature check passed: t °C",其中t是温度。
julia> temperaturecheck(nothing)
ERROR: ArgumentError: sensor is broken

julia> temperaturecheck(800)
ERROR: DomainError with 800:
"overheating detected"

julia> temperaturecheck(500)
[ Info: temperature check passed: 500 °C

3. 定义自定义错误

对于下一个任务,你需要定义一个更通用的、能包罗所有情况的错误。除了它是一个错误、且名称为MachineError之外,实现细节并不重要。你可以随意添加自己觉得有用的字段和消息。

4. 监控机器

现在机器已经能检测错误,你也有了自定义的机器错误,接下来要添加一个包装函数,用于报告各项的运行情况。除了返回前面那些函数产生的日志,这个包装函数还需要根据出现的失败类型添加日志。

  • 检查湿度和温度。
  • 如果湿度检查抛出ErrorException,应添加一条 Error 日志,消息为"humidity level check failed: h%",其中h是湿度百分比。
  • 如果温度检查抛出ArgumentError,应添加一条 Warn 日志,消息为"sensor is broken"。
  • 如果温度检查抛出DomainError,应添加一条 Error 日志,消息为"overheating detected: t °C",其中t是温度。
  • 如果两项检查中有一项或两项失败,应在添加日志后抛出单个MachineError。
  • 如果一切正常,只会添加humiditycheck和temperaturecheck产生的日志。

实现一个函数machinemonitor(),接受湿度和温度作为实参。

julia> machinemonitor(42, 450)
[ Info: humidity level check passed: 42%
[ Info: temperature check passed: 450 °C

julia> machinemonitor(42, 550)
[ Info: humidity level check passed: 42%
┌ Error: overheating detected: 550 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(82, 521)
┌ Error: humidity level check failed: 82%
└ @ Main # output truncated
┌ Error: overheating detected: 521 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(42, nothing)
[ Info: humidity level check passed: 42%
┌ Warning: sensor is broken
└ @ Main # output truncated

Error: MachineError
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Julia Exercism

准备好开始 工厂传感器 了吗?

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