GVKun编程网logo

Go语言之error(Go语言之父)

11

此处将为大家介绍关于Go语言之error的详细内容,并且为您解答有关Go语言之父的相关问题,此外,我们还将为您介绍关于C语言和go语言之间的交互操作方法、Go十大常见错误第5篇:go语言Error管理

此处将为大家介绍关于Go语言之error的详细内容,并且为您解答有关Go语言之父的相关问题,此外,我们还将为您介绍关于C语言和go语言之间的交互操作方法、Go十大常见错误第5篇:go语言Error管理、Go语言 关于go error处理风格的一些讨论和个人观点(上)、Go语言 关于go error处理风格的一些讨论和个人观点(下)的有用信息。

本文目录一览:

Go语言之error(Go语言之父)

Go语言之error(Go语言之父)

写在前面的话:Go语言提供了一个error类型,专门用来表示程序出现异常的错误信息。但是有时候,error本身提供的这些错误信息不能完全满足我们的要求,这种情况下我们就需要自己去改造error,来实现自己希望的error格式以及内容。

下面笔者主要来讲下这件事情,不过在这之前,我们先看下error的定义吧。

1. error介绍


error其实就是实现了Error()函数的一个接口,返回值 nil表示是成功,例子如下所示:


Output:


结果分析:因为没有go.txt这个文件,所以在尝试打开这个文件的时候,会返回错误信息“文件不存在”的非nil信息。(备注:Println能够打印出来错误信息,是因为Println函数调用了error里面的Error()函数。)

2. 自定义错误

2.1. 通过New()来自定义错误信息

在Go的errors包中实现了New()这个函数,所以我们能够直接使用New()函数来自定义我们希望的错误输出,例子如下所示:


有了上面的实现,我们便可以自定义错误信息了,例子如下所示:


结果分析:通过输出结果,我们可以看出来,我们自定义的错误信息"error:自定义错误信息!!!"可以成功的打印出来。

2.2.借助struct来实现error更多内容

我们可以采用struct来实现error这个接口,进而达到实现自定义错误信息的方式。例子如下:


结果分析:通过输出的结果我们可以看出来,对于我们自定义的错误码和错误信息都可以打印出来。这也就表明,如果我们希望去扩展error的其他信息,那么我们可以采用struct的方式去添加不同的属性来实现。



灰子学技术:



本文分享自微信公众号 - 灰子学技术(huizixueguoxue)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

C语言和go语言之间的交互操作方法

C语言和go语言之间的交互操作方法

一、go代码中使用C代码

go代码中使用C代码,在go语言的函数块中,以注释的方式写入C代码,然后紧跟import “C” 即可在go代码中使用C函数

NewImage

代码示例:

go代码:testC.go

package main
/*
#include <stdio.h>
#include <stdlib.h>
void c_print(char *str) {
 printf("%s\n",str);
}
*/
import "C"  //import “C” 必须单起一行,并且紧跟在注释行之后
import "unsafe"

func main() {
 s := "Hello Cgo"
 cs := C.CString(s)//字符串映射
 C.c_print(cs)//调用C函数
 defer C.free(unsafe.Pointer(cs))//释放内存
}

运行结果:

$ go run testC.go
Hello Cgo

讲解:

1、go代码中的C代码,需要用注释包裹,块注释和行注释均可,其次import “C”是必须的,并且和上面的C代码之间不能用空行分割,必须紧密相连

如果执行go run **时出现

# command-line-arguments
Could not determine kind of name for xxx

那么就需要考虑 是不是improt “C”和上面的C代码没有紧挨着导致了

2、import “C” 并没有导入一个名为C的包,这里的import “C”类似于告诉Cgo将之前注释块中的C代码生成一段具有包装性质的Go代码

3、访问C语言中的函数需要在前面加上C.前缀,如C.Cstring C.go_print C.free

4、对于C语中的原生类型,Cgo都有对应的Go语言中的类型 如go代码中C.int,C.char对应于c语言中的int,signed char,而C语言中void*指针在Go语言中用特殊的unsafe.Pointer(cs)来对应

而Go语言中的string类型,在C语言中用字符数组来表示,二者的转换需要通过go提供的一系列函数来完成:

C.Cstring : 转换go的字符串为C字符串,C中的字符串是使用malloc分配的,所以需要调用C.free来释放内存

C.Gostring : 转换C字符串为go字符串

C.GoStringN : 转换一定长度的C字符串为go字符串

需要注意的是每次转换都会导致一次内存复制,所以字符串的内容是不可以修改的

5、17行 利用defer C.free 和unsafe.Pointer显示释放调用C.Cstring所生成的内存块

二、C语言中使用go语言

NewImage

代码示例:

go代码:print.go

package main
import "C"
import "fmt"
//export go_print
func go_print(value string) {
  fmt.Println(value)
}
func main() {//main函数是必须的 有main函数才能让cgo编译器去把包编译成C的库
}

讲解:

1、第11行 这里go代码中的main函数是必须的,有main函数才能让cgo编译器去把包编译成c的库

2、第3行 import “C”是必须的,如果没有import “C” 将只会build出一个.a文件,而缺少.h文件

3、第6行 //exoort go_print 这里的go_print要和下面的的go函数名一致,并且下面一行即为要导出的go函数

4、命令执行完毕后会生成两个文件 nautilus.a nautilus.h

nautilus.h中定义了go语言中的类型在C中对应的类型 和导出的go函数的函数声明

如:

typedef signed char GoInt8;//对应go代码中的int8类型

typedef struct { const char *p; GoInt n; } GoString;//对应go中的字符串

extern void go_print(GoString p0);//go中导出的函数的函数声明

C代码: c_go.c

#include “nautilus.h”//引入go代码导出的生成的C头文件
#include <stdio.h>

int main() {
 char cvalue[] = "Hello This is a C Application";
 int length = strlen(cvalue);
 GoString value = {cvalue,length};//go中的字符串类型在c中为GoString
 go_print(value);
 return 0;
}

编译步骤

// as c-shared library
$ go build -buildmode=c-shared -o nautilus.a print.go

或者

// as c-archive
$ go build -buildmode=c-archive -o nautilus.a print.go

$ gcc -o c_go c_go.c nautilus.a

运行结果

$ ./c_go
Hello This is a C Application

讲解:

1、第1行 #include “nautilus.h"包含go代码导出生成的C头文件

2、第7行 go中字符串类型在c中为GoString 定义为typedef struct { const char *p; GoInt n; } GoString; p为字符串指针,n为长度;所以这里通过GoString value = {cavalue,length}将C中的char赋值给GoString

3、第8行 go_print调用对应函数

三、C语言中使用go语言,使用的go语言又使用了c语言

NewImage

代码示例:

被go调用的C代码 hello.h

#ifndef HELLO_H
#define HELLO_H


#include <stdio.h>
#include <stdlib.h>7
void go_print_c(char *str);

#endif

被go调用的C代码 hello.c

#include "hello.h"

void go_print_c(char *str) {
  printf("%s\n",str);
}

被C调用的go代码 print.go

package main

//#include "hello.h"
import "C"

//export go_print
func go_print(value string) {
 cs := C.CString(value)
 C.go_print_c(cs)
}

func main() {
}

讲解:

1、这里在函数前面加上了inline关键字

如果把C代码放入go代码注释块中并且没有inline关键字中,会出现重定义的错误

p.go

package main

/*
#include <stdio.h>
#include <stdlib.h>
void go_print_c(char *str) {
 printf("%s\n",str);
}
*/
import "C"
import "unsafe"

//export go_print
func go_print(value string) {
 cs := C.CString(value)
 C.go_print_c(cs)
}
... 

go build -buildmode=c-shared -o nautilus.a print.go执行失败

duplicate symbol _go_print_c in:
$WORK/_/Users/baidu/go_code/t/_obj/_cgo_export.o
$WORK/_/Users/baidu/go_code/t/_obj/p.cgo2.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command Failed with exit code 1 (use -v to see invocation)

解决办法是给函数加上inline或者static关键字将函数改成内部链接,或者是像上面那样include头文件

C代码 _c_go.c

#include "nautilus.h"
#include3
int main() {
 printf("This is a C Application.\n");
 char cvalue[] = "hello world";
 int length = strlen(cvalue);
 GoString value = {cvalue,length};
 go_print(value);
 return 0;
}

编译步骤:

// as c-shared library
$ go build -buildmode=c-shared -o nautilus.a 

或者

// as c-archive 
$ go build -buildmode=c-archive -o nautilus.a 

$ gcc -o c_go_c c_go.c nautilus.a

运行结果

$ ./c_go_c.o
This is a C Application.
hello world

以上这篇C语言和go语言之间的交互操作方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持编程小技巧。

Go十大常见错误第5篇:go语言Error管理

Go十大常见错误第5篇:go语言Error管理

前言

这是Go十大常见错误系列的第5篇:go语言Error管理。素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi。

本文涉及的源代码全部开源在:Go十大常见错误源代码,欢迎大家关注公众号,及时获取本系列最新更新。

场景

Go语言在错误处理(error handling)机制上经常被诟病。

在Go 1.13版本之前,Go标准库里只有一个用于构建error的errors.New函数,没有其它函数。

pkg/errors包

由于Go标准库里errors包的功能比较少,所以很多人可能用过开源的pkg/errors包来处理Go语言里的error。

比较早使用Go语言做开发,并且使用pkg/errors包的开发者也会犯一些错误,下文会详细讲到。

pkg/errors包的代码风格很好,遵循了下面的error处理法则。

An error should be handled only once. Logging an error is handling an error. So an error should either be logged or propagated.

翻译成中文就是:

error只应该被处理一次,打印error也是对error的一种处理。所以对于error,要么打印出来,要么就把error返回传递给上一层。

很多开发者在日常开发中,如果某个函数里遇到了error,可能会先打印error,同时把error也返回给上层调用方,这就没有遵循上面的最佳实践。

我们接下来看一个具体的示例,代码逻辑是后台收到了一个RESTful的接口请求,触发了数据库报错。我们想打印如下的堆栈信息:

unable to serve HTTP POST request for customer 1234
 |_ unable to insert customer contract abcd
     |_ unable to commit transaction

假设我们使用pkg/errors包,我们可以使用如下代码来实现:

func postHandler(customer Customer) Status {
    err := insert(customer.Contract)
    if err != nil {
        log.WithError(err).Errorf("unable to serve HTTP POST request for customer %s", customer.ID)
        return Status{ok: false}
    }
    return Status{ok: true}
}

func insert(contract Contract) error {
    err := dbQuery(contract)
    if err != nil {
        return errors.Wrapf(err, "unable to insert customer contract %s", contract.ID)
    }
    return nil
}

func dbQuery(contract Contract) error {
    // Do something then fail
    return errors.New("unable to commit transaction")
}

函数调用链是postHandler -> insert -> dbQuery

  • dbQuery使用errors.New函数创建error并返回给上层调用方。
  • insertdbQuery返回的error做了一层封装,添加了一些上下文信息,把error返回给上层调用方。
  • postHandler打印insert返回的error。

函数调用链的每一层,要么返回error,要么打印error,遵循了上面提到的error处理法则。

error判断

在业务逻辑里,我们经常会需要判断error类型,根据error的类型,决定下一步的操作:

  • 比如可能做重试操作,直到成功。
  • 比如可能直接打印错误日志,然后退出函数。

举个例子,假设我们使用了一个名为db的包,用来做数据库的读写操作。

在数据库负载比较高的情况下,调用db包里的方法可能会返回一个临时的db.DBError的错误,对于这种情况我们需要做重试。

那就可以使用如下的代码,先判断error的类型,然后根据具体的error类型做对应的处理。

func postHandler(customer Customer) Status {
    err := insert(customer.Contract)
    if err != nil {
        switch errors.Cause(err).(type) {
        default:
            log.WithError(err).Errorf("unable to serve HTTP POST request for customer %s", customer.ID)
            return Status{ok: false}
        case *db.DBError:
            return retry(customer)
        }

    }
    return Status{ok: true}
}

func insert(contract Contract) error {
    err := db.dbQuery(contract)
    if err != nil {
        return errors.Wrapf(err, "unable to insert customer contract %s", contract.ID)
    }
    return nil
}

上面判断error的类型使用了pkg/errors包里的errors.Cause函数。

常见错误

对于上面的error判断,一个常见的错误是如下的代码:

switch err.(type) {
default:
  log.WithError(err).Errorf("unable to serve HTTP POST request for customer %s", customer.ID)
  return Status{ok: false}
case *db.DBError:
  return retry(customer)
}

可能的错误在哪里呢?

上面代码示例里对error类型的判断使用了err.(type),没有使用errors.Cause(err).(type)

如果在业务函数调用链中有一个环节对*db.DBError做了封装,那err.(type)就无法匹配到*db.DBError,就永远不会触发重试。

推荐阅读

  • Go十大常见错误第1篇:未知枚举值
  • Go十大常见错误第2篇:benchmark性能测试的坑
  • Go十大常见错误第3篇:go指针的性能问题和内存逃逸
  • Go十大常见错误第4篇:break操作的注意事项
  • Go面试题系列,看看你会几题?

开源地址

文章和示例代码开源在GitHub: Go语言初级、中级和高级教程。

公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。

个人网站:Jincheng''s Blog。

知乎:无忌。

福利

我为大家整理了一份后端开发学习资料礼包,包含编程语言入门到进阶知识(Go、C++、Python)、后端开发技术栈、面试题等。

关注公众号「coding进阶」,发送消息 backend 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。还可以发送消息「进群」,和同行一起交流学习,答疑解惑。

References

  • https://itnext.io/the-top-10-...

Go语言 关于go error处理风格的一些讨论和个人观点(上)

Go语言 关于go error处理风格的一些讨论和个人观点(上)

原创文章,转载请注明出处:服务器非业余研究-sunface

最近看谷歌go group里面有很多讨论go error处理风格的问题,颇有启发,现在跟大家分享一下,首先请看一个提问:

Hi folks,


When I look at a lot of go code,I see the following pattern:
//当我看了许多go代码后,我发现了以下模式

x,err := foo()
if err != nil {
return err
}

y,err := bar(x)
z,err := baz(y)
if err != nil {
return err
}

// do something w/ z
return nil

This is cool,but comes off as a bit verbose. I've been using the following as a replacement,which I think is a bit nicer:
//上段代码很cool,但是表现的有点冗余,我已经用以下一种error机制作为替代,我认为我的更好

var (
err error
)
if err == nil {
if err == nil {
}
if err == nil {
// do something w/ z
}
return err


//出于好奇,请问有人用过我这种风格然后有发现过缺点吗?或者这种风格并不好?虽然我认为很好

Just curIoUs,has anyone else used this sort of thing and found a pitfall? Or perhaps has an opinion as to why it isn't actually nice? I think it's nice =].

看了上面两种错误处理风格,大家应该已经看出了部分端倪:第一种是比较传统的error处理方式,第二种是作者认为比较创新的error处理方式。先不说第一种的优缺点,

第二种首先在开头设置了所有接收error的变量,然后后面的error处理方式是环环相扣,冗余度非常之高,且直到最后才return,也就是说如果代码够长,那你得看完中间所有的处理过程,在这里你只有读完了整段代码才知道foo()产生的错误到底是怎么处理的。

然后一个评论中提出了一种个人觉得不错的风格:

value,err := bar()

if err != nil {

goto handleError

}

.

.

.

.

handleError:

return err

在这种风格中不需要去知道goto和label中间的代码,可以直接进行错误处理并返回,因此代码简洁、可读性强、性能高且冗余度低。

还有一个评论提出了一种很特别的风格:

if x,err := foo(); err != nil {
return err
} else if y,err := bar(x); err != nil {
return err
} else if z,err := baz(y); err != nil {
return err
} else {
// do something w/ x,z

return nil
}

这种写法的代码比较简洁,return的地方一眼便可得知。

下面这种风格可以节省大量的if语句

func checkErr( err error) {

if err != nil {

//deal error here

}

}

func main() {

ting,err := whatever()

checkErr(err)

}

下面再说说我个人对提问中第二段代码的看法,主要有3点:

1.在第二段代码中,所有的有意义的代码都是环环相扣,每一个if和return都要记在脑袋里,具体可以看下这篇文章:代码的嵌套——各种状态的组合。在第一段代码中所有的错误处理都是在产生错误代码的下面,很容易发现什么代码导致了某个错误,而且在那些if代码块之后,你不需要担心之前的x,z,因为它们都是合法的。但是在第二段代码中,永远不能知道x,z是否合法,每次都得继续验证前面的值。

2.因为第二段代码的错误处理和返回方式(环环相扣),我们就无法得知error的具体位置,唯一能做的就是彻底放弃然后无奈的说一句“代码有BUG了”,仅此而已。

3.每次你往第二段代码的函数增加新的代码的时候,都要放在一个if代码快中。相反在第一段代码中,就不需要,因为只有错误处理在if中,其他的都在外部处理

这里只是一个简单的抛砖引玉,希望大家能发表下平常项目中使用的错误处理机制,一起讨论。

总结

以上是小编为你收集整理的Go语言 关于go error处理风格的一些讨论和个人观点(上)全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

Go语言 关于go error处理风格的一些讨论和个人观点(下)

Go语言 关于go error处理风格的一些讨论和个人观点(下)

原创文章,转载请注明出处:服务器非业余研究-sunface

对于Go来说错误如何处理是非常关键和重要的,这里我总结了一些方法可以避免错误的错误处理机制(具体见关于go error处理风格的一些讨论和个人观点(上))

1.错误代码常用风格:

以下两个例子第一个例子明显好过第二个

f,err := os.Open(path)
if err != nil {
// handle error
}
// do stuff

f,err := os.Open(path)
if err == nil {
// do stuff
}
// handle error

2.自定义错误处理风格:

首先定义错误接口:

type Error string

func (e Error) Error() string { return string(e) }

然后用类型断言判断错误类型

result,err := yourpackage.Foo()
if ype,ok := err.(yourpackage.Error); ok {
// use ype to handle error
}

分析文件,并输出错误信息:

type OpenError struct {
File *File
Error string
}

func (oe *OpenError) Error() string {
// format error string here
}

func ParseFiles(files []*File) error {
for _,f := range files {
err := f.parse()
if err != nil {
return &OpenError{
File: f,
Error: err.Error(),
}
}
}
}

这样就知道具体哪个文件在分析的时候失败了。

有一点要特别注意:封装错误,因为当你封装错误的时候,原本的错误信息可能会丢失:

var c net.Conn
f,err := DownloadFile(c,path)
switch e := err.(type) {
case net.Error:
//关闭连接

c.Close()
return e
case error:
//error != nil
return err

default:
// err == nil的时候会执行这里

}
// do other things.

如果在这段代码中你封装了net.Error,则这段代码不会再认为是net方面出错了,也就不会关闭连接,继续使用之前错误的连接。一个比较好的办法是:当使用外部接口的时候,不要封装它们产生的错误!!

3.错误就像状态一样:

有的时候你会暂时不处理一个错误,可能因为想延后处理,也可能因为错误会很快再出现一次。

对于第一种情况,bufio就是一个很好的例子,当bufio.Reader遇到一个错误的时候,它会持有那个错误直到缓存为空,才会处理这个错误。

对于第二种情况,go/loader是一个很好的例子,当它被调用且发生错误的时候,会持有那个错误,因为很可能它会被用相当的参数再调用一次。

4.使用函数来避免重复

如果某种错误在不断重复的出现,就可以使用函数来简化:

func handleError(c net.Conn,err error) {
//进行错误后续处理
}

func DoStuff(c net.Conn) error {
f,err := downloadFile(c,path)
if err != nil {
handleError(c,err)
return err
}

f,err := doOtherThing(c)
if err != nil {
handleError(c,err)
return err
}
}

或者可以使用下面这种风格:

func handleError(c net.Conn,err error) {
if err == nil {
return
}
//错误后续处理

} func DoStuff(c net.Conn) error { defer func() { handleError(c,err) }() f,path) if err != nil { return err } f,err := doOtherThing(c) if err != nil { return err } }

今天关于Go语言之errorGo语言之父的介绍到此结束,谢谢您的阅读,有关C语言和go语言之间的交互操作方法、Go十大常见错误第5篇:go语言Error管理、Go语言 关于go error处理风格的一些讨论和个人观点(上)、Go语言 关于go error处理风格的一些讨论和个人观点(下)等更多相关知识的信息可以在本站进行查询。

本文标签:

上一篇04-MirrorGate 安装脚本备注(mirror插件)

下一篇Windows Kernel Exploitation Notes(一)——HEVD Stack Overflow(windows-kernel-explorer)