世界を動かす技術を、日本語で。

Goを使おう

概要

Goは シンプルさ堅牢性 を重視した言語。 依存関係の問題複雑なデプロイ から解放される。 標準ライブラリだけで フル機能のWebアプリ が実現可能。 ツールチェーン が一体化しており、運用も容易。 「 退屈」こそがGoの最大の強み。

Goは「退屈」こそが正義

  • Go2秒でコンパイル単一バイナリ でデプロイ可能
  • npm の依存関係トラブルや ビルドツールの乱立 からの解放
  • Node.jsRailsRust の過剰設計へのアンチテーゼ
  • HTML が進化しつつも本質を維持してきたのと同様の思想

言語仕様のシンプルさ

  • デコレーターメタクラスマクロトレイト などの複雑な抽象化を排除
  • structfunctioninterfacegoroutinechannel のみで構成
  • 言語仕様 は昼休みに読めるレベルの分量
  • gofmt による自動整形で コードスタイルの統一
  • 抽象化の暴走 を防ぎ、 可読性と保守性 を担保

標準ライブラリが最強のフレームワーク

  • 標準ライブラリ だけで Webアプリ が完結
    • embed でテンプレートをバイナリ内に埋め込み
    • html/template でサーバサイドレンダリング
    • net/http でWebサーバ機能
  • WebpackVitedev server巨大なnode_modules 不要
  • database/sql でDB接続、 encoding/json でJSON処理
  • net/http はクライアント機能も提供
  • goroutine で並列処理、 go test でテスト、 pprof でプロファイリング

標準ライブラリの奥深さ

  • io.Readerio.Writer という2つのインターフェイスがエコシステムの基盤
  • context.Context でリクエスト単位のキャンセルやタイムアウト管理
  • encoding/json/xml/csv/binary は同じパターンで学習コスト低減

泣かない並行処理

  • goroutine はスレッドではなく、2KB程度で数十万単位の同時実行が可能
  • channel で型安全な通信と同期
  • sync.Mutex で共有状態の排他制御、 race detector で競合検知
  • async/await や複雑なコールバック不要

実践的なCRUD例

  • Postgres 接続、 HTMLレンダリングHTTPハンドラ を1画面で完結
  • リクエストのcontext をDBクエリに渡すことで安全なキャンセル処理
  • ORM やDIコンテナ、サービス層、抽象クラス不要
  • トップダウンで読める 明快な構造

依存管理の安心感

  • go mod init でプロジェクト初期化
  • go.modgo.sum のみで依存管理
  • node_moduleslockfileの不整合peerDependencies 問題なし
  • go mod vendor でオフラインビルドも容易
  • セキュリティ 面でも信頼性抜群

ツールチェーンの一体化

  • gofmt でコードフォーマット、 go vet で静的解析
  • go test でテスト、 -race で競合検知、 -bench でベンチマーク、 -cover でカバレッジ
  • pprof で本番環境のプロファイリングも簡単
  • 全て標準搭載、サードパーティや設定ファイル不要

デプロイは「コピー」だけ

  • バイナリをビルドしてサーバにコピーし、実行するだけ
  • DockerfileKubernetesHelm 不要
  • 12MBの静的バイナリ20行のsystemdユニットファイル で本番運用
  • Docker が必要な場合も FROM scratch でOK

他フレームワークとの比較

  • Rails は複雑なデプロイ手順と儀式
  • Django は独自ORMやミドルウェア学習が必須
  • Express は脆弱なnpmエコシステム依存
  • Next.js は仕様変更が頻繁で不安定
  • Goバイナリ は数年後も動き続ける信頼性

モノリス推奨、マイクロサービス不要

  • 1バイナリ、1Postgres、必要ならRedis で十分
  • HTMLとAPI を同じポートで提供
  • VPS1台 で1万RPSも余裕
  • 本当に分割が必要なら、パッケージをリポジトリ分割するだけ

ジェネリクス・エラーハンドリングについて

  • if err != nil が特徴であり、例外隠蔽を防ぐ
  • ジェネリクス も1.18で導入済み、必要な時だけ使えば良い

まとめ:「退屈」こそ最適解

  • フレームワーク不要マイクロサービス不要Rustリライト不要
  • go mod initmain.go 作成、 テンプレート埋め込みビルドしてデプロイ
  • 退屈な選択 が最良の選択
  • Go は最初から最後まで現場主義の「出荷できる」言語

Hackerたちの意見

よくGoは「より良い」Pythonだと思う。学びやすくて使いやすいし、パフォーマンスもいい。モジュールシステムやパッケージマネージャーも少しスッキリしてるしね。(フレームベイトごめん)でも、似たようなユースケースをどれだけカバーできるのかな?GoはDevOpsやウェブバックエンドには最適だけど、AIやデータサイエンスはどうなんだろう?

GoがCと同じくらいPythonとインターフェースできたら、もっと使うのに。でも、もっと多くのものと統合できる遅い言語を使ってる。例えば、AIがPythonに集中している理由の一つは、CUDAが基本的にCエコシステムの一部だから(つまりビルドシステム)。

以前はPythonでデータエンジニアリングをたくさんやってたけど、今は主にGoでいろんなエンジニアリングをしてる。Goのデータエコシステムは非常に限られてるよ。広くサポートされているデータフレームライブラリ(古いpandasや新しいRustで書かれたpolarsなど)がないし。データサイエンスライブラリもほとんどないし、いくつかの良い生成AIライブラリはあるけど、Pythonの仲間たちほど人気じゃない。今やってる仕事のほとんどはストリーミングデータと非常に小さなバッチ処理。これにはGoが素晴らしい。JSONを変換して、他のデータと結合してデータベースに書き込むのにデータフレームは必要ない。ロジックを書いて、速く動かせばいいだけだから。Goではすごく簡単だよ。

Goはシンプルなアプリケーション、特にインターネットに接続するバックエンドにはいいと思う。NodeやJS、TSよりも好きだな。多くのプロジェクトはJavaや他の言語で書くよりGoで書いた方が良かったと思う。万能ではないけど、足を引っ張ることも簡単にできちゃう。簡単なことは簡単だけど、難しいことは本当に難しい。Rustの方がちょっと好きだけど、書き直すことはない。最初に選ぶのはRust。好みだけど、好きにやってくれ。

Goが大好き。でも、ウェブ開発には.NETの方が好き。バイナリにコンパイルできるし、ライブラリやパッケージのエコシステムも素晴らしい。Goの標準ライブラリが使えるなら最高だけど(多くの場合は使えるけど)、非標準ライブラリを使う必要が出てくると、制限が出てくることもある。例えば、Goでデータベースを使ったフルプロダクションのウェブアプリを作るには、素晴らしいマイグレーションツールがない。もちろん良いサードパーティのライブラリもあるけど、.NETのEFCoreと比べると、そこまで近くはない。今は.NETがあって、その後にGo。もちろん、ウェブじゃないことをやるときはGoも使うけどね。

Goと.NETを比べるのは、ホンダと車輪のついた空母を比べるようなもんだよ。

"バイナリにコンパイルされる" っていうのは、あんまり役に立たない基準だよ。Goが勝ってる基準は「単一の、完全に自己完結したバイナリにコンパイルされる」ってこと。つまり、libcや外部のランタイムに依存しないってことだよね。.NETについてはそう言えないし、ほとんどの他のプログラミング言語についても言えない。これは非常に珍しいことなんだ。.NETがバイナリパッケージ形式を使ってるっていうのは、まあ、だから何?って感じだね。

これにはたくさんのメリットがあると思う。Goはプログラミング界のホンダオデッセイミニバンだと呼んでる。特別に何かが得意ってわけじゃないけど、たくさんのことをシンプルで信頼性のある方法でうまくやってくれる。特にReactのフロントエンドに対するバックエンドにはぴったり。でも、書くのは結構面倒で、足を引っ張る要素も多い。特にNullの扱い。どうやっても他の言語より悪化させてしまったみたい。

Nilawayのリンター最高!

Goのヌル処理が他の言語よりも悪い理由って何だろう?

Goは好きだけど、愛せない小さな理由がたくさんある。例えば、enum。JavaやKotlinでenumを使うと、すごく便利なんだ。Stringとの変換も簡単だし、型安全性も…ある。Goでもできるけど、表現したいenumのタイプごとにハックしないといけない。言語にenumがないから、一度に言語を頭に入れておくのは簡単だけど、自分が書いているソフトウェアを頭に入れておくのは難しくなる。「このenum」はあの「enum」と同じ?コードを読まないとわからない。でもGoはたくさんのことが得意だよ。コンパイル時間、静的バイナリ、リソースがそのバイナリに組み込まれてる、実行速度…好きなところがたくさんある。

くだらないジェネリクスの代わりに、列挙型を追加してほしかったな。

メソッド呼び出しの後に明示的なエラーハンドリングをするっていう習慣は、私には全く理解できない。真剣なことには絶対使わないな。

このリストを見ても、C#や.NETよりGoを使う理由が見当たらない。 .NETはほとんどの利点を持ってるし、(今は)他の言語に移行しやすい非同期モデル(async/await)がある。

同意だね。C#にはたくさんの利点があって、書くのもずっと簡単だよ(もちろん、これは人によるけど)。それに、エコシステムもかなり大きいしね。唯一思いつくのは、C#はGoみたいに簡単に単一の実行可能バイナリにコンパイルできないんじゃないかな(Rustでもそうだけど)。

Goが使えるなら、.NETを使いたくない理由はわかるよ。.NETには利点もあるけど、膨れすぎてるし、コンパイルも遅いし、ツールも本当にイライラするんだよね。私にとってはGoがC#よりちょっと上で、どちらもそんなに高い順位にはないかな。

理由は一つだけ、今のGoが期待されるdevopsエコシステムがあるから。何かのプラグインみたいな感じね。それ以外はJava、.NET、TypeScript(C++のアドオンもあり得る)。

AIコーディングを始めてから、コードの90%をGoに切り替えた。ほとんどのことに対して本当に素晴らしい。大規模なUIフレームワークを持つ開発コミュニティは足りないけど、既存のフレームワークは「十分良い」。AIを使って、Windows、Linux、Mac、iOS、Androidで動くAIエージェントを作った。CLI、GUI、ウェブサーバーもある。LOC: 5575。バイナリサイズ: 35MB。あと、なんで今フラグが付いたストーリーに対して保証できないの?この投稿は実際に良いし、面白いし、会話する価値があるよ。

"err != nil"はバグじゃなくて機能なんだよね。これによって、何が間違う可能性があるかをすべて見て、どうするかを決めなきゃいけなくなる。 いや、全然そんなことないよ。コードに似たようなif文が散らばって、唯一違う必要があるやつを見つけるのが難しくなるんだ。最近の人は多分「tab」押して、LLMアシスタントが一発で全部間違ったブロックを埋めてくれるし、他のパターンをコピーするだけだよね。でも、LLMがその問題を作ったわけじゃない。何かをタイプすることが、考える必要があるって意味じゃないし、何千もの「sudo shutdown -r now」コマンドが本番データベースで実行されることもなかったはず。だって「sudo」ってタイプするだけで、誰かが考えることになるわけじゃないから。ただのキーボードメモリーなんだよね。コードをレビューして、他のエラーハンドリングブロックと違うやつを見つける問題は、常に人間のレビュアーには残る。Rustは共通のケースのボイラープレートを一文字「?」に変換して、ほぼ同じように見えるif文の壁ではなく、特別なエラーハンドリングに集中できるようにしてくれる。そしてコンパイラは、関数呼び出しからのResultを無視していることを見つけて、明示的に何かをするように強制してくれる。さらに、モナドを使うのにモノイドやエンドファクター、カテゴリー理論について何も知らなくても大丈夫だし、友達を驚かせることもできるよ。

いや、全然そんなことないよ。コードに似たようなif文が散らばって、唯一違う必要があるやつを見つけるのが難しくなる。これは、コードが取る可能性のあるすべてのパスの静的解析を可能にする。throw/catchでそれをやってみて。多くの業界ガイドライン(misra、jsfなど)が隠れたパスを禁止する理由がある。実際に壊滅的な結果を招いてきたから。多くの現代言語(GoやRustなど)がエラーを明示的に扱うことを望む理由がある。Goのドキュメントでは、エラーを値として扱う理由やその理由について具体的に説明している - https://go.dev/doc/faq#exceptions Rustは共通のケースのボイラープレートを一文字「?」に変換して、特別なエラーハンドリングに集中できるようにしてくれる。 再度、エラーを見ている間違った視点だよ。エラーはコンパイラが解決すべき問題じゃない。エラーはビジネスロジックの一部なんだ。明示的に扱う必要がある。言語の構文の違いがエラーハンドリングのポイントじゃない。エラーは気を散らすものじゃない。コードが取る可能性のある各パスをレビューするのが君の仕事だよ。コードは一度書かれるけど、レビューや監査、参照されるのは何度もある。誰も、君がメタマジックのワンライナーを書くことに感心しないよ。むしろ、君のコードを読んで、どれだけのパスが結果を達成するためにあるのかを一度で理解できる方が感心するよ。すぐに説明を求めるために君に連絡するよりもね。 君はそれらを見ていると目がくらんで、違いがわからなくなる。 最近の人は多分「tab」を押して、LLMアシスタントが一発で全部間違ったブロックを埋めてくれる。 今、誰かがエラーチェックを見て「目がくらむ」なら、それはその関数がやることが多すぎて、失敗ポイントが多すぎるからだよ。もしかしたら、コードの構造をどうするかにもっと集中する必要があるかもしれないね。「tab」を押す回数を減らすために。

そうだね、著者がMicrosoftに抗議するためにGithubからGitlabに移動すると壊れるんだよね。世界中のすべてのGoプロジェクトでGitlabへのコード参照を更新する時間が必要になるか、URLマッピング間のリダイレクトを設定する時間を費やすかだね。ゴミ収集がないことを除けば、Turbo Pascal 7 for MS-DOSの方が言語機能ではずっと現代的で、20 MHzのコンピュータでコンパイル時間も速かったよ。

そうだね、著者がMicrosoftに抗議するためにGithubからGitlabに移動すると壊れるんだよね。 インポートパスがビルドシステムがファイルを探す場所から切り離されている。もちろん、デフォルトはインポートパスをURLとして使うことだけど、強制されるわけじゃない。もしそうするなら、自分が使っているホスティングにロックされることになる。でも、それはGoのせいじゃないよ。ドキュメントはこちら: https://pkg.go.dev/cmd/go#hdr-Remote_import_paths 完全な例: https://github.com/rsc/swtch/blob/master/app/rsc-io/main.go 要するに、これを自分のドメインで提供して、コードのホストプラットフォームを変更する際にリポジトリのルートを更新すればいいだけ。誰も他に何もしなくていいんだ。

こういう投稿を見るたびに、一見合理的に思えるんだけど、実際には典型的なウェブアプリはHTMLテンプレートを提供するだけじゃないことを思い出す。例えば、HTMLにスタイリングやJavaScriptが必要な場合はどうする? 2003年のようにすべて手書きするか、Goで複雑なパイプラインをゼロから作り始めるかだよ。無数の人がこの問題を解決するのに何千時間も費やしてきたけど、君はGoを使ってるから、今は自分でゼロからやらなきゃいけない。もしかしたら、アプリが比較的複雑なデータベースアクセスを必要とするかもしれないし。SQLスパゲッティを書くのも限界があるし、条件付き結合やユニオン、パラメータ化されたサブクエリが必要になることもある。マイグレーションも必要なんてことになったら、Goにはその答えがないし、そういうものを全部手作りするか、必要な機能を持ってるライブラリを探して比較するのに膨大な時間を費やすことになる。機能が豊富すぎる意見のあるフレームワークを使う方が、GoでマイグレーションやCSSの前処理をゼロから再発明するよりずっといいと思うよ。