📚

Goのimport順気にしてますか?

2021/12/06に公開

この記事はGo アドベントカレンダー2の6日目の記事です

https://qiita.com/advent-calendar/2021/go

goimportsはtoolsにあるコマンドの一つで、Goのプログラムで利用されているimportに空行を追加したり、無駄な空行を除いたりして順番を整理してくれます。

Goの開発では欠かせないツールの一つなのでGopherの皆さんは日常的にお世話になっていると思います。

import順について

整形された後のimport順は基本的に空行を挟んで

  1. 標準パッケージ
  2. 外部パッケージ
  3. -localオプションで指定したパッケージ

の順番になります。

どのパッケージが上記のどのグループに所属するかを判別している箇所を確認すると、かなりシンプルになっています

// importToGroup is a list of functions which map from an import path to
// a group number.
var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){
	func(localPrefix, importPath string) (num int, ok bool) {
		if localPrefix == "" {
			return
		}
		for _, p := range strings.Split(localPrefix, ",") {
			if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath {
				return 3, true
			}
		}
		return
	},
	func(_, importPath string) (num int, ok bool) {
		if strings.HasPrefix(importPath, "appengine") {
			return 2, true
		}
		return
	},
	func(_, importPath string) (num int, ok bool) {
		firstComponent := strings.Split(importPath, "/")[0]
		if strings.Contains(firstComponent, ".") {
			return 1, true
		}
		return
	},
}

func importGroup(localPrefix, importPath string) int {
	for _, fn := range importToGroup {
		if n, ok := fn(localPrefix, importPath); ok {
			return n
		}
	}
	return 0
}

https://github.com/golang/tools/blob/v0.1.8/internal/imports/fix.go#L35-L60

それぞれのグループを判別する関数はint型を返していますが0が一番、1が二番目といった順番になっています。
ちょっと面白いのがappengineをプレフィックスに持つパッケージだけ特別扱いされていることでしょうか。Appengineを使ったことがないので詳しくないですが、現在はgoogle.golang.org/appengineの方が新しいパッケージのようなので古い時代の名残のようです。

3つと言わず4つ以上に分割したい人も中にはいるかも知れませんが、そういった場合にはgoimports-reviserのような外部ツールを使うことになります。

空行が入った場合のimport順

しかし、上記のimport順は空行が入るとおかしくなってしまいます。たとえば以下のようになっていたとします。

  • before
import (
	"fmt"
	"strings"

	"github.com/google/go-cmp/cmp"
	"strconv"
)

感覚的には標準パッケージの三つをまとめて、外部パッケージだけ別にしたいので以下のようになってほしいと思います

  • なってほしいimport順
import (
	"fmt"
	"strings"
	"strconv"

	"github.com/google/go-cmp/cmp"
)

しかし実際には以下のようになってしまいます。

  • 実際のgoimportsの結果
import (
	"fmt"
	"strings"

	"strconv"

	"github.com/google/go-cmp/cmp"
)

これは空行だけでなく、コメントが挿入されている場合でも同様になります。

この問題に関しては長い間以下のissueで議論されており、自作のオルトgoimportsを作成する人がいたり、goimportsにoptionを追加して制御できないか提案する人などがいますが、依然としてgoimportsの仕様は決定されていない状態です

https://github.com/golang/go/issues/20818

なぜかで言えばこのコメントがそのままなのかなという感じがしていて、goimportsでコードスタイルを矯正しないようにしたいというのが一番の原因なのかなと思います。

https://github.com/golang/go/issues/20818#issuecomment-443977878

import順を気にするべきか

Go本体のコードを見ていても、import順が必ずグルーピングされているわけでもありませんでした。

しかし、自分が今までGoでチーム開発してきて一定数はimport順を気にするGopherがいるように思います。一方でimport順が違うからといってコードの動作に影響があるわけではないのでimport順を気にしないGopherの方が多数派ではないのかなと思います。

特にチーム開発している場合、チーム全員がimport順を守らないとどこかで崩れてしまうので、import順を直すだけのPRができてしまったり、開発と関係ない部分で労力が発生してしまうので、リーダーが強制しない限り気にし続けるのは難しそうです。

自分でも必ずimportのグループをまとめるようにしたツール(gouperを作ってみたりもしましたが、あまり使えてません。

もし強制したい方がいたら上記のissueにもあるこちらのツールgosimportsなどを使ってみるのがいいのではないでしょうか

おまけ

ちなみにですが、私の愛用するGolandではimportの部分が省略されているため、importの順番がおかしくなっていても気づきにくかったりします

Discussion