原著バージョン: https://github.com/google/styleguide/blob/9ae38d43e38a5b182092723d0f1295c3b80b1e06/go/guide.md
https://google.github.io/styleguide/go/guide
Overview | Style Guide | Style Decisions | Best practices |
Note: これは、Googleにおける Go Style の概要を説明する一連のドキュメントの一部です。 この文書は normative と canonical です。 詳しくは the overview を参照してください。
読みやすいGoコードを書くための考え方をまとめた、いくつかの包括的な原則があります。 読みやすいコードの属性として、重要度の高い順に次のようなものがあります:
可読性の中核となる目標は、読み手にとって明確なコードを作成することです。 わかりやすさは、主に効果的なネーミング、役に立つ解説、効率的なコード構成によって達成されます。
明瞭さは、コードの作者ではなく、読者のレンズを通して見るべきものです。 コードは書きやすさよりも、読みやすさが重要です。 コードの明確さには、2つの明確な側面があります:
Goは、コードが何をしているのかを比較的簡単に確認できるように設計されています。 不明な点がある場合、あるいはコードを理解するために予備知識が必要な場合、将来の読者のためにコードの目的を明確にするために時間を費やす価値があります。 例えば、以下のようなことが考えられます:
ここでは、一概には言えませんが、Goのコードを開発する際には、わかりやすさを優先することが重要です。
コードの根拠は、変数名、関数名、メソッド名、パッケージ名などで十分に伝えられることが多いです。 そうでないところは、解説を加えることが重要です。 「なぜ」は特に読み手がよく知らないニュアンスがコードに含まれている場合に重要です。
APIを正しく使うためには、注意が必要な場合があります。 例えば、あるコードがパフォーマンス上の理由から複雑に入り組んでいてフォローが難しい場合や、複雑な一連の数学的演算が予期せぬ方法で型変換を使用する場合があります。 このような場合、将来の保守者が間違わないように、また読者がリバースエンジニアリングしなくてもコードを理解できるように、付属の解説書や文書でこれらの点を説明することが重要です。
また、解説を追加するなどして分かりやすくしようとすると、かえってコードが乱雑になったり、コードが既に述べていることを再掲したり、コードと矛盾したり、コメントを最新に保つためのメンテナンスの負担が増えたりして、コードの目的が不明瞭になることがあることも知っておく必要があります。 冗長なコメントを追加するのではなく、コードが自ら語るようにしましょう(例えば、シンボル名自体を自己記述するようにする)。 コメントは、コードが何をやっているかではなく、なぜそうなるのかを説明する方が良い場合が多いのです。
Google のコードベースは、ほぼ統一されており、一貫性があります。 見慣れないパターンを使うなどして目立つコードには、それなりの理由があることがよくあります(一般的にはパフォーマンスのため)。 この指針を維持することは、新しいコードを読むときに、読者がどこに注意を向けるべきかを明確にするために重要です。
標準ライブラリには、この原則を実践している例が数多く掲載されています。 その中でも特に:
package sort
のメンテナコメントstrings.Cut
はわずか4行のコードですが、コールサイトの明快さと正しさを向上させます。Go のコードは、それを使う人、読む人、保守する人にとってシンプルであるべきです。 Go のコードは、動作と性能の両面から、目的を達成するために最もシンプルな方法で書かれるべきです。Google Go のコードベース内では、シンプルなコードです:
コードの簡略化と API 利用の簡略化の間でトレードオフが生じることがあります。 例えば、API のエンドユーザーがより簡単にAPIを正しく呼び出せるように、コードをより複雑にすることに意義があるかもしれません。 逆に、API のエンドユーザーに少し余分な作業を任せて、コードがシンプルでわかりやすい状態を維持することも意義があるかもしれません。
コードが複雑さを必要とする場合、複雑さは意図的に追加する必要があります。 これは通常、追加のパフォーマンスが必要な場合や、特定のライブラリやサービスの複数の異なった利用者がいる場合に必要です。 複雑さは正当化されるかもしれませんが、利用者や将来の保守者が複雑さを理解し、ナビゲートできるように、付随する文書が付属している必要があります。 特に、そのコードを使うのに「単純」な方法と「複雑」な方法の両方がある場合は、正しい使い方を示すテストや例で補足する必要があります。
この原則は、複雑なコードはGoで書けない、または書くべきではない、あるいは Go のコードは複雑であってはいけないということを意味するものではありません。 私たちは、不必要な複雑さを避けるコードベースを目指しており、複雑さが現れた場合は、問題のコードを理解し維持するために注意が必要であることを示すものです。 理想的には、その根拠を説明し、取るべき注意を明らかにする解説が付随していることが望ましいです。 バッファを事前に確保し、それをゴルーチンのライフタイムを通して再利用するような、より複雑なアプローチを必要とすることがよくあります。 メンテナンス担当者がこれを見たとき、問題のコードがパフォーマンスクリティカルであることを示す手がかりとなり、今後の変更に際しての注意に影響を与えるはずです。 一方、不必要に使用された場合、この複雑さは、将来そのコードを読んだり変更したりする必要のある人々にとって負担となります。
目的が単純であるはずのコードが非常に複雑になった場合、同じことを達成するためにもっと単純な方法がないか、実装を見直す合図になることが多いのです。
同じアイデアを表現する方法がいくつかある場合、最も標準的なツールを使用するものを選びます。 洗練された機構はしばしば存在するが、理由なく採用すべきではありません。 必要に応じてコードに複雑さを加えるのは簡単だが、既存の複雑さが不要とわかった後に取り除くのはずっと難しいです。
例として、テスト時にオーバーライドしなければならないデフォルト値を持つ変数に束縛されたフラグを含むプロダクションコードを考えてみましょう。
プログラムのコマンドラインインタフェース自体をテストするのでなければ (例えば os/exec
を使って)、 flag.Set
を使うよりも、バインドされた値を直接オーバーライドする方が簡単で好ましいです。
同様に、あるコードがセットメンバーシップのチェックを必要とする場合、Boolean 値のマップ(例:map[string]bool
)で十分なことが多いです。
集合のような型や機能を提供するライブラリは、マップでは不可能な、または過度に複雑な操作が必要な場合にのみ使用する必要があります。
簡潔な Go コードは、高い S/N 比を持っています。 関連する詳細を見分けるのは簡単で、命名と構造によって読者を導いてくれます。
その時々に最も重要なディテールを浮上させるためには、さまざまなことが邪魔になります:
特に繰り返しコードは、ほぼ同一の各セクション間の差異を不明瞭にし、読み手は類似のコード行を視覚的に比較して変更点を見つける必要があります。 テーブル駆動テストは、各繰り返しの重要な詳細から共通のコードを簡潔に因数分解できる仕組みの良い例ですが、どの部分をテーブルに含めるかの選択が、テーブルのわかりやすさに影響します。
複数のコード構成方法を検討する場合、どの方法が重要な詳細を最も明確にすることができるかを検討する価値があります。 一般的なコード構文やイディオムを理解し使用することも、高い S/N 比を維持するために重要です。 例えば、以下のコードブロックは エラー処理で非常によく使われるもので、読者はこのブロックの目的をすぐに理解することができます。
// Good:
if err := doSomething(); err != nil {
// ...
}
これとよく似たコードでも、微妙に違っている場合、読者はその変化に気づかないかもしれません。このような場合は、エラーチェックのシグナルを意図的に “boosting” し、注意を喚起するコメントを追加する価値があります。
// Good:
if err := doSomething(); err == nil { // if NO error
// ...
}
コードは、書かれるよりも何度も編集されるものです。 読みやすいコードは、その仕組みを理解しようとする読者にとって意味があるだけでなく、それを変更する必要のあるプログラマーにとっても意味があるのです。 わかりやすさは重要です。
保守性の高いコード:
インターフェースや型のように、定義上、使用されるコンテキストから情報を取り除く抽象化を使用する場合、十分な利点を提供することを保証することが重要です。 エディターや IDE は、具体的な型を使用する場合はメソッドの定義に直接接続し、対応するドキュメントを表示できますが、そうでない場合はインターフェースの定義にしかアクセスできません。 インターフェイスは強力なツールですが、インターフェイスを正しく使用するために、メンテナンス者が基本的な実装の詳細を理解する必要がある場合があり、インターフェイスの文書内またはコールサイトで説明しなければならないため、コストがかかります。
また、保守性の高いコードは、見落としやすい場所に重要な詳細を隠すことも避けられます。 例えば、以下の各コード行では、1文字の有無がその行を理解する上で重要です:
// Bad:
// The use of = instead of := can change this line completely.
if user, err = db.UserByID(userID); err != nil {
// ...
}
// Bad:
// The ! in the middle of this line is very easy to miss.
leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))
どちらも間違いではありませんが、もっと明確に書くか、重要な動作に注意を促すコメントを添えるか、どちらかでしょう:
// Good:
u, err := db.UserByID(userID)
if err != nil {
return fmt.Errorf("invalid origin user: %s", err)
}
user = u
// Good:
// Gregorian leap years aren't just year%4 == 0.
// See https://en.wikipedia.org/wiki/Leap_year#Algorithm.
var (
leap4 = year%4 == 0
leap100 = year%100 == 0
leap400 = year%400 == 0
)
leap := leap4 && (!leap100 || leap400)
同じように、重要なロジックや重要なエッジケースを隠すようなヘルパー関数は、将来の変更でそれを適切に考慮できなくなる可能性があります。
予測可能な名前は、保守可能なコードのもう一つの特徴です。 パッケージのユーザーやコード片のメンテナは、パッケージの名前を予測することができるはずです。 変数、メソッド、または関数を指定されたコンテキストで使用します。 同じ概念の関数パラメータとレシーバー名は、通常、同じ名前を共有する必要があります。これは、ドキュメントを理解しやすくし、最小限のオーバーヘッドでコードのリファクタリングを容易にするためです。
保守性の高いコードは、依存関係を最小限に抑えます(暗黙的、明示的の両方)。 より少ないパッケージへの依存は、動作に影響を与えるコードの行数を減らすことを意味します。 内部的な動作や文書化されていない動作への依存を避けることで、将来それらの動作が変わったときに保守の負担になる可能性が低くなります。
コードをどのように構成するか、あるいは書くかを考えるとき、そのコードが時間の経過とともにどのように変化していくかを考える時間を取る価値があります。 あるアプローチの方が、将来的な変更をより簡単かつ安全に行えるのであれば、たとえ設計が多少複雑になったとしても、それは良いトレードオフとなることが多いです。
一貫性のあるコードとは、より広いコードベース全体、チームやパッケージのコンテキスト、さらには1つのファイル内でも、同様のコードに見え、感じ、動作するコードのことを指します。
一貫性の問題は、上記の原則を覆すものではありませんが、もし同点が破られなければならない場合、一貫性を優先して破ることが有益であることが多いのです。
パッケージ内の一貫性は、多くの場合、最も直接的な重要レベルです。 同じ問題がパッケージ全体で複数の方法でアプローチされていたり、同じコンセプトがファイル内で多くの名前を持っていたりすると、非常に不快に感じることがあります。 しかし、このような場合でも、文書化されたスタイルの原則やグローバルな一貫性を覆すべきではありません。
これらのガイドラインは、すべての Go コードが従うことが期待される Go スタイルの最も重要な側面を集めたものです。 これらの原則は、可読性が付与されるまでに学習され、遵守されることを期待しています。 これらは頻繁に変更されることはないと思われ、新しく追加されるものは高いハードルをクリアしなければならないでしょう。
以下のガイドラインは、Effective Go の推奨事項を拡張したもので、コミュニティ全体の Go コードに共通のベースラインを提供します。
すべての Go ソースファイルは gofmt
ツールが出力するフォーマットに準拠する必要があります。
このフォーマットは、Google コードベースの presubmit チェックによって強制されます。
生成されたコードも一般的にはフォーマットされるべきです。(例えば、コード検索で閲覧することができるため、 format.Source
を使って)
Go のソースコードでは、複数単語の名前を書くときにアンダースコア(スネークケース)ではなく、MixedCaps
または mixedCaps
(キャメルケース)を使用します。
これは、他の言語での慣例を破っている場合でも適用されます。
例えば、定数は、エクスポートされた場合は MaxLength
(MAX_LENGTH
ではなく)、エクスポートされていない場合は maxLength
(max_length
ではなく)となります。
ローカル変数は、初期資本金を選択する目的で、unexported とみなされます。
Go のソースコードには、決まった行の長さはありません。 行が長すぎると感じたら、改行するのではなく、リファクタリングする必要があります。 すでに実用的なほど短くなっている場合は、その行は長いままでよいでしょう。
行を分割しないでください:
ネーミングは科学というより芸術です。 Go では、他の多くの言語よりも名前がやや短くなる傾向がありますが、同じ general guidelines が適用されます。
ネーミングは下記であるべきです:
ネーミングに関するより具体的なガイダンスは、decisions を参照してください。
スタイルガイドが特定のスタイルについて何も述べていない場合、近接するコード(通常は同じファイルやパッケージ内ですが、チームやプロジェクトのディレクトリ内の場合もあります)がその問題について一貫した姿勢をとっていない限り、著者は自分の好きなスタイルを自由に選択することができます。
有効なローカルスタイルの考慮の例:
%s
または %v
を使用する無効なローカルスタイルの考慮の例:
ローカルスタイルがスタイルガイドと異なるが、可読性への影響が1つのファイルに限られる場合、一般的にはコードレビューで表面化し、一貫した修正は問題の CL の範囲外で行ないます。 この場合、修正を追跡するためにバグを報告することが適切です。
変更によって既存のスタイルの逸脱が悪化したり、より多くの API サーフェスで露出したり、逸脱が存在するファイルの数が増えたり、実際のバグが発生したりする場合は、ローカルな一貫性は、新しいコードでスタイルガイドに違反する正当な理由とはならなくなります。 このような場合、作者は同じ CL で既存のコードベースをクリーンアップするか、現在の CL より先にリファクタリングを行うか、少なくともローカルな問題を悪化させない代替案を見つけることが適切です。