文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

我们应该怎么样编写 Go 语言库,有哪些标准可以参考?

2024-12-24 19:41

关注

[[314580]]

上面说的每一个库都存在一些基本问题以至于它们在真实场景中不可用。并且每个库都以这样一种方式编写:不以非向后兼容的方式修改现有库的 API,这样是不可能修复问题的。不幸的是,由于很多其他的库也存在同样的问题,所以我会在下面列出一些作者错误的地方。

不要对 HTTP 客户端硬编码

很对库都包含了对 http.DefaultClient 的硬编码。虽然对库本身来说这并不是问题,但是库的作者并未理解应该怎样使用 http.DefaultClient 。正如 default client 建议它只在用户没有提供其他 http.Client 时才被使用。相反的是,许多库作者乐意在他们代码中涉及 http.DefaultClient 的部分采用硬编码,而不是将它作为一个备选。这会导致在某些情况下这个库不可用。

首先,我们很多人都读过这篇讲述 http.DefaultClient 不能自定义超时时间的文章《Don’t use Go’s default HTTP client (in production)[1]》,当你没法保证你的HTTP 请求一定会完成(或者至少要等一个完全无法预估时间的响应)时,你的程序可能会遇到奇怪的 goroutine 泄漏和一些无法预知的行为。在我看来,这会使每一个对 http.DefaultClient 采用硬编码的库不可用。

其次,网络需要一些额外的配置。有时候需要用到代理,有时候需要对 URL 进行一丢丢的改写,甚至可能 http.Transport 需要被一个定制的接口替换。当一个程序员在你的库里用他们自己的 http.Client 实例时,以上这些都很容易被实现。

在你的库中处理 http.Client 的推荐方式是使用提供的客户端,但是如果需要的话,有一个默认的备选:

 

  1. func CreateLibrary(client *http.Client) *Library {    if client == nil {        client = http.DefaultClient    }    ...} 

或者如果你想从工厂函数中移除参数,请在你的 struct 中定义一个辅助方法,并且让用户在需要时设置其属性:

 

  1. type Library struct {    Client *http.Client}func (l *Library) getClient() *http.Client {    if l.Client == nil {        return http.DefaultClient    }    return l.Client} 

另外,如果一些全局的特性对于每个请求来讲都是必须的,人们经常感觉到需要用他们自己的实例来替换 http.Client。这是一个错误的方法 — 如果你需要在你的请求中设置一些额外的 headers,或者在你的客户端引入某类公共的特性,你只需要简单为每个请求进行设置或者用组装定制客户端的方式来代替完全替换它。

不要引入全局变量

另一个反面模式是允许用户在一个库中设置全局变量。举个例子,在你的库中允许用户设置一个全局的 http.Client 并被所有的 HTTP 调用执行:

 

  1. var libraryClient *http.Client = http.DefaultClientfunc SetHttpClient(client *http.Client) {    libraryClient = client} 

通常在一个库中不应该存在一堆全局变量。当你写代码的时候,你应该想想用户在他们的程序中多次使用你的这个库会发生什么。全局变量会使不同的参数没有办法被使用。而且,在你的代码中引入全局变量会引起测试上的问题并造成代码上不必要的复杂度。使用全局变量可能会导致在你程序的不同模块有不必要的依赖。在写你的库的时候,避免全局状态是格外重要的。

返回 structs,而不是 interfaces

这是一个普遍的问题(实际上我在这一点上也犯过错)。很多库都有下面这类函数:

 

  1. func New() LibraryInterface {    ...} 

在上面的 case 中,返回一个 interface 使 struct 的特性在库里被隐藏了。实际上应该这么写:

 

  1. func New() *LibraryStruct {    ...} 

在库里不应该存在接口的声明,除非它被用在某个函数参数中。如果出现上面的 case,你就应该想想你在写这个库的时候的约定。当返回一个 interface 时,你基本上得声明一系列可用的方法。如果有人想用这个接口来实现他们自己的功能(比如说为了测试),他得打乱他们的代码来添加更多的方法。这意味着尽管在 struct 里添加方法是安全的,但在 interface 里不是。这个想法在这篇文章中被总结得很好《Accept Interfaces Return Struct in Go[2]》。这个方案也能解决配置的问题。你想修改库中的一些特性,你可以简单的修改 struct 中一些公开的字段。但是如果你的库只提供给用户一个 interface,这就玩不转了。

使用配置结构体来避免修改你的APIs

另一种配置方法是在你的工厂函数中接收一个配置结构体,而不是直接传配置参数。你可以很随意的添加新的参数而不用破坏现有的 API。你只需要做一件事情,在Config结构体中添加一个新的字段,并且确保不会影响它原本的特性。

 

  1. func New(config Config) *LibraryStruct {    ...} 

下面是一种添加结构体字段的正确的场景,如果一个用户初始化结构体的时候忘了添加字段名,这是一种我认为修改他们的代码能得到原谅的场景。为了维护兼容性,你应该在你的代码中用 person{name: "Alice", age: 30} 而不是 person{"Alice", 30}。

你能在 golang.org/x/crypto[4] 包里看到对上面的补充。总之,对配置来说,我认为允许用户在返回的结构体里设置不同的参数是一个更好的方法,并且只在编写复杂方法时才使用这种特定方法。

总结

根据经验来讲,在写一个库的时候,你应该总是允许用户指定他们自己的 http.Client来执行 HTTP 调用。而且考虑到未来迭代修改带来的影响,你可以尝试用可扩展的方式编写代码。避免全局变量,库不能存储全局状态。如果你有任何疑问-参考标准库是怎么写的。

我认为有一个很好的想法,在你的程序中用你的库来测试并问自己一些问题:

 

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯