Go言語: 私たちが正しくやったことと、間違えたこと
(commandcenter.blogspot.com)- Goは2009年11月10日にオープンソースとして公開されてから14年間、新しい言語そのものよりも プロダクションソフトウェアを簡単に作る方法 を目標に発展してきた
- 形式仕様、複数のコンパイラ実装、容易なクロスコンパイル、Go 1.0以降の 互換性保証、標準ライブラリとgofmtが採用を後押しした
- サーバーソフトウェアを構造化する 並行性 は強みだったが、初期に並列性との違いや適した用途を十分に説明できず、混乱を招いた
- インターフェース はライブラリ、テスト、依存関係管理、合成中心設計の基盤となり、ジェネリクス不在はその後10年以上にわたり多相的設計を難しくした
- パッケージ管理、ドキュメント、コミュニティ協業では試行錯誤が大きかったが、Goは作者や時代が違っても似た見た目で、継続的にビルドできる環境を志向している
Goの目標: 言語よりもソフトウェア開発のやり方
- 2023年11月10日は、Goがオープンソースプロジェクトとして公開されて 14周年 となる日だった
- 2009年の公開当時、Ken Thompson、Robert Griesemer、Russ Cox、Ian Taylor、Adam Langley、Jini Kimらがプロジェクト公開を見届けた
- Goの本来の目標は新しいプログラミング言語そのものではなく、より良い方法で 高品質なソフトウェアを書くこと だった
- 当時のボトルネックは言語機能そのものよりも、Googleで現代的なサーバーソフトウェアを作る過程の複雑さにあった
- 依存関係の制御
- 大規模チームと変化する人員構成
- 保守のしやすさ
- 効率的なテスト
- マルチコアCPUとネットワークの活用
- Goは今でも、プロダクションソフトウェアの構築をより簡単かつ生産的にするプロジェクトとして定義されている
Gopherマスコットが作った共同体のアイデンティティ
- Go gopherはGo成功の初期要因のひとつだった
- Renee Frenchが作ったgopherは、Goプログラマーが共有する 象徴 であり、コミュニティの旗印として機能した
- GopherConという名前のカンファレンスのように、gopherは技術的な卓越性と楽しさが同居するプロジェクトの雰囲気を作った
- Creative Commons Attributionライセンスで公開した選択には長所と短所があった
- リミックスや改変を促し、コミュニティ精神を育てた
- attribution条件のために論争が生じたり、Renee Frenchが作っていない作品に誤ってクレジットが付く問題もあった
- マスコットが本来の精神を保つよう管理することは、今なお難しい課題として残っている
Goがうまくやった技術的選択
- 形式仕様 を先に書くことでコンパイラ実装の挙動を固定し、複数の実装が同じ挙動に合意できるようにした
- コンパイラひとつだけでは仕様にはなりえないという判断があった
- 最初の仕様草案はSydney Darling Harbourの18階の建物で書かれた
- 複数のコンパイラ実装 は言語と仕様を磨く助けになった
- Ian Taylorは2008年6月7日にGoの仕様草案を読み、gccフロントエンドを作ったとメールを送った
- その後もさまざまな互換実装が登場した
- クロスコンパイル を容易にしたことで、開発者は好きなプラットフォームで作業し、必要なプラットフォームへ配布できた
- Go 1.0で言語を整理した後に 互換性保証 を導入した選択は、Go採用に大きな影響を与えた
- 強い互換性を維持するコストはあるが、新しいGoリリースでプロジェクトが壊れる心配を減らせる
- 標準ライブラリは21世紀のサーバーコードを書くために必要な機能の大半を提供し、コミュニティが同じツール箱の上で作業できるようにした
- 初期にはGoコードをインストールする別の場所がなかったため、ライブラリが大きくなった側面もあった
- 派生ライブラリの乱立を減らし、コミュニティを統合する助けになった
ツールとgofmtが作った開発体験
- Goはパースしやすい言語として設計されており、ツール作成が容易だった
- 初期にはIDEが必要だと考えられていたが、ツール化が容易だったため、時が経つにつれてIDEがGoをサポートし、goplsも登場した
- コンパイラとともに、自動テスト、カバレッジ、コードvettingのような補助ツールが提供された
- goコマンド はビルド工程全体を統合し、多くのプロジェクトでGoコードのビルドと保守に必要なことの大半をこのコマンドで解決できた
- gofmt はGoだけでなく、プログラミングコミュニティ全体に影響を与えたツールになった
- Robert Griesemerは最初からgofmtを作るべきだと主張していた
- gofmt以前は自動フォーマッタの品質が高くなく、ほとんど使われていなかった
- 空白や改行をめぐる議論に費やす時間が減った
- gofmt内部がライブラリになったことで、ASTをパース・修正し、人間が読める出力として再生成できるようになった
- simplifier、analyzer、コードカバレッジツールのような別のツールも可能になった
並行性: 強力だったが説明が遅れた機能
- 2002年当時、Googleのソフトウェアはスレッドをほとんど避けており、John Ousterhoutの「threads were bad」という立場を引用しながら利用を抑える雰囲気があった
- Goはpthreadのような低レベルパッケージよりもよい方法で並行性を使えるという経験から出発した
- Goはプログラミングの世界が 並行性 を強力な道具として受け入れるうえで重要な役割を果たした
- とくにマルチコアとネットワーク環境で効果が大きかった
- 今日ではほとんどの主要言語が並行性サポートを備えている
- Goはcoroutine、task、threadのような名前を使わず、goroutine という新しい用語を作った
- async/awaitはpthreadより大きな改善だが、goroutine、channel、selectと比べると、実装者にとってはより簡単で小さい一方、複雑さの一部をプログラマーに返してしまうことがある
- Bob Nystromの「What Color is Your Function?」問題が例として挙げられる
- GoのCSPモデルは手続き型言語にうまく適合するが、正しく実装するにはランタイムの複雑さが必要になる
- ひとつの環境に複数の並行性実装があると問題になりうるため、Goはこれをライブラリではなく言語に組み込んだ
並行性で間違えた2つのこと
- Goチームは、並行性をどこで使うべきかを初期に十分案内できなかった
- 主な用途は
net/httpのような中核ライブラリでサーバーソフトウェアをより単純にすることだった - すべてのプログラムのあらゆる場所で使うべき機能ではなかった
- 主な用途は
- 並列性 と並行性の違いを説明するのが遅れたのも問題だった
- 多くの開発者がgoroutineでコードを並列化して高速化しようとしたが、逆に遅くなって戸惑った
- 並行コードが並列化によって速くなるのは、問題自体が並列的な場合、たとえばHTTPリクエスト処理のようなケースに限られる
- これを補うため、2012年のWazaで「Concurrency is not Parallelism」講演が行われた
- この説明はもっと早く行われるべきだった
- それでもGoは、サーバーソフトウェアを構造化する方法として並行性を広く普及させることに貢献した
インターフェースと合成中心設計
- インターフェース は並行性と並んでGoを特徴づける中核的なアイデアである
- Goのインターフェースは、本来の振る舞い中心のオブジェクト指向設計に対するGoなりの答えに近い
- どの型がどのインターフェースを実装するかを事前宣言する必要のない動的な方式は、初期には批判も受けたが、Goスタイルのプログラミングに重要だった
- 標準ライブラリ、テスト、依存関係管理は、インターフェースの開放的な性質に大きく依存している
- Goの
sortはインターフェース上で動作する関数として実装されている- 多くの人が慣れていたオブジェクト指向スタイルとは異なるが、強力なアイデアだった
Reader、Writer、empty interfaceのような小さなインターフェースはGoライブラリの基盤になった- この有名な3つのインターフェースは平均してそれぞれ2/3個のメソッドを持つ
- 小さなメソッド集合はGoらしいイディオムになった
ジェネリクスをめぐる長い迂回
- Goチームは、ジェネリックプログラミングが型をアルゴリズムより先に考えさせる傾向を見てきた
- 初期抽象化よりも有機的な設計を好んだ
- コンテナより関数を重視した
- Goはmap、slice、array、channelのような ジェネリックコンテナ を言語に含めていたが、そのジェネリック性をユーザーに直接は提供しなかった
- 単純なプログラミング作業の大半にはこれらの型で十分だと判断していたが、そうでない場合もあり、この境界が一部のユーザーには不便だった
- インターフェースはGoプログラミングの基盤だったため、新しい多相モデルは必ずインターフェースとうまく適合する必要があった
- 複数の試みと中断された実装があった
- 多くの議論の末、Phil Wadler率いる型理論家たちの助けも得た
- 最終的にGoは、インターフェースを「メソッド集合」から「型集合」へ一般化する形でジェネリクスを設計した
- 「generics」という用語はデータ構造中心の多相性から来た言葉で、Goが提供する parametric polymorphism を正確に指してはいないが、実際にはgenericsという言葉が使われるようになった
コンパイラ: 最初はC、その後はGo
- 初期のGoコンパイラがCで書かれていたことは、言語コミュニティの一部を不快にさせた
- LLVMのようなツールキットを使うべきだ、あるいはGo自身でself-hostingすべきだという意見があった
- 最初にコンパイラをCで書いた理由は実用的だった
- 新しい言語をブートストラップするには既存の言語が必要だった
- Ken ThompsonはすでにCコンパイラを書いており、その内部構造をGoコンパイラの基盤として使えた
- 言語を設計しながら同時にその言語でコンパイラを書くと、コンパイラ作成に都合のよい言語へ偏るおそれがあった
- 初期コンパイラは生成コードが平凡で内部もきれいではなかったが、小さく、なじみがあり、素早く変更しやすかった
- 自動的に拡張するsegmented stackの追加は、この選択の利点を示した
- LLVMのようなツールキットを使っていたら、ABIやガベージコレクタ支援の変更まで含める必要があり、統合は難しかったはずだ
- もともとPlan 9コンパイラ群の方式に由来する構造は、クロスコンパイルにもよく適合していた
- Go 1.5でRuss Coxは、コンパイラをCからGoへ半自動翻訳するツールを書いた
- この時点では言語が完成しており、コンパイラ中心の言語設計への懸念はなくなっていた
- Goへ移した後は、テスト、ツール、自動書き換え、性能分析といったGoの利点をコンパイラ開発にも適用できるようになった
- 現在はLLVMベースのGoコンパイラや、ほかの複数のコンパイラも存在する
オープンソースプロジェクト運営とコミュニティ
- Goは成功するにはオープンソースプロジェクトでなければならないと、最初から判断していた
- ただし中核アイデアと動作実装を整理するまでは、非公開で開発するほうが生産的だと考えた
- 公開前の最初の2年間は目標を明確にするうえで重要だった
- オープンソースへ移行した後、コミュニティからの入力は圧倒的で、とくにIan Taylorは質問に答えるのに多くの時間を費やした
- WindowsポートはAlex Brainmanの案内のもと、コミュニティが全面的に実施した
- Goチームはコミュニティと協業する最良の方法を理解するのに長い時間がかかった
- うまく対話できていると思っていても、誤解や期待の食い違いで時間が無駄になることがあった
- Goプロジェクトはコードを素早く受け入れて後で整える方式ではなく、マージ前に 高いコード品質 を確保する方式を選んだ
- 必須のコードレビュー
- 細部への徹底した注意
- この方式はコミュニティにより大きな負担をかけるため、参加者がその価値を理解しなければ歓迎されていないと感じることがある
- Goプロジェクトのリポジトリ履歴はSVN、Perforce、Mercurial、Gitを経てきたが、Russ Coxが履歴を維持したため、現在のGitリポジトリにも初期SVNの変更履歴が残っている
- GoogleはGoを支援しているが、Goチームのアジェンダを決めているわけではない
- Google内部の大規模なGoコードベースは、リリースのテストと検証に使われている
- 流れは公開リポジトリからGoogleへ取り込む形であり、その逆ではない
- 中核のGoチームはGoogleから給与を受け取っているが、独立して動いている
パッケージ管理での試行錯誤
- Go言語のpackage設計そのものは優れていたが、パッケージ管理の開発過程はうまく進まなかった
import文でパスを普通の文字列として指定したのは、重要かつ柔軟な選択だった- 問題は、標準ライブラリしかなかった状態から、Web上のコードを取得する方式へ移る移行が険しかったことだった
- 初期のGoチームはGoogleのモノレポとheadでビルドする方式に慣れていた
- 複数バージョンのパッケージを持つパッケージマネージャの使用経験が十分ではなかった
- 依存関係グラフを解決する技術的複雑さに最初から対処できなかった
- deps.dev の取り組みは、この問題への一種の埋め合わせのように扱われている
- コミュニティとともに依存関係管理の問題を解こうとした試みは善意によるものだったが、最終設計が出たとき、多くのコミュニティ参加者は疎外感を覚えた
- この経験は、コミュニティ参加が実際にはどう行われるべきかについての教訓となり、その後大きく改善された
- 現在では設計は安定しており、技術的に優秀で、大半のユーザーにうまく機能しているように見える
ドキュメントと実行可能な例
- 初期ドキュメントは多く書かれたが、コミュニティが望む水準とは異なっていた
- 最も不足していたのは、最も単純な関数にさえ 使用例 が必要だという点だった
- 何をするかを語るより、どう使うかを示すほうが価値があった
- 現在のドキュメントには多くの例があり、その大半はオープンソース貢献者によって提供されている
- Goは初期から、例をWeb上で実行可能にした
- 2012年のGoogle I/O発表では、並行性を示すコード片をブラウザで実行できるようにした
- Andrew Gerrandがそれを可能にするWeb技術を書いた
- その後ブログやオンラインのパッケージドキュメントにも展開された
- The Go playground は、人々が自由にコードを試し開発できる公開サンドボックスとして提供されている
14年後、Goが到達した位置
- Goは、言語設計と開発過程で下した決定のおかげで、単なるプログラミング言語を超えて、ソフトウェアを書く方法として定着した
- Goが到達した位置には、次の要素が貢献している
- サーバーコードに必要な基本機能の大半を実装した強力な標準ライブラリ
- 言語の第一級構成要素である並行性
- 継承より 合成 に基づくアプローチ
- 依存関係管理を明確にするパッケージングモデル
- 統合された高速なビルドおよびテストツール
- 厳格で一貫したフォーマット
- 巧妙さより可読性を重視する姿勢
- 互換性保証
- Goコミュニティは多様で積極的なGopherたちで構成されており、プロジェクト成功の中核的役割を果たしている
- 結果として、Goコードは誰が書いても似た見た目と振る舞いを持ち、言語の異なる部分集合を使う派閥が比較的少なく、時間が経っても継続してコンパイル・実行できることが保証されている
- こうした特性は、主要プログラミング言語の中では珍しい成果と見なせる
1件のコメント
Hacker News のコメント
中心人物たちが時間を取って振り返りを残してくれるのは本当にありがたいことで、いま本気で変化を起こしたい人たちにとって大きな助けになる。
ただ、Rob Pike は Go がうまくやった点を十分に明確には語っていないように思う。Go は言語そのものだけでなく、プロジェクトに作用する力もうまく管理していた。対象用途をシステムプログラミングに限定し、言語と原則を明確に定義し、リリース前に問題を直す品質優先の姿勢を保ち、言語・リリース・中核メッセージは厳しく統制しながらも、下位領域ではコミュニティが主導する余地を与えていた。
過小評価されている点は、Google 自身が大きく干渉しなかったことだ。Go が Google の目標に実際に貢献しており重要だったからかもしれない。今日の新しいプロジェクトでもそうできるかは疑問だ。
Dart は Flutter の外ではほとんど採用されていないが、アプリケーションコードがシステムコードよりはるかに多いという現実と比べると興味深い。Go はサーバー側ソフトウェアを Java の肥大化した環境からネイティブコンテナへ移す中核技術であり、この10年の Web アプリケーションインフラの大部分の基盤になった。その成長の中でもチームは小さく保たれ、中核人物たちも残っていたが、今後もそうかは分からない。
そのため設計上、システムプログラミング言語には向いていない。初期に自らそう呼んでいたせいで奇妙な神話が長く残ったが、その文言は数年前に削除されたようだ。
Go は C や Rust の競合というより Java 系の競合に近く、実際の使用領域を見ればそれが分かる。ただし限定的な型システムと優れた並行処理サポートのおかげで、スタックの少し低い層に置かれることが多い。
特にバージョン管理がそうだった。最初は Go が問題そのものを認めず、コミュニティの合意が大きくなると、それまでの議論を忘れて、これからは modules で行くと決めた。
Cloudflare が夏時間の障害を経験するまで、単調増加時刻(monotonic time)を公開 API にしようとしなかったことも覚えている。
Java の世界では、新機能、たとえば優れた streams のようなものを使ってコードベースをリファクタリングする楽しさがある。Go では、最も単純で平凡な抽象化を見つけるところに楽しさがある。
どの言語にも、特定領域に合ったライブラリとフレームワークが必要だ。Flutter は Dart でクライアントアプリを書くためのフレームワークであり、Flutter がなければ、Dart 言語をどれほど気に入っていても、Android と iOS で画面にピクセルを描く方法から膨大な時間を費やさなければならない。
アプリケーションを書く人は誰でも、ライブラリとフレームワークのスタックの上に構築する。Go と Flutter 付きの Dart の違いは、サーバーに必要なネットワーキング・シリアライズ・暗号化のような領域ライブラリが、Go では標準ライブラリに入っている程度のことだ。
Dart はコレクションや非同期のような基本要素を組み込みライブラリとして持っているが、領域別の機能は Flutter のような外部パッケージに依存する。これは Dart がかなり初期から堅実なパッケージ管理方式を持っていたことが大きい。Dart チームが作成・保守する多くの中核ライブラリも、組み込みではなくパッケージマネージャーで配布されており、その方がはるかに発展させやすい。
UI フレームワークは言語より寿命が短い傾向にあるので、Flutter が Dart 標準ライブラリに埋め込まれていない方がよいと思う。Flutter は素晴らしいが、20年後にもっと良い何かが出てきても驚かないし、そのとき Flutter が組み込みでなければ Dart ユーザーはより簡単に乗り換えられる。
Android のような偽の FOSS は戦略的に容認するが、それ以外は広告クリックにつながらなければ役に立たないと見る雰囲気だ。
2012年に Python 開発者として Modbus 用のビット操作をする必要があり、Go に手を出した。そのコードはデプロイしなかったが、ビットやバイトを扱う作業があまりにも簡単にそのまま動いて衝撃を受けた。
10年が経ち、ほぼ専業に近い Go の仕事を何度か経験した後でも、ほとんどがそのまま動くという点には今でも驚かされる。
Go 言語と Go コミュニティが好きだ。Rob、Ian、Russ など Go を作っている人たちの仕事に感謝しているし、この発表・文章がコミュニティとの「荒れた時期」を率直に扱っている点もありがたく思う。
ただ、その時期を経験した立場からすると、特にパッケージ管理の混乱において、自分たちがどう振る舞っているのか本当に分かっていなかったのだと信じるのは非常に難しい。それでも今は良い解決策にたどり着いたという本文の評価も正しい。
これから10年の Go と、Zig・Deno のような影響を受けた似た言語たちが、健全なコミュニティとして成長し続けることを願っている。
プログラマーというより計算生物学者なので Go の使用はまちまちだが、Go に戻ってくると昔のコードがコンパイルされ、言語が期待どおりに動く。
同時に、Rob Pike がきらびやかな機能をすべて受け入れなかった一方で、コミュニティ参加を学ぶ過程での失敗を認めた点は好意的に見ている。
いくつかはすぐ動き、いくつかは AI モデルが API 情報を少し古い状態で把握していたため、多少手を入れる必要があった。
それでもプログラムをサーバーに
scpでコピーすればそのまま動くという点は、Python で毎回「今回の流行りの venv システムは何だっけ」という踊りをしていたのと比べると驚きだ。いい記事ではあるが、かなり自己称賛的にも見える。言語のより深い問題についての認識があるのかと思ったが、おそらく要点は、彼らにとっては言語が完璧で、問題は私にある、ということらしい。
だから Go は勧めにくい。型システム、エラー処理、安全でない並行処理、単純すぎる構文、
nil、デフォルトのゼロ値、放置された主流パッケージの多さといった問題がある。今は Rust を主言語として使っている。エコシステムは活発で、Go には欠けている多くの面でビジョンがある。
もっと辛辣に言えば、Go には PHP とバックエンド言語として競っていた時代には、それなりの時代性があったのだと思う。
最初の段落は「嫌い」、2つ目は皮肉、3つ目も「嫌い」、4つ目は「Rust のほうが良い」、5つ目はまた皮肉に見える。
deps.devをなくした後で諦めた。数年後、会社で仕事を終わらせるためにチームへ Go を導入しようとしたが、別のエンジニアが時間をかけて言語を調べ、なぜ良くないのか理由を列挙し、彼が正しかった。
要点は、単純ではあるものの、エラー処理や未使用 imports のような些細なことが利用を苦痛にする、という点だった。個人的にはエラー処理は好きだったが、型システムは嫌いだった。
以前 OCaml を使っていたので、try/catch や
nilよりもOptionとResult型に価値があることは知っていたが、簡単だったので Go を使っていた。しかしその簡単さには長期的な保守コストが伴う。最初からきちんと合わせておき、後で困難に直面しないほうがいい。軽く言うつもりではないが、Go は過去50年間のプログラミング言語の進歩を持ってきて捨てた言語だ、という説明をよく聞いた。
悪い意味ではない。Rob Pike は並行処理が安全でないとか、
nilが10億ドルの失敗だという点を気にしていないし、Go ユーザーの大半もそうだ。だからといって Go が良い言語になるのかというと、そうではない。だがそこが要点ではない。Go は、使いやすくする説得力のあるツール群と組み合わさった、便利で十分にまともな言語だ。プログラミング言語界の Crocs のような存在だ。
高い完全性と安全性が必要なら Ada や、形式検証を付けた Ada/SPARK を使う。それ以外では Go のほうが Rust よりはるかに高い生産性をもたらす。
個人的には Rust は C++ と同じく、過剰設計の代表例だと思う。
恨みがましく聞こえるだろうが、10年前に
go-nutsで嘲笑され、Rob Pike からも見下した返答を受けた。go getとネットワーク越しのモジュール import の方式が機能し、初心者向けドキュメント全体で推奨され、コミュニティ全体でそのように使われるやり方は、最終的に有害で近視眼的だと、あえて言ったからだ。奇妙なグローバル名前空間ツリーはその中では筋が通るし、チェックインされたコード生成物を重視するのも、Google がモノレポにビルド成果物まで含めることを考えれば理解できる。
もちろん
v2問題や永遠のv0問題はある。それでも npm を使ったり、思わず十字を切りたくなる Python の何かを扱ったりするよりはましだ。この記事は ACM に載った記事 [1] の、より個人的な版のように見える。どちらも、言語仕様の観点で優れた新しいプログラミング言語を作ったのではなく、プログラミング言語の周辺にあるものを非常にうまく構築し、それがむしろより重要かもしれないことを認めている。
投稿された記事では、インターフェースの利用アプローチと並行処理のアプローチを発明したと言っている。だが goroutine は Haskell thread と同じで、インターフェースは今のようにジェネリック引数をサポートすると Haskell typeclass と非常によく似ている。Haskell が Go に先行していたことを考えると、手続き型プログラマーが関数型プログラミングのアイデアの力を独自に発見していく様子は興味深い。
Go の言語上の革新の一つは、インターフェース実装が、自分が実装するインターフェースを宣言しなくてよい点だ。安全性の観点ではひどいが、実際にはほとんど問題にならず、Haskell や Rust で経験するひどい循環依存の問題を取り除いてくれる。
[1] https://cacm.acm.org/magazines/2022/5/260357-the-go-programm...
型が、自分が実装するインターフェースを事前に宣言しなければならなかったなら、Go はまったく別の言語になっていただろう。これは Go の主要な差別化要因の一つだった。少なくとも TypeScript が別の理由で構造的型のインターフェースを持ち出すまではそうだった。
インターフェースを定義すると、そのインターフェースの実装を必ず提供するよう強制する、我々が気にするに値するプログラミング言語を一つでも挙げられるだろうか。
Go の暗号化関連の問題は、まだあまり言及されていないように思う。OpenSSH が SHA1 を廃止した後、Go チームが
x/crypto/sshに SHA2 サポートを追加するまでに 1 年もかかった [1]。Gitea は有名な被害者だった [2]さらに、暗号化のメンテナーが GnuPG を攻撃し [3]、Dan Bernstein の信頼を損なおうとしているように見えること [4] は、信頼感を与えない
[1]: https://github.com/golang/go/issues/49269
[2]: https://github.com/go-gitea/gitea/issues/17798
[3]: https://twitter.com/FiloSottile/status/1127643698676797441
[4]: https://twitter.com/FiloSottile/status/1555669786826244096
x/パッケージはかなり放置されがちで、新しいコントリビューターがパッチをうまく送るのを難しくする開発モデルに従っているのが問題だ一方で、標準ライブラリの crypto パッケージは Go の大きな成功の一つだと思う。OpenSSL は長い間ひどい状態で、Go、とりわけ agl は非常に広く使われる代替実装を作って配布し、平均的な品質も高かった。特に公開 API におけるフットガンがはるかに少ないという観点ではそうだ
この記事では
gofmtを「うまくやったこと」として挙げているが、これは特に強調に値する多くの言語設計者や支持者には些細に見えるかもしれないが、Go プロジェクトの初日から、ありがちな自転車置き場論争を一つ完全になくしているので、非常に大きな価値がある
より新しい言語のいくつかが、これを意図的にまねたり、Go にもあるのだから自分たちも作るべきだと考えて取り入れたりするのを見てきた
かなり奇妙なフォーマット規則も見たことはあるが、慣れられない規則はほとんどなかった。逆に、強制フォーマット規則のないコードベースでは、その結果としてコードを理解しにくくなっていたことが多かった
go fmtがそれをほとんど無意味にしてくれるのでありがたい「21世紀のサーバーコードを書くのに必要な大半を備えた、堅実でよく作られたライブラリの存在は大きな資産だった」という意見に同意する
GoはGoogleに問題があったため、Googleが資金を出した。サーバーサイドのアプリケーションコードを大量に書く必要があり、Pythonは遅すぎ、C++は脆すぎた。Goはその隙間に非常によくはまった
大きなボーナスは、Googleの人々がそうした作業に必要なライブラリを書き、社内でも使っていたことだった。だからライブラリを使えば、エラーパスまで含めて実際にかなり踏まれたコードだった
言語について技術的な異論はある。特に初期にあらゆるものにキューを使おうとしていた強調、さらにはキューでミューテックスを作っていた点がそうだ。しかしユースケースに合ったgreen threadは正しく捉えていた。関数の色問題は現実の問題であり、Rustで非同期とスレッドを一緒に使わせる難解な小技はやり過ぎだ。Goは多少の性能を諦め、大きな単純さを得ている
昔のスレッド嫌いも興味深い。UNIVACメインフレームから始めたが、1967年からスレッドがあり、当時は「activities」と呼んでいた。1972年にはユーザー空間スレッドがあっただけでなく、対称型マルチプロセッサ上で動いていた。非同期入出力とユーザー空間の完了関数、ロックのための組み込み命令、内部的にスレッド化されたオペレーティングシステムもあった
スレッドとマルチプロセッサは普通のものだと見なしていたので、UNIXへ移ったことでそれらを失ったように感じた。UNIX/Linuxがこの領域に追いつくまで数十年かかり、何世代ものプログラマーは共有メモリ・マルチプロセッサの使い方を知らなかった
Dijkstraの初期の並行性理論は、別の用語と、しばしばより悪い設計で再発明された。Goの人々はDijkstraのbounded bufferと、長さ0および1のbounded bufferがなぜ有用なのかを理解していた。それを再び見るのは良かった。正しいプリミティブがあれば、並行性はそれほど難しくない。古典的なpthreads的発想のように、あちこちにロックをばらまいて解決しようとすると、うまく動かない。UNIX/Linux初期にCPUスケジューラが本当にひどく、スレッドのブロック解除がひどい挙動をしていたことも助けにならなかった
GoはRob Pikeに問題があったために作られた。彼はC++、Java、Pythonが好きではなく、Rob Pikeだったので、Googleは数年間、新しい言語に取り組むことを許した。LarryやSergey、Sundar、EricがRobのところへ何かをしてくれと頼みに行ったわけではない
要するにGoは、Rob Pikeが支援を求め、GoogleがRob Pikeを引き留めたいと思い、彼がほぼ確実に何か興味深いことをするだろうと見ていたために資金が出たものだ。Google Engineeringにおける会社のお金とは、実質的には人々が働けるように許可することに近い
Rob Pikeが優れたエンジニアだったため、Goはいくつかの面でかなり良い出来になり、Googleの運用アプリケーションを書くにはC++、Python、Javaを使わなければならなかったという彼の当初の問題によく合っていた。Google社内の一部の人々も同意してGoを使い始め、その中の影響力のある人々が、Pythonの代わりにGoを使うよう他の人々を「奨励」した
2009年のGoogleでは、YouTube買収の事例を除けば、Pythonは社内用ユーティリティ数個以外のサーバーにはほとんど使われていなかったので、その考えはすぐに捨ててよい。Googleが自前で作った最大のPythonサーバーはGuido van Rossumが書いたコードレビュー工具Mondrianで、後にJavaで書かれたCritiqueに置き換えられた
当時のGoogleには、プログラミング言語争いをなくすため、かなり厳格な三言語ポリシーがあった。C++、Java、Pythonを使うことができ、Pythonはスクリプト、C++はインフラ、JavaはWebサーバーに使われていた
重なる部分もあった。JavaはC++より遅く導入され、一部のWebサーバーは書き直しが難しくC++のままだった。Javaへ段階的に移植しようとする試みもあったが、大きく定着はしなかった。Megastoreのような一部のインフラサーバーはJavaで書かれ、コアライブラリはおおむねC++にJNIとPythonバインディングが付いたものだった
ポリシーの抜け道もあり、一部のチームは社内インフラ用の独自言語を作っていたし、実際のコードベースにはカスタム設定言語やRob Pikeが作ったSawzallも使われていた。構文はGoに少し似ていた
全体として、人々には選択の自由がかなりあり、新しいWebサーバーは主にJavaで、新しいデータベースエンジンのようなものは主にC++で書かれていた
非同期コードを書くのは確かに大変で、Goがその部分をきちんと解決したのは事実だ。しかしGoはGoogleの問題を解決するために上級経営陣が始めたプロジェクトではなかった。経営陣がそうしたプロジェクトを恐れていたからではなく、開発者の苦痛を直接解決するため、関連チームが作って人員を割り当てた社内インフラプロジェクトは非常に多かった。当時進められた大規模なネットワーキングアップグレードなどがその例だ。そうしたプロジェクトは事前に告知されていた
その状態について大きな不満があった記憶はない。ビルド時間はBazel/Blazeがリモートビルドクラスタと連携して動くようになるまでは問題だったが、その後はすべてがリモートキャッシュされるため、巨大なコードベースでも数秒でコンパイルできた。ローカルコンパイラの速度はほとんど重要ではなくなり、
javacも非常に速かったGoの発表はかなり驚きだった。本当にGoogleの問題を解決するために開発されたのなら、まず社内でリリースし、社内ユーザーと数年かけて発展させてから公開するのが、通常のGoogle流だったはずだ。しかしGoは公開が先だった。周囲の人々はこれが何のためのものなのか不思議に思っていたが、もし本当に社内需要に主導されていたなら、期待しにくい反応だ
Ken ThompsonのCコンパイラをLLVMではなく使うことにした判断に関する部分が興味深い。人々は不満を言っており、特に初期バージョンでは生成コードの最適化が劣る結果にもなっていた。
その見返りとして、分割スタックを素早く作ることができた。LLVMで実装し、LLVM ABIに合わせなければならなかったなら、そもそも実現できなかったかもしれない。
記事では、この判断による唯一の利点ではなく、利点の一例として挙げている。
X86FrameLoweringにはすでにサポートがあり、Goがリリースされた当時にも存在していた。スタック分割を有効にすると、LLVMは関数プロローグにチェックを出力し、必要なら
__morestackを呼び出してスタックを追加で割り当てる。Windows MSVC ABIも_chkstkサポートのために非常によく似たコードを必要とするので、__morestackサポートとは自然にかみ合う。むしろGDBに分割スタックを理解させる作業のほうが、コンパイラ実装のどの部分よりも難しかった。これはバックエンドとは無関係である。
おそらく筆者が混同しているのは、再配置可能なスタックのことだろう。当時は精密ガベージコレクションが必要だったため実装が難しかったが、今ではAzulがLLVMに実装している。当時、精密ガベージコレクションを実装する最も簡単な方法は、関数呼び出しをまたいで全レジスタをspillすることだった。これは実装の手間は増えるが、過度なものではなかった。Plan 9コンパイラはいずれにせよそうしているようなので、6g/8gに対する性能退行にもならなかったはずだ。いずれにしても、Azulのガベージコレクションサポートは今ではルートをレジスタに保存できる、きちんとした実装を備えている。
だから実験できる能力に価値があったのは確かだが、この具体例が完璧な事例というわけではない。
[1]: https://go.dev/doc/go1.3#:~:text=Go%201.3%20has%20changed%20...
静的型付けの趣味言語を構想しながら、インターフェースとジェネリクスを一緒に扱う何かを試しているのだが、Robの言うことが正しいと身をもって感じた。両者をうまくかみ合わせるのは本当に難しい。
それでも、やる価値はあると思う。個人的には、ユーザーが自分でジェネリック型を定義できない静的型付け言語を使うのは、あまり報われない気がする。
以前、BASICでサブルーチン用の
GOSUBはあったが、引数を渡すサブルーチンを書く方法がない環境でプログラミングしていた。そうした経験を型システムのレベルで繰り返したくはない。だからGoチームが良い設計を見つけるのに時間がかかったことは理解できる。良い言語を設計するのは難しく、型システムのある良い言語を設計するのはその10倍難しく、ジェネリクスのある型システムを設計するのはさらにその10倍難しい。
Goはジェネリクス実装の中で最も簡単な仕事を引き受けたようなものだ。やらなかった理由は技術的なものではなかった。イデオロギー的なもので、純粋に無知に基づいており、Go設計者の大半が90年代後半以降、プログラミング言語理論の分野をもはや追いかけていなかったからだ。