- Go言語設計におけるさまざまな決定は、不必要であったり既存の経験を無視したまま行われてきた
- エラー変数のスコープ管理の問題が、コードの可読性とバグの追跡を難しくしている
- nilの二重性、メモリ使用、コードのポータビリティなど、さまざまな点で直感的でなく現実に合わない設計が見られる
- defer構文の限界と標準ライブラリの例外処理方式により、例外安全性の確保が難しくなっている
- メモリ管理やUTF-8処理の不備など、蓄積された問題がGoコードベースの品質に長期的な悪影響を及ぼしている
Go言語に対する長期的な批判
エラー変数スコープの非直感性
- Goの文法は、エラー変数(err)のスコープを不必要に広げ、ミスの可能性を高めている
- 例示コードではerr変数が関数全体で生き続け、再利用され、その結果コードの可読性と保守性が低下する
- 熟練した開発者であっても、このスコープ問題のせいでバグ調査において誤解や時間の浪費を経験する
- 変数を適切に局所化する方法が文法上許されていない
2つの形のnil
- Goには、interface型とポインタ型でそれぞれnilが異なる挙動を示すという混乱がある
- 下の例のように、s(ポインタ)とi(interface)にnilを代入しても、s==iの評価が異なるなど、一貫性のない挙動を示す
- これはnull処理で一般に避けたい問題であり、設計上十分に検討されたとは思えない痕跡である
コードの移植性の限界
- 条件付きコンパイルのためにコメントを使う方式は、保守性とポータビリティの面で著しく非効率だ
- 実際にポータブルソフトウェアを作った経験があれば、この方式が煩雑でエラーを招くことはわかる
- 歴史的に蓄積された経験(コードのポータビリティ、実務上の事例)が無視されている
- 詳細はGo programs are not portableを参照
appendの所有権の不明確さ
- append関数とスライスの所有権関係が明確でないため、コードの予測が難しい
- 例を通じて、foo関数でスライスにappendしたとき、実際に元のデータへどんな影響があるのかを事前に把握しづらい
- 言語の覚えておくべき「quirk」が増え、ミスを招く
defer構文設計の不備
- RAII(Resource Acquisition Is Initialization)の原則のように、リソース解放を明確に支援していない
- JavaやPythonの構造化されたリソース管理構文と比べ、Goではどのリソースをdeferで解放すべきかが明確でない
- 例のようにファイル操作では、double-close問題まで自分で扱わなければならず、正しい解放順序や方法が不明瞭だ
標準ライブラリの例外処理
- Goは明示的な例外(exception)をサポートしない構造だが、panicなどの例外的状況は依然として発生する
- panicが一部の状況では完全にプログラムを終了させず、潜り込んでしまう場合もある
- 標準ライブラリ(fmt.Print、HTTPサーバーなど)には例外を無視するパターンがあり、真の例外安全性の保証は不可能だ
- 結局、例外安全なコードを書くことは必須だが、例外を直接使うことはできない
UTF-8処理と文字列
- string型に任意のバイナリデータを入れても、Goは特別な検証なしに動作する
- UTF-8エンコーディング以前に作成されたファイル名などが、静かに欠落してしまうケースを経験しうる
- バックアップなどで重要なデータが失われる可能性があり、実務の状況を反映しない単純な処理方式だ
メモリ管理の限界
- RAM使用量を直接制御しにくく、GC(ガベージコレクション)の信頼性にも限界がある
- Goのメモリ使用量が増え、長期的にはコストや性能の問題につながる
- 複数インスタンスやコンテナ環境で、コストとスケーラビリティの問題が実際に発生する
結論: もっと良い道があった
- すでに効果的であると証明された言語設計が存在していたにもかかわらず、Goは多くの点でそれに背を向けた
- Java初期案の問題点とは異なり、Goが登場した当時にはすでにより良いアプローチがあった
参考資料
まだコメントはありません。