mmuulan
GoBackend設計思想

Goを理解するための設計思想

Created 2026-07-05 · Updated 2026-07-012

目次


概要

このドキュメントは、一般的なWeb開発の経験はあるけれど、Goはこれから、という人に向けたものです。Goの文法(ifforの書き方)を一から教えるものではありません。そうではなく、Goがなぜこういう書き方・設計になっているのか、その「考え方の根っこ」を15〜30分でつかむことを目的にしています。

なぜ根っこから入るのか。Goには「なんでこう書くの?」と感じるルールがいくつもあります。それを一つずつ丸暗記するのは大変ですし、忘れます。でも、おおもとにある一つの価値観さえ分かってしまえば、個別のルールは「ああ、だからこうなるのか」と自然につながります。AIが書いたGoコードを見て「これはGoらしくないな」と気づけるようになるのも、この根っこを持っているかどうかで決まります。

そしてその根っこは、突き詰めるとたった一つの言葉に集約できます——複雑さを増やさない。この先の各章は、この一つの価値観が場面ごとにどう顔を出すか、という話だと思って読んでください。


そもそもGoは何を最優先しているのか

新しい言語を学ぶとき、多くの人はまず「何ができるか(機能の多さ)」を気にします。ところがGoは、その逆を誇りにしている、少し変わった言語です。Goがアピールするのは「何を"あえて入れなかったか"」なのです。

Goは、Googleという巨大な組織で、たくさんのエンジニアが長い年月をかけて一つのコードを触り続ける、という状況のために生まれました。人数が多く、期間が長いほど、コードは複雑になり、読めなくなり、壊れやすくなります。この「複雑さ」こそが最大の敵だ、というのがGoの出発点です。だからGoは、機能を足すかどうかを「便利かどうか」ではなく「複雑さが増えないか」で判断してきました。後で出てくる「継承がない」「例外がない」といった特徴は、うっかり入れ忘れたのではなく、複雑さを持ち込まないために意図して外した結果です。

この価値観がよく表れているのが、gofmtという公式ツールの存在です。Goには「インデントはスペースかタブか」「括弧はどこで改行するか」といった、コードの見た目をめぐる論争がありません。gofmtを実行すれば、誰が書いても同じ形に自動整形されるからです(出典: Effective Go)。「書き方で人を悩ませない・チームを揉めさせない」ことまで、言語のしくみとして用意してしまう。ここにGoの思想がよく出ています。

これから先の章は、すべてこの「複雑さを増やさない」という一点が、いろいろな場面でどう形になっているか、という話です。まずこの一本の軸を頭に置いてください。


エラーはなぜ「値」なのか

他の言語からGoに来た人が、最初に「えっ」と声を上げるのがエラー処理です。多くの言語では、エラーが起きるとtryで囲んだ処理の途中からcatchへ一気に飛びます。ところがGoにはこの仕組みがありません。代わりに、関数が処理結果と一緒にエラーを"戻り値として"返してきます。そして呼び出す側は、そのたびに if err != nil { ... } と書いてエラーを確認します。最初は「毎回これを書くの?冗長では?」と感じるはずです。

ですが、これはGoがサボっているのではなく、はっきりした考えがあってのことです。try/catchのような例外は、便利な反面「どこで発生して、どこまで飛んでいくのか」がコードの見た目からは分かりません。処理が突然遠くへジャンプするので、追いかけるのが難しくなります。これはまさにGoが嫌う「複雑さ」です。だからGoは、エラーの発生と受け渡しを、隠さず目に見える場所に置くことを選びました。一見くどい if err != nil の繰り返しは、「エラー処理を省略させない・見えなくさせない」という意思表示なのです。

同じ理由から、エラーを_に代入して握りつぶす(無視する)ことも、強く避けるべきとされています。返ってきたエラーは、その場で対処するか、呼び出し元に返すか、どうしようもなければ処理を止める(panic)か、いずれかをはっきりさせます。なおpanicは、本当に例外的で回復不能なとき専用の最終手段であって、通常のエラー処理には使いません(出典: Effective Go)。想定外の状態を、必ずコードの表面に出しておく——この徹底ぶりが、Goで書かれたプログラムの壊れにくさを支えています。


継承がないのはなぜか

JavaやPython、Rubyに慣れた人がGoを書き始めると、たいてい早い段階で手が止まります。「クラスの継承って、どう書くんだ?」と探して、いくら探しても見つからないからです。それもそのはず、Goには継承がありません。これも、削り忘れたのではなく、意図して外したものです。

継承は確かに便利です。共通の性質を親クラスにまとめ、子クラスがそれを受け継げます。しかし規模が大きくなると、これが重荷に変わります。親を少し変えただけで、それを受け継ぐたくさんの子に予期しない影響が出る。ある子クラスの動きを理解するために、親、そのまた親…と上へ上へ辿らなければならない。この「一箇所の変更があちこちに響く」状態こそ、Goが避けたい複雑さです。だから継承という仕組みごと持たないことにしたわけです。

ではどうやって組み立てるのか。Goが使うのは、コンポジション(構造体の中に別の構造体を埋め込む)とインターフェースです。考え方が継承とは違います。「AはBの一種だ(is-a)」という上下関係ではなく、「AはBという機能を持っている・組み合わせて使う(has-a)」という発想です。小さな部品を必要なぶんだけ組み合わせていくので、どこで何が起きているかを追いやすく、変更の影響も狭い範囲にとどまります。この「小さく作って組み合わせる」感覚は、次の章にもそのまま続きます。


インターフェースはなぜ「小さいほど良い」のか

他の言語でインターフェース(共通の約束事を定める仕組み)を使ってきた人は、「関連するメソッドをたくさんまとめた立派なインターフェース」を作りがちです。ところがGoの世界をのぞくと、メソッドが1つしかないような、拍子抜けするほど小さなインターフェースが標準ライブラリのあちこちにあります。Reader(読む)、Writer(書く)のように、「〜する能力」を最小の単位で切り出しているのです。

なぜこんなに小さくするのか。Goでよく語られる言い回しに「インターフェースは大きいほど、抽象化としては弱くなる」というものがあります。あれもこれもできる巨大なインターフェースは、それを実装する側に「全部のメソッドを用意しろ」と強います。結果、使い回しづらくなり、複雑さが一箇所に溜まっていきます。反対に、小さなインターフェースは「読む能力さえあればいい」というふうに、必要な部分だけを求めます。だから組み合わせが効き、テストもしやすい。前の章の「小さな部品を組み合わせる」コンポジションの考え方と、まったく同じ発想がここにも流れています。最初から何でもできる万能な設計を目指さないのが、Goらしさです。


「唯一正しい構成」を決めないのはなぜか

多くのフレームワークには「このフォルダにこれを置く」という決まった型があり、そこに沿えば迷いません。Goに来た人はここでも少し戸惑います。「ファイルをどう置くのが正解なの?」と公式ドキュメントを探しても、たった一つの正解が書かれていないからです。

これはGoが不親切なのではなく、「正しい構成は、作るものによって違う」という前提に立っているためです。実際、公式のガイドも、単純なパッケージなのか、コマンドラインツールなのか、サーバーなのかで、勧める構成を分けて説明しています(出典: Organizing a Go module)。裏を返せば「規模と目的に合わせて自分で考えなさい」ということです。

ここから導かれる実務的な姿勢は、「最初から複雑な構成にしない」ことです。ごく小さなプログラムなら、フォルダを分けず、ルートに数個のファイルを置くだけで十分だとされています(出典: Organizing a Go module)。必要になってから構造を足していく。まだ要らない構造を先回りして作り込むのは、複雑さの先食いだからです。分割の単位がパッケージ(機能のまとまり)であることも同じ発想で、一つのパッケージには一つのはっきりした役割を持たせます。ただし、言語がレイアウトを決めてくれない以上、「今回のプロジェクトはこう置く」という方針を自分たちで決めて共有する責任は残ります。この点は、最後のAIの章にそのままつながります。


名前の頭文字が、なぜ設計判断になるのか

たいていの言語には、外から使わせたいものにpublic、隠したいものにprivateと印をつける仕組みがあります。Goでこのキーワードを探すと、やはり見つかりません。代わりにGoは、驚くほど簡単な方法をとります——名前の頭文字が大文字かどうかで、外に公開するかを決めるのです。頭が大文字なら外から使える、小文字ならそのパッケージの中だけ、というルールです。

これは単なる書き方の決まりではなく、なかなか深い考えを含んでいます。Goでは、名前をつけるという行為が、そのまま「これを外に見せるかどうか」という設計の意思表示になっているのです。他の言語では「名前を決める作業」と「公開範囲を指定する作業」は別々ですが、Goはこの二つを一つに畳んでいます。仕組みを二重に持たず、一つにまとめる。ここにも「複雑さを増やさない」という軸が効いています。だからGoの世界では、命名は「見た目の好み」ではなく「設計そのもの」として、とても真剣に扱われます。

大文字・小文字の使い分けや、ファイル名の付け方といった具体的なルールは「書き方(How)」の話なので、別のドキュメントに譲ります。ただ、それらのルールの根っこには必ず「名前は設計の一部だ」というこの考えがある、と知っておくと、細かい決まりもすんなり頭に入るはずです。


この思想をAIコーディングにどう活かすか

ここまでの「複雑さを増やさない」という軸を握っておくと、AIに書かせたGoコードが「Goらしいかどうか」を、細かいルールを覚えていなくても見抜けるようになります。AIは膨大なコードを学んでいるぶん、他の言語のクセをGoにそのまま持ち込みがちだからです。よくあるズレを挙げます。

まず、他言語の例外処理のクセです。通常のエラーなのにpanicを使ったり、エラーを_で捨てたりするコードが出てきたら要注意です。「エラーは値として扱い、握りつぶさない」という軸に照らせば、直すべき箇所だと判断できます。

次に、継承のクセや、作り込みすぎた抽象です。あれもこれもできる巨大なインターフェースや、継承をまねた密結合な構造を提案してきたら、「小さく作って組み合わせる」という考えに反していないかを見ます。

さらに、構成の作りすぎです。小さなプログラムなのに、いきなり何層ものフォルダに分けた構成を出してくることがあります。「規模と目的に合わせて、最小から始める」という姿勢で判断します。

最後に、方針のブレです。Goは構成を強制しないぶん、AIは頼むたびに違うフォルダ構成を出しがちです。だからこそ、「今回はこう置く」「公開範囲はこう考える」といった方針を、人間があらかじめ決めてAIに渡すことが大切になります。

まとめると、レビューのコツは「この書き方は正しいルールか」を一つずつ照合することではありません。もう一段深く、**「複雑さを増やさないという軸に沿っているか」**という目で見ることです。この軸さえ持っていれば、細かなルールをそらで言えなくても、違和感にちゃんと気づけます。


参考文献一覧