<commit_before>---
layout:     post
title:      "Stop Writing Broken Go Libraries"
subtitle:   ""
date:       2019-08-13 12:00:00
author:     "Dingding"
header-img: "img/post/thrift-header.jpg"
header-mask: 0.3
catalog:    true
tags:
    - golang 
---

首发于：[https://studygolang.com/articles/22835]https://studygolang.com/articles/22835

不久前我和朋友们想出一个主意，准备合并我们的 IRC bots，并用 Go 重写它们。为了防止重写大部分现有功能，我们试图找到支持 bots 程序中使用的 `Web API` 的现有库。我们的项目需要一个 Reddit API 的库。这篇文章启发于我找到的前三个库，我不打算说出它们的名字，以免羞辱它们的作者。

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

# 不要对 `HTTP` 客户端硬编码

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

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

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

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

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

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

```go
type Library struct {
    Client *http.Client
}<commit_msg>fix<commit_after>---
layout:     post
title:      "Stop Writing Broken Go Libraries"
subtitle:   ""
date:       2019-08-13 12:00:00
author:     "Dingding"
header-img: "img/post/thrift-header.jpg"
header-mask: 0.3
catalog:    true
tags:
    - golang 
---

首发于：[https://studygolang.com/articles/22835](https://studygolang.com/articles/22835)

不久前我和朋友们想出一个主意，准备合并我们的 IRC bots，并用 Go 重写它们。为了防止重写大部分现有功能，我们试图找到支持 bots 程序中使用的 `Web API` 的现有库。我们的项目需要一个 Reddit API 的库。这篇文章启发于我找到的前三个库，我不打算说出它们的名字，以免羞辱它们的作者。

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

# 不要对 `HTTP` 客户端硬编码

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

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

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

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

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

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

```go
type Library struct {
    Client *http.Client
}
{'fraction_non_alphanumeric': 0.11928306551297899, 'fraction_numerical': 0.023485784919653894, 'mean_word_length': 6.052287581699346, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4906557', 'n_tokens_mistral': 2118, 'n_tokens_neox': 2256, 'n_words': 196}