My External Storage

Oct 6, 2019 - 5 minute read - Comments - Go

[Go]imported and not usedエラー・declared and not usedエラーとの向き合いかた

先日の登壇資料ブコメでコメントいただいていたので私の考えを述べたいと思う。
結論から書くと、やはり「単純さや簡潔性を保つため」が動機になるのだと思う。
なお、このブログでは敬体は使わない方針なので、常体なのはご容赦願いたい。

そういや “imported and not used” “declared and not used” でコンパイル通らなくなるのはどういう哲学なんだろうこれ。

“imported and not used"エラー

imported and not usedは依存していないパッケージをimportしていたときに発生するエラーだ。

次のようなコードを実行しようとすると、./prog.go:5:2: imported and not used: "log"というエラーが発生する。

package main

import (
	"fmt"
	"log"
)

func main() {
	fmt.Println("Hello, playground")
}

“imported and not used"エラーに対する向き合いかた

まず、設計論としてあるパッケージが依存するパッケージは少ないほうがよいだろう。依存先のパッケージが他のパッケージに依存していた場合、さらに依存先が増えてしまう。 また、ビルドでもそうだろう。不要なコードをimportして、使わないコードをビルドに含めるのはビルド時間の増大と、バイナリサイズの肥大化を招くだろう。 単純性・簡潔性を考えたときに、不要なパッケージに依存するデメリットは大きい。

唯一の例外例として、「直接依存していないが、そのパッケージのinit関数を実行する必要がある」場合がある。具体的に言うと、データベースに接続したいときの各種ドライバーパッケージなどだ。 そのような場合はblank importimportする。こうすると、コード上そのパッケージを利用していなくてもimported and not usedエラーは発生しない。

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, err := sql.Open("mysql", "user:password@/dbname")
}

“imported and not used"エラーの対処方法

エディタやIDEを正しく設定しているとimported and not usedエラーにはほとんど遭遇することはない。 goimportsコマンドというimport文を整理するコマンドがある。インストール方法は次の通り。

$ go get golang.org/x/tools/cmd/goimports

これを使うと不要なimport文は削除される。

たとえば利用していないlogパッケージをimportしているgoファイルに対してgoimportsコマンドを実行すると、以下の通り。

$ goimports -d three_index.go
diff -u three_index.go.orig three_index.go
--- three_index.go.orig 2019-10-06 09:42:41.000000000 +0900
+++ three_index.go      2019-10-06 09:42:41.000000000 +0900
@@ -3,7 +3,6 @@

import (
       "fmt"
-       "log"
)

func main() {

-wオプションを使えばそのままファイルを上書きしてくれる。 ただ、goimportsコマンドをそのまま利用している人は少ないだろう。 エディタやIDEを使っていれば、ファイル保存時にgoimportsコマンドを自動的に実行してくれるはずだ。 なので、imported and not usedエラーを見ることは通常ほとんどないと思う。 「あとで使うからimportしておいたのにファイル保存したときに消えちゃった!」という失敗のほうが多い。

VS Codeの場合はgoプラグインを使って所定のオプションを有効にしていれば自動でgoimportsコマンドが実行されるだろう。

Vimでvim-goプラグインを使っている場合は、go_fmt_autosaveオプションが有効になっていて、go_fmt_commandオプションにgoimportsをしておけば、ファイル保存時に自動実行される。

“declared and not used"エラー

declared and not usedエラーは関数やメソッド内で利用されていない変数があった場合のエラーだ。

次のようなコードはunused変数が利用されていないので、./prog.go:8:6: unused declared and not usedというエラーが発生する。

package main

import (
	"fmt"
)

func main() {
	var unused int
	fmt.Println("Hello, playground")
}

“declared and not used"エラーに対する向き合いかた

不必要な変数を定義しておくのは明瞭性や可読性を下げるのではないだろうか。 将来的に使うかもしれないのだろうが、今使っていないならば消しておくのが単純性を保つ秘訣だろう。 (これらの原則は関数や機能レベルの話だろうが、)KISSの原則YAGNIの考え方が近いのだろうか?

“declared and not used"エラーの対処方法

declared and not usedエラーは該当の変数名や行数が一緒に出力されているので、言われたとおりに消してしまえばいいと思う。

「ある関数の戻り値の中に使わない値がある」場合にdeclared and not usedエラーが出る場合は_で受け取れば問題ない。

// func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
// 戻り値のint64は利用しない場合
_, err = stdcopy.StdCopy(&outBuf, &errBuf, aresp.Reader)
if err != nil {
	return err
}

逆にdeclared and not usedエラーは関数やメソッド内の未使用変数にしか発生しないエラーだ。 「不要なpackage変数などもエラーで教えてほしい」というさらにストイックな人もいるだろう。 そういう場合は、golanci-lintなどで3rdパーティ製のlinterを利用すればよい。

終わりに

やはり「単純さや簡潔性を保つため」が動機になるのだと思う。
私としてはGoに慣れていると他の言語で不要なimportusingがあるほうが気になってしまう… 途中参考として書いたYAGNIなどはうろ覚えだったので個人的に復習が必要だと感じた。

参考

関連記事