Google Colabで学ぶgofmt入門 ── できること・できないことを検証する
Goには、コードの見た目を自動で整えてくれるgofmtというツールが、最初からバンドルされています。Goを用いた開発プロジェクトにおいて、コード整形ツールは、原則gofmtを使えばよく、別の整形ツールは通常不要です。
gofmtは、整形ツールの選定・整形ルールの策定をまるごと不要にします。このことは、シンプルさ重視のGoの特徴をよく反映していると思います。
本記事では、「わざと少し崩したコード」をgofmtにかけて、実際にどこまできれいになるかを検証していきます。
前提
Google Colabで学ぶGoのプログラム入門(ハローワールド編)などで、Goの基本文法に軽く触れていると読みやすいです。とはいえgofmt自体は非常にシンプルな道具なので、前提知識がなくても十分についていけると思われます。
すぐに使えるチートシートはこちら Google Colab版
すぐに実行して、試せるコードレシピはこちら。
0. 準備 ── gofmtは追加インストール不要
gofmtは、Go本体をインストールした時点で、同梱されています。goコマンドが使える環境であれば、それだけでgofmtも使えます。
!apt-get install -y golang-go
!gofmt --help
golang-go is already the newest version (2:1.18~0ubuntu2).
0 upgraded, 0 newly installed, 0 to remove and 3 not upgraded.
usage: gofmt [flags] [path ...]
-cpuprofile string
write cpu profile to this file
-d display diffs instead of rewriting files
-e report all errors (not just the first 10 on different lines)
-l list files whose formatting differs from gofmt's
-r string
rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')
-s simplify code
-w write result to (source) file instead of stdout
0-1. Colab用の事前準備:表示の体裁くずれを防止
ここからは、「わざと崩したコード」をgofmtにかけて、before/afterを見比べていきます。等幅フォントを明示的に指定したHTMLとして表示できるよう、次のような小さなヘルパー関数を用意しておきます。
from IPython.display import HTML
import html as html_lib
def show_fmt_result(file_name):
with open(file_name) as f:
content = html_lib.escape(f.read())
display(HTML(f"""
<pre style="font-family: 'Courier New', Consolas, monospace; white-space: pre; line-height: 1.4;">{content}</pre>
"""))
以降の検証では、ファイルの中身を表示するときに、このshow_fmt_result()を使っていきます。
このshow_fmt_result()は、あくまで、本記事の検証に使うGoogle Colabの出力欄で見た目を揃えて表示するための工夫です。したがって、gofmtの動作そのものとは関係ありません。
Google Colabでは、!catでファイルの中身をそのまま表示すると、Colabの出力欄が完全な等幅フォントで描画してくれないことがあります。それゆえ、本来はぴったり揃っているはずの列でも、表示上歪んでしまう場合があります。
GitHub画面上での表示においては、この関数を適用せずとも、整形に成功している点が確認できます。また、ご自身のPC上のターミナルやローカルのJupyter環境、!cat ファイル名(またはターミナルならcat ファイル名)とするだけで、問題なく整列されたことが確認できます。
検証1: インデントの乱れを直せるか
まずは、今回のいちばんの見せ場です。スペースとタブが混在し、インデントの深さもバラバラな、わざと汚く書いたコードを用意します。
package main
import "fmt"
func main() {
x:=1
y := 2
fmt.Println(x+y)
}
インデント・タブ・スペース、すべてバラバラです。おまけに:=や+の前後にスペースもありません。これをgofmt -w(書き換えモード)にかけてみます。
!gofmt -w messy1.go
show_fmt_result('messy1.go')
実行結果です。
package main
import "fmt"
func main() {
x := 1
y := 2
fmt.Println(x + y)
}
バラバラだったインデントは、すべてタブ1つに統一され、:=や+の前後にもきちんとスペースが入りました。
検証2: 演算子・区切りまわりの詰まった書き方を直せるか
続いて、比較演算子やスライスの要素区切りが詰まって書かれたコードを試します。
package main
import "fmt"
func main() {
a := 1
b := 2
if a<b {
fmt.Println("a<b")
}
c := []int{1,2,3}
fmt.Println(c)
}
!gofmt -w messy2.go
show_fmt_result('messy2.go')
実行結果です。
package main
import "fmt"
func main() {
a := 1
b := 2
if a < b {
fmt.Println("a<b")
}
c := []int{1, 2, 3}
fmt.Println(c)
}
if a<b → if a < b、{1,2,3} → {1, 2, 3} のように、演算子やカンマの前後にきちんとスペースが入りました。
実は上の結果には、意図的な誤りを1つ混ぜています。fmt.Println("a<b")の中の"a<b"は文字列リテラルの中身なので、gofmtは一切手を加えません。
gofmtはコードの構造を理解した上で整形するため、コードとして意味のあるa<bにはスペースを入れます。他方、ただの文字列に関しては整形の対象外とし、勝手に改変しない賢さを持っています。
検証3: structやconstの縦の整列までしてくれるか
gofmtの機能の中でも特に「賢い」と言われるのが、構造体フィールドや定数の縦の整列です。
package main
type User struct {
Name string
Age int
Email string
}
const (
Red= "red"
Green = "green"
Blue ="blue"
)
!gofmt -w messy3.go
show_fmt_result('messy3.go')
実行結果です。
package main
type User struct {
Name string
Age int
Email string
}
const (
Red = "red"
Green = "green"
Blue = "blue"
)
型名や=の位置が、余白でぴったり揃いました。単に不要な空白を削るだけでなく、「読みやすくするために、あえて空白を足す」判断までしてくれます。
検証4: importの並び替えはしてくれるか
importの順番がバラバラなコードを試します。
package main
import (
"os"
"fmt"
)
func main() {
fmt.Println(os.Args)
}
!gofmt -w messy4.go
show_fmt_result('messy4.go')
実行結果です。
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args)
}
1つのimportのかたまりの中身が、アルファベット順に並び替えられました。
gofmtが並び替えてくれるだけという点です。足りないimportを追加したり、使っていないimportを消したりはしない点に注意しましょう。
検証5: 変数名やロジックの中身は直してくれるか
gofmtが触るのは見た目(空白・改行・整列)だけで、名前や処理の意味には踏み込まない、という点を確認します。
package main
import "fmt"
func main() {
a := 10
b := 20
tmp := a
a = b
b = tmp
if true {
fmt.Println(a, b)
} else {
fmt.Println(b, a)
}
}
aやb、tmpといった、意味のわかりづらい名前で、あえて変数を宣言しています。また、if true { ... } else { ... }という冗長なロジックも混ぜています。
!gofmt -w messy5.go
show_fmt_result('messy5.go')
実行結果です。
package main
import "fmt"
func main() {
a := 10
b := 20
tmp := a
a = b
b = tmp
if true {
fmt.Println(a, b)
} else {
fmt.Println(b, a)
}
}
見た目上の整形が必要な箇所がそもそも無いため、内容は一字一句変わりません。tmpという名前も、if trueという無駄なロジックも、そのまま残ります。gofmtは「フォーマッタ」であって「リンタ」ではないため、名前の良し悪しや、ロジックの無駄には関与しないということです。
検証6: 未使用の変数やimportがあると、どうなるか
Goには「使っていないローカル変数があるとコンパイルエラーになる」という、他の言語に比べてかなり厳格なルールがあります。これはgofmtが捕まえてくれるのか、それともgofmtはスルーするのか、確認します。
package main
import "fmt"
func main() {
x := 1
y := 2
fmt.Println(x)
}
yを宣言だけして、一度も使っていません。
!gofmt -l messy6.go
実行結果です。
(何も表示されない)
gofmt -lは「整形が必要なファイルの名前だけ」を表示するオプションですが、このファイルはインデントも空白も既にきれいなので、何も表示されません。つまりgofmtは、未使用変数があってもエラーにも警告にもしない、ということです。
試しに、そのまま実行してみます。
!go run messy6.go
実行結果です。
./messy6.go:7:2: declared and not used: y
gofmtは素通りしたのに、go run(実際にはコンパイラ)は容赦なくエラーにします。これは、未使用変数のチェックが「構文解析」ではなく「意味解析(型チェック)」の領域になるからです。gofmtは構文木を整形するだけで、変数が使われているかどうかまでは見ません。したがって、「フォーマットが通る」からといって、「コンパイルが通る」とは限らないということです。
検証7: 構文が壊れていると何が起きるか
最後に、gofmtが唯一はっきりと「エラー」を返すケースを確認します。閉じ括弧を1つ、わざと忘れてみます。
package main
import "fmt"
func main() {
fmt.Println("hello")
!gofmt -l broken.go
実行結果です。
broken.go:6:23: expected '}', found 'EOF'
未使用変数のときとは違い、今度ははっきりとエラーが返ってきます。gofmtは、Goの構文として正しく解析できることを大前提にして動くので、「構文として明らかに壊れている」ことは検知できます。gofmtがチェックしてくれるのはここまでで、「構文としては正しいが、意味的にはおかしい」という問題(未使用変数、型の不一致など)は、対象外となります。
発展編: gofmtとgo fmt ./...の違い
Goのプロジェクトでは、gofmtだけでなく、go fmt ./...という指定も登場します。
-
gofmt── ファイルパスを直接指定して使う、素の道具本体。-l(対象ファイル名の一覧)、-d(差分表示)、-w(書き換え)、-s(冗長な書き方の簡略化)といった、細かいオプションもある -
go fmt ./...── Goのモジュールシステムを理解したgoコマンドが、指定したパッケージ(./...は「このモジュール以下すべて」の意味)を解決した上で、内部的にgofmt -l -w相当を実行してくれる、薄いラッパー
パッケージ単位でまとめて整形したいことがほとんどなのでgo fmt ./...、特定の1ファイルだけを見たい・差分だけ見たいといった細かい操作をしたいときはgofmt本体を直接使う、という使い分けとなります。
できること・できないことの整理
ここまでの検証結果をもとに、できること・できないことで整理します。
gofmtができること
- インデントを、タブ1つに統一する
- 演算子・カンマまわりのスペースを整える(文字列リテラルの中身は変えない)
- 構造体のフィールドや
constブロックを、縦に整列させる - 1つの
importブロックの中身を、アルファベット順に並び替える
gofmtができないこと
- importの追加・削除(→
goimportsの役割) - 変数名やロジックの書き換え(→ 人間、またはリンタ・レビューの役割)
- 未使用の変数・importの検出(→ コンパイラの役割)
- 構文として壊れているコードの実行・自動修正(→ そもそもエラーで止まる、これだけは唯一検出する)
まとめ
gofmtは、設定オプションがほぼありません。「個人の好み」を極力排除し、シンプルな管理を追求する思想なのだと考えられます。
開発言語としてGoを採用すれば、gofmtによって、チーム全体のコードスタイルもほぼ確定します。フォーマットに関する不毛な議論やレビューの指摘も不要となり、プロダクト管理がシンプルになるメリットがあります。
発展編:CI活用Tips
gofmt -lは、整形が必要なファイル名だけを一覧表示し、既に整形済みのファイルには何も出力しません。この性質を使うと、CI(継続的インテグレーション)でとても簡単なチェックが組めます。
#!/bin/sh
# gofmt -l の出力が1行でもあれば、フォーマット漏れとみなしてビルドを失敗させる
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "gofmtが未適用のファイルがあります:"
echo "$unformatted"
exit 1
fi
「フォーマットを直し忘れたままPRを出してしまう」といった問題も、ここまでシンプルに予防できます。
弊社について
本記事を書いている 合同会社インクルーシブソリューションズ は、データ基盤構築・分析基盤設計・システム改善支援を中心に活動している小規模IT法人です。
主な領域は、
- データマート設計・データパイプライン構築
- SQL / Python を用いたデータ処理設計
- BI導入支援・分析基盤の整備
- 既存システムの運用改善・可視化支援
といった、「データを使える状態にする」ための活動です。
弊社の企業活動に興味がある方は、ぜひ公式サイトも覗いてみてください。