Golangを使い続けるために私たちが自分自身につく嘘(2022)
(fasterthanli.me)- Go批判で繰り返される「大企業も使っている」「良い点もある」「正確性にはコストがかかる」という反論は、言語設計の欠陥が長期コストとして蓄積する問題を見えにくくする可能性がある
- Goは非同期ランタイム、GC、パッケージ管理・リファクタリング・クロスコンパイルのツールが強力だが、カスタムツールチェーンやビルドシステム、呼び出し規約、単一GC、エコシステムまで含めて受け入れる必要がある
- sum typeの不在、可変性制御の弱さ、zero value、
nilslice・map・channelの異なる挙動により、コンパイラは多くの不変条件を守ってくれない - cgoなしでは他言語・他ツールとの統合が難しく、cgoを使うとGCやポインタ規則、ランタイム同梱、大きな静的ライブラリの負担が生じるため、ネットワーク境界が最も現実的な統合ポイントになる
- 「プロトタイプだけGoで作り、あとで書き直せばよい」という期待は、実際には書き直しの回避、既存Goコードとの結合、リンタ・ドキュメント・手動検証の負担によって運用コストを増やしかねない
Go批判で繰り返される防御ロジック
- 2020年の記事 I want off Mr Golang’s Wild Ride 以降、Go批判をめぐる反応は繰り返し現れている
- Windowsの事例はGoが得意な領域ではない、という反応
- Goの良い点を語っていない、という反応
- Goが選んだトレードオフを理解していない、という反応
- 大企業がGoを使っているのだから、そこまで悪いはずがない、という反応
- 問題を「正しく」モデル化するコストは高いので、正確性の議論は無意味だ、という反応
- Rustにも欠点があるのだから、Go批判は無効だ、という反応
- 核心はGoに長所があるかどうかではなく、Goを使い続けるために繰り返される防御ロジックが実際のコストを覆い隠していないかにある
批判者の資格を先に攻撃するやり方
- 聞きたくないフィードバックが出てきたとき、発言者が無能だとか隠れた動機があると見なせば、内容を検討しなくて済む
- 問題を見つけるのに必ずしも高度な熟練が必要なわけではなく、シニア開発者は慣れてしまった不便さをかえって見過ごすことがある
- ジュニア開発者はまだ奇妙さに慣れていないため、不便さをより早く感じて質問できる
- フィードバックの価値は誰が言ったかではなく、実際に問題があるかどうかで検討されるべきである
「他社も使っている」を根拠にすることの限界
- 技術選定で他社が何を使っているかを見ることは発見には有用だが、採用の根拠としては十分ではない
- 企業の技術ブログは通常、採用コストを完全には明らかにしにくい
- すでに決定が下った後に書かれることが多い
- 企業イメージを良く見せ、採用に役立てる目的がある
- 強い技術批判は企業より個人から出てくることが多い
- TailscaleのGo利用は、Goの専門家チームがコストを理解し、引き受けられる事例に近い
- netaddr.IP: a new IP address type for Go は、IPv4またはIPv6アドレスを表現する過程でGoの制約を露呈している
- Hey linker, can you spare a meg? は、Goリンカに関する深い知識を必要とする問題を扱っている
netaddr.IPで表面化した苦労の原因は、Goの複数の制約が絡み合っていることにある- Goにはsum typeがなく、「IPv4またはIPv6」という型を表現しにくい
- sliceは64ビットマシンで24バイトのコストを持つ汎用データ構造として使われる
- 演算子オーバーロードがないため、
a == bとa.equals(b)のような区別が生まれる - 不変データを支援しないため、コピーを渡し、内部変更に注意しなければならない
- 透過しない
newtypeを作りにくく、別パッケージやインターフェースによる回避が必要になる
- TailscaleのようなチームはGo内部の挙動や境界ケースを深く扱えるが、すべての組織がGoogleやTailscaleのようにGo専門家とそのためのエンジニアリングコストを持っているわけではない
Goが実際に得意な部分
- Goはかなり優れた非同期ランタイム、強い意見を持つデフォルト、2つの調整ノブを持つ現代的なGC、優れたツールチェーンを提供している
- 非同期ランタイムが言語の中心にあるため、Rust開発者がうらやむようなツールもある
- 実行中のすべての goroutine のバックトレースを簡単にダンプできる
- deadlock 検出が可能である
- 独自の profiler がある
- function coloring をあまり意識せずに済む
- パッケージ管理、リファクタリング、クロスコンパイルのツールは、Cエコシステムの
pkg-config、autotools、CMake と比べると、最初は改善のように感じられる - ただし、Goの便利なランタイムだけを選んで使うわけではない
- カスタムツールチェーン
- ビルドシステム
- 呼び出し規約
- 単一GC
- 同梱された標準コンポーネントとエコシステム
- 偶然形作られたような言語設計まで含めて採用することになる
型システムと言語設計の隙間
- GoはTCP、HTTP、TLS、HTTP/2、DNS、Webサービス実装のためのランタイムを中心に据え、Java/C/C++/Pythonを学んだGoogleの開発者に馴染みやすい形を取っている
- Cのようにエラー処理自体を大きく扱わず、多くの可変状態を開発者が
ifやelseで直接管理しなければならない - Javaのように値と参照の区別を曖昧にし、呼び出し箇所だけを見ても値が変更されるか分かりにくい
- メソッド receiver が
Aか*Aかによって、mainのローカル変数aが変更されることもあれば、されないこともある
- メソッド receiver が
- 可変性と不変性を型で明確に制限しにくいため、参照を渡すと内部の値が変わったり、長く保持されて解放されなかったりする危険がある
- Goは次のようなエラー群を十分には防げない
- mutex を誤ってコピーして無効化してしまう
- struct フィールドを初期化せず zero value が入る
- 誤った
nil、zero、empty string がシステムの深部まで伝播する
zero value と「気をつければよい」の限界
- struct に新しいフィールドを追加して実質的に関数入力の形が変わっても、composite literal で新フィールドを書き忘れてもコンパイラは防いでくれない
- 例では
Paramsにb int32を追加したが、呼び出し側がa: 47だけを渡すとb=0で実行される - 逆に関数引数を直接追加した場合は、引数不足でコンパイルエラーになる
- 例では
- Goの原則の1つはzero valueが有用であることだが、実際の意味は型ごとに異なる
nilslice にlenを呼ぶと 0 になり、空の slice のように振る舞うnilmap に値を入れるとpanic: assignment to entry in nil mapが発生する
- channel にも別の規則がある
nilchannel に send すると永久に block するnilchannel から receive すると永久に block する- 閉じた channel に send すると panic が発生する
- 閉じた channel から receive すると zero value が即座に返る
- Goにはsum typeがないため、「既知の選択肢のうち1つだけを許可する」入力を型で表現しにくく、Cのように「とにかく気をつけろ」というやり方に近い
- すべての問題を防げないのだから一部の問題も防ぐ必要がない、という態度は誤った二分法であり、型システムが助けられる問題まで手動の注意に委ねることになる
Rustとの比較は「完全な代替品」論争ではない
- Rustが完璧だという立場ではなく、GoとRustは異なる成功パターンの例と見なせる
- Goの成功は、同梱されたコンポーネントと意見あるデフォルトに大きく依存している
- Rustの成功は、段階的導入と他言語との統合のしやすさに大きく依存している
- 実際のRust導入事例は、全面置き換えというより段階的移植に近い
- Firefoxは大規模なC++コードベースの中に複数の重要なRustコンポーネントを含んでいる
- AndroidはBluetooth stackをRustで再実装している
- Rustの暗号化コードはPythonに入り、RustのHTTPコードはcurlの複数バックエンドの1つとして組み込まれている
- Linux kernel の Rust patch は繰り返し改善されている
- Rustユーザーもビルド時間、言語機能の不足、その他の欠点を積極的に語っている
- 論点はGoとRustのどちらかを無条件に選ぶことではなく、可変性・ライフタイム・抽象化を型で表現できる道具がどんな問題を減らせるかにある
- Rustでもすべてのケースを厳密にモデル化するとは限らない
Goは他のエコシステムから離れた島に近い
- cgoを使わなければ、Goは一般的なアセンブリ、リンカ、デバッガ、メモリ検査器、呼び出し規約から遠い
- GoはC/C++より closed-world 言語に近く、Node.js・Python・RubyよりFFIに不向きである
- この違いには利点もある
- TLSやHTTP stack の内部を、ビジネスロジックと同じように profiler で見られる
- 動的言語で stack trace が OpenSSL で止まるのとは対照的である
- ランタイムが non-blocking I/O と scheduling を処理できる
- しかし、多くの既存ツールや制度的知識をそのまま使いにくく、Goと他言語を統合するのも厄介である
- Goの統合方法にはそれぞれコストが伴う
- GoからCを呼ぶと、FFI境界のコストに加えてGCを壊さないための手動の descriptor 追跡が必要になる
- 他言語からGoを呼ぶと、GCを含むGo runtime 全体を一緒に組み込む必要があり、大きな static library と運用負担が生じる
- Goと接する最適な境界はネットワーク境界に近い
- REST風のHTTP/1、JSON-RPC、gRPC のような RPC over TCP は、レイテンシコストを許容できるなら比較的まだ苦痛が少ない
- それでも両側で不変条件を維持する必要があり、Go側では検証パッケージを継続的に使わない限り、コンパイラは保証してくれない
プロトタイプ用Goという期待の落とし穴
- Goは学びやすく採用もしやすいため、まずプロトタイプを作り、後で難しくなったら書き直すか専門家を連れてくればよい、という発想がある
- しかし「捨てるコード」は現実にはほとんど存在しない
- 書き直しには時間がかかる
- 円滑な移行の調整が難しい
- 細部が失われる可能性がある
- 新機能開発が止まる
- 人材を新技術に合わせて再訓練しなければならない
- Goコンポーネントが増えるほど、既存のGoコードと相互作用するためにGoを選び続ける理由も増えていく
- リンタは多少は役に立つが、そもそも問題を真剣に扱った言語のコンパイラほどのことはできず、開発速度も落とす
- 言語にない複雑さはコードベースに移される
- 型で表現されていない不変条件はコードで直接書かなければならない
- 関数シグネチャだけでは、データが変更されるか、保持されるか、zero value が許されるか、goroutine が開始されるか、
nilchannel の可能性があるか、interface{}に実際に渡せる型が何かを把握しにくい - ドキュメントへの依存が強まり、ドキュメントには更新コストと未更新コストの両方が大きい
最後に整理される「嘘」
- Goを使い続けるために自分たちに言い聞かせる言葉は、次のように整理できる
- 他の人たちが使っているのだから、自分たちにも向いているはずだ
- 懸念を語る人はエリート主義者か無礼な人だ
- 魅力的な非同期ランタイムとGCが他のすべての欠点を相殺してくれる
- 個々の言語設計上の欠陥は、単独でも組み合わさっても問題ない
- 「気をつけること」、リンタ、より多くのレビュアーで克服できる
- 書きやすいのだから、本番ソフトウェア開発も簡単だ
- 言語が単純なのだから、他も単純である
- 少しだけ使う、あるいは最初だけ使うつもりで、簡単に抜け出せる
- 後からいつでも書き直せる
- 選んだ道具が複雑さを消せないなら、その複雑さは他の開発者、運用担当者、顧客へと移される
nil値が入ってはいけない場所に入り、運用中の夜中に起こされるような事態は、Billion Dollar Mistake の繰り返しにつながる
2件のコメント
Go言語を集中的に触っている期間はほんの一瞬のようなものですが、そんなアマチュアが文章を書いてよいのかとも思います……。ただ、Go言語は長所と短所が本当にはっきりしているので、選ぶ人にも避ける人にも明確な理由があるように見えます。個人的にはRustと比較するものではなく、Kotlin(Java)と比較するのが適切な気がします。
Goのgoroutineは本当に素晴らしいですが、魔法ではありません。特にバックエンドでMySQLを1つだけ使う小さなプロジェクトでは、この並行性というものを管理するのが本当に厄介です。JS/TSランタイムではそこまで気にしなくてもよいMySQLのリソース枯渇やプール管理が、思った以上に難しいんです。結局この状況ではDBがボトルネックになるので、Go言語の並行性の利点は一部薄れてしまいます。(JS/TSランタイムの非同期I/Oやイベントループのほうが、むしろ適しているかもしれません)実際、heyのようなツールで
-c 100と流し込んでみれば分かります。それから優れたGCはありますが、だからといってむやみにオブジェクトをポインタだけ渡して使い、後始末は知らないというわけにはいきません。何事にもトレードオフはありますが、Go言語でも可能なら小さなオブジェクトは単に値コピーで渡して使い、関数が終わればすぐ処理されるようにしたほうがよいです。自分が古い考えに縛られているのかもしれませんが、C/C++のように効率の観点だけでポインタへ気軽に手を伸ばすべきではありませんでした。
関数のreturn時に
errorをほぼ毎回返し、それを毎回if err != nil {}でチェックしなければならないのは本当に面倒ですが、これは長所です。try catchよりコストが低いからです。そしてfinally {}のような役割をしてくれるdeferキーワードも素晴らしいです。リソース解放のタイミングを悩む必要がないのはよいですね。標準ライブラリだけで優れたバックエンドサーバー構成がすぐ可能な点も良いです(1.23以上)。何より、ターゲットOSに合わせてビルドすれば、他のランタイムや事前インストールが必要ない点が一番良いです。Go言語を長く使ってきたわけではないのに、あまりに個人的な意見を長々と書いている気がするので、この辺でやめておきます。笑 私はGo言語も好きですし、ほかの言語も好きです!
Hacker News の意見
この記事は Go の欠点を多く的確に指摘しているが、明示的なエラー処理がその一つだとは思わない
例外処理は、あまりにも簡単に壊せてしまう「魔法」の層を追加するので嫌ってきたし、技術的に完璧な論証ではないとしても、数十年の経験から、エラーが発生したまさにその場所で処理される方式を好む
Rust のほうがよりエレガントにやっていると言うことはできるし、個人プロジェクトでは Rust のほうが好きだが、熟練度の異なる開発者が出入りする大きなプロジェクトでは、Go の哲学は現代的なエラー処理方式の中でもかなり妥当だと思う
私の地域では、Go は単純さのおかげで他の「新しい」言語よりはるかに多く採用されている。最高の言語ではないが、自分で自分を傷つけないようにしてくれる組み込みの判断が多いので、汎用言語としてはしばしば最善になる
スタックトレースがなく、エラーのラップが必要なので、固有のエラーメッセージだけでなく呼び出し地点ごとに固有のラップ用メッセージまで作らなければならず、そうして初めてメッセージを
grepしてスタックトレースのようなものを推定できる「戻り値タプル」はエラーのためだけに存在する奇妙な仕組みで、言語の他の場所ではタプルを使うことすらできない。ぎこちない変数初期化規則のせいで、いつかは間違った
err変数を使うことになるGo には列挙型があるべきだったし、エラー専用の奇妙な「戻り値タプル」の代わりに Rust の
?のような、よりよいシンタックスシュガーを入れるべきだった。今の方式は退屈で、エラーを起こしやすいGo には頑固さがあるが、あまりにも頻繁に、その言語が批判されないよう守ろうとする頑固さのように見える。残念ながら、プログラミング言語が人気を得るにはマーケティング、つまり嘘も重要なので、これが効いている
例外はどんなライブラリでも自分のコードに投げ込めるひどい
gotoであり、ほとんど適切に処理されない。例外が「処理された」後でも、アプリケーションはたいてい未知の状態になり、推論できなくなるGo のエラーはジュニアエンジニアでも概ね簡単にデバッグできるが、Java の例外の本当の原因は熟練した principal でも把握しにくい
逆に Maybe、Option、Result のような合併型に慣れていながら、Go 方式が少しでもまともだと見ている人にはまだ出会ったことがない
あるメソッドのロジックを読もうとしているのに、75% がエラーのノイズで、25% だけが実際の動作を理解するのに必要な行なら、読みにくい
初めてコードベースを見るときは、エラーの境界ケースよりも、まず何を達成しようとしているのかを知りたいし、その次にそれが正しいことを保証する方法を見たい
この点で Go のエラー処理は大きく失敗している。明示的なエラー処理が悪いということではなく、成功経路とエラー処理を一行ずつ混ぜ込まなければならないという頑固さが問題なのだ。専用のエラー処理区域でも明示的に処理できる
同意する部分は、コードが生み出し得るあらゆる可能性を考えるよう強制する点だ。ただしこれは他の言語というより C に近い問いである
コードを抽象化するときは、エラーを即座に処理する必要がなかったり、呼び出しスタックの下のほうで処理したかったりすることがある
言語が特定の方式を押し通そうとして、可読性を大きく犠牲にしているわけだ
RustとGoは非常に異なっており、人々は現時点では存在しない中間地点を望んでいるように思う
ガベージコレクションがあり、比較的シンプルで、静的リンクされたバイナリにコンパイルされる一方で、Rustに似た型システムや、その他の型まわりの仕組みを備えた言語ということだ
構文的にはGleamとKotlinがある程度近いが、完全にそうというわけではない。Rustは好きだが、何かを作る能力はあっても、計算機科学専攻でもなく、プログラマーとして働いてもいない多くの人にとっては複雑すぎると思う
たまにしかその言語を使わないなら、vtableが何なのか、値がいつどのように解放されるのか、といったことは覚えていられない。「完璧な言語」はないが、GoとRustはどちらも素晴らしいものをもたらしたので、誰かが両者から着想を得て、広く使えるシンプルな言語を作ってくれればと思う
実際のKotlinは非常に複雑で、賢くやろうとして時間を使ってしまう。やり方が1000通りもあり、演算子オーバーロード、プロキシ、プロパティ、companion object、例外と結果型がすべて存在する
Gradleを使う前提なら、ビルドシステムは
go buildに慣れた人にとって拷問に近いコルーチンAPIはGoroutineよりも、同時により複雑で、より制限が多いように感じる。より構造化されているが柔軟性は低く、使うのにより多くの労力がかかる
アクセス制御もぎこちない。型をパッケージ専用にする方法がなく、ファイル専用かモジュール全体公開かのどちらかだ。そのため、望まない形でAPIの表面積が大きくなるか、複雑性を複数ファイルに分けられなくなる
Kotlin/NativeとKotlin/JVMは実質的に別物だ。Kotlin/JVMは成熟しているが、JVM上で動くため、Rustを持ち出すような用途の一部が消えてしまう
Kotlin/Nativeのエコシステムは非常に弱く、ドキュメントも不足している。Issue trackerには怖いバグもある
Kotlin/Nativeはソースのみのライブラリ配布もできないため、すべてのOSとアーキテクチャ向けのバイナリをビルドする複雑なCIが必要になる。そうでなければライブラリをそもそも配布しなくなり、これも弱いエコシステムをさらに悪化させているように思う
ジェネリクスを追加したように、ちょうど3つだけ追加すればよい。エラー用のジェネリックコンテナであるResult、よりよいnil処理のためのOptional、そしてElvis演算子のような小さな構文糖だ
Goの成功をほぼ単独で説明できる大きな利点は、巨大企業が作り、自社で使いながら、優れたツールとオープンソースライブラリを作る余力があったことだ。すでに使われていてライブラリがあるという事実こそ、言語の成功を左右する最大の要因のように見える
基本文法はとても直接的で、すでに採用が非常に広がっており、エコシステムは大きく、ランタイムは改善され続け、WASMへのコンパイルも可能だ
そこから奇妙な部分、たとえば再定義の柔軟性が高すぎること、グローバルスコープの乱用、数値まわりのおかしさなどを取り除き、型、合併型/直積型、Result、Maybe、decimalなどを追加する
さらに、すべてが式であるスタイル、パターンマッチング、カリー化のような関数型機能と、包括的な標準ライブラリを入れれば、多くの人が望むものに最も近い言語ができるかもしれない
開発者ツールで見たいもう一つの大きな進歩は、HTMLを刷新して、まともな高度なコントロールを組み込み、インラインCSSで簡単にスタイリングできるようにすることだ。そうすればクライアントで必要なJSの量が減る
この2つが、Web関連開発のプログラミング生産性を大きく高めると思う
dotnet publish -p:PublishAot=trueで静的ネイティブバイナリにコンパイルできる。F#ではprint*の代わりにConsole.*メソッドを使う必要がある。printには%Aフォーマット指定子の構造的出力のために内部に非限定的なリフレクションがあり、バイナリサイズに悪影響を与え、コンパイラが文句を言うからだ特にF#は「より簡単でビジネス指向のRust代替」として勧められる。式指向で、判別共用体、完全なHM型推論、段階的な型適用がうまく合っている。データ分析とドメインモデリングも非常に快適だ
システムプログラミングにはC#が選択肢になる。優れた並行処理プリミティブ、高速で、ときにはほぼコストのないネイティブ相互運用、Goより小さいネイティブバイナリ、移植可能なSIMDを含む多くの低レベルAPIを提供する
Goはこうした作業にはあまり合わないか、迂回策なしではそもそもできない場合が多い。.NETはこの領域の非ゲーム系コミュニティで人気を得つつあり、高性能ライブラリも多く生まれている。また、兄弟コメントで提案されているニッチな言語とは異なり、巨大な既存エコシステムの恩恵を受けられるため、基盤となる作業をすべて自分でやる必要がない
Go:「私はシンプルな言語だよ!」
ユーザーがしばらく Go を使う
ユーザー:「君が嫌いだ。君はシンプルな言語じゃないか!」
私が50代以上だからか、シンプルな言語が好きだ
この「批判」はあまりバランスが取れているようには見えず、技術のすべてはトレードオフなのに、バランスを欠いた判断は弱いと思う
もちろん私の結論は違う: https://www.inkmi.com/blog/why-we-chose-go-over-rust-for-our...
記事には言語のいら立たしい不整合を指摘する例が多くあり、そういうものはシンプルさの反対だ
Rob Pike の Go の発表は好きだし、一部は自分にとって目を開かせるものだった。ただ、実際に目にする Go が、Go ファンたちが抽象的に描写する Go にもっと近ければいいのにと思う
見た目と違い、Go を使い始めるとすぐにシンプルではないことが明らかになる。隠れた複雑さと落とし穴が多い。例: https://archive.ph/WcyF4
それでも有用な言語で、かなり多く使ってはいるが、「シンプル」ではない
シンプルな言語なら、高い一貫性があるべきだと思う。規則は少なく、どこでも普遍的で一貫しているべきだ。しかし Go には奇妙さ、不整合、回避策があちこちにある
良い例が型の省略だ。配列、スライス、マップの宣言では可能だが、構造体ではできない。構造体にも許せば可読性の面でいくつも利点があり、匿名構造体引数による名前付きパラメータも可能になって、現在使っている醜い回避策より設計がずっと良くなり得る
もちろん Go の妥当な代替の多くも、この観点ではさらに悪い。Python、Ruby、JS などだ
どんな言語であれ、ここまで長々とこき下ろす動機が気になる。記事のいくつかの妥当な点でさえ建設的な文体では書かれていないので、非難と呼びたい
批判され得ない言語なんてあるのか?
そのプロジェクトで失敗したことを言語のせいにして気分を良くするために書いた記事なのか? それとも、誰もが同じように考え同じように働くわけではなく、ある人には耐え難く気に障ることが、別の人にはほとんど目に入らないことを理解していないのか?
実際のニーズを満たせない言語はたいてい自然に消えていくもので、わざわざ手助けする必要はほとんどない
Go は「より洗練された」言語と違う点があっても、私が出会ったプロジェクトでは見事に機能した。筆者が Go で働くことを強制されないよう願う。コミュニティの残りはサービスを作り続け、Go チームにフィードバックを送り、本番環境を壊さずに言語がゆっくり進化していくのを見守るだろう
本質的な複雑さは、目を閉じても消えない
複雑さを気にしないことを選ぶなら、その複雑さを組織内の別の開発者、運用担当者、顧客、誰かに押し付けているだけだ。今度は彼らが、すべてを円滑に回すためにあなたの前提を回避しなければならない
最近は私がその「誰か」であることが多く、もう疲れた
Go が、あなたの遭遇したプロジェクトで見事に機能したのは当然だ。C、C++、PHP、JavaScript も機能する。私たちの世界では、実に多くのものが「機能」する。車線を1本追加することも機能するし、警察国家も機能する
だが、別の何かがはるかにうまく機能する可能性もあるのではないか?
なぜ書いたかといえば、おそらく原文の筆者が自分の考えが「世の中で」十分に代表されていないと感じたからだろう。私も Go ファンではないので、2022年に Go が人気だったことには混乱した。かなり明確な欠点が無視されているように見えたからだ
PHP が悪い言語だという長いブログ記事を今では人々があまり書かないのは、その話題がすでにあまりにも多く扱われたからだ
このブログ記事が自分の好む書き方で書かれていなかったからといって、他の人にとって価値がないという意味ではない
Go がしたことは、それが最も基本的な実装であろうと、他の言語がすでにもっとうまくやっていたことであろうと、必須で、最高で、その問題を解く唯一の方法だと包装された
Go がしなかったことはすべて不要で、気づいていない非 Go ユーザーの私たちをシンタックスシュガーに中毒させようとする Big Complexity の陰謀のように扱われた。まともな null 処理、エラー処理、少ないボイラープレート、ジェネリクス、あるいはあらゆるものに特殊ケースを作らず一貫した解法を作ることのような機能がそうだった
このスレッドでもストローマン論法は続いている。エラー処理の話になると、Go のエラー処理はずっと良くなり得るという明白な事実を認める代わりに、JavaScript のようなものを持ち出す。まるで Go を批判する人が JavaScript をエラー処理の頂点だと思っているかのように言う
他の言語で作業するたびに、Go が完璧な言語ではないとしても、いつもまた戻りたくなる
文字どおり ただ動くところがいい。Go をインストールして、コードを取得して、コードを書けば終わり
どのバージョン、ランタイム、設定、ビルドツール、パッケージマネージャを使うべきかを突き止める必要がない。ただインストールして Go を使えばいい
同じ体験を提供する他の言語は、おそらく Rust くらいだろう
自分に言い聞かせている嘘かもしれないが、Python、TypeScript、Java を使うたびに、ただコードを書きたいだけなのに、設定、パッケージマネージャ、ビルドツール、バージョン関連の問題をデバッグしたり把握したりするせいで、プログラミングが怖くなる
Go 批判を読むたびに同じことを思う。それでも自分は Go を使い続ける
理論上は簡単に文書化できる問題が多く、実際に人々がよくぶつかっていることは理解しているが、それでも実務では依然としてより良いプログラミング言語の一つだと感じる
よく欠点として列挙されるものの中には同意しないものもあり、たとえばどこでも明示的にエラーを処理するやり方は好きだ。他の部分には同意するが、他のプログラミング言語の欠点よりはるかに気になるわけではない
Go が成功できなかった領域に特に敏感な人たちは、一生その不満を言い続けることになるのだろうと思うと、少し気の毒でもある
だがプロジェクトにどの言語を使うか選ぶとき、「最高の言語は何か」という理性的な論理を大量に持ち出したりはしない。自分に合っていて、良いソフトウェアを書き続けられると感じ、作業しやすい言語を選ぶ
正確性を重視する人間として、その言語がもっと Rust に近ければいいのにという願いはあるが、今はそうではない。Rust が嫌いなわけでもないが、急いでいるときに手が伸びる言語ではない
Go がどれほどひどいかについての記事は、もう十分すぎるほど書かれた。少なくとも数年前に PHP ファンでいるのがどんな気分だったかは、今ならわかる。大げさではあるが、自分の考えではそれほど大きな誇張ではない
Go を使うことになる
そして後悔することになる
しかしその時にはもう遅く、そこに縛り付けられている
筆者はかなり説得力のある主張をしており、反論するのも非常に難しい。明示的に列挙された多くのケースや、技術的に有能でない会社が不適切な技術を使う多くの職業上の状況では、Go は悪い選択であり、本番投入したことを後悔することになるだろう
会社は 退屈なツールにこだわるべきだ。個人プロジェクトなら、もちろん好きにすればいい
それ以外では、単純さを強みだと見ている。使い心地がよく、学びやすく、概して性能もかなり良い。あらゆる状況で選ぶわけではないが、良い道具になる場面は多い
同じように Java と Python もある程度好きで、どちらも十分うまく扱える。これらの言語は、それぞれが意図した用途に合った良い道具だ
人々がなぜプログラミング言語にそこまで熱烈に執着するのかわからない。言語は道具だ。ある道具をより好むことはあっても、嫌いな道具が無効になるわけではない
if err != nilの悪魔たちに顔を殴られる悪夢を見るこの問題を直す良い提案はたくさんあったが、極端に保守的で声の大きいコミュニティの一部が、罵倒に近いフィードバックですべての提案を潰した
今や Go チームはそういう人たちを心理的に恐れて、エラー処理の改善をめぐる戦いを諦めた状態だ
すべての Go 開発者アンケートで、コミュニティは圧倒的にエラー処理の改善に票を投じているが、少数の過激派がすべての進展を脱線させた
Go は、どんな機能が必要かを自分たちが決めてやるという傲慢な態度を取り、作業に使えるものをごくわずかしか提供しなかった
当時の議論:
(130ポイント、148コメント) https://news.ycombinator.com/item?id=34188528
(748ポイント、544コメント) https://news.ycombinator.com/item?id=31205072
「文法よりもセマンティクスをはるかに気にするようになり、Zig、Nim、Odin なども見なかった。もう『より良い C』には興味がない」という部分がある
記事はかなり冗長だと思う。Go が「偶然的」だという部分はでたらめで、Rust も起源と設計の面では同じくらい「偶然的」だからだ
一つ目についたのは、Nim は決して「より良い C」ではないという点だ。ガベージコレクションがあり、参照カウントも使える。望むなら、より良い C のように使うことはできる
Nim の型システムは Go が抱える多くの問題を避けるが、Rust ほど厳格ではない
結局のところ、Go で多くのソフトウェアが書かれてデプロイされ、高速に動作し、ダウンタイムが少なく、概して効果的で、セキュリティ問題も少ないように見える。Rust でデプロイされたソフトウェアプロジェクトははるかに少ないと思うし、Firefox も依然として 95% 以上が C++ だ
私はこの争いに利害関係はなく、職場では Python と C++ を満足して使っている。Rust で書かれたソフトウェアがはるかに少ないとしても、それでも Rust で 多くのソフトウェアが書かれてデプロイされている と見なせる。大企業による投資も見逃せない
0: https://x.com/m_ou_se/status/1599173341117435905
なぜ Go がいつも Rust と比較されるのか分からない。最も適切な比較対象は Java だと思う
Go と Rust の比較のほうが一般的だが有用性が低いのは、どちらも新しい言語の波の一部であり、どちらも基本的に静的リンクされたバイナリを作るからだろう
Java が良い選択になる状況では、Go は本当に悪い選択になる。言語が制限的なだけでなく、エコシステムも Java と比べると非常に限られている
私は Go、C#、TypeScript を主な言語として維持しており、この組み合わせは優れたカバー範囲を提供してくれる。生産性低下を受け入れられる6か月ができるか、品質要件の低いプロジェクトができたら、Rust を追加するつもりだ
言語を習得するには1〜2週間で十分だが、エコシステムの中でエンジニアリングする方法を学ぶには時間がかかる
性能への懸念が大きくないほとんどの会社は、ネットワークサービスに C++ を使わない。Rust がすでに成熟していたなら良い選択だっただろうが、初期の重要な問題の1つだったコンパイル時間は、Rust のコードベースではさらに悪いかもしれない
実際にも Rust の代わりに Go を使う例を見たことがあり、最大の理由は Go のほうがはるかに速くコンパイルできることだった
Rust と Go が十分に異なるので、それぞれ別の場合に選ばれるべきかと問うなら、そうだ。だからこそ両方を一緒に検討し、どちらかを選ぶことになる
かつて「より良い」Python を探していた。C/C++ より単純で安全だが Python より速く、何より 単一バイナリ を作れる言語が欲しかった
それで Rust から Hare のようなあまり知られていない言語まで、いろいろ見た
Go が当然の選択になるはずだったが、理由は分からないものの構文が嫌だった。Rust は理解できる。覚えにくく、非 QWERTY キーボードで打ちにくい特殊文字をたくさん使うし、ほかにも痛点がある。いくつかの Lisp は括弧と逆ポーランド記法が問題だった。しかし Go については、なぜ嫌なのか合理化できない
結局、Python コードを Nuitka でコンパイルすることにした。速度向上は分からないが、これでバイナリを提供できる
C# もますます見るようになっている。AOT コンパイルが進歩しているからだ。ただしデフォルトモードの冗長さと、Windows に縛られすぎている点は嫌いだ
Nim と Crystal はかなり気に入ったが、小さなコミュニティが障壁だった。それでも Nim があれほど小さなコミュニティで成し遂げていることには本当に感心したし、優れた言語だと思うようになった
挙げた言語のうちどれか1つを学ぶよう、自分に動機づけしてみるつもりだ
最近は実際かなり使いやすい
C-API を諦めて Go や Rust のような言語へつなぐ橋を作る、別のアプローチもある
私は数年間 py2many に取り組んできた。このアプローチについてフィードバックが欲しい