脱Go言語初心者への道のり #1 〜オフィシャルガイドラインの作法【前編】〜
こんにちは!エウレカの田野です。
エウレカではPairsの開発に2014年からGo言語を採用してきました。現在はPairs台湾のプロダクション環境で運用しています。その過程でプロダクションコードを書きかたのノウハウを蓄積してきました。
そのノウハウを「脱Go言語初心者への道のり」という連載でご紹介してまいります。
第1回目ですが、オフィシャルガイドラインに従ったGoコーディングの作法 について紹介いたします。
Go言語の公式リポジトリでは、コードレビューにおいてよく指摘されることについてまとめた「Go Code Review Comments」というガイドラインを用意しています。
このガイドラインに従ってGo言語のコーディングを行うことで、誰でも「良いコード」を書くことができます!
Go言語における良いコードとは?
私見ですが良いコードというのは、以下のようなコードと考えています。
- 標準的なコード規約に準拠している。
- 誰が書いても似たようなコードになり、他の人も読みやすい。
- マシンリソースに配慮している。
- 適切なエラーハンドリングできており、保守しやすい。
Go言語ではgofmt
, golint
など、Code formatterやLinterが標準で用意されており、これらのツールを使えば、初めてGo言語を書く人でも簡単に良いコードを書くことができます。
※「良いコード」については、このブログの内容が個人的によくまとまっていると思います。プログラミング中級者に読んでほしい 良いコードを書くための20箇条
紹介したい項目が多いので、前後編でお送りします!
Code formatterにgoimportsを使う
Go言語の標準Code Formatterはgofmtですが、「Go Code Review Comments」ではgofmt互換ツールであるgoimportsを推奨しています。
理由はgofmt相当のコードフォーマットに加えて、以下の機能を持っているためです。
- 使っていない
import
を削除する。 import
を標準パッケージと外部パッケージで分ける。import
をパッケージ名のアルファベット順にソートする。
各種エディタの設定で、保存時にgoimports
を自動的に実行するのが好ましいとされています。
参考:いくつかのエディタでgoimports
を自動実行するための設定方法を紹介します。
vim
vim-goを導入し、以下の設定をします。
let g:go_fmt_command = “goimports”
let g:go_fmt_autosave = 1
SublimeText
GoSublimeを導入し、以下の設定をします。
“fmt_cmd”: [“goimports”],
参考: SublimeText3でのGo言語開発環境セットアップ
Atom
go-plusを導入し、以下の設定をします。
Run Format Tool on Save
にチェックを入れる。Format Tool
にgoimports
と設定する。
IntelliJ
External Tool
を導入し、Save動作をMacro登録したものに置き換える必要があります。
参考: Intellij IDEA でのGo言語開発環境セットアップ
中身のないスライスの宣言は、メモリを確保しないように書く
中身のないスライスの宣言は、以下のような書き方が好ましいとされています。
var t []string // 好ましい
t := []string{} // 好ましくない
var t []string
の書きかたは、スライスに値を追加するときに初めてメモリを確保します。
安易にPanicを使わない
Go言語の思想として、Fatalでないエラーをerror
型の変数でreturn
することをエラーハンドリングの原則としています。Panic
を利用すると、Recover
しないかぎりはそれ以上のエラーハンドリングができず、プロセスが停止します。
例えばWebアプリケーションであれば、controller層までerror
を返してinternal server error
で返すなどの処理をするのが好ましいです。
Go言語は関数の戻り値を複数個返すことができますので、返り値にerror
も返すようにしましょう。
error
とPanic
、どちらを使うべきなの? という疑問の回答は、このブログが参考になりました。 => Golang の defer 文と panic/recover 機構について
エラーを握りつぶさない
Effective Goのエラーハンドリング にもありますが、Go言語に慣れると関数の返り値であるエラーを_
で握りつぶしがちです。
// エラーを握りつぶしている例
num, _ := strconv.Atoi("-42")
確実なエラーハンドリングをするために、エラーを握りつぶす行為はやめましょう。
標準出力エラーにすらエラーが出力されないので、問題発見の遅れにつながります。
余談ですが、カンファレンスなどでGopher同士が集うと、「エラーを握りつぶしてデバッグが困難になった!」という話題で盛りあがりがちですw
import文は空行を使ってまとめる
import文は空行でグルーピングしましょう。標準パッケージは先頭のグループに書きます。
package mainimport (
"fmt"
"hash/adler32"
"os""appengine/foo"
"appengine/user""code.google.com/p/x/y"
"github.com/foo/bar"
)ドットインポートはプロダクションコードに書かないGo言語のimportの際にドットインポートをすると、パッケージ名を書かずに該当のパッケージの関数をコールできます。
ドットインポートを使うと、インポートの循環参照を解消できますが、「循環参照を解消しないといけない」ということは、基本的なモジュール設計が間違っている可能性が高いため、モジュール設計の再検討をまず行うべきです。ドットインポートはテストコードで使う程度にとどめ、プロダクションコードで使うのはやめましょう。package foo_testimport (
"bar/testutil" // also imports "foo"
. "foo"
)この例はfooもfoo_testもbar/testutilをインポートしていて循環参照となりますが、fooをドットインポートして循環参照を回避しています。エラーのif文でelseの利用を避けるelseの利用は避けてreturnを使いましょう。elseの中にifを書いていくと、ネストが深くなり可読性が落ちるためです。
不要なelseはgolintがチェックしてくれます。// elseを使った書きかた
if err != nil {
// error handling
} else {
// normal code
}// 上記はreturnを使って書き変える
if err != nil {
// error handling
return // or continue, etc.
}
// normal codeまたif文の条件式内で;を使うと変数のスコープを限定できますが、そのためにelseが生じるのであれば、変数のスコープを広めることを検討するべきです。// xのスコープが限定的なので、elseを使わざるを得ない
if x, err := f(); err != nil {
// error handling
return
} else {
// use x
}// 上記のコードは `x, err := f()` を外に出して使う
x, err := f()
if err != nil {
// error handling
return
}
// use xまとめいかがでしたでしょうか。
ここで紹介したいガイドラインの項目は全部で15個あり、残り7項目の紹介も後編コンテンツで近日公開予定です。内容は以下です、お楽しみに!
- 省略語は大文字・小文字を統一する
- 変数の命名はパスカルケースもしくはキャメルケース
- パッケージネームと変数名(関数名)の命名は被らないようにする
- メモリサイズの小さいオブジェクトはポインタで渡さない
- レシーバの名前はアルファベット1–2文字で統一する
- レシーバーのタイプに値もしくはポインタを使うケースはルールがあります
- 限定的で狭いスコープの変数名は短くする
宣伝:もくもく会のお知らせエウレカではGoもくもく会を定期的に開催していますので、ぜひお越しください!
http://eure.connpass.com/event/24488/