当前位置: 首页 > 科技观察

Go语言库应该怎么写,可以参考哪些标准?

时间:2023-03-11 22:44:13 科技观察

前段时间我和我的朋友们想出了一个想法来合并我们的IRC机器人并用Go重写它们。为了防止重写大部分现有功能,我们试图找到支持机器人中使用的WebAPI的现有库。我们的项目需要一个用于RedditAPI的库。这篇文章的灵感来自于我发现的前三个库,为了不让它们的作者蒙羞,我不打算给它们命名。上面提到的每个库都有一些基本问题,使它们无法在现实场景中使用。并且每个库的编写方式使得如果不以非向后兼容的方式修改现有库的API就无法解决问题。不幸的是,由于许多其他库也有同样的问题,我将在下面列出一些作者的错误。不要对HTTP客户端进行硬编码。大多数库都包含硬编码的http.DefaultClient。虽然这对库本身来说不是问题,但库的作者不了解如何使用http.DefaultClient。默认客户端建议仅在用户未提供其他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的推荐方法是使用提供的客户端,但如果需要,有一个默认的替代方法:funcCreateLibrary(client*http.Client)*Library{ifclient==nil{client=http.DefaultClient}...}或者如果你想从工厂函数中删除参数,在你的结构中定义一个辅助方法,并让用户在需要时设置它的属性:typeLibrarystruct{Client*http.Client}func(l*Library)getClient()*http.Client{ifl.Client==nil{returnhttp.DefaultClient}returnl.Client}另外,如果每个请求都需要一些全局特性,人们往往会觉得需要用自己的实例替换http.Client。这是错误的方法——如果您需要在您的请求中设置一些额外的标头,或者在您的客户端中引入某种通用功能,您可以简单地根据请求设置它或组装自定义客户端而不是完全替换它。不要引入全局变量另一个反模式是允许用户在库中设置全局变量。例如,允许用户在你的库中设置一个全局的http.Client,并被所有的HTTP调用执行:varlibraryClient*http.Client=http.DefaultClientfuncSetHttpClient(client*http.Client){libraryClient=client}通常在一堆库中不应存在全局变量。当您编写代码时,您应该考虑如果用户在他们的程序中多次使用您的库会发生什么。全局变量使得无法使用不同的参数。此外,在您的代码中引入全局变量可能会导致测试问题并不必要地使您的代码复杂化。使用全局变量会导致程序的不同模块产生不必要的依赖。在编写库时避免全局状态非常重要。返回结构,而不是接口是一个常见问题(实际上我在这个问题上也错了)。许多库都有如下函数:funcNew()LibraryInterface{...}在上面的例子中,返回一个接口,这样结构的特征就隐藏在库中了。其实应该这样写:funcNew()*LibraryStruct{...}库中不应该有接口声明,除非在函数参数中使用。如果出现上述情况,你应该考虑一下你写这个库时的契约。返回接口时,基本上必须声明一组可用方法。如果有人想使用这个接口来实现他们自己的功能(例如用于测试),他将不得不破坏他们的代码以添加更多方法。这意味着虽然在结构中添加方法是安全的,但它不是在接口中。这个想法在这篇文章《Accept Interfaces Return Struct in Go[2]》中得到了很好的总结。这个方案也可以解决配置的问题。如果你想修改库中的一些特性,你可以简单地修改结构中的一些公共字段。但如果你的图书馆只为用户提供一个界面,这就行不通了。使用配置结构来避免修改API配置配置的另一种方法是在工厂函数中接收配置结构,而不是直接传递配置参数。您可以在不破坏现有API的情况下自由添加新参数。你只需要做一件事,在Config结构中添加一个新的字段,并确保不会影响它原有的特性。funcNew(configConfig)*LibraryStruct{...}以下是添加结构字段的正确方案,如果用户在初始化结构时忘记添加字段名称,这是我认为修改他们的代码会得到宽恕的一种方式。为了保持兼容性,您应该在代码中使用person{name:"Alice",age:30}而不是person{"Alice",30}。您可以在golang.org/x/crypto[4]包中看到对上述内容的补充。总的来说,对于配置,我认为允许用户在返回的结构中设置不同的参数是一种更好的方法,并且只在编写复杂方法时才使用这种特定方法。总结根据经验,在编写库时,您应该始终允许用户指定他们自己的http.Client来执行HTTP调用。并且考虑到未来迭代变化的影响,可以尝试以可扩展的方式编写代码。避免全局变量,库不能存储全局状态。如果您有任何疑问-请参阅标准库的编写方式。我认为在您的程序中测试您的库并问自己一些问题是个好主意:如果您尝试多次导入该库会发生什么?你的图书馆有单元测试吗?有没有一种非侵入式的方法可以在不破坏原始代码的情况下扩展您的库?是否可以在不破坏原始代码的情况下添加额外的配置参数?