標準パッケージから見るパッケージ構成のパターンの解説

こんにちは！これは[Go3 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/go3)、2日目の記事です。
1日目は、[higasgt](https://qiita.com/higasgt)さんの[redisを扱うコードをユニットテストする](https://qiita.com/higasgt/items/145ad74c83fcb3dcf6c2)でした。

## TL;DR
* Goのパッケージ構成は、標準パッケージでも複数パターンの構成が存在している
* 習う際は、いくつかのパッケージ構造を比較してどのように実装するかを決めるのが良い
* io と database/sql は有名な標準パッケージだがパッケージ構成のアプローチは異なる

## ioパッケージの構成について
ioパッケージは `io.Reader` `io.Writer` をはじめとして有名なパッケージです。
Goを触っている人は `ioutil.ReadAll()` にお世話になった人も多いのではないでしょうか。

```bash
io
├── example_test.go
├── io.go
├── io_test.go
├── ioutil
│   ├── example_test.go
│   ├── ioutil.go
│   ├── ioutil_test.go
│   ├── tempfile.go
│   ├── tempfile_test.go
│   └── testdata
│       └── hello
├── multi.go
├── multi_test.go
├── pipe.go
└── pipe_test.go
```

ioパッケージを構成するディレクトリ構造は上記のようになっています。
1つづつ解説していきます。

### io.go
```go
type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

type Closer interface {
	Close() error
}
```

io.go は `Reader` `Writer` `Closer` をはじめとする抽象を実装しています。
パッケージのコメントに書いてある通りです。

```
Package io provides basic interfaces to I/O primitives.
Its primary job is to wrap existing implementations of such primitives, such as those in package os, into shared public interfaces that abstract the functionality, plus some other related primitives.
```

### ioutil/ioutil.go

ioutilパッケージについては、下記のコメントのようにいくつかのutilityを実装しています。


```
Package ioutil implements some I/O utility functions.
```

具体的な実装として呼び出す際は下記のように `ioutil` の具象実装を利用します

```go
r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")

b, err := ioutil.ReadAll(r)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("%s", b)
```

なので、パッケージ構成としては下記のように、ioが抽象 / io/ioutilが具象となっています。

```bash

io // 抽象
├── example_test.go
├── io.go
├── io_test.go
├── ioutil // 具象
│   ├── example_test.go
│   ├── ioutil.go
│   ├── ioutil_test.go
│   ├── tempfile.go
│   ├── tempfile_test.go
│   └── testdata
│       └── hello
├── multi.go
├── multi_test.go
├── pipe.go
└── pipe_test.go
```

## database/sql

database/sqlパッケージについてもioの時と同様に追いかけていきましょう。

```bash
sql/
├── convert.go
├── convert_test.go
├── ctxutil.go
├── doc.txt
├── driver
│   ├── driver.go
│   ├── types.go
│   └── types_test.go
├── example_test.go
├── fakedb_test.go
├── sql.go
└── sql_test.go
```

パッケージ構成は上記のようになっています。

### sql.go

```go
func Open(driverName, dataSourceName string) (*DB, error) {
	driversMu.RLock()
	driveri, ok := drivers[driverName]
	driversMu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
	}

	if driverCtx, ok := driveri.(driver.DriverContext); ok {
		connector, err := driverCtx.OpenConnector(dataSourceName)
		if err != nil {
			return nil, err
		}
		return OpenDB(connector), nil
	}

	return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
```

sql.goは上記のように、 `driveri, ok := drivers[driverName]` という行で `Driver` を取り出して処理します。
`Driver` は ioの時とは逆に、driver ディレクトリの中に実装されており、 `Driver` インターフェースなど抽象実装がされています

```go
type Driver interface {
	// Open returns a new connection to the database.
	// The name is a string in a driver-specific format.
	//
	// Open may return a cached connection (one previously
	// closed), but doing so is unnecessary; the sql package
	// maintains a pool of idle connections for efficient re-use.
	//
	// The returned connection is only used by one goroutine at a
	// time.
	Open(name string) (Conn, error)
}
```

### go-sql-driver/driver.go

このDriverを使用しているのが go-sql-driverパッケージです。
以下のように `init()` 関数でDriverを登録し、その後の処理で利用しています。

```go
func init() {
	sql.Register("mysql", &MySQLDriver{})
}
```

パッケージの構成がioとは逆で、以下のようになります。

```bash
sql/ // 具象
├── convert.go
├── convert_test.go
├── ctxutil.go
├── doc.txt
├── driver // 抽象
│   ├── driver.go
│   ├── types.go
│   └── types_test.go
├── example_test.go
├── fakedb_test.go
├── sql.go
└── sql_test.go
```

## まとめ

* パッケージの構成は標準パッケージでも異なる
* 自分の実装の参考にする際は、複数のパッケージ構成を見比べた上でメリットがある方をえらぶとよい

## おわりに

今日は標準パッケージの読み方を紹介しました。
明日は [m_green14](https://qiita.com/m_green14) さんです！