golang如何更优雅地处理Error

golang如何更优雅地处理Error 今天想要分享的是,golang中如何更优雅地处理错误。 怎么处理Error是golang中一个非常关键的事情,因为golang的设计导致代码中到处都是类似以下的代码 if err != nil { ... } 如果处理不得当,会导致代码膨胀得非常快且难以维护,比如: if err != nil { metrics.Emit(...) log.Print(...) event.Emit(...) ... ... return err } 面对这样的代码,可能错误处理所占的代码行数都会多于逻辑代码,显然不是我们愿意看到的。 上面描述的代码膨胀现象只是错误处理中常出现的问题之一,接下来我们聊聊日常开发中该如何优雅地处理错误。 错误的处理方式 错误的传递方式无非两种: 返回 不返回 选择返回or不返回的场景无非几种: 当函数位于顶层,比如 API的接入层、conumer的handle 等,此时无法返回 发生的错误是 致命的,会影响到整个程序的运行,此时应该抛出panic,阻止程序发生更加不可控的事情 发生的错误是 预期的,比如查重动作中查询数据库的数据不存在时,得到了一个 NotFound 的错误 其他情况均应该返回错误 而面对错误发生时,常见有以下的处理方式: 记录(日志、metrics、事件) 做一些业务逻辑 直接返回 这里只对 记录 单独展开说下,其他两项暂时没什么需要注意的。 记录 记录的常见手段有三种:log, metrics, event。 通常来说我们只需要记录其中一种即可,有的同学可能会有疑问,明明这三种不同的技术都有不同的适用场景,为什么说通常了一种就够了。 首先我们要聊聊这三个记录的核心目标是什么: log: 最传统的形式,是为了在程序运行时留下可追溯的信息,来辅助人类了解程序发生了什么,一般都会进行分级处理。 metrics: 由于不同的 metrics 体系,实现不同,这里只概述下,metrics是用于观测某个属性的趋势 event: 用于敏感操作的审计 or 广播变更 我在过往的工作中,见过某些同学,在某个很关键的场景出错后采用了以下的做法:
查看更多 →

golang context的正确打开姿势

查看更多 →

简单易懂的 pprof 手册

简单易懂的 pprof 手册 推荐阅读 实战Go内存泄露 High Performance Go Workshop 完整格式: go tool pprof <format> [options] [binary] <source> ... 省略 <format> 可以使用交互式的控制台,或者换成为 -http 可以启动一个 web 服务器查看性能分析图 go tool pprof -http [host]:[port] [options] [binary] <source> ... <source> 支持三种格式: profile.pb.gz: 压缩的 profile 文件,可以通过 -proto 格式输出 pprof的 http 接口 legacy_profile: 遗留格式,不推荐 pprof 的 http 服务的路径如 /debug/pprof/{res}, res 支持以下几种类型采样: allocs: 内存分配 blocks: 阻塞操作 cmdline: 显示程序启动命令 goroutine: 协程 heap: 堆内存信息 mutex: 锁争用信息 profile: cpu threadcreat: 系统线程 trace: 程序运行跟踪信息 我们通常可以有几种方式进行采样:
查看更多 →

如何在Go中优雅地进行测试

如何在Go中优雅地进行测试 本文是 快速迭代中保证项目的高质量——DevOps实践漫谈,一文中关于测试的延伸阅读,主要展示在 go 项目中该如何去做测试,以及测试是如何驱动设计的演化。 虽然是以 go 为示例,但里面的多数测试方法和思想都与语言无关,不会 go 的同学也可以阅读。 简述 测试的重要性这里不多复述,可以参考我关于 单元测试的必要性?由谁来写 的 回答。 测试分为很多种,本文这次要讨论的是关乎后台服务最重要的两种测试:单元测试 与 集成测试。它们两属于测试金字塔图的最下两层,如下图: 图中位于第二层的测试名为服务测试(Service Tests) ,其实它职责包含了狭义的集成测试,我将其理解为广义的集成测试。 开始进入主题之前,我觉得应该和阅读的同学对齐一下一些概念,因为关于测试的术语网上各种概念都各不相同,我这里只列举本文会使用到的一些概念( 这些是我的理解,并不一定完全恰当与正确 ): 模块: 指逻辑上紧密耦合的一组函数,通常它们会以对象形式实现,以接口的形式提供给其他模块使用。 外部环境:数据库,文件系统,其他服务等 单元测试:对于单个模块的测试,此时应该通过桩模块隔离被测模块以外的模块 桩(stub):用于在单元测试中替换被测模块的依赖模块,使其总是返回我们期望的值 集成测试:狭义的集成测试只测试那些访问外部环境的代码,比如: 读写数据库 调用外部服务的 API 读写队列 读写文件 广义的集成测试指对整个服务进行测试,包括外部环境与所有模块。 从概念上我们可以看出集成测试是最能保证业务价值的,但是由于需要集成的东西过多,所以它的执行很缓慢且用例容易失效(任何一个子模块的变动都可能让相关用例的数据不再有效),维护成本很高。所以测试金字塔为我们指出了测试中的一个最佳实践:使用单元测试保障更多的边界境况,集成测试来确保模块与模块,模块与外部环境的正常集成 接下来,让我们一起来看看在一个 go 项目中该如何落地这个最佳实践。 从一个简单的业务开始 让我们从一个简单的银行来开始我们的实践,假设有以下需求: 用户需要用一个手机号码来创建一个账户,支持更换手机号 支持付款、转账、销户 需要记录账单流水 我们的需求非常简单,但已经足够用于展示了。 基础环境准备 由于需求的简单性,这里我们直接跳过建模分析的过程,把DDL放在下面: SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for accounts -- ---------------------------- DROP TABLE IF EXISTS `accounts`; CREATE TABLE `accounts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `phone` varchar(16) NOT NULL, `balance` double NOT NULL, `status` varchar(16) NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- ---------------------------- -- Table structure for bills -- ---------------------------- DROP TABLE IF EXISTS `bills`; CREATE TABLE `bills` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `desc` varchar(128) NOT NULL, `account_id` int(11) NOT NULL, `amount` double NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 开始开发 本次的业务场景采用 DDD(领域驱动设计) 的设计风格开发,但不会展开说明,因为这是一个很大的话题,有兴趣了解的同学可以找我私聊,或者评论留言,我可以把自己过往实践 DDD 的经验再单独写一篇文章。
查看更多 →

Go语言笔记

go语言笔记 这里记录一些与 go 相关的点 GODBUEG 可以通过环境GODEBUG 来输出 go 程序的相关信息,可选命令如下: allocfreetrace clobberfree cgocheck efence gccheckmark gcpacertrace gcshrinkstackoff gctrace madvdontneed memprofilerate invalidptr sbrk scavenge scheddetail schedtrace tracebackancestors asyncpreemptoff 完整命令参考 这里 值得一提得是,可以通过name=val,name=val来启用多个命令,如: `` http库的 ServeHttp 不能修改 request 的内容 很有意思的一点,在 ServeHttp 代码注释中写着 Except for reading the body, handlers should not modify the provided Request. 稍微调查了下原因,是因为大量的现存代码会受到影响,所以在 http.StripPrefix 函数中,对 Request 进行了深拷贝。 func StripPrefix(prefix string, h Handler) Handler { if prefix == "" { return h } return HandlerFunc(func(w ResponseWriter, r *Request) { if p := strings.
查看更多 →