はじめに
この記事はand factory.inc Advent Calendar 2022 10日目の記事です。
数時間の遅刻です。。寝るまでが10日目なのでセーフ!(ではない)
昨日は@yasukotelin さんの「【Jetpack Compose】Pagerを使った画面でもCollapsing Toolbarを実装したい」でした。
こんにちは。
and factory サーバサイドエンジニアのnsym__mです。
先月、GoogleからGoのスタイルガイドが公開されました。
これまでGoでのスタイルガイドやコーディング規約としてはまずEffective Goが前提にあり、その補足としてGo Code Review Comments、他企業のスタイルガイドとしてUber Go Style GuideやGitLab Go standards and style guidelinesなどがありました。
Googleの出した公式のスタイルガイドというのは存在しなかったので、今後Goの書き方で迷った際などはここを参照すると良いでしょう。
補足: Effective Goの注意点として、これは2009年のGoリリース以降大きな更新が加えられていません。その為ライブラリや、ビルド システム、テスト、モジュール、ポリモーフィズムなどGoエコシステムの大きな変更については何も書かれていません。更新される予定もないので、これらのついての情報は別途キャッチアップする必要があるようです。
概要
まず、スタイルガイドは以下の3つに分かれています。
ドキュメント | リンク | 主な読者 | 規範的 | 規約 |
---|---|---|---|---|
Style Guide | https://google.github.io/styleguide/go/guide | 全員 | Yes | Yes |
Style Decisions | https://google.github.io/styleguide/go/decisions | Goのレビュワーとなるメンター(Readability Mentors) | Yes | No |
Best Practices | https://google.github.io/styleguide/go/best-practices | 興味のある人 | No | No |
-
Style Guide は、GoogleでのGoスタイルの基礎を概説しています。
このドキュメントは決定的なものでありStyle DecisionsとBest Practicesの推奨事項の基礎として使用されています。 -
Style Decisions は、特定のスタイル ポイントに関する決定を要約し、必要に応じて決定の理由を説明する、より冗長な文書です。
これらの決定(Decisions)は、新しいデータ、新しい言語機能、新しいライブラリ、新しいパターンに基づいて変更されることがありますが、Googleの個々のGoプログラマーがこのドキュメントを常に最新に保つことは期待されていません。 -
Best Practices は、一般的な問題を解決し、読みやすく、コードのメンテナンスの必要性に強い、時間をかけて進化してきたパターンのいくつかを文書化したものです。
これらのベストプラクティスは正規のものではありませんが、 Google の Go プログラマは、コードベースの統一と一貫性を保つために、 可能な限りこれらを使用することが推奨されています。
今回はこの3つのうち"Style Guide"の部分についてDeepLで翻訳しつつまとめました。
"Style Decisions"と"Best Practices"は結構長いのでいずれ機会があれば。
Style Guide
スタイルの原則
読みやすいGoコードの書き方には、いくつかの包括的な原則があります。以下は、読みやすいコードの属性で、重要なものから順に並べています。
- 明瞭性:コードの目的と根拠が読者にとって明確であること。
- 単純さ:コードが最もシンプルな方法で目的を達成していること。
- 簡潔さ:S/N比(信号対雑音比)が高いこと。
- 保守性:保守しやすいコードであること。
- 一貫性:Googleのコードベースと整合性がとれていること。
明瞭性
明瞭さは、主にわかりやすい命名、有益なコメント、効率的なコード構成により達成されます。
明瞭さには次の異なる2つの側面があります。
- コードが何をしているのか
- わかりやすい変数名、適切なコメント、コードの分割、モジュール化などで「何をしているのか」わかりやすくすることができる
- コードはなぜそう動くのか
- 変数名、関数名、メソッド名、パッケージ名から「なぜ」を伝えられることが多い。
- そうでない場合コメントが重要
- コードの読者に馴染みのないニュアンスがあると「なぜ」がより重要となる
- 言語の独自のニュアンスがある場合
- ビジネスロジック独自のニュアンスがある場合
- 標準ライブラリにはこれらを実践しているものが多い
-
package sort
にあるメンテナコメント - 同じパッケージ内にある実行可能な良いサンプルは、ユーザー(godocに表示される)とメンテナ(テストの一部として実行される)両方に利益をもたらす
-
strings.Cut
はたった4行のコードですが、呼び出しの明快さと正確さを向上させる
-
- 変数名、関数名、メソッド名、パッケージ名から「なぜ」を伝えられることが多い。
単純さ
- Goのコードはできるだけシンプルであるべき
- GoogleのGoのコードは以下のようなシンプルなコード
- 上から下まで読みやすい
- 何をしているかを知っていることを前提としない。
- 直前のコードをすべて記憶していることを前提としない。
- 不必要な抽象化レベルを持ちません。
- ありふれたものに注意を喚起するような名前をつけない。
- 値や判断の伝達を読者に分かりやすくする
- 将来の逸脱を避けるために、コードが何をしているかではなく、なぜそうするのかを説明するコメントがある。
- それ自体で成り立つドキュメントを持っている
- 有用なエラーと有用なテストの失敗がある
- "賢い"コードと相反する場合がある
- GoogleのGoのコードは以下のようなシンプルなコード
- 複雑なコードはGoでは書けない、書くべきではないという意味ではない
- コードが複雑であることで価値が生じる場合がある
- 例:
- APIのエンドユーザーがより簡単に呼び出せるように、コードをより複雑にする
- 逆に、コードをシンプルにする為にエンドユーザに少し余分な作業を任せる場合もある
- パフォーマンスを向上させる必要がある場合
- 特定のライブラリやサービスに対して、複数の異なる顧客が存在する場合
- APIのエンドユーザーがより簡単に呼び出せるように、コードをより複雑にする
- 例:
- これらの複雑さを必要とする場合、それが意図的である必要がある
- 意図しない不必要な複雑さだとしたらそれは避けるべき
- 複雑さに付随する正しいドキュメント、テスト、使い方を示す例が補足されるべき
- コードが複雑であることで価値が生じる場合がある
最小限の仕組み
- 同じアイデアを表現する方法がいくつかある場合、最も標準的なツールを使用する方法を選択する
- 探す順序
- 使用するケースに応じて、コアとなる言語構成要素(チャネル、スライス、マップ、ループ、構造体など)を使用することを目指す
- ない場合は、標準ライブラリにあるツール(HTTPクライアントやテンプレートエンジンなど)を探す
- 最後に、新しい依存関係を導入したり、独自のライブラリを作成したりする前に、Googleのコードベースに十分なコアライブラリがあるかどうかを検討する
簡潔さ
- 簡潔なGoコードは、S/N比が高い
- 命名と構造により詳細を理解することもできる
- 詳細を理解するのを阻む要素が以下
- 繰り返しの多いコード
- 余計な構文
- 不透明な名前
- 不必要な抽象化
- 空白文字
- テーブル駆動テストは各繰り返しの重要な詳細から共通のコードを簡潔に因数分解できる仕組みの良い例ですが、どの部分をテーブルに含めるかの選択は、テーブルの理解しやすさに影響します。
また、一般的なコード構成やイディオムを理解し使用することも、高い信号対雑音比を維持するために重要です。
例えば、次のコードブロックはエラー処理で非常によく使われるもので、読者はこのブロックの目的をすぐに理解することができます。
// Good:
if err := doSomething(); err != nil {
// ...
}
上記とよく似ていて微妙に違うと、読者は気づかない可能性があるのでエラーチェックのシグナルを意図的に "ブースト "して、注意を喚起するコメントを追加するとよいでしょう。
// Good:
if err := doSomething(); err == nil { // if NO error
// ...
}
保守性
コードは書かれた時より何倍も変更されるので、わかりやすさは重要です。
保守しやすいコード
- 将来のプログラマーが正しく修正しやすい。
- APIが優雅に成長するような構造になっていること。
- 前提条件を明確にし、コードの構造ではなく、問題の構造に対応する抽象化を選択する。
- 不必要な結合を避け、使用されない機能は含めない
- 約束された動作が維持され、重要なロジックが正しいことを確認するための包括的なテストスイートがあり、テストは失敗の場合に明確で実用的な診断を提供します。
メンテナンス可能なコードは、見落としやすい場所に重要な詳細を隠さないようにします。
例えば、以下の各コード行では、1文字の有無がその行を理解する上で重要です。
// Bad:
// :=の代わりに=を使用すると、この行を完全に変更することができる
if user, err = db.UserByID(userID); err != nil {
// ...
}
// Bad:
// 行の真ん中にある"!"はとても見逃しやすい
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:
// グレゴリオ暦の閏年は、year%4==0だけではありません。
// 参照: https://ja.wikipedia.org/wiki/閏年#コンピュータシステムと閏年
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 のソースコードでは、複数単語の名前を書くときにスネークケースではなくキャメルケースを使用します。
これは、他の言語での慣例に反する場合でも適用されます。たとえば、定数はエクスポートされた場合は MaxLength (MAX_LENGTHではない)、エクスポートされていない場合は maxLength (max_lengthではない) となります。
ローカル変数は、最初の大文字を選択する目的では、エクスポートされていないものとみなされます。
行の長さ
Goのソースコードには決まった行の長さはありません。行が長すぎると感じたら、改行するのではなく、リファクタリングすべきです。すでに実用的なほど短くなっている場合は、長いままにしておくべきです。
以下のケースで行を分割してはいけません。
- インデントを変更する前 (例: 関数宣言、条件分岐)
- 長い文字列 (例: URL) を複数の短い行に収める場合。
命名
命名は科学というより芸術です。Goでは、他の多くの言語よりも名前が多少短くなる傾向がありますが、同じ一般的なガイドラインが適用されます。
名前は次のようなものでなければなりません。
- 使用時に繰り返しを感じさせない
- 文脈を考慮する
- すでに明確になっている概念を繰り返さない
名前付けに関するより具体的なガイダンスは、Style Decisionsで見つけることができます
ローカルな一貫性
スタイルガイドが特定のスタイルについて何も言及していない場合、近接したコード(通常は同じファイルまたはパッケージ内ですが、チームまたはプロジェクトのディレクトリ内の場合もあります)がその問題について一貫した立場をとっていない限り、作者は自分の好むスタイルを自由に選択することができます。
ローカルスタイルに関する有効な考慮事項の例
- エラーの出力に
%s
や%v
を使用する。 - ミューテックスの代わりにバッファード・チャネルを使用する。
ローカルスタイルに関する無効な考慮事項の例
- コードの行数制限
- アサーション・ベースのテスト・ライブラリの使用
ローカルスタイルがスタイルガイドと一致しないが、可読性への影響が1つのファイルに限られる場合、一般的にはコードレビューで表面化し、一貫した修正が当該CLの範囲外となる。
そのような場合には、修正を追跡するためにバグを報告するのが適切です。
もし変更が既存のスタイルの逸脱を悪化させ、より多くのAPIサーフェスで露出させ、逸脱が存在するファイル数を拡大し、実際のバグを導入するなら、ローカルな一貫性は新しいコードでスタイルガイドに違反する正当な理由とはならなくなる。
このような場合、作者は同じCLで既存のコードベースをクリーンアップするか、現在のCLより先にリファクタリングを行うか、少なくともローカルな問題を悪化させないような代替案を見つけることが適切と言えます。
最後に
以上になります。
適切でないまとめや訳などあった場合ご指摘いただけると嬉しいです。
最後まで読んでいただいてありがとうございました。
11日目は@y-okuderaさんのスクロールで伸縮するヘッダをSwiftUIで実装するです!
すでに11日目に入っているので公開されてますw
ぜひ読んでみてください!