0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

闭包在错误处理中的应用模式探索

马哥Linux运维 来源:华为云社区 2024-03-15 09:57 次阅读

错误

通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil,则没有错误发生——并且主调(calling)函数总是应该检查收到的错误。

处理错误并且在函数发生错误的地方给用户返回错误信息:照这样处理就算真的出了问题,你的程序也能继续运行并且通知给用户。panic and recover 是用来处理真正的异常
库函数通常必须返回某种错误提示给主调(calling)函数。

为了防止发生错误时正在执行的函数(如果有必要的话甚至会是整个程序)被中止,在调用函数后必须检查错误。

if value, err := pack1.Func1(param1); err != nil {
fmt.Printf(“Error %s in pack1.Func1 with parameter %v”, err.Error(), param1)
return // or: return err
}
// Process(value)

错误处理

Go 有一个预先定义的 error 接口类型

type error interface {
Error() string
}

错误值用来表示异常状态;

程序处于错误状态时可以用 os.Exit(1) 来中止运行。

定义错误

任何时候当你需要一个新的错误类型,都可以用 errors (必须先 import)包的errors.New 函数接收合适的错误信息来创建,像下面这样:err := errors.New(“math - square root of negative number”)在下面中你可以看到一个简单的用例:

// errors.go
package main
import (
"errors"
"fmt"
)
var errNotFound error = errors.New("Not found error")
func main() {
fmt.Printf("error: %v", errNotFound)
}
// error: Not found error

可以把它用于计算平方根函数的参数测试:

func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New (“math - square root of negative number”)
}
// implementation of Sqrt
}

你可以像下面这样调用 Sqrt 函数:

if f, err := Sqrt(-1); err != nil {
fmt.Printf(“Error: %s
”, err)
}
```## 用 fmt 创建错误对象
通常你想要返回包含错误参数的更有信息量的字符串,例如:可以用 fmt.Errorf() 来实现:它和fmt.Printf() 完全一样,接收有一个或多个格式占位符的格式化字符串和相应数量的占位变量。和打印信息不同的是它用信息生成错误对象。
比如在前面的平方根例子中使用:
```go
if f < 0 {
return 0, fmt.Errorf(“math: square root of negative number %g”, f)
}

第二个例子:从命令行读取输入时,如果加了 help 标志,我们可以用有用的信息产生一个错误:

if len(os.Args) > 1 && (os.Args[1] == “-h” || os.Args[1] == “--help”) {
err = fmt.Errorf(“usage: %s infile.txt outfile.txt”, filepath.Base(os.Args[0]))
return
}

运行时异常和 panic

当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时 panic,伴随着程序的崩溃抛出一个 runtime.Error 接口类型的值。这个错误值有个 RuntimeError() 方法用于区别普通错误。

panic 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 panic 函数产生一个中止程序的运行时错误。panic 接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。

package main
import "fmt"
func main() {
fmt.Println("Starting the program")
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}

输出如下:

Starting the program
panic: A severe error occurred: stopping the program!
panic PC=0x4f3038
runtime.panic+0x99 /go/src/pkg/runtime/proc.c:1032
runtime.panic(0x442938, 0x4f08e8)
main.main+0xa5 E:/Go/GoBoek/code examples/chapter 13/panic.go:8
main.main()
runtime.mainstart+0xf 386/asm.s:84
runtime.mainstart()
runtime.goexit /go/src/pkg/runtime/proc.c:148
runtime.goexit()
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
---- Program exited with code -1073741783

一个检查程序是否被已知用户启动的具体例子:

var user = os.Getenv(“USER”)
func check() {
if user == “” {
panic(“Unknown user: no value for $USER”)
}
}

可以在导入包的 init() 函数中检查这些。

当发生错误必须中止程序时, panic 可以用于错误处理模式:

if err != nil {
panic(“ERROR occurred:” + err.Error())
}

Go panicking:

在多层嵌套的函数调用中调用 panic,可以马上中止当前函数的执行,所有的 defer 语句都会保证执行并把控制权交还给接收到 panic 的函数调用者。这样向上冒泡直到最顶层,并执行(每层的) defer,在栈顶处程序崩溃,并在命令行中用传给 panic 的值报告错误情况:这个终止过程就是 panicking。

从 panic 中恢复(Recover)

正如名字一样,这个(recover)内建函数被用于从 panic 或 错误场景中恢复:让程序可以从 panicking重新获得控制权,停止终止过程进而恢复正常执行。

recover 只能在 defer 修饰的函数中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil,且没有其它效果。

总结:panic 会导致栈被展开直到 defer 修饰的 recover() 被调用或者程序中止。下面例子中的 protect 函数调用函数参数 g 来保护调用者防止从 g 中抛出的运行时 panic,并展示 panic中的信息:

func protect(g func()) {
defer func() {
log.Println(“done”)
// Println executes normally even if there is a panic
if err := recover(); err != nil {
log.Printf(“run time panic: %v”, err)
}
}()
log.Println(“start”)
g() // possible runtime-error
}

log 包实现了简单的日志功能:默认的 log 对象向标准错误输出中写入并打印每条日志信息的日期和时间。除了 Println 和 Printf 函数,其它的致命性函数都会在写完日志信息后调用 os.Exit(1),那些退出函数也是如此。而 Panic 效果的函数会在写完日志信息后调用 panic;可以在程序必须中止或发生了临界错误时使用它们.下面展示 panic,defer 和 recover 怎么结合使用的完整例子:

// panic_recover.go
package main
import (
"fmt"
)
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s
", e)
}
}()
badCall()
fmt.Printf("After bad call
") // <-- wordt niet bereikt
}
func main() {
fmt.Printf("Calling test
")
test()
fmt.Printf("Test completed
")
}

输出:

Calling test
Panicing bad end
Test completed

defer-panic-recover 在某种意义上也是一种像 if , for 这样的控制流机制。

自定义包中的错误处理和 panicking

这是所有自定义包实现者应该遵守的最佳实践:
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic()。
2)向包的调用者返回错误值(而不是 panic)。
在包内部,特别是在非导出函数中有很深层次的嵌套调用时,对主调函数来说用 panic 来表示应该被翻译成错误的错误场景是很有用的。

// parse.go
package parse
import (
"fmt"
"strings"
"strconv"
)
// ParseError 表示将单词转换为整数时出错。
type ParseError struct {
Index int // 以空格分隔的单词列表的索引。
Word string // 生成分析错误的单词。
Err error // 引发此错误的原始错误(如果有)。
}
// 
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
// Parse 将 put 中以空格分隔的单词解析为整数。
func Parse(input string) (numbers []int, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
fields := strings.Fields(input)
numbers = fields2numbers(fields)
return
}
func fields2numbers(fields []string) (numbers []int) {
if len(fields) == 0 {
panic("no words to parse")
}
for idx, field := range fields {
num, err := strconv.Atoi(field)
if err != nil {
panic(&ParseError{idx, field, err})
}
numbers = append(numbers, num)
}
return
}
// panic_package.go
package main
import (
"fmt"
"./parse/parse"
)
func main() {
var examples = []string{
"1 2 3 4 5",
"100 50 25 12.5 6.25",
"2 + 2 = 4",
"1st class",
"",
}
for _, ex := range examples {
fmt.Printf("Parsing %q:
 ", ex)
nums, err := parse.Parse(ex)
if err != nil {
fmt.Println(err) 
continue
}
fmt.Println(nums)
}
}

输出:

Parsing “1 2 3 4 5”:
[1 2 3 4 5]
Parsing “100 50 25 12.5 6.25”:
pkg parse: error parsing “12.5” as int
Parsing “2 + 2 = 4”:
pkg parse: error parsing “+” as int
Parsing “1st class”:
pkg parse: error parsing “1st” as int
Parsing “”:
pkg: no words to parse

一种用闭包处理错误的模式

每当函数返回时,我们应该检查是否有错误发生:但是这会导致重复乏味的代码。结合
defer/panic/recover 机制和闭包可以得到一个我们马上要讨论的更加优雅的模式。不过这个模式只有当所有的函数都是同一种签名时可用,这样就有相当大的限制。一个很好的使用它的例子是 web 应用,所有的处理函数都是下面这样:

func handler1(w http.ResponseWriter, r *http.Request) { ... }

假设所有的函数都有这样的签名:

func f(a type1, b type2)

参数的数量和类型是不相关的。

我们给这个类型一个名字:

fType1 = func f(a type1, b type2)

在我们的模式中使用了两个帮助函数:

1)check:这是用来检查是否有错误和 panic 发生的函数:func check(err error) { if err != nil { panic(err) } }

2)errorhandler:这是一个包装函数。接收一个 fType1 类型的函数 fn 并返回一个调用 fn 的函数。里面就包含有 defer/recover 机制。

func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if e, ok := recover().(error); ok {
log.Printf(“run time panic: %v”, err)
}
}()
fn(a, b)
}
}

当错误发生时会 recover 并打印在日志中;除了简单的打印,应用也可以用 template 包为用户生成自定义的输出。check() 函数会在所有的被调函数中调用,像这样:

func f1(a type1, b type2) {
...
f, _, err := // call function/method
check(err)
t, err := // call function/method
check(err)
_, err2 := // call function/method
check(err2)
...
}

通过这种机制,所有的错误都会被 recover,并且调用函数后的错误检查代码也被简化为调用 check(err)即可。在这种模式下,不同的错误处理必须对应不同的函数类型;

审核编辑:黄飞

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 程序
    +关注

    关注

    114

    文章

    3630

    浏览量

    79613
  • 函数
    +关注

    关注

    3

    文章

    3974

    浏览量

    61377
  • Printf
    +关注

    关注

    0

    文章

    79

    浏览量

    13493

原文标题:一种用闭包处理错误的模式

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    嵌入式编程错误处理机制设计

    本文主要总结嵌入式系统C语言编程中,主要的错误处理方式。文中涉及的代码运行环境如下。
    发表于 04-28 09:59 564次阅读
    嵌入式编程<b class='flag-5'>错误处理</b>机制设计

    嵌入式系统C语言编程中主要的错误处理方式

    本文主要总结嵌入式系统C语言编程中,主要的错误处理方式。
    发表于 07-24 16:40 557次阅读
    嵌入式系统C语言编程中主要的<b class='flag-5'>错误处理</b>方式

    Rust语言中错误处理的机制

    在Rust语言中,错误处理是一项非常重要的任务。由于Rust语言采用静态类型检查,在编译时就能发现很多潜在的错误,这使得程序员能够更加自信和高效地开发程序。然而,即使我们在编译时尽可能地考虑了所有
    的头像 发表于 09-19 14:54 793次阅读

    labviEW错误处理的问题

    为什么这个程序在启用自动错误处理和C:\data.txt不存在的情况下,没有显示错误对话框啊?
    发表于 04-01 10:03

    LabVIEW错误处理问题

    我想问一下,就是连接硬件采集波形时,需要滤掉直流波,但是采集到的波形时断断续续的,所以错误处理时会停止程序,我想问一下,运行时怎么忽略掉这个错误
    发表于 09-18 18:29

    原创|高速PCB设计中网表导入的常见错误处理

    Footprint,是由于原理图中信息缺少造成的。如下图:网表常见错误处理12、器件位号定义重复,如下图:网表常见错误处理23、器件位号信息缺失,如下图:网表常见错误处理3在原理图设计工具
    发表于 01-10 10:51

    AF错误处理

    想问一下关于AF的错误处理,例如我进行串口通讯,打开串口错误,但是我不想停止AF,想继续尝试连接要怎么做?
    发表于 02-03 15:44

    LabVIEW中的错误处理

    如何合理使用 LabVIEW 中的自定义错误处理功能;对于可预见的错误,是否可以选择直 接忽略,或者前几次尝试忽略直到该特定错误出现很多次后才通知用户需要纠正该错误 了;是否可以对
    发表于 05-24 11:07 6次下载

    Spring Boot框架错误处理

    》 《strong》翻译《/strong》:雁惊寒《/p》 《/blockquote》《p》《em》摘要:本文通过实例介绍了使用Spring Boot在设计API的时候如何正确地对异常进行处理。以下是译文《/em》《/p》《p》API在提供错误消息的同时进行适当的
    发表于 09-28 15:31 0次下载

    嵌入式系统C语言编程中的错误处理资料总结

    本文主要总结嵌入式系统C语言编程中,主要的错误处理方式。文中涉及的代码运行环境如下:
    发表于 11-28 10:39 1764次阅读

    Rust代码启发之返回值异常错误处理

    这样的代码,错误处理代码和业务逻辑交织在一起,也容易忽略处理错误。以及把返回值只用于错误返回,有点浪费的感觉。因为很多时候把计算结果作为返回值,更符合思考的逻辑。
    的头像 发表于 09-22 09:24 1682次阅读
    Rust代码启发之返回值异常<b class='flag-5'>错误处理</b>

    西门子博途: 有关处理程序执行错误的示例

    本地错误处理可单独编程,也可与其它代码一起编程。为确保可识别程序中的所有错误,建议使用本地错误处理组合方案,如以下示例所示。
    的头像 发表于 07-12 17:43 2181次阅读
    西门子博途: 有关<b class='flag-5'>处理</b>程序执行<b class='flag-5'>错误</b>的示例

    RS232通信时怎么处理错误?RS232通信中的错误处理方法

    RS232通信时怎么处理错误?RS232通信中的错误处理方法  RS232通信是一种电气标准,它定义了计算机和串行通信设备之间的通信协议。尽管RS232通信很稳定,但仍然可能会出现错误
    的头像 发表于 10-17 16:33 1868次阅读

    西门子博图:错误处理机制概览

    可通过以下几种不同的错误处理机制进行参数跟踪或编程或访问错误
    的头像 发表于 11-25 11:35 1091次阅读
    西门子博图:<b class='flag-5'>错误处理</b>机制概览

    C语言中的错误处理机制解析

    C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。
    的头像 发表于 02-26 11:19 239次阅读