Go言語にXXXがないのはXXXにはこういう問題があるからだとコードで実証するスレ
わたしの考えでは、「Goに XXX がない合理的理由」をアンチに理解させるには、「XXX だと不都合がある事例」を他の言語のコードで端的に表現してぶつけるしかないだろう。だが、なかなか面倒な作業だ
ということで、思いつき次第、書いてゆくことにする
関数名の多重定義はどの関数を呼ぶべきかが曖昧になる
Go ではパラメータの型違いで同じ名前の関数を定義できない(C++ や .NET は可能)。2つ問題がある
- 本来は別の役割の関数を同じ振る舞いであるかのように命名できてしまう
- null / nil など、どちらの型でも解釈できてしまうようなパラメータが与えられた時、多重定義された、どちらの関数が呼ばれるか不明確になってしまう。そして、.NET の場合だと、どちらでも呼べるようにコンパイルされた挙げ句、実行時エラーになってしまうことがあった。
後者について、事例があったはずなので、いずれコード片を探し出す(この投稿への返信に続ける)
多数の種類の例外を発信されると、取りこぼす。取りこぼしをさけると、逆に拾うべきでないものまで拾ってしまう
.NET の System.IO.StreamWrter のコンストラクタは次のように多数の種類の例外を発信する
- UnauthorizedAccessException
- ArgumentException
- DirectoryNotFoundException
- PathTooLongException
- IOException
- SecurityException
これらの全ての Exception を拾おうとすると、その共通項は SystemException というすごく範囲の広いExceptionを Catch しなくてはいけない。それを広い try ブロックに対して行うと、間違いなく他の処理での例外まで誤って拾うおそれがある。頻繁に起きるオープン処理での例外と、稀にしか起きないが DISK FULL など重篤な事態で起きるクローズ処理の例外は取り扱いを同一にすべきではない。
となると、こういうオープン処理だけをピンポイントに try ブロックで囲んだ書き方にならざるをえない
System.IO.StreamWriter w = null;
try {
w = new System.IO.StreamWriter("...",false,System.Text.Encoing.Default)
} catch ( System.Exception e ){
例外処理
}
こういう範囲をきめ細かに設定してエラー処理を行おうとすると、try~catch ブロックの方がかえって冗長になってしまう。そして我々は強制されないと、こういうきめ細かなエラー処理がなかなかできない。えてして
try{
using( var w = new System.IO.StreamWriter("...",false,System.Text.Encoing.Default) ){
:
}
} catch( System.SystemException ex ){
例外処理
}
と書いてしまう。結果として using ブロックの内側の関係ないエラーも同一の例外処理で雑に扱ってしまう。
Go はそういうのを許さない
そうか、分かった。
「例外処理は、発生場所によって区別して別の取り扱いをすべき〈エラー〉を、大まかな種類のみによって区別して同じ扱いにしてしまう」のが大きな問題だ。
本来はきめ細かく場所によって、tryブロックを区分けせねばならないが、我々は怠惰故につい広い範囲で try ブロックを設けてしまう。結果として関係ないエラーも同一のエラー処理でひとくくりで処理をしてしまい、意図せぬエラー処理を行ってしまう場合がある。
Go はそれを許さない。大きな try ブロックで設けるような雑なエラー処理を許さない
try-catch 方式できめ細かにエラー処理するには、エラークラスをきめ細かに作るという方法がある。だが、それも結構な手間が逆にかかる。
トータルで考えると、やはり Go のように個別に返してきたエラーを逐次チェックする方がコンパクトだ。
例外システムは設計に失敗しやすい
- 例1:上に書いたとおり、New System .IO.StreamWriter なんかは、6種類くらい例外を送信するが、共通項が SystemException というほぼルート的な例外クラスなので、処理をまとめられない
- 例2:Python の KeyError は例外にすべきだったのか? Ruby のように nil(None)を返すだけにすべきではなかったか? KeyError でとんでいってしまうのを避けるため、インデックス演算子を使わず、ほとんどのケースでは get メソッドを使ってしまう。イレギュラーなケースがあった時、例外をとばすか、エラー値を返すか、常に迷いが発生する(安直に例外にするとめんどくさくなる)
本題とは違うけど、こういうコードに Ninja code という名前があるのはある意味ありがたい。
アンチGo言語の人はコードは短ければ短い程よいと考えがちだが、コードが必要以上に短くなると、パズル的要素が強くなってしまい、逆に保守がたいへんになることをアピールするのに「Ninja code」という名称は便利だ。
参考になる
C++の例外がよくないという話、直感的には分かるけど、それを他人に自分の言葉で説明できるほどは分かっていない(C++の例外に伴うトラブルを経験していないから)
Goに例外がないことに対して、自分が有効な反論をできないのは、C++の例外トラブルについて自分の言葉で語るほど巻き込まれていないからだろう。なぜなら、わし C++ でも例外使わないから(Windowsだしね)。これはちょっとジレンマではある
Go の panic と同じですねぇ…