Goでソースコードをディレクトリ分けする方法のメモ


自分用メモ

Go便利でよく使ってるけど長く育ててるとプロジェクトが大きくなってコードが読みにくくなるので整理できるといいですよねって話。

# 難しい話（スキップしてもOK）
[関心の分離](https://ja.wikipedia.org/wiki/%E9%96%A2%E5%BF%83%E3%81%AE%E5%88%86%E9%9B%A2)でぐぐるといろんな記事が出てくるので時間があれば読んでみてもいいかも。

# サンプルコード
https://gitlab.com/mkiuchi/qiita-go-organize

サンプルとしてGo Echoで社員情報を返すAPIサーバを作った。3つのエンドポイントがあって情報を返すことができる。それぞれの関数の構造は以下の図のようになっている。以下3段階でファイルを分割していくがこの関数と呼び出しの関係は変わらない。

![Qiita - goファイル整理の図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/b1b355db-c726-1cc5-a5c8-c6143ed2639f.png)

実行する場合は以下のようにすればいい
```shell
$ cd levelN
$ go run ./*.go
```

# レベル0: すべてmain.goにベタ書き

![Qiita - goファイル整理の図 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/3a42e7e8-a5ba-79d0-dd65-eb3e59cecdae.png)

```shell:ディレクトリ構造
level0/
├── docs/ "<- swaggerドキュメントを生成する部分"
│   ├── docs.go
│   ├── swagger.json
│   └── swagger.yaml
├── go.mod "<- module example.com/ore_no_program"
├── go.sum
└── main.go "<- package main"
```

moduleとして `example.com/ore_no_program` と宣言していて、そのなかに含まれるpackageは `main` 1つだけという状態。 Goのmoduleとpackageの関係性はぐぐるといろいろ出てくる。

https://zenn.dev/masaruxstudy/articles/7965c98289caf5

# レベル1: ファイルを分割
`main.go`が長くなって可読性が低くなってきたという仮定で、機能ごとにファイルを分割する。

![Qiita - goファイル整理の図 (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/c1b403b7-26f3-d592-7d94-361a58706782.png)

```shell:ディレクトリ構造
level1/
├── docs/
│   ├── docs.go
│   ├── swagger.json
│   └── swagger.yaml
├── main.go "<- package main"
├── web.go "<- package main"
├── ap.go "<- package main"
├── db.go "<- package main"
├── go.mod "<- module example.com/ore_no_program"
└── go.sum
```
ファイルが異なっていても、同じpackage mainに属していればひとまとまりと認識されて相互参照できる。

# レベル2-1: サブディレクトリを切ってさらにファイルを分割
さらにサブディレクトリを切ってファイルを分割する。こんな感じになる。

![Qiita - goファイル整理の図 (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/c089bc16-835a-4b10-366c-a04822c3f9b3.png)


```shell
level2-1/
├── docs/
│   ├── docs.go
│   ├── swagger.json
│   └── swagger.yaml
├── go.mod "<- module example.com/ore_no_program"
├── go.sum
├── main.go "<- package main"
└── src/
    ├── app/
    │   ├── listBusho.go "<- package app"
    │   └── listGender.go "<- package app"
    ├── db/
    │   ├── busho.go "<- package db"
    │   └── person.go "<- package db"
    └── web/
        ├── busho.go "<- package web"
        ├── gender.go "<- package web"
        └── shain.go "<- package web"

```
`src/web`, `src/app`, `src/db` にそれぞれコードを分割して配置し、別のpackage名を指定した。

packageまたぎで関数を参照するにはpackage側から外部に公開されていないといけないので[関数の先頭を大文字に](https://zenn.dev/msksgm/articles/20220527-go-package-scope#go-%E3%81%AE%E5%85%AC%E9%96%8B%EF%BC%88exported%EF%BC%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)した。
```go:(変更前)level1/web.go
package main
(略)
func web_get_shain(e *echo.Echo) {}
```
```go:(変更後)level2-1/src/web/shain.go
package web
(略)
func Get_shain(e *echo.Echo) {}
```

参照する側では <モジュール名>+<パス> でパッケージにアクセスできる。ディレクトリ構造とパッケージ名が異なる場合はimport文の先頭にインポートするパッケージ名を書けばいい。

```go:level2-1/main.go
package main
import (
   web "example.com/ore_no_program/src/web"
)

func main() {
   web.Get_shain()
}
```

## go run するときにエラーが出たら

ファイル構造を直しているときに以下のようなエラーが出ることがある。

```shell:エラー例
main.go:XX:Y: no required module provides package <module><path>: go.mod file not found in current directory or any parent directory; see 'go help modules'
```

project rootの`go.mod`, `go.sum`を消してコマンドラインから`go mod tidy`するとうまくいくかも。vscodeのチェックはリアルタイムで追従できないようなので一回フォルダをクローズしてから開き直すといいかも。

エラーが出たときに `go.mod`を作れとか、`go.work`にパスがないとか言われるときがあるけど、モジュール依存関係を作り直せば必要ない。少なくとも私のプロジェクトでは必要なかった。

# (非推奨)レベル2-2: マルチモジュール構成
マルチモジュール構成で書くこともできる。
![Qiita - goファイル整理の図 (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/8a28c45b-4207-0a7e-9be4-eeeda8310026.png)
```shell
./
├── docs/
│   ├── docs.go
│   ├── swagger.json
│   └── swagger.yaml
├── go.mod "<- module example.com/ore_no_program"
├── go.sum
├── go.work
├── go.work.sum
├── main.go "<- package main"
└── src/
    ├── app/
    │   ├── go.mod "<- module example.com/ore_no_program/app"
    │   ├── listBusho.go "<- package app"
    │   └── listGender.go "<- package app"
    ├── db/
    │   ├── go.mod "<- module example.com/ore_no_program/db"
    │   ├── busho.go "<- package db"
    │   └── person.go "<- package db"
    └── web/
        ├── go.mod "<- module example.com/ore_no_program/web"
        ├── busho.go "<- package web"
        ├── gender.go "<- package web"
        └── shain.go "<- package web"
```
`module example.com/ore_no_program`から他のモジュールを見えるようにするため、`go.work`は以下のように書く。
```go:go.work
go 1.22.2

use .

use (
	./src/web
	./src/app
	./src/db
)
```

各`go.mod`のgoバージョンは合わせておいたほうがいい。多少ぶれがあっても許容されるようだけど。

この構成は[Go 1.18以降で使用できるワークスペースモード](https://go.dev/doc/tutorial/workspaces)を使っている。他にも[`go.mod`の中で`replace`句を使うやり方](https://zenn.dev/nobonobo/articles/1d4afb9f4e8873#%E5%95%8F%E9%A1%8C%E3%81%AE%E6%89%8B%E6%B3%95)があるようだ。動くことは動くのだけど、1つのアプリケーションを動かすだけなのに公開するつもりのないモジュールを作成するのはやりすぎ、というのが非推奨の理由。

# 実際にやってみて気になったところ

## packageは循環してimportできない

https://qiita.com/ko-watanabe/items/0141fa20da9f6f30b754

https://zenn.dev/nobonobo/articles/c3ead54a87d136

言われてみればあたりまえなんだけど既存のコードを分け始めるとこれで怒られてどうやってわけるか工夫するようになる。関数の中を変えるほど時間的気力的余裕がない ~~（もちろん設計するほど意識も高くない）~~ ときは1つのpackage複数ファイルでまとめておいて、とりあえずしのぐのはありかも。

## main packageの関数、typeを子packageに渡すのはやめたほうがいい

https://stackoverflow.com/questions/29077678/how-do-i-access-functions-in-the-main-package

できなくもないけどトリッキーな方法になるので、違う方法を考えたほうがいい。

## typeだけ切り出してひとつのpackageにするのはあり

いろいろなファイルに分けると上の循環importの制約にひっかかりやすくなるし、型宣言は全部このパッケージにあると決めておくとわかりやすいと思った。

# 参考にしたところ

https://go.dev/ref/mod

https://zenn.dev/foxtail88/articles/824c5e8e0c6d82

https://zenn.dev/nobonobo/articles/1d4afb9f4e8873

# 最後に

公式ドキュメントが難しいんだよな・・・