golang

Golangの格言(Go Proverbs)

1 likes

Go言語における有名な格言集(Go Proverbs)は、Rob Pike(ロブ・パイク)が GopherCon 2015 のクロージングキーノートで発表したものです(もう10年以上経つのか)

単にルールを並べるのではなく、Goのデザイン哲学を短い言葉に凝縮したもので、現在でも開発者の指針となっています。

1. メモリを共有することで通信するな。通信することでメモリを共有せよ

原文

Don't communicate by sharing memory, share memory by communicating.

解説

この格言は「sync.Mutex を使った共有状態のロックを避け、チャネル (chan) を使ったデータの受け渡し(所有権の移動)を使え」ということを説いています。

チャンネルの使い方

「メモリを共有することで通信するな。通信することでメモリを共有せよ」をもっと詳しく

closeをもっと詳しく

2. 並行性は並列性ではない

原文

Concurrency is not parallelism.

解説

複数のタスクを同時にさばけるような段取り(並行性)と、物理的に全く同時に動くこと(並列性)』は全くの別物だよということを説いています。

「並行性は並列性ではない」をもっと詳しく

3. channel は段取りを司り、mutex は直列化する

原文

Channels orchestrate; mutexes serialize.

解説

Go言語には並行処理を安全に行うための道具が2つ用意されていますが、これらは「似たようなもの」ではなく、根本的に役割が異なります。

  • channel(段取り・連携): データの受け渡しや、複数のゴルーチン間の「処理の流れ(ワークフロー)」を設計・指揮(オーケストレーション)するための道具です。バケツリレーや工場のベルトコンベアのように、データそのものを移動させることで安全性を保ちます。
  • mutex(直列化・保護): 複数のゴルーチンが同じデータ(共有メモリ)を同時に触って壊さないよう、一時的に処理を「1列に並ばせる(シリアライズする)」ための道具です。トイレの個室の鍵のように、誰かが使っている間は他の人を順番待ちさせます。

Go言語では「通信によってメモリを共有する(channel)」設計が推奨されることが多いですが、単純なカウンターの増減やキャッシュの更新など、単一の「状態」を保護したい場面では「メモリを共有してロックで制御する(mutex)」方がシンプルで適していることもあります。目的に応じて使い分けることが重要です。


【コード例:裏表の比較】

それぞれの道具が「何を解決しているか」を実際のコードで比較してみましょう。

パターン1:Mutex による「直列化(Serialize)」

複数人が同時に1つの金庫(共有変数)にお金を入れる場面を想像してください。同時に手を入れると計算が狂ってしまうため、Mutex という「鍵」を使って、必ず1人ずつ順番にアクセスするように強制(直列化)します。

パターン2:Channel による「段取り(Orchestrate)」

次はデータの保護ではなく、「作業の連携」に焦点を当てます。Aさんが仕込みをして、Bさんが調理するような「段取り(オーケストレーション)」を組む場合、Mutex のような「鍵」は不要です。channel を使ってバトンを渡していくことで、美しく処理を連携させます。

まとめ

  • Mutex を使うべき時: 単純な「状態(変数)」を複数のゴルーチンから安全に読み書きしたいとき。(例:アクセスカウンター、インメモリのキャッシュなど)
  • Channel を使うべき時: 「データ」を次の工程へ渡したいときや、処理の「順番」を制御したいとき。(例:パイプライン処理、タスクのワーカープール、非同期イベントの通知など)

4. interface は大きいほど抽象として弱くなる

原文

The bigger the interface, the weaker the abstraction.

解説

大きな interface は、実装側に余計な責任まで押しつけやすいです。 小さい interface の方が、役割が明確で差し替えもしやすいです。 「本当に必要な振る舞いだけ」を定義するのが基本です。

5. ゼロ値を有用にせよ

原文

Make the zero value useful.

解説

変数に初期値を与えずに宣言した際、Goが自動的に割り当てるデフォルト値のことです。 数値型は0、文字列型は空文字("")、bool型はfalse、ポインタや参照型はnilになります。 メモリの未初期化による予測不能な動作を防ぎ、常に安全な状態から処理を開始できます。

単に「変数宣言だけで使えて便利」なのではなく、**「変数宣言だけで安全に使えるように、内部のロジックや状態遷移を洗練させよ」**という要求こそが、この格言の重みです

6. interface{} は何も語らない

原文

interface{} says nothing.

解説

interface{}(Go 1.18以降では any)は「どんな型でも受け取れる」という一見便利な型です。しかし裏を返せば、「その値が何を持ち、何ができるのか」をコンパイラにもコードを読む人間にも一切伝えられない、情報ゼロの型でもあります。

APIの引数や戻り値に interface{} を多用すると、使う側は「何を渡せばいいか」、作る側は「渡されたものが何か」を都度確認しなければならず、型の判定(型アサーション)コードが増殖していきます。そして想定外の型が紛れ込んだとき、コンパイラは黙って通過させ、実行時にパニックで初めて問題が発覚します。

「何でも受け取れる関数」を目指すより、「特定の振る舞い(メソッド)だけを定義した小さな interface」を切り出す方が、コードは堅牢で、意図は明確で、バグは早期に発見できます。


NG例:interface{} を使ったコード

引数が interface{} だと、関数を呼ぶ側は「何を渡せばいいか」わからず、関数の中身は型の判定処理だらけになります。しかも判定漏れのある型を渡してもコンパイルエラーにならないため、バグが実行時まで隠れ続けます。


OK例:小さな interface を定義したコード

「何でも受け取れる関数」ではなく、「何ができるかを定義した interface を受け取る関数」に変えるだけで、型安全性はコンパイラが保証してくれます。不正な型を渡せば、実行前に気づけます。


まとめ

  • interface{}any)は型の安全性を手放す最終手段。濫用は禁物。
  • 共通化したいとき、着目すべきは「どんなデータか」ではなく「何ができるか(振る舞い)」。小さな interface を定義するのが Go らしい解法。

7. gofmt のスタイルは誰の一番好みでもないが、皆のお気に入りである

原文

Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.

解説

人ごとの書き方の好みを持ち込まないという文化です。 見た目の議論を減らして、レビューを中身に集中させられます。 Go では「整形は gofmt に任せる」が強い共通認識です。

GoのインデントはTabって決まっているんですよね。 gofmtが有効だと、保存時に自動的にTabインデントで整形されます。 空白インデントでもかけますが、Goの作法を知らない新参者みたいにみられるくらい、gofmtはスタンダードな規格です。

※gofmt は通常、単体で別インストールするものではなく、Go本体に同梱されています。

8. 少しの依存より少しのコピーの方がよい

原文

A little copying is better than a little dependency.

解説

「コードの重複(DRY原則の違反)」よりも「無駄な依存」のほうが、システムにとって致命的な負債になりうるというGoの重要な哲学です。

ほんの少しの共通化のために安易に外部ライブラリや共通パッケージ(utilsなど)に依存すると、破壊的変更や依存の連鎖など、コントロールできないリスクまで背負い込むことになります。

数十行程度の短い処理であれば、他者に依存して結合度を高めるよりも、コードをそのまま自分の手元に「コピー」する方が、独立性が保たれ、結果的に保守しやすいシステムになるという教えです。

9. syscall は常に build tags で守らなければならない

原文

Syscall must always be guarded with build tags.

なぜビルドタグで守る必要があるのか?

一言でいえば、「Goのクロスコンパイル能力を損なわないため」です。

システムコールはOS・アーキテクチャに完全依存しています。LinuxのEpollはmacOSに存在せず、WindowsのファイルI/O定数もLinuxとは異なります。ビルドタグで保護せずにLinux固有の syscall を書いてしまうと、Windows向けのビルド時点でコンパイルエラーになります。

Goの標準ライブラリ(osnet)がOS差異を内部で吸収しクリーンなAPIを外部に提供しているように、私たちもOS固有の処理と共通処理を明確に隔離する必要があります。

具体的な実装パターン

共通インターフェースを main.go に宣言し、実装だけをOSごとのファイルに分離します。

悪い例

良い例(3ファイル構成)

どのプラットフォームでも go build / go test が通るようになります。

補足: 現代のGoでは golang.org/x/sys を使う

標準の syscall パッケージは後方互換性のために残されていますが、実質的に凍結されており新機能の追加はありません。現在システムコールを直接叩く必要があるなら、準標準パッケージを使うことが推奨されています。

パッケージ対象
golang.org/x/sys/unixLinux / macOS / BSD
golang.org/x/sys/windowsWindows

「OS固有コードはビルドタグで隔離する」という原則はどちらも同様です。

まとめ: ビルドタグはOS固有の処理を封じ込める防波堤。アプリ本体をクリーンでポータブルに保つための、Goらしい設計規律です。

10. cgo は常に build tags で守らなければならない

原文

Cgo must always be guarded with build tags.

解説

cgo を使うコードは、cgo が有効な環境でだけ使うのが安全です。 そうしないと、クロスコンパイルや環境差分で詰まりやすくなります。 これも複数ファイルで分けるのが基本です。

main.go

abs_cgo.go

abs_nocgo.go

実行方法:

11. cgo は Go ではない

原文

Cgo is not Go.

解説

cgo を使うと、Go の単純さや移植性が落ちやすくなります。 だから cgo 部分は外から見えないように、境界を薄く閉じ込めるのが大切です。 この格言の本質は「使うな」ではなく、「広げるな」に近いです。

12. unsafe パッケージには保証がない

原文

With the unsafe package there are no guarantees.

解説

unsafe は、Go の安全性の一部を外して触る機能です。 今たまたま動いても、将来まで安全とは限りません。 使うとしても、必要最小限に留めるべきです。

13. 巧妙さより明快さ

原文

Clear is better than clever.

解説

一見「頭が良さそう」に見えるトリッキーな書き方よりも、パッと見て意図が伝わるコードの方が遥かに価値があります。 コードは書く時間よりも読まれる時間の方が長いからです。

自分にしか分からない「巧妙なパズル」のようなコードを避け、誰でもメンテナンスできる「明快さ」を優先しましょう。

例1:偶数かどうかの判定

巧妙な(Clever)コード

ビット演算を使って判定しています。処理速度はわずかに速いかもしれませんが、一目見ただけでは意図が伝わりにくいです。へぇそんなやり方あるんだって思う人も多いです。

明快な(Clear)コード

算術演算の「余り(Modulo)」を使います。誰が見ても「2で割り切れるかどうか」を確認していることが瞬時に分かります。

例2:スライスのコピーとフィルタリング

巧妙な(Clever)コード

Goの言語仕様をフル活用し、代入と条件式を1行に詰め込んだパターンです。

明快な(Clear)コード

新しいスライスを用意し、単純に append していきます。メモリ効率より「何をしているか」の可読性を優先します。

なぜ「明快さ」が重要なのか?

  • デバッグが楽: 巧妙なコードでバグが起きた時、その解読だけで疲弊してしまいます。
  • チーム開発に適している: 他人のコードをレビューする際、意図を推測する必要がなくなります。
  • 未来の自分への親切: 3ヶ月後のあなたは、今のあなたの「巧妙なテクニック」を忘れています。

「そもそもデバッグは、コードを書くことより2倍難しい。したがって、もし自分の持てる知能をすべて注ぎ込んで『巧妙な』コードを書いてしまったら、あなたにはそれをデバッグするための知能が残っていないことになる。」 —— ブライアン・カーニハン(C言語開発者の一人)

14. リフレクションは決して明快ではない

原文

Reflection is never clear.

解説

reflection は柔軟ですが、何が起きているか見えにくくなります。 型安全性も弱く、壊れたときの原因追跡も難しくなりがちです。 普通のコードで書けるなら、まずそちらを優先した方がよいという教えです。

パフォーマンスと文化

Javaの場合、フレームワーク(Springなど)の根幹としてリフレクションが多用される文化があります(近年はコンパイル時のアノテーション処理で回避する動きもありますが)。 Goの場合だと「リフレクションは遅い」「型安全性を損なうため、コードが読みにくくなる」という理由から、「明確な必要性がない限り避けるべき」という文化が強いです。Goでは、インターフェースによる抽象化や、コード自動生成(go generate)によってリフレクションを回避することが推奨されるケースが多いです。

15. エラーは値である

原文

Errors are values.

解説

error は特別な魔法ではなく、普通の値として扱います。 だから、返す・受け取る・包む・比較する、という扱いができます。 Go のエラー処理は、この考え方が土台です。

16. エラーはただチェックするだけでなく、丁寧に扱え

原文

Don't just check errors, handle them gracefully.

解説

err != nil を書くだけでは不十分です。 失敗したときに、続行するのか、代替値を使うのか、中断するのかを考える必要があります。 「どう失敗させるか」も設計の一部です。

17. アーキテクチャを設計し、部品に名前を付け、詳細を文書化せよ

原文

Design the architecture, name the components, document the details.

解説

いきなり実装に入るのではなく、まず全体構造を考えるべきという意味です。 良い名前は、その部品の役割をコード上に残します。 細かい仕様はコメントや文書で補うのが大切です。

18. ドキュメントは利用者のためのもの

原文

Documentation is for users.

解説

コメントやドキュメントは、実装者の独り言ではなく、使う人の助けになるべきです。 何を受け取り、何を返し、どう使うかが分かることが重要です。 「この関数は何のためにあるか」を伝える意識が大切です。

19. panic するな

原文

Don't panic.

解説

普通に起こりうる失敗は、panic ではなく error で返すべきです。 panic は「通常運用で想定しない壊れ方」に近いものです。 回復可能な異常は、呼び出し側に判断を返す方が安全です。