go init函数_Go 佳库面面观

news/2024/7/24 4:01:47 标签: go init函数

Go语言中文网,致力于每日分享编码、开源等知识,欢迎关注我,会有意想不到的收获!

cd62127e2bd3b33c630f262c37b78194.png

本文将列出从一个好的 Go 库里,我希望得到的东西的一个简短清单(排名不分先后)。这是对高效 Go(effective go)列表、Go 代码评审意见列表和 Go 箴言列表的补充。

一般来说,当做某事有两种合理的方式的时候,选择不违反这些规则的那一项。只有在有非常强力的理由时才违反这些规则。

依赖

加标签的库版本

使用 git 标签来管理你的库版本。语义版本化是一个合理的系统。如果你对语义版本化的反对在意,那么,你就不是本文的目标读者

没有非标准库依赖

这个有时候会难以达成,但是,管理一个库的依赖使得升级到最新版本变得轻松,并且允许库用户更好地得出应用中的逻辑。如果你将依赖树维持得相当相当小,那么,你就真的可以让你的程序维护更简单。

抽象非标准库依赖到它们自己的包里

这是上面非标准库依赖要求的必然结果。如果你的库绝对需要非标准库依赖,那么,试着将其分成两个包:一个包用于核心逻辑,而另一个包拥有外部依赖,使用前一个包的逻辑。

打个比方,如果你正在写一个包,它捕获 Go 的堆栈追踪,然后将其上传到 Amazon’s S3,那么,写两个包。

  1. 一个包捕获 Go 的堆栈追踪,然后将其交到一个接口
  2. 第一个包使用的接口的 S3 实现。

第二个包可以为用户简化两部分的粘合。这种抽象层次让用户利用你的核心功能,同时用自己的存储层来替换 S3。而在第二个包中的粘合操作,可以避免给那些想要将他们的数据上传到 S3 的大部分人增加额外的负担。

这里的关键部分是两个分开的包。这使得用户可以注入你的核心逻辑,而不会污染到他们自己的依赖树。

不要使用 /vendor

如果你的库用 vendor 来管理依赖,那么在包管理器试图展开 vendor 的时候,奇奇怪怪的副作用可能会发生;或者 vendor 污染你的公共 API,使得集成变得困难或者不可能。如果你真的需要一个确切的实现,那么请明确复制它

使用依赖管理,这样别人就可以跑你的测试了

你的库应该尝试不要有外部依赖。但是,如果必须有,那么使用某种类型的依赖管理,传达初始运行你的测试的依赖版本,从而使得库用户可以以一种一致的方式运行你的单元测试。

API

没有全局可变状态

全局可变状态使得代码难以被推断、展开、存根、测试和退出。

空对象具有合理的行为

零值有用

nil 实例上的读操作与空实例上的读操作行为一致

许多 Go 的内部结构反映了这种行为。

若非必要,避免构造器函数

这是让零值有用的副作用。

最小化公共函数

做更少事情的库倾向于做得更好。

拥有少数函数的小接口

接口越大,抽象越弱.

接受接口,返回结构

阅读这里 https://medium.com/@cep21/what-accept-interfaces-return-structs-means-in-go-2fe879e25ee8 ,获取更多细节。

在不违反竞争的情况下配置运行时可变

如果你的库维护复杂的状态,那么仅仅重新初始化,允许用户在应用运行的时候修改合理的配置参数,这非简单的过程。

我可以接口化,并且无需导入你的库的 API

你的库很棒,但是终有一天我会想要将其淘汰出去。Go 的类型系统有时会造成阻碍。然而,总的来说,隐式接口胜于过于强大的静态类型。如果有可能的话,最好将标准库类型作为函数参数类型和返回值类型,这样,用户就可以根据你的结构创建接口,以便于后面进行库替换。

type AvoidThis struct {} type Key string func (a *AvoidThis) Convert(k Key) {... }type PreferThis struct {} func (p *PreferThis) Convert(k string) { ... }

API 调用时创建最小的对象(GC)

CPU 通常是避无可避的,但是,重新考虑你的 API 会使得最小化 API 调用期间的垃圾回收成为可能。例如,创建不强制垃圾回收的 API。事后优化实现很容易,但是事后优化 API 则几乎不可能

type AvoidThis struct {} func (a *AvoidThis) Bytes() []byte { ... }type PreferThis struct {} func (p *PreferThis) WriteTo(w Writer) (n int64, err error) { ... }

无副作用导入

我个人不赞同基于导入创建 全局副作用行为Go 标准库 模式。这种行为通常涉及全局可变状态,当多次包含 /vendor 中的库时会引起有趣的问题,并且删除许多自定义选项。

当存在替代方法的时候,避免使用 context.Value

我前一篇文章 https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39 上有关于这个想法的扩展。

init 中避免使用复杂逻辑

init 函数对默认值的创建很有用,但是也是用户不可能自定义或忽略的逻辑。如果没有好的理由的话,不要从用户那里拿走你的库的控制权。因此,避免在 init 中生成后台 goroutine,而是最好让用户明确地要求后台行为。

允许注入全局依赖

例如,当允许用户提供 http.Client 时,不要强制使用 http.DefaultClient。

错误

检查所有错误

如果你的库接受一个接口作为输入,而有人给了你一个返回错误的实现,那么用户会期望你会检查并以某种方式处理错误,或者以某种方式把它传回给调用者。检查错误不只是意味着将其返回给上层调用栈,尽管这样有时是合理的,但是,你可以记录它,改变返回值并将其作为结果,使用后备代码,或者只是增加一个内部统计计数器,这样,用户就会知道发生了一些事情,而不是在不知道有什么不对的情况下失败。

通过行为暴露错误,而不是类型

这是以库为中心的 Dave的 Assert errors for behaviour, not type。的等价说明。更多信息,看这里

不要 panic

就是不要

并发

避免创建 goroutine

这是由 CodeReviewComments 中的同步函数部分推导出的更明确的规则。同步函数为库用户提供更多的控制权。goroutine 有时在并行化逻辑时是有用的,但是,作为一名库作者,你应该从不使用 goroutine 并且找出使用它们的原因开始,而不是先使用 goroutine,然后争论着摆脱它们。

允许后台 goroutine 干净地停止

这是 goroutine 生命周期 反馈的首选限制。应该有一种方式,以一种不会发出虚假错误的方式,结束你的库创建的任意 goroutine。

公开 API 中避免使用 channel

这是一种代码异味,暗示并发发生在库级别,而不是让你的库用户控制并发。

所有长的阻塞操作都采用 context.Context

Context 是让你的库用户控制何时应该中断操作的标准方式。

调试

导出内部统计信息

我需要监控你的库的效率、使用模式和耗时。以某种方式公开这些统计数据,这样,我就可以将其导入到我最爱的 度量系统中。

公开 expvar.Var 信息

通过 expvar 公开内部配置和状态信息,从而允许用户快速调试他们的应用使用你的库的方式,而不仅仅是他们认为他们如何使用它。

支持可调试性

最终,你的库会有错误。或者用户会错误地使用你的库,然后需要弄清楚原因。如果你的库具备任何合理的复杂性,那么请提供调试或者跟踪此信息的方法。可以使用调试日志,或者上下文调试模式

合理的 Stringer 实现

Stringer 是的人们更容易用你的库来调试代码。

轻松可定制的日志器

没有被广泛接受的 Go 日志库。公开一个日志接口,从而不强制我导入你最爱的日志库。

代码的整洁

通过一个合理的 gometalinter 检查子集

Go 简单的语法和优秀的标准库函数允许广泛的静态代码检查器,这些检查器汇总在 gometalinter 中。你的默认状态,特别是当你是 Go 的小萌新的时候,应该只是通过所有这些检查器。只有在你能够提供原因的时候才违反它们,然后提供两种合理的实现,并且选择那个通过 linter 的那个实现。

没有 0% 单元测试覆盖率的函数

100% 测试覆盖率是极端的,而 0% 测试覆盖率几乎不是什么好事。这是一项难以量化的规则,所以我已经决定“没有任何函数应该具备 0% 的测试覆盖率”是最低限度了。你可以使用 Go 的 cover 工具获取每个函数测试覆盖率。

# go test -coverprofile=cover.out context ok context 2.651s coverage: 97.0% of statements# go tool cover -func=cover.out context/context.go:162: Error 100.0% context/context.go:163: Timeout 100.0% context/context.go:164: Temporary 100.0% context/context.go:170: Deadline 100.0% context/context.go:174: Done 100.0% context/context.go:178: Err 100.0%  ...

存储布局

避免将一个结构的函数拆分到多个文件中

Go 允许你把一个结构的函数放到多个文件中。这在使用构建标志时,是非常有用的,但是,如果你把它作为组织结构的方式,那么,这说明你的结构太大了,应该把它分解成多个部分。

使用 /internal

/internal 包严重使用不足。我推荐二进制文件和库都利用 /internal 来隐藏不打算导入的公共函数。隐藏你的公共导入空间也使得用户更清楚应该导入哪些包,以及要到哪里寻找有用的逻辑。


文中包含大量链接,可以查看。


http://www.niftyadmin.cn/n/763846.html

相关文章

vue使用push数组值相等不push,不相等才push

首先得定义俩个数组,一个空数组,一个你需要push的arr,这里用到了foreach和indexOf this.arr.push(e.node.dataRef.title) // 这个是你push的数组this.arr // 首先你得循环这个this.arr因为你要判断你push的值是否相同this.arr.forEach(item …

idea 打包war_IDEA相关配置【java项目改造成web项目】

在平时写项目学习java的时候,有没有这么一种情况:我们一开始建立了一个普通的java项目,但是随着我们编码的进行,发现项目需要部署到Tomcat服务器上运行测试。此时有两种解决方案:【1】创建一个web项目,从0到…

ai直线怎么变折线_设计小妙招AI定义艺术画笔

导读优秀的设计师在成长的过程中需要提升审美能力、造型能力,不断的丰富文化知识的储备提高人文修养,养成良好的学习习惯,多思考勤练习。以上都做到了你的眼界自然而然就比较宽广,离优秀的设计师就更近了一步!本系列视…

理解js splice slice split replace

当我们用这些的时候特别容易混淆,所以就记录一下。 先说splice splice的方法是对数组进行添加或者删除,第一个参数是数组对应的下标,第二个参数是从第一个参数的index开始,删除几个元素,第三个参数是你需要添加的元素&…

php 邮件发送是html 没样式_「快学springboot」使用springboot实现发送邮件功能

前言在实际项目中,经常需要用到邮件通知功能。比如,用户通过邮件注册,通过邮件找回密码等;又比如通过邮件发送系统情况,通过邮件发送报表信息等等,实际应用场景很多。这篇文章,就教大家通过spri…

vue动态添加数组归类

昨天在写完项目之后出现一个bug,我想简单了,我以为直接把数据传过去就行,谁知道还需要归类数据,把数据整合起来,这个是动态的整合起来。格式是这样子的 [{name: 123,children: []}]children数组里面放的是name底下的种…

java 判断对象是否为空_java虚拟机——对象存活判断与垃圾回收算法

Java识堂,一个高原创,高收藏,有干货的微信公众号,一起成长,一起进步,欢迎关注本文主要教书在java虚拟机垃圾回收机制中,如何判断对象是否存活和图解垃圾回收算法。一、概述对于java程序员来说&a…

ant design vue table栏getCheckboxProps的使用

今天业务有一个需求,从一个页面跳到另一个页面的时候,需要把以前选择的table的数据带上,然后展示出来,这可难倒我了,研究好长时间,在网上搜一堆,看个大概然后还是决定自己写一下,毕竟…