Hatena::ブログ(Diary)

プログラミング言語を作る日記

2010-01-30 Failure and Exceptions (James Gosling氏へのインタビューより)

[][]Failure and Exceptions (James Gosling氏へのインタビューより)

前回、続きを「数日中に書きます。」と言ったくせにその後いろいろ忙しかったり酒飲んだりしていて書いている余裕がなくて、そうこうしている間に、以前のC#作者Anders Hejlsbergのインタビュー記事へのJames Goslingからの反論を見つけてしまいましたので(こちら経由)また訳してみました。「The trouble with Checked Exceptions」は有名ですし以前から知っていましたが、これの存在は今回はじめて知りました。

例によって私の英語力はアレなので、間違いや訳し切れなかったところについてはご指摘をよろしくお願いいたします。

原文はこちら。

http://www.artima.com/intv/solid.html

Summary

James Gosling talks with Bill Venners about how to build solid apps, organize your catch clauses, scale checked exceptions, and deal with failure.

James Goslingが、いかにして頑健なアプリケーションを構築し、あなたのcatch節を体系化し、検査例外をスケーリングし、どのように失敗に対処するかについて、Bill Vennersと対話する。

Much has been written recently about the value that checked exceptions add, or subtract, from Java. Some programmers feel that checked exceptions help them build robust applications. Others find that checked exceptions are an annoyance that hinders their productivity. In this article, Java's creator James Gosling voices his opinions about checked exceptions and error handling.

検査例外がJavaに加える、あるいは減じる価値について、最近多くのことが書かれています。あるプログラマは、検査例外が頑健なアプリケーションを構築する助けになると感じています。別のプログラマは、検査例外は彼らの生産性の邪魔になる苛立ちの元であると感じています。この記事では、Javaの作者であるJames Goslingが、検査例外とエラーハンドリングについて自分の意見を述べます。

In this interview, which will be published in multiple installments, James Gosling talks about many aspects of programming.

いずれ複数回に分けて出版されるであろうこのインタビューでは、James Goslingは多くのプログラミングの局面について語ります。

  • In Part I: Analyze this!, Gosling describes the ways in which Jackpot can help programmers analyze, visualize, and refactor their programs.
  • In this installment, Gosling talks about how to build solid apps, organize your catch clauses, scale checked exceptions, and deal with failure.

Creating Solid Software(Solidなソフトウエアを作る)

Bill Venners: In your weblog, you wrote:

Bill Venners: ブログであなたはこう書きました。

Lots of newbie's coming in from the C world complain about exceptions and the fact that they have to put exception handling all over the place―they want to just write their code. But that's stupid: most C code never checks return codes and so it tends to be very fragile. If you want to build something really robust, you need to pay attention to things that can go wrong, and most folks don't in the C world because it's just too damn hard.

One of the design principles behind Java is that I don't care much about how long it takes to slap together something that kind of works. The real measure is how long it takes to write something solid.

Cの世界からきた多くの初心者は、例外と、彼らがあちこちに例外のハンドリングを書かなければならないという事実に対して不平を言います―彼らはただ自分のコードを書きたがっているのです。しかし、それは愚かなことです。ほとんどのCのコードは決して戻り値をチェックせず、そのため非常に脆弱な傾向があります。もしあなたが何か本当に頑健なものを作りたければ、あなたは支障を起こす可能性があるものに対して注意を払う必要がありますが、Cの世界のほとんどの人は、単にそれがひどく難しいために、それをしません。Javaの背景にある設計原則のひとつは、私が、何かそこそこ動くものを適当にでっちあげるのにどれぐらい時間がかかるか、ということは気にしない、ということです。真の尺度は何かSolidなものを書くのにどれぐらいかかるのかということです。

What does "solid" mean?

「Solid」とはどのような意味でしょうか?

James Gosling: Solid means you can run the software day in and day out. When the usual crazy things happen, the software survives. The software deals with the problems. If you change the system configuration around, the system, as much as possible, copes――or at least recognizes when things are wrong.

James Gosling: Solidとは、あなたがそのソフトウエアを来る日も来る日も実行できる、ということです。よくある狂った事態が発生しても、ソフトウエアは生き残ります。ソフトウエアは問題に対処します。もしあなたがシステムの構成を変えたとしても、システムはできるだけうまく処理しますし、少なくとも何かが間違っているときそれを認識します。

One of the traditional things to screw up in C code is opening a data file to read. It's semi-traditional in the C world to not check the return code, because you just know the file is there, right? So you just open the file and you read it. But someday months from now when your program is in deployment, some system administrator reconfigures files, and the file ends up in the wrong place. Your program goes to open the file. It's not there, and the open call returns you an error code that you never check. You take this file descriptor and slap it into your file descriptor variable. The value happens to be -1, which isn't very useful as a file descriptor, but it's still an integer, right? So you're still happily calling reads. And as far as you can tell, the world is all rosy, except the data just isn't there.

Cのコードにおいて伝統的な失敗のひとつは、読み込みのためにファイルをオープンすることです。リターンコードをチェックしないのはCの世界ではほとんど伝統のようなものです。なぜならあなたはファイルがそこにあることを知っているから。そうだよね? そこで、あなたはただファイルを開き、それを読みます。しかし、今から数ヵ月後のある日、あなたのプログラムが配置された時、システム管理者はファイルを再構成して、ファイルは結局間違ったところに置かれます。あなたのプログラムは、ファイルをオープンしようとします。それはそこになく、openの呼び出しはあなたにエラーコードを返すのですが、あなたは決してそれをチェックしません。あなたはこれをファイルディスクリプタとみなして、あなたのファイルディスクリプタ用の変数にそれを放り込みます。値はたまたま-1*1であり、ファイルディスクリプタとしてあまり役には立ちませんが、それでもそれは整数型です。そうだよね? そこであなたはまだ幸福にreadを呼び出します。あなたが関知する限りにおいて、世界はすべてバラ色です。ただ、データがそこにないということを除いて。

Problems like that are really hard to test for. It is really hard to test the unlikely things, if only because the unlikely things never really occur to you. In this example, the programmer will think, "Well of course the file is there. Why would anybody not have the file there?"

こういう問題は本当にテストしにくいものです。ありそうもない事柄というのは、けっしてあなたの心に浮かばないものであり、それだけでもテストしにくい理由としては十分です。この例では、プログラマはこう考えるでしょう。「もちろんファイルはそこにあるさ。誰がそこにファイルを持っていないって?」

A programming language can't solve all the problems. A language can't guarantee that no matter how screwed up the environment gets the program will survive. But anything the language can do to increase the probability that programs will be reasonably graceful under fire is a good thing. For example, just making people at least willfully ignore return codes helps. In Java you can ignore exceptions, but you have to willfully do it. You can't accidentally say, "I don't care." You have to explicitly say, "I don't care."

プログラミング言語はすべての問題を解決することはできません。言語は、いかに環境がぐちゃぐちゃにされていても動き続けることを保証することはできません。しかし、ひどい状況下でもプログラムが正当に優雅に動く確率を増加させるよう、言語が支援できることは良いことです。たとえば、人々に、少なくとも意識的に無視させるようにすることは、助けになります。Javaでは、例外を無視できますが、あなたは意識的にそれをしなければなりません。あなたは「私は気にしません」とうっかり言うことはできません。あなたは「私は気にしません」と明示的に言わなければなりません。

Bill Venners: You don't have plausible deniability.

Bill Venners: 都合のよい言い逃れ*2を持たないわけですね。

James Gosling: Yeah, there's no plausible deniability when it comes to checked exceptions.

James Gosling: はい。検査例外ということになると、いかなる都合のよい言い逃れもできません。

Orgnanizing Your Catch Clauses(catch節を体系化する)

Bill Venners: I recently published an interview with C#'s creator Anders Hejlsberg in which we talked about checked exceptions and why C# doesn't have them. I wanted to ask you about some of his comments. He said:

Bill Venners: 私が最近発表したC#作者のAnders Hejlsbergとのインタビューにおいて、我々は、検査例外と、なぜC#がそれを持たないのかについて話しました。彼のコメントについてあなたに聞きたいと思います。彼はこう言いました:

It is funny how people think that the important thing about exceptions is handling them. That is not the important thing about exceptions. In a well-written application there's a ratio of ten to one, in my opinion, of try finally to try catch.

どうしたわけか、例外について重要なのはそれをハンドルすることだと考えられていますが、それはおかしな話です。それは例外について重要なことではありません。よく記述されたアプリケーションにおいて、私の意見では、try finallyはtry catchの10倍もあるのです。

In the finally, you protect yourself against the exceptions, but you don't actually handle them. Error handling you put somewhere else. Surely in any kind of event-driven application like any kind of modern UI, you typically put an exception handler around your main message pump, and you just handle exceptions as they fall out that way. But you make sure you protect yourself all the way out by deallocating any resources you've grabbed, and so forth. You clean up after yourself, so you're always in a consistent state. You don't want a program where in 100 different places you handle exceptions and pop up error dialogs. What if you want to change the way you put up that dialog box? That's just terrible. The exception handling should be centralized, and you should just protect yourself as the exceptions propagate out to the handler.

finallyの中では、あなたは例外から自分自身を防御することになります。しかし、自分で例外をハンドルするわけではありません。あなたは例外処理をどこか他の場所に置くわけです。近代的なUIのようなどんな種類のイベントドリブンアプリケーションにおいても、あなたは、通常メッセージポンプの外側に例外ハンドラを置きます。そして、あなたは、そこまで落っこちた例外のみをハンドルするのです。しかし、あなたは、あなたが確保したあらゆるリソースを開放等することで、自分自身のコードを確実に防御できます。自分で後始末するので、あなた自身は常に一貫した状態にあります。あなたは100の異なった場所において、自分で例外をハンドルし、エラーダイアログポップアップするプログラムを欲しくはありません。あなたがそのダイアログボックスを提供する方法を変えたい場合、どうなるでしょうか? それはまさにひどいものとなります。例外処理は集結されるべきです。そして、例外がハンドラの外に伝播するとき、あなたはただ自分自身を防御すべきです。

I do catch exceptions in an outer loop sometimes. But most times my catch clauses tend to be spread around the program. I find that catch clauses usually have a natural home――the method that has enough contextual knowledge to know how to deal with the exception. How would you recommend people organize their catch clauses?

私は時々外側のループで例外をcatchします。しかし、多くは、私のcatch節はプログラムの周りに配置されます。私は、通常、catch節には本来あるべき場所があると感じています――例外をどう扱えばよいか、文脈上十分な知識のあるメソッドです。あなたは、人々がどのようにcatch節を体系化するよう勧めますか?

James Gosling: I tend to do catches much more frequently than Anders would have you do, because the knowledge of the situation is always fairly localized. When you try to open a file and it's not there, you're coping strategy is really determined by what you were going for. Some guy miles away isn't going to know what to do. The code that tried to open the file knows what to do, whether it be trying a backup file, looking in a different directory, or asking the user for another filename.

James Gosling: 私は、Andersがあなたに勧めるよりも頻繁に例外をcatchする傾向があります。なぜなら、状況に関する知識はいつも相当に局所化されているからです。あなたがファイルを開こうとしてそれがなかったとき、あなたの対処方法はあなたが何をしたいかによって真に決定されるのです。何マイルも離れたところでは、何をすべきかわからないでしょう。ファイルを開こうとしたコードは、何をすべきか知っています。それがバックアップファイルを試すことであれ、別のディレクトリを見に行くことであれ、別のファイル名をユーザに聞くことであれ。

Having one big catch clause on the outside really only works if your exception handling philosophy is simply to die. If you have an event loop, you can maybe cause that one event to just be tossed. If you have a plugin architecture, the enclosing environment could respond to a failure in the plugin by disabling it――like an app server deciding to disable a servlet if it sees failures. But if you're not doing an event driven program or plugins, there isn't an outside place where you can take big hunks of functionality and saw them off. On the other hand, typically you should have last ditch try catch blocks. If you're writing a web server, for example, it's a good thing to put a last ditch try catch block around processing a request. But pretty much all that a try catch block like that can do is blow the request away. There's no ability to respond gracefully. There's no ability to take account of local context to cope and adapt, which is really one of the key hallmarks of truly reliable software.

あなたの例外ハンドリングの哲学が、短に死ぬことであるという場合だけ、外側に巨大なひとつのcatch節を持つという方法は真に機能するのです。あなたがイベントループを持っているのなら、あなたはおそらく、その単一のイベントをただ受け渡すだけで済むでしょう。プラグイン構造があるのなら、それを包み込む環境は、それを無効化することにより失敗に対処できます――アプリケーションサーバが、失敗を見つけたときにサーブレットを無効化するように。しかしあなたがイベントドリブンのプログラムプラグインをやっているのでないのなら、機能性という名の大掛かりな塊を持って来て、それらを切り出す作業のできるような、外側の世界というものがありません。一方で、定型的なことを言えば、最後の牙城としてのtry catch節を書くことも勧めます。たとえば、あなたがウェブサーバを書いているのなら、リクエストの処理の外側に、最後の溝としてのtry catchを置くのはよいことです。しかし、そのようなtry catchブロックができることは、単にリクエストを吹き飛ばすことだけです。優雅に応答する能力はまったくありません。うまく対処したり適応したりするために、局所的な文脈を考慮する能力がまったくないのです。それは、本当に信頼できるソフトウエアの主要な特徴のひとつです。

Bill Venners: It adapts to problems?

Bill Venners: 問題に適応して対処する、ということでしょうか?

James Gosling: Instead of just rolling over and dying.

James Gosling: ただ転んで死んでしまう代わりにね。

Scalability of Checked Exceptions(スケーラビリティと検査例外)

Bill Venners: Another concern Anders Hejlsberg had about checked exceptions, which is a concern I've heard many people express, is scalability. He said:

Bill Venners: Anders Hejlsbergが検査例外に対して持っていた他の懸念事項――それは多くの人が表明しているのを私が聞いた懸念事項なのですが――スケーラビリティです。彼は以下のように言いました。

In the small, checked exceptions are very enticing. With a little example, you can show that you've actually checked that you caught the FileNotFoundException, and isn't that great? Well, that's fine when you're just calling one API. The trouble begins when you start building big systems where you're talking to four or five different subsystems. Each subsystem throws four to ten exceptions. Now, each time you walk up the ladder of aggregation, you have this exponential hierarchy below you of exceptions you have to deal with. You end up having to declare 40 exceptions that you might throw. And once you aggregate that with another subsystem you have 80 exceptions in your throws clause. It just balloons out of control.

小さなプログラムでは、検査例外はとても魅力的です。小さな例で言えば、あなたは、FileNotFoundExceptionをちゃんと捕捉したことを確認した、ということを示すことができます。これってすごくない? さて、あなたがひとつのAPIだけを読んでいる場合、これはすばらしいものです。あなたが4つか5つの異なるサブシステムと対話するような大規模システムを作り始めた時に問題が始まります。各サブシステムは、4〜10の例外を投げます。いまや、あなたはモジュールの階層を上がるたびに、指数関数的に増加する、対処すべき例外を持つことになります。あなたは結局、投げる可能性のある40個の例外を宣言しなければなりません。そして、ひとたびそれを別のサブシステムと組み合わせると、あなたはthrows節に80個の例外を並べることになります。それは制御不能なほど膨張していきます。

James Gosling: I've almost never seen that actually happen in large systems. One of the coping strategies I've seen for dealing with that kind if situation is exception wrapping. If you have a subystem that might be hit with hundreds of different exceptions, you can always take those exceptions and wrap them in a new one. You can create a new exception and list other exceptions as their cause. Also, the exception class hierarchy can help. For example, there are dozens of different IOExceptions, but it's common to not declare that you throw the specific subclasses. You just throw IOException.

James Gosling: 私は大規模システムでそんなことが起きているのを見たことはほとんどありません。私が見たことがある、その手のシチュエーションに対応する対処戦略のひとつは、例外ラッピングです。もし何百もの異なる例外を起こすサブシステムがあるのなら、あなたはひとつの新しい例外でそれをラッピングできます。あなたは新しい例外を生成し、他の例外は、その原因(cause)として記載できます。また、例外のクラス階層も助けになります。たとえば、たくさんの異なったIOExceptionがありますが、特定のサブクラスを投げると宣言しないのは一般的です。あなたはただIOExceptionを投げるだけなのです。

But in general, when you are writing a declaration in a throws clause, it is good to try to be as specific as possible.

しかし、一般的に、throws節を書くときには、できるだけ特有であろうとすることは、よいことです。

Bill Venners: Why?

Bill Venners: なぜでしょうか?

James Gosling: Because it gives more information to the context about what exactly went wrong. That helps client programmers discriminate among the different sources of failure in their catch clauses.

James Gosling: 正確に何がおかしくなったのかという文脈についてより多くの情報を与えるからです。それは、クライアントプログラマがcatch節においてさまざまな原因を区別することを助けます。

Bill Venners: Have you seen the throw clause scalability problem in your visits out in the industry?

Bill Venners: あなたが業界に出向いて行った際に、throws節のスケーラビリティの問題を目にしたことがありますか。

James Gosling: I've never seen issues where people have a gazillion items in their throws clause. Three, four, or five, maybe?and even those numbers are rather large. Almost always it's zero or one.

James Gosling: 私はthrows節に何億兆もの項目があるような問題は見たことがありません。3つか4つ、5つぐらいでしょうか? それらの数さえかなり大きいほうです。たいていは0か1です。

Bill Venners: I've found that many people don't seem to think about programming so much in terms of the interface as an abstract contract and the implementation as one way to implement that contract. They just think in terms of "the code." But the throws clause is part of the interface. If someone makes an implementation change that results in a method being called that throws a new checked exception, that doesn't mean that exception should necessarily appear in throws clauses up the call stack. If you automatically plop exceptions into throws clauses, you're letting the implementation drive the interface, instead of thinking about what the throws clause should contain at the abstract contract level.

Bill Venners: 多くの人々は、抽象的な契約としてのインタフェースと、その契約を実践するためのひとつの方法としての実装という観点からのプログラミングについて、よく考えているようには見えません。彼らは単に「コード」の観点から考えます。しかし、throws節はインタフェースの一部です。もし誰かが実装を変更して、その結果として新しい例外を投げるメソッドが呼ばれることになったとしても、その例外は必ずしもコールスタックをさかのぼってthrows節に現れるはずだ、ということにはなりません。機械的に例外をthrows節にどさどさ書いていくなら、あなたは実装からインタフェースを変化させることになります。抽象的な契約のレベルでthrows節が何を含むべきかを考える代わりに。

James Gosling: That's a place where exception translation can be a good thing. If you have something that's supposed to be reading a database, and somewhere deep inside it, it could conceivably get a MalformedURLException, it probably doesn't make sense to propagate that outward as a MalformedURLException. It's better to think about what kind of exceptions are really a part of the interface, and not just an artifact of how the underlying method was implemented. In the Java exceptions mechanism, exceptions can have a reference to other exceptions as their cause. So you create a new IOException, and you set its cause to this MalformedURLException. And you bundle them together. That's been very effective.

James Gosling: それは例外翻訳がうまく使える場所です。データベースを読んでいると仮定される何かがあるとき、その中の奥深いどこかで、もしかしたらMalformedURLExceptionを得るかもしれません。それはおそらくMalformedURLExceptionとして外部に伝播させることは無意味です。下層のメソッドの実装のされ方によって生じた単なる遺物だと考えるのではなく、どのような種類の例外が真にインタフェースの一部であるべきかを考えるほうが良いです。Javaの例外メカニズムでは、例外はその原因(cause)として他の例外への参照を持つことができます。そこで、あなたは新しいIOExceptionを生成し、そのcauseとしてこのMalformedURLExceptionをセットします。そしてあなたはそれらを一緒にまとめます。それは非常に効果的です。

Exception Handling versus the One True Path(例外処理 vs. ひとつの正しい経路)

Bill Venners: What percentage of time do you think people should be spending programming the one true path――the path taken if nothing goes wrong――versus handling all the potential errors?

Bill Venners: 潜在エラーのすべてをハンドリングするのと対比して、何パーセントぐらいを、ひとつの正しい経路*3――何もおかしくならなかった場合の経路――を書くために費やすべきだと思いますか?

James Gosling: It's all over the map. Ideally you should be spending almost all of your time on the one true path. But if you look at various domains, the percentages are different. The worst case would be people doing avionics software. If they spend a few percent of their time working on the one true path, they feel very good about themselves. That's the kind of area where the high nineties percentage of your time is spent dealing with things that could go wrong, and speculating about things that could go wrong. In that area, life is truly dynamic. There are real random number generators up there, with turbulence doing strange things to wing tips and all kinds of weird stuff that may never actually happen.

James Gosling: 場合によります。理想的には、あなたはほとんどすべての時間をひとつの正しい経路(正常系)に費やすべきです。しかし、いろいろなドメインを見るなら、割合は変わってきます。最悪のケースは、航空電子工学ソフトウエアを書く人々でしょう。彼らにとっては、労働時間のほんの数パーセントを単一の正常経路に割くだけでも、ずいぶん気持ちのよいものでしょう。それは、90パーセント台後半の時間が、何かがおかしくなったり、おかしくなることが推測される場合の対応に費やされる領域です。その領域では、人生は本当にダイナミックです。そこには本当の乱数発生器があって、ウイングチップ*4におかしなことをしようとする乱気流や、実際には決して起こらないであろうあらゆる奇妙なことがあります。

Almost as bad as avionics is networking software. When you're trying to write some piece of software that is dealing with something across the network, trying to make that truly reliable can be a lot of work. I've done code that tries to maintain reliable database access, for example, and that can be 90% error handling. You look at most protocol implementations and at some level they're almost all error handling. If you believed everything was perfect all you would do was spew packets. You wouldn't worry about timeouts, flow control negotiations, and any of the other things that go on. Networking is all about how you deal with errors at all various different levels. But when you're in a fairly well-controlled regime like, "Here's a web request, compute the result please," there shouldn't be a whole lot that you're actually worried about.

航空電子工学ソフトウエアとほとんど同じぐらいに悪いのは、ネットワークソフトウエアです。あなたがネットワークをまたがって何かを扱うソフトウエア片を書こうとしたとき、それが真に信頼性があるようにするのは大変な仕事となるでしょう。私はたとえば信頼性のあるデータベースアクセスを維持しようとするコードを書いたことがありますが、90%がエラー処理だったかもしれません。たいていのプロトコル実装を考えてみても、ある意味で、それらはほとんどエラー処理なのです。あらゆるものが完璧だと信じられるようなことにでもなれば、あとはパケットを吐くだけでよくなります。あなたはタイムアウトや、フローコントロールネゴシエーション、その他起こりうることを何も心配しないでしょう。ネットワークは、あなたがさまざまな異なるレベルでどうエラーに対処するかということがすべてです。しかし、あなたが「ここにWebリクエストがあります。結果を計算してください」といったようなよく管理された体制の中にいるとき、あなたが実際に心配することはそんなに多くはないはずです。

Bill Venners: Yeah, I think there's a spectrum. At one end you have pace makers and airplanes. If you don't deal with certain kinds of unexpected situations people can die. At the other end you have one off scripts. I remember one script I wrote that was pulling information out of web pages and putting it into XML files, and the script worked on maybe 90% of the pages I was processing. And that was good enough because it was faster for me to fix the other 10% by hand than to make the script better at dealing with badly formed input files. In that case, there was a system administrator who was quite happy to spend an hour fixing things by hand, because that was cheaper than spending 2 or 3 hours trying to get the script to be more solid.

Bill Venners: はい。私はスペクトラムがあると思います。その片端にはペースメーカーや飛行機があります。もしあなたがある種の予期しない状況に対処しなければ、人が死ぬかもしれません。もう片端には使い捨てのスクリプトがあります。私は、Webページから情報を引っ張り出してXMLファイルに出力するために自分で書いたスクリプトのことをおぼえています。そのスクリプトは、私が処理したページの90%でちゃんと動きました。そして、残りの10%を手で修正するほうが、おかしなフォーマットの入力ファイルを処理するためにスクリプトを直すより早かったので、それは十分によいものでした。このケースでは、手で直すために1時間を費やしても幸せであるシステム管理者がいたわけです。なぜなら、スクリプトをよりsolidにするために、2〜3時間を費やすより安いので。

James Gosling: Right. It all depends on what the consequences of failure are. If you're just extracting information from pages like that and you're a system administrator, the consequences of failure are pretty minor. But if you're writing transaction reconciliation software for some bank, you could find that oh, thirteen billion dollars leaked out of some system because of a bug. I actually had a friend who basically lost thirteen billion dollars in one night because of a bug.

James Gosling: そのとおり。すべては失敗の結果がどうなるかに依存します。もしあなたがそのようにページから情報を引っ張り出そうとしているだけで、かつあなたがシステム管理者だったら、失敗の結果はごく小規模なものです。しかし、もしあなたが、銀行向けのトランザクション調整ソフトウエアを書いているのであれば、あなたは見つけることでしょう――バグのため、システムから漏れた130億ドルとか。私には、バグのため、一晩で130億ドルを失った友人が実際にいたのです。

Bill Venners: Thirteen billion dollars?

Bill Venners: 130億ドル?

James Gosling: Yep. Thirteen billion. They were actually able to reconstruct it from transaction logs, but it was a real big mess. Thirteen billion dollars is real money, so you're actually probably willing to test, willing to spend some time in dealing with handling those exceptions and making things reliable.

James Gosling: そう、130億ドル。彼らはトランザクションログからそれを再構築できましたが、それはまったく大混乱でした。130億ドルは本当のお金ですから、あなたはきっと喜んでテストするでしょうし、例外に対処して高信頼性にするために喜んで時間を費やすことでしょう。

Bill Venners: I have heard a lot of people complain that in practice Java's checked exceptions encourage a lot of empty catch clauses. This empty catch clause problem seems to be more common than exceptions being propagated in throws clauses going all the way up the call stack. The compiler forces a programmer to catch an exception, so they catch it but they don't do anything. Then if the exception is ever thrown, it ends up being swallowed. It is not handled and not propagated. It is lost. Do you think that in a lot of places there's a lack of a culture of programmers wanting to deal with failure, perhaps because in the past they didn't have to deal with failure as much?

Bill Venners: 私は、多くの人が、現実にはJavaの検査例外は空のcatch節を奨励していると不満を言うのを聞きました。この空のcatch節問題は、コールスタックをさかのぼることでthrows節に増殖する例外の問題よりも一般的であるように見えます。コンパイラプログラマに例外をcatchすることを強制するので、彼らはそれをcatchするものの、何もしないのです。そのため、例外が投げられても、結局それは飲み込まれます。それはハンドルされませんし、伝播もしません。それは失われてしまうのです。多くの場所で、失敗に対処しようとするプログラマの文化が欠如している――おそらくは、過去には彼らはそれほど失敗に対処する必要がなかったため――ことについてどう思われますか?

James Gosling: Anytime somebody has an empty catch clause they should have a creepy feeling. There are definitely times when it is actually the correct thing to do, but at least you have to think about it. In Java you can't escape the creepy feeling.

James Gosling: 誰かが空のcatch節を書くときはいつだって、彼らは気持ち悪く感じるべきです。それが実際に正しいことであることもありますが、少なくともあなたはそれについて考えなければなりません。Javaでは、あなたはその気持ち悪さから逃れることができません。

It really is a culture thing. When you go through college and you're doing assignments, they just ask you to code up the one true path. I certainly never experienced a college course where error handling was at all discussed. You come out of college and the only stuff you've had to deal with is the one true path. You get a job working in some IT department's data center, and they're using the software in production runs. If it doesn't work, it's a real problem. All of a sudden there's all this painful stuff that you don't like to do, and you're feeling grumpy about it because this isn't as much fun as just writing clean code.

これはまさに文化の問題です。あなたが大学に通って課題をしているとき、それはあなたにひとつの正しい経路をコード化させます。私は確かに大学でエラー処理についてとにかく議論したコースを経験しませんでした。あなたが大学からでてきて、あなたが対処する必要に迫られたことがあるものは、ひとつの正しい経路だけです。あなたはどこかのIT部署のデータセンターで働く仕事を得て、そこではソフトウエアを生産工程に使っています。それが動かなければ、それは実際の問題になります。突然、あなたがやりたくないすべての苦痛なものが現れます。そして、これは単にクリーンなコードを書くほど面白いことではないので、あなたは不機嫌になります。

There's really nothing a hacker likes more than having an empty editor buffer. You can just start to write code. That's the way university assignments are: here's a problem, just write the code. The real world is much cruftier.

空のエディタバッファ以上にハッカーが望むものは何もありません。あなたは単にコードを書き始めることができます。それは、大学の課題での方法です――問題がある。コードを書け。実際の世界は、もっと粗雑なものです。

But there's also a spectrum. You talk to people in banks, where large quantities of money get lost if there's a problem. They take failure very seriously. The spookiest folks are the people who have been doing real time software for a long time. I've spent a certain amount of time with that crowd. By and large these folks are very conservative and very careful. A lot of them know what it's like to go visit the family of the deceased and explain the bug to them. There are a number of places that have policies that if a test pilot augers in, then once it's all figured out what happened, the people who engineered the thing that failed have to go explain it to the family. I've actually only met one person who has ever actually had to do that. That would change your attitude about dealing with failure really quickly.

しかしそこにもまたスペクトラムがあります。あなたは銀行の人々と話します。そこでは、もし問題があれば多くのお金が失われます。彼らは失敗をとても真剣に受け止めます。最も神経質な人々は、リアルタイムソフトウエアを長年やっている人々です。私はそういった人々とある程度の時間を過ごしたことがあります。概して、これらの人々は、非常に保守的であり、かつ非常に慎重です。彼らの多くは、遺族を訪問してバグについて彼らに説明することがどんなことであるかを知っています。多くの場所に、テストパイロットが墜落し、何が起きたのかが解明できたら、失敗したものを設計した人がそれについて遺族に説明しに行かなければ行けない、というポリシーがあります。私は、ひとりだけ、実際にそれをしなければならなかった人に会ったことがあります。それは、失敗に対して対処することについてのあなたの態度をあっという間に変えることでしょう。

感想

――そうだよねえ、としか。Goslingがこう言ってくれるなら私はもうなにも言うことはありません。

You can't accidentally say, "I don't care." You have to explicitly say, "I don't care."

特にこのあたりなんか、前回の記事で私が書いたこととそっくりで、わが意を得たりと思ったことですよ。

2010/2/11追記:メールでご指摘をいただきあちこち修正しました。

*1:ここでの「open」はUNIXシステムコールを指していると思います。UNIXシステムコールのopen()は、エラー時、-1を返します。

*2:「plausible deniability」は、「秘書がやったことで、わしは知らん」といったように、地位の高い人間が、地位の低い人間から事を知らされなかった事を理由に、実際は存在する責任を逃れようとすることを意味するそうです参考

*3:one true pathって「正常系」のことですよね。

*4:飛行機の翼の先についている小さな翼。よく考えたら航空電子工学ソフトの話なんだから当たり前じゃないか。…とはいえ訳に自信がないことに変わりなし。

2010-01-01 The Trouble with Checked Exceptions(Anders Hejlsbergのインタビュ

[][]The Trouble with Checked Exceptions

JavaからC#に移った人は、C#にはなぜ検査例外がないのか? と疑問に思うと思います。それに対するC#作者Anders Hejlsbergのインタビュー記事を訳してみました(いままでにもまして訳に自信がないところが多いんですが)。

原文はこちら。

http://www.artima.com/intv/handcuffs.html

拙著「プログラミング言語を作る」内でも少し言及しています(p.340)。

ところで、JavaC#例外処理の違いというと「検査例外の有無」が取り上げられることが多いのですが、「スタックトレースが生成されるタイミング」も異なっており、Javaプログラマはたまにはまることがあります。その点も「プログラミング言語を作る」では言及しておりますのでぜひどうぞ(宣伝)。

関連記事:

MSDN内の記事

http://msdn.microsoft.com/en-us/vcsharp/aa336812.aspx

チェック例外がJavaにあってC#にない理由 - じゅんいち☆かとうの技術日誌にて紹介あり。

http://www.ibm.com/developerworks/jp/java/library/j-jtp05254/

Summary

Anders Hejlsberg, the lead C# architect, talks with Bruce Eckel and Bill Venners about versionability and scalability issues with checked exceptions.

C#アーキテクト長であるAnders Hejlsbergが、Bruce Eckel、Bill Vennersと、検査例外のversionabilityとスケーラビリティの問題について会話します*1

Anders Hejlsberg, a distinguished engineer at Microsoft, led the team that designed the C# (pronounced C Sharp) programming language. Hejlsberg first vaulted onto the software world stage in the early eighties by creating a Pascal compiler for MS-DOS and CP/M. A very young company called Borland soon hired Hejlsberg and bought his compiler, which was thereafter marketed as Turbo Pascal. At Borland, Hejlsberg continued to develop Turbo Pascal and eventually led the team that designed Turbo Pascal's replacement: Delphi. In 1996, after 13 years with Borland, Hejlsberg joined Microsoft, where he initially worked as an architect of Visual J++ and the Windows Foundation Classes (WFC). Subsequently, Hejlsberg was chief designer of C# and a key participant in the creation of the .NET framework. Currently, Anders Hejlsberg leads the continued development of the C# programming language.

Anders Hejlsbergは、Microsoftの著名なエンジニアであり、C#(Cシャープと発音されます)の設計チームを率いました。Hejlsbergはまず1980年代前半にMS-DOSCP/M向けのPascalコンパイラを作ることでソフトウエア業界に躍り出ました。Borlandと呼ばれる非常に若い会社はすぐにHeijlsbergを雇って彼のコンパイラを買い取りました。それは後にTurbo Pascalとして販売されることになりました。

Borlandでは、HejlsbergはTurbo Pascalの開発を続け、最終的にはTurbo Pascalを代替するもの:Delphiの設計チームを率いることになりました。1996年、Borlandでの13年の後、HejlsbergはMicrosoftに加わりました。そこでは彼は最初はVisual J++とWindows Foundation Classes(WFC)のアーキテクトとして働いていました。その後、HejlsbergはC#のチーフデザイナと、.NET Frameworkの構築における主要メンバーとなりました。現在、Anders Hejlsbergは、C#プログラミング言語の継続的な開発を率いています。

On July 30, 2003, Bruce Eckel, author of Thinking in C++ and Thinking in Java, and Bill Venners, editor-in-chief of Artima.com, met with Anders Hejlsberg in his office at Microsoft in Redmond, Washington. In this interview, which will be published in multiple installments on Artima.com and on an audio CD-ROM to be released this fall by Bruce Eckel, Anders Hejlsberg discusses many design choices of the C# language and the .NET framework.

2003年7月30日、Thinking in C++とThinking in Javaの著者であるBruce EckelおよびArtima.comの編集主任であるBill Vannersが、ワシントンのレドモンドにあるMicrosoftの彼のオフィスでAnders Hejlsbergに会いました。このインタビューは、今秋にはArtima.comとオーディオCD-ROMでBruce Eckelにより複数回に分けて公開されるでしょうが、Anders HejlsbergはC#言語と.NET frameworkにおける多くのデザイン上の選択について議論しています。

  • In Part I: The C# Design Process, Hejlsberg discusses the process used by the team that designed C#, and the relative merits of usability studies and good taste in language design.
  • In this second installment, Hejlsberg discusses versionability and scalability issues with checked exceptions.
  • Part I: The C# Design Processにおいては、HejlsbergはC#を設計したチームが使用したプロセスについて論じ、さらに言語設計の場において、使い勝手の調査を行うことと、良質なセンスを有していること、この優劣について議論しています。
  • 2回目となる今回は、Hejlsbergは検査例外のversionabilityとスケーラビリティにおける問題について議論します。

検査例外について中立的立場にとどまる(Remaining Neutral on Checked Exceptions)

Bruce Eckel: C# doesn't have checked exceptions. How did you decide whether or not to put checked exceptions into C#?

Bruce Eckel: C#には検査例外がありません。あなたは、C#に検査例外を入れるかどうか、どのように決めましたか?

Anders Hejlsberg: I see two big issues with checked exceptions: scalability and versionability. I know you've written some about checked exceptions too, and you tend to agree with our line of thinking.

Anders Hejlsberg: 私が見るに、検査例外にはふたつの大きな問題があります。スケーラビリティとversionabilityです。私は、あなたが検査例外についていくつか書いているのも知っていますが、あなたは我々の一連の考えにどちらかというと同意しているようです。

Bruce Eckel: I used to think that checked exceptions were really great.

Bruce Eckel: 私はかつては検査例外が本当にすばらしいと思っていました。

Anders Hejlsberg: Exactly. Frankly, they look really great up front, and there's nothing wrong with the idea. I completely agree that checked exceptions are a wonderful feature. It's just that particular implementations can be problematic. By implementing checked exceptions the way it's done in Java, for example, I think you just take one set of problems and trade them for another set of problems. In the end it's not clear to me that you actually make life any easier. You just make it different.

Anders Hejlsberg: まさしく。率直に言って、検査例外は傑出してすばらしく、何の悪いところもないアイディアに見えます。私は検査例外がすばらしい機能であることに完全に同意します。単に特定の実装が問題があるかもしれない、というだけです。たとえば、Javaで行われている検査例外の実装によって、あなたはひとそろいの問題をとりあげて、それを別の問題と交換しているだけだと私は考えます。結局、私にとって、あなたが人生を少しでも簡単にしているかどうかは明白ではありません。あなたは単にそれを異なるようにしているだけです*2

Bruce Eckel: Was there a lot of disagreement in the C# design team about checked excpetions?

Bruce Eckel:C#設計チームにおいて、検査例外について意見の食い違いはありましたか?

Anders Hejlsberg: No, I think there was fairly broad agreement in our design group.

Anders Hejlsberg: いいえ。われわれの設計チームには、かなり幅広い同意があったと思います。

C# is basically silent on the checked exceptions issue. Once a better solution is known――and trust me we continue to think about it――we can go back and actually put something in place. I'm a strong believer that if you don't have anything right to say, or anything that moves the art forward, then you'd better just be completely silent and neutral, as opposed to trying to lay out a framework.

C#は検査例外の問題について基本的に沈黙しています。ひとたびよい解決方法が見つかったなら――我々がそれについて考え続けているということを信じて欲しいのですが――我々は戻ってきてそれを実際にしかるべき場所に配備することができます*3。私は、あなたが何か正しい言い分や、技術を前進させる何かを持っているのでないのなら、あなたは完全に沈黙を保ち中立であるべきだ、ということを強く信じています。フレームワークを拡張しようとするのとは反対に。

If you ask beginning programmers to write a calendar control, they often think to themselves, "Oh, I'm going to write the world's best calendar control! It's going to be polymorphic with respect to the kind of calendar. It will have displayers, and mungers, and this, that, and the other." They need to ship a calendar application in two months. They put all this infrastructure into place in the control, and then spend two days writing a crappy calendar application on top of it. They'll think, "In the next version of the application, I'm going to do so much more."

もしあなたが駆け出しプログラマにカレンダーコントロールを書くように頼んだら、彼らはひそかに思うことでしょう。「よし、世界一のカレンダーコントロールを書いてやろう。それはカレンダーの種類に対して多態となって、displayerやmungerやあれやこれやを持っている」。彼らはそれを2ヵ月後に出荷する必要があります。彼らはこれらのインフラストラクチャをすべてコントロールに突っ込み、そして、その上にくだらないカレンダーコントロールを書くのに2日かけます。彼らは、「アプリケーションの次のバージョンでは、もっとずっとよいものにします」と言うでしょう。

Once they start thinking about how they're actually going to implement all of these other concretizations of their abstract design, however, it turns out that their design is completely wrong. And now they've painted themself into a corner, and they have to throw the whole thing out. I have seen that over and over. I'm a strong believer in being minimalistic. Unless you actually are going to solve the general problem, don't try and put in place a framework for solving a specific one, because you don't know what that framework should look like.

あるとき彼らが彼らの抽象的な設計の他のすべての具体化を実装しようと考え始め、しかし、その設計が完全に間違っていると判明します。そして今、彼らは自分の落ち度で困った状態に陥り、すべてを放り投げなければなりません。私は何度もそういうケースを見ました。私はミニマリズムの熱烈な信奉者です。実際に一般的な問題を解決しないなら、特定の問題を解決するためにフレームワークをいじくろうとしないでください。なぜならあなたはフレームワークがどうあるべきかについてまだわかっていないのですから。

Bruce Eckel: The Extreme Programmers say, "Do the simplest thing that could possibly work."

Bruce Eckel: エクストリームプログラミングプログラマは、「動くであろう最も単純なことをやる」と言います。

Anders Hejlsberg: Yeah, well, Einstein said that, "Do the simplest thing possible, but no simpler." The concern I have about checked exceptions is the handcuffs they put on programmers. You see programmers picking up new APIs that have all these throws clauses, and then you see how convoluted their code gets, and you realize the checked exceptions aren't helping them any. It is sort of these dictatorial API designers telling you how to do your exception handling. They should not be doing that.

Anders Hejlsberg: はい。アインシュタインはそれを「物事は可能な限りシンプルにやれ。手抜きでない限り」と言いました。検査例外について私が思うことは、それはプログラマにかけられた手錠だということです。あなたが、throws節を持つ新しいAPIを手に入れたプログラマを見たとします。その後、あなたはそれらのコードがどれくらい複雑になるかわかります。そして、あなたは、検査例外がそれをまったく助けていないということを認識するのです。いわば、APIの設計者たちのほうが支配的になって、例外処理の方法をあなたに指図しているのです。そんなことをさせるわけにはいきません。

検査例外とversioning(Versioning with Checked Exceptions)

Bill Venners: You mentioned scalability and versioning concerns with respect to checked exceptions. Could you clarify what you mean by those two issues?

Bill Venners: あなたは検査例外に関してスケーラビリティとバージョン関連のことについて言及しました。このふたつの問題が意味することをはっきりさせて頂けますか?

Anders Hejlsberg: Let's start with versioning, because the issues are pretty easy to see there. Let's say I create a method foo that declares it throws exceptions A, B, and C. In version two of foo, I want to add a bunch of features, and now foo might throw exception D. It is a breaking change for me to add D to the throws clause of that method, because existing caller of that method will almost certainly not handle that exception.

Anders Hejlsberg: versioningから始めましょう。この問題はかなり理解しやすいので。私が、例外A, B, Cを投げるかもしれないメソッドfooを作ったとしましょう。fooの第2バージョンでは、私は多くの特徴を加えたいと思います。よってfooは例外Dを投げるかもしれません。メソッドのthrows節に例外Dを付け足すことは、破壊的な変更です。なぜなら、既存の呼び出し元は、ほぼ確実にその例外をハンドルしないからです。

Adding a new exception to a throws clause in a new version breaks client code. It's like adding a method to an interface. After you publish an interface, it is for all practical purposes immutable, because any implementation of it might have the methods that you want to add in the next version. So you've got to create a new interface instead. Similarly with exceptions, you would either have to create a whole new method called foo2 that throws more exceptions, or you would have to catch exception D in the new foo, and transform the D into an A, B, or C.

新しいバージョンで、新しい例外をthrows節に書き足すことは、クライアントのコードを破壊します。それはインタフェースにメソッドを加えるようなものです*4。あなたがインタフェースを公開した後は、それは事実上変更不能になってしまいます。なぜなら、いかなるインターフェースの実装も、将来のバージョンであなたが付け加えたくなるメソッドまで含んでいてくれればよいですが、そういうわけには行かないからです。よって、あなたは代わりに新たなインタフェースを作らなければなりません。同様に、あなたはより多くの例外を投げるメソッドfoo2をまるごと作り直すか、新しいfooの中でDをcatchして、それをA, B, Cのどれかに変換しなければならないでしょう。

Bill Venners: But aren't you breaking their code in that case anyway, even in a language without checked exceptions? If the new version of foo is going to throw a new exception that clients should think about handling, isn't their code broken just by the fact that they didn't expect that exception when they wrote the code?

Bill Venners: しかしあなたは検査例外のない言語においても、いずれにせよそれらのコードを破壊していませんか? fooの新しいバージョンが、クライアントがハンドルすべきである新しい例外を投げる場合、呼び出し元のコードは、彼らがそのコードを書いたときにその例外を予期しなかったという事実によって破壊されるのではないですか?

Anders Hejlsberg: No, because in a lot of cases, people don't care. They're not going to handle any of these exceptions. There's a bottom level exception handler around their message loop. That handler is just going to bring up a dialog that says what went wrong and continue. The programmers protect their code by writing try finally's everywhere, so they'll back out correctly if an exception occurs, but they're not actually interested in handling the exceptions.

Anders Hejlsberg: いいえ。なぜなら、多くの場合、人々はそれを気にかけないからです。どうせ彼らはそれらの例外をどれもハンドルしないでしょう。最低限の例外ハンドラが彼らのメッセージループの外側にいます。そのハンドラは、何がおかしくなったのかということを示すダイアログを表示し、処理を続行することでしょう。プログラマは、彼らのコードを、try finallyをあらゆるところに書くことで保護しようとしており、よって例外が起きたとき彼らは正しくもみ消しますが、例外をちゃんとハンドルすることには興味を持っていないのです。

The throws clause, at least the way it's implemented in Java, doesn't necessarily force you to handle the exceptions, but if you don't handle them, it forces you to acknowledge precisely which exceptions might pass through. It requires you to either catch declared exceptions or put them in your own throws clause. To work around this requirement, people do ridiculous things. For example, they decorate every method with, "throws Exception." That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn't help anybody.

throws節は、少なくともJavaの実装においては、あなたに必ずしも例外のハンドルを強制しません。しかし、あなたがそれをハンドルしなくても、あなたに正確にどの例外があなたのメソッドをすり抜けていくかを認識することを強制します。それはあなたに、宣言された例外をcatchするか、自分のメソッドのthrows節に記述するかのどちらかを要求しているわけです。この要求の中でどうにかしようとするために、人々はおかしなことをします。たとえば、彼らはすべてのメソッドを「throws Exception」で修飾してしまうのです。これは、(検査例外の)特徴を完全にぶち壊しており、あなたは単にプログラマにわけのわからない汚物を書かせただけなのです。それは誰も助けません。

Bill Venners: So you think the more common case is that callers don't explicitly handle exceptions in deference to a general catch clause further up the call stack?

Bill Venners: それではあなたはより一般的なケースでは、呼び出し元はコールスタックのもっと上に一般的なcatch節があることを想定して、例外を陽にハンドルしないと思いますか?

Anders Hejlsberg: It is funny how people think that the important thing about exceptions is handling them. That is not the important thing about exceptions. In a well-written application there's a ratio of ten to one, in my opinion, of try finally to try catch. Or in C#, using statements, which are like try finally.

Anders Hejlsberg:

どうしたわけか、例外について重要なのはそれをハンドルすることだと考えられていますが、それはおかしな話です*5。それは例外について重要なことではありません。よく記述されたアプリケーションにおいて、私の意見では、try finallyはtry catchの10倍もあるのです。または、C#においては、try finallyに似たusing文となります。

Bill Venners: What's in the finally?

Bill Venners: finallyの中には何があるのでしょうか?

Anders Hejlsberg: In the finally, you protect yourself against the exceptions, but you don't actually handle them. Error handling you put somewhere else. Surely in any kind of event-driven application like any kind of modern UI, you typically put an exception handler around your main message pump, and you just handle exceptions as they fall out that way. But you make sure you protect yourself all the way out by deallocating any resources you've grabbed, and so forth. You clean up after yourself, so you're always in a consistent state. You don't want a program where in 100 different places you handle exceptions and pop up error dialogs. What if you want to change the way you put up that dialog box? That's just terrible. The exception handling should be centralized, and you should just protect yourself as the exceptions propagate out to the handler.

Anders Hejlsberg: finallyの中では、あなたは例外から自分自身を防御することになります。しかし、自分で例外をハンドルするわけではありません。あなたは例外処理をどこか他の場所に置くわけです。近代的なUIのようなどんな種類のイベントドリブンアプリケーションにおいても、あなたは、通常メッセージポンプの外側に例外ハンドラを置きます。そして、あなたは、そこまで落っこちた例外のみをハンドルするのです。しかし、あなたは、あなたが確保したあらゆるリソースを開放等することで、自分自身のコードを確実に防御できます。自分で後始末するので、あなた自身は常に一貫した状態にあります。あなたは100の異なった場所において、自分で例外をハンドルし、エラーダイアログポップアップするプログラムを欲しくはありません。あなたがそのダイアログボックスを提供する方法を変えたい場合、どうなるでしょうか? それはまさにひどいものとなります。例外処理は集結されるべきです。そして、例外がハンドラの外に伝播するとき、あなたはただ自分自身を防御すべきです。

検査例外のスケーラビリティ(The Scalability of Checked Exceptions)

Bill Venners: What is the scalability issue with checked exceptions?

検査例外に関するスケーラビリティの問題とは何でしょうか?

Anders Hejlsberg: The scalability issue is somewhat related to the versionability issue. In the small, checked exceptions are very enticing. With a little example, you can show that you've actually checked that you caught the FileNotFoundException, and isn't that great? Well, that's fine when you're just calling one API. The trouble begins when you start building big systems where you're talking to four or five different subsystems. Each subsystem throws four to ten exceptions. Now, each time you walk up the ladder of aggregation, you have this exponential hierarchy below you of exceptions you have to deal with. You end up having to declare 40 exceptions that you might throw. And once you aggregate that with another subsystem you've got 80 exceptions in your throws clause. It just balloons out of control.

Anders Hejlsberg: スケーラビリティの問題は、versionabilityの問題とも関連します。小さなプログラムでは、検査例外はとても魅力的です。小さな例で言えば、あなたは、FileNotFoundExceptionをちゃんと捕捉したことを確認した、ということを示すことができます。これってすごくない? さて、あなたがひとつのAPIだけを読んでいる場合、これはすばらしいものです。あなたが4つか5つの異なるサブシステムと対話するような大規模システムを作り始めた時に問題が始まります。各サブシステムは、4〜10の例外を投げます。いまや、あなたはモジュールの階層を上がるたびに、指数関数的に増加する、対処すべき例外を持つことになります。あなたは結局、投げる可能性のある40個の例外を宣言しなければなりません。そして、ひとたびそれを別のサブシステムと組み合わせると、あなたはthrows節に80個の例外を並べることになります。それは制御不能なほど膨張していきます。

In the large, checked exceptions become such an irritation that people completely circumvent the feature. They either say, "throws Exception," everywhere; or―and I can't tell you how many times I've seen this―they say, "try, da da da da da, catch curly curly." They think, "Oh I'll come back and deal with these empty catch clauses later," and then of course they never do. In those situations, checked exceptions have actually degraded the quality of the system in the large.

大規模システムでは、検査例外は、人々が完全にその特徴を回避してしまうほどの苛立ちとなります。彼らはまた、いたるところで「throws Exception」と口にします*6。または――私はこれを何回見たか言うことができません――「try, なんとかかんとか、catch { }」。彼らはこう考えます。「ああ、あとで戻ってきてこの空のcatch節をちゃんと対応しよう」。そして、もちろん彼らはその後決してそれをやりません。こういうシチュエーションでは、検査例外は、実際に大規模システムの品質を下げました。

And so, when you take all of these issues, to me it just seems more thinking is needed before we put some kind of checked exceptions mechanism in place for C#. But that said, there's certainly tremendous value in knowing what exceptions can get thrown, and having some sort of tool that checks. I don't think we can construct hard and fast rules down to, it is either a compiler error or not. But I think we can certainly do a lot with analysis tools that detect suspicious code, including uncaught exceptions, and points out those potential holes to you.

そして、あなたがこれらの問題すべてを受け入れるときでも、我々が何らかの検査例外のメカニズムC#に組み込む前にもっと検討が必要に見えます。そうは言っても、どんな例外が投げられるかを知ることができ、かつそれをチェックするなんらかのツールがあれば、確実に途方もない価値があります。コンパイルエラーにするか、それ以外のところに落とし込むか、いずれにしろ、私は杓子定規なルールを構築してもよいとは考えていません。しかし、我々は、(catchされない例外を含む)疑わしいコードを検出し、潜在的な穴を指摘してくれる分析ツールを使って多くのことができると思います*7

感想

いやもう全然納得できない。メソッドシグニチャは利用者側との契約なんだから、契約が変わるのなら呼び出し側に対応が必要なのは当たり前だし、対応していないのならエラーを出してくれなければ困る。下位モジュールの例外はラッピングすればいいし。「どうせみんなちゃんと対応しないんだから検査例外不要」と言われてもなあ。

2010/1/10追記:

メールで色々ご指摘をいただいたので反映しました。ありがとうございました。

もともと脚注をつけていたところには、注番号がずれないように、「ご指摘により修正」という注を入れています(修正箇所はそこに限るわけではありません)。

*1:versionabilityって、意味はわかるのですが、いい訳語が思いつかないので英語のままとします。

*2:ここのitってlifeのことで、easierにすることはできてなくてdifferentにしているだけだ、ということでよいんでしょうか。

*3:ご指摘により修正

*4:ここのinterfaceとmethodは、C#の文法要素としてのinterfaceとmethodでいいんでしょうか……インタフェースにメソッドを追加してもクライアントのコードは壊さないので、意味が通らないような。

*5:ご指摘により修正

*6:ご指摘により修正。

*7:points outの主語は何なんでしょう? toolsならpoint"s"にはならないはずですし……

2009-12-27 Exceptions(Joel Spolsky氏の記事より)

[][]Exceptions(Joel Spolsky氏の記事より)

この記事

間違ったコードは間違って見えるようにする - The Joel on Software Translation Project

の下のほうに

終わる前に、もうひとつ約束していることがあった。もう一度例外を攻撃するということだ。前にやったときには、すごいトラブルに見舞われた。Joel on Softwareのホームページに即席のコメントで例外が嫌いだと書いた。例外は実質的に見えないgotoであり、目に見えるgotoよりいっそう悪いという議論をしたのだ。もちろん何百万という人々が私ののど元に飛びかかってきた。私の擁護に立ち上がった唯一の人間は、もちろんレイモンド・チェンで、彼は世界最高のプログラマなわけだから、それは意味のあることなはずだ。そうだよね?

という記述があったので、その中のリンク先を翻訳してみました。

私がこの記事に言及するのは初めてではなくて、拙著「プログラミング言語を作る」の中でも部分的に引用しています(p.341)。

よろしければそちらもどうぞ。(宣伝)

原文へのリンク

http://www.joelonsoftware.com/items/2003/10/13.html

People have asked why I don't like programming with exceptions. In both Java and C++, my policy is:

人々が私に、私がなぜ例外を使うプログラミングを好まないのか、と聞いたことがある。JavaC++の両方において、私のポリシーはこうだ。

  1. Never throw an exception of my own
  2. Always catch any possible exception that might be thrown by a library I'm using on the same line as it is thrown and deal with it immediately.
  1. 自分では絶対に例外を投げない。
  2. 使用しているライブラリにが投げる可能性があるすべての例外は、常にそれを投げられたのと同じ行でcatchし、即座に対応する。

The reasoning is that I consider exceptions to be no better than "goto's", considered harmful since the 1960s, in that they create an abrupt jump from one point of code to another. In fact they are significantly worse than goto's:

そのようにする論拠は、私が例外を、コード上のある箇所から別の箇所までの突然のジャンプを作り出してしまうという点で、1960年代から有害と考えられてきたgotoよりもよいものだとは考えていないからだ。実のところ例外はgotoよりかなり悪い。

  1. They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn't reveal potential bugs.
  2. They create too many possible exit points for a function. To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don't catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn't think about.
  1. 例外はコード上で見えない。例外を投げるかもしれないし投げないかもしれない関数を含むコードブロックを見たとき、そこからどの例外が投げられるのかどうかを知る手段はない。これは、いかに注意深くコードの精査を行っても、潜在バグを検出できないということを意味する。
  2. 例外は関数に多くの「脱出口」を作り出す。正しいコードを書くには、あなたは自分の関数内のあらゆるコードの実行経路について本当に考慮しなければならない。例外を上げるかもしれない関数を呼び、それをその場でcatchしないときはいつも、あなたは、データを整合性のない状態にしたまま突然中断された関数や、あなたが考慮しなかった別のコードの実行経路による驚くようなバグが発生する機会を作り出しているのだ。

A better alternative is to have your functions return error values when things go wrong, and to deal with these explicitly, no matter how verbose it might be. It is true that what should be a simple 3 line program often blossoms to 48 lines when you put in good error checking, but that's life, and papering it over with exceptions does not make your program more robust.

よりよい代替手段は、何かがおかしくなったときは戻り値としてエラーを返し、それに明示的に対処することだ。それがどんなに冗長になったとしても。シンプルな3行のプログラムであるべきものが、適切なエラーチェックを入れたら48行になってしまうということはしばしばあるというのは事実だが、それが人生というものであり、例外でそれを取り繕ってもあなたのプログラムを頑健にしてくれはしないのだ。

I think the reason programmers in C/C++/Java style languages have been attracted to exceptions is simply because the syntax does not have a concise way to call a function that returns multiple values, so it's hard to write a function that either produces a return value or returns an error. (The only languages I have used extensively that do let you return multiple values nicely are ML and Haskell.) In C/C++/Java style languages one way you can handle errors is to use the real return value for a result status, and if you have anything you want to return, use an OUT parameter to do that. This has the unforunate side effect of making it impossible to nest function calls, so result = f(g(x)) must become:

C/C++/Javaスタイルの言語を使っているプログラマが例外に惹きつけられた理由は、単純に、文法が複数の値を返す関数を呼び出す簡単な方法を持たないため、戻り値とエラーのどちらか一方を返す関数が書きにくかったからではないかと私は考える(私が手広く使った中で、複数の値をうまく返させてくれる言語はMLHaskellだけだ)。C/C++/Javaスタイルの言語では、あなたがエラーをハンドルできるひとつの方法は、真の戻り値を結果ステータスとして使うことであり、かつ、何であれ関数から返したいものはOUTパラメタを使うことだ。これには、関数呼び出しのネストが不可能になるという不幸な副作用がある。result = f(g(x))は、こう書かなければいけない。

T tmp;
if (ERROR == g(x, tmp))
     errorhandling;
if (ERROR == f(tmp, result))
     errorhandling;

This is ugly and annoying but it's better than getting magic unexpected gotos sprinkled throughout your code at unpredictable places.

これは醜いしうっとうしいが、予測できない魔法のgotoを、あなたのコードの全域にわたって、予想不能な箇所にばらまくよりはマシだ*1

感想

言わんとするところはわからなくはないのですが、個人的には全然賛同できません。

戻り値でちまちまエラーケースを上位に戻していってうまくいくと思えるほど、私は(自分を含む)プログラマを信用していない、ということなんだと思います。だからcrowbarにもDiksamにも例外処理機構を入れました。

プログラミング言語を作る」中でも例を挙げていますけど、確かに例外で処理がブッチされることでデータ構造が壊れることはあり得ます。じゃあ戻り値ちまちま方式ならそれが防げるかと言うとそうとも思えないし、どっかで対処を怠り例外をもみ消してしまう危険の方が高いと思います。

*1:at unpredicable placesがどこにかかるのかよくわかりませんでした……(2010/1/7追記:yuyaさんのコメント参照のこと。)

2009-12-23 On programming language design

[][]On programming language design

InfoQの以下の記事経由で、

Andrej Bauer氏の語るプログラミング言語の設計

こういう記事を見つけたので、

On programming language design | Mathematics and Computation

日本語に(勝手に)訳してみました。

英語が得意なわけでもないので(ていうか苦手なほうなので)変なところ等ありましたらご指摘願います。

――というかHaskellをちゃんと勉強したくなった。

In a recent post I claimed that Python’s lambda construct is broken. This attracted some angry responses by people who thought I was confused about how Python works. Luckily there were also many useful responses from which I learnt. This post is a response to comment 27, which asks me to say more about my calling certain design decisions in Python crazy.

最近のポストで、私は、Pythonのlambdaの機構は壊れていると主張した。このポストは、私がPythonがどう動くかについて混乱していると考えた人々による、いくつかの怒った応答を呼び寄せたが、幸運にも、そこから私が学ぶことができる、多くの有用な応答も含まれていた。このポストは、私が、Pythonの設計上の決定について狂っていると称している点について、もっと語ってほしいと私に依頼したコメント27番への応答である。

Language design is like architecture. The architect is bound by the rules of nature, he has to take into account the properties of the building materials, and he must never forget the purpose that the building will serve. Likewise, the designer of a programming language is bound by the theorems of computability theory, he must take into account the properties of the underlying hardware, and he must never forget that the language is used by programmers.

言語設計は建築に似ている。建築家は自然の規則に縛られ、建築材料の特性を考慮に入れなければならない。そして、彼はビルが今後役に立つであろうその目的を決して忘れてはいけない。同様に、プログラミング言語の設計者は計算可能性理論の定理によって縛られ、基盤となるハードウェアの特性を考慮に入れなければならない。そして、彼は言語がプログラマによって使用されるということを決して忘れてはいけない。

When I teach the theory of programming languages, I tell my students that there is a design principle from which almost everything else follows:

“Programmers are just humans: forgetful, lazy, and they make every mistake imaginable.”

私がプログラミング言語の理論について教えるとき、私は学生に、他の設計原理のほとんどすべてが従うべき設計原理がある、と教える。

 「プログラマはただの人間である――忘れやすく、怠惰で、およそ想像できるすべての誤りをしでかしてくれる――」

Therefore, it is the task of the designer to make a programming language which counters these deficiencies. A language must not be too complex, lest the programmer forget half of it. A language must support the programmer’s laziness by providing lots of useful libraries, and by making it possible to express ideas directly and succinctly. The language must allow good organization of source code, otherwise the programmer will use the copy-paste method. The language must try really hard to catch programming mistakes, especially the mundane ones that happen to everyone all the time. When it finds a mistake, it must point to the true reason for it, preferably with an error message that humans understand.

したがって、言語設計者がやるべきことは、これらの欠点に対抗するようなプログラミング言語を作ることである。プログラマがそれを半分忘れてしまったりしないよう、言語は過度に複雑であってはならない。言語はたくさんの便利なライブラリを提供し、かつアイディアを直接的かつ簡潔に表現できるようにすることで、プログラマの怠惰さをサポートしなければならない。言語は、ソースコードのよい組織化を可能にしなければならない。さもなければプログラマはコピー&ペーストを使ってしまうであろう。言語は、プログラミング上のミスを捕らえるよう最大限の努力をしなければならない。とりわけ誰にとってもいつでも起きるようなありふれたミスについては。ミスを見つけたときには、なるべく人間が理解できるエラーメッセージとともに、その理由を正確に指摘しなければならない。

You will notice that so far I have not said a word about efficiency. If this were the year 1972 we would talk about efficiency first and forget about the programmers, because 37 years ago hardware and processing time were the scarcest resources. Today we live in different times when the most expensive resource is development time. In 1972 it was a good design decision to implement arrays in C so that they did not carry with them information about their lengths (save a couple of bytes on each array), it was a good decision not to check for out-of-bounds errors in array indexing (save a couple of CPU cycles), and it was a good decision not to have garbage collection (it didn’t work well anyhow). From today’s point of view all these decisions were horrible mistakes. Buffer overflows, which are a consequence of missing out-of-bounds checks, cost the industry huge amounts of money every year, while lack of automated garbage collection results in memory leaks that cause programs to be unstable.

あなたも気づくように、私は今までのところ効率という言葉を使っていない。もしこれが1972年であれば、我々はまず効率について議論しプログラマのことを忘れ去ってしまったことであろう。なぜなら37年前は、ハードウエアとCPU時間が最も不十分な資源であったから。今日では、我々は、最も高価なリソースは開発に要する時間である、という異なった時代に生きている。1972年においては、(配列ごとに数バイトを節約するために)それ自体の長さに対する情報を持たないというCの配列の実装は、よいデザイン上の決定であった。(数CPUサイクルを節約するために)配列参照にて範囲エラーのチェックを行わないというのもよい決定であった。そしてガベージコレクション(それはいつもうまくいかない)を行わないというのもよい決定であった。今日の視点から見れば、これらの決定は恐ろしい間違いであった。範囲チェックの欠如から引き起こされるバッファオーバーフローは、毎年産業界に莫大な金額を支払わせており、おまけに自動ガベージコレクションの欠如は、プログラムを不安定にするメモリリークをもたらしている。

Of course, even today C might be just the right tool for your specific task. I am not saying that memory efficiency and speed are not important. They are not as important as they used to be. The first objective in a programming language design today should be friendliness to programmers. A lot is known about how to write an optimizing compiler and how to generate efficient code, so usually the design of the language does not prevent generation of efficient compiled or interpreted code.

もちろん、今日においてもCは特定のタスクにおいてはよいツールでありうる。私はメモリ効率や速度が重要でないとは言っていない。それらがかつて重要であったのと同じようには、現在では重要ではない、と言っているのである。今日では、プログラミング言語のデザインにおける最初の目標は、プログラマに対し親切であることである。最適化コンパイラの書き方や効率的なコード生成の方法については多くのことが知られており、言語のデザインが効率的なコンパイルやインタープリトを妨げることは通常ない。

People do not make bad design decisions because they are evil or stupid. They make them because they judge that the advantages of the decision outweigh the disadvantages. What they often do not see is that they could have achieved the same advantages in a different way, without introducing the disadvantages. Therefore, it is very important to get the order right: first make sure the design avoids the disadvantages, then think about how to get the advantages back.

人々は、邪悪であったりおろかであるから悪いデザイン上の決定をしてしまうわけではない。彼らは、その決定による利点が欠点を上回ると判断したからそうしたのだ。かれらがしばしば見落とすのは、他の方法を取ることで、欠点を取り込むことなく利点を獲得できたかもしれないということだ。よって、正しい順番がきわめて重要である。まずデザインは欠点を避けるべきであり、それから利点をいかに取り戻すかを考えるべきなのだ。

Let us now apply these principles to several examples.

いくつかの例に、この原則を適用させてほしい。

未定義値(NULL, null, undef, None)――Undefined values (NULL, null, undef, None)

Suppose we want a language with references (pointers in C). The principle tells us that it is a bad idea to allow invalid references because programmers will create them. Indeed, most recently designed languages, say Java and Python, do not allow you to write obviously risky things, such as

int *p = (int *)0xabcdef;

我々が、参照(Cでいうポインタ)がある言語を欲しがっているとする。原則によれば、不正な参照を許すのはよくないアイディアである。なぜならプログラマはそれを作り出してしまうから。実際に、ほとんどの最近デザインされた言語、たとえばJavaやPythonでは、以下のような明らかに危険なことを書くことは許されていない。

int *p = (int *)0xabcdef;

Unfortunately, many designers have still not learnt that the special NULL pointer or null object is an equally bad idea. Python’s None, perl’s undef, and SQL’s NULL all fall in the same category. I can hear you list lots of advantages of having these. But stick to the principle: NULL is wrong because it causes horrible and tricky mistakes which appear even after the program was tested thoroughly. You cannot introduce NULL into the language and tell the programmer to be careful about it. The programmer is not capable of being careful! There is plenty of evidence to support this sad fact.

不幸にして、多くの設計者が、特別なNULLポインタやnullオブジェクトが同じように悪いアイディアである、ということを学んでいない。PythonのNone, Perlのundef, SQLのNULLはすべて同じカテゴリに入る。私は、あなたから、これらがあることによる利点をたくさん聞くことができるだろう。しかし、原則にこだわるなら、NULLは、それがプログラムを徹底的にテストした後でさえ発生する、恐ろしくかつやっかいな間違いを引き起こすという点において、間違っているのだ。あなたは、言語にNULLを取り入れ、プログラマに注意しろと言うことはできない。プログラマは注意深くあることなどできないからだ! この悲しい事実を支持する証拠は山ほどある。

Therefore NULL, null, None and undef must go. I shall collectively denote these with Python’s None. Of course, if we take away None, we must put something else back in. To see what is needed, consider the fact that None is intended as a special constant that signifies “missing value”. Problems occur when a given value could be either “proper” or “missing” and the programmer forgets to consider the case of missing value. The solution is to design the language in such a way that the programmer is always forced to consider both possibilities.

よって、NULL, null, None, undefは消え去るべきである。私は、これらをまとめてPythonのNoneで表そうと思う。もちろん、Noneを取り去ってしまうなら、我々は他の何かを導入しなければならない。何が必要かを見ていくには、Noneは、「欠損した値」を意味する特別な定数を意図しているとみなすことだ。与えられた値が「正しい」ことも「欠損している」こともあり、かつ、プログラマが欠損した値のケースの考慮を忘れたときに問題が発生する。解決策は、そのようなケースでは、プログラマに両方の値を考慮することを強制することだ。

For example, Haskell does this with the datatype Maybe, which has two kinds of values:

Nothing, meaning “missing value”

Just x, meaning “the value is x“

たとえば、HaskellはこれをMaybeというデータ型で実現している。これは2種類の値を持つ。

Nothing, 欠損値を意味する。

Just x, 値がxであることを意味する

The only way to use such a value in Haskell is to consider both cases, otherwise the compiler complains. The language is forcing the programmer to do the right thing. Is this annoying? You will probably feel annoyed if you are used to ugly hacks with None, but a bit of experience will quickly convince you that the advantages easily outweigh your tendency for laziness. By the way, Haskell actually supports your laziness. Once you tell it that the type of a value is Maybe, it will find for you all the places where you need to be careful about Nothing. C, Java, Python, and perl stay silent and let you suffer through your own mistaken uses of NULL’s, null’s, None’s, and undef’s.

Haskellにおいては、このような値を扱う唯一の手段は、両方のケースを考慮することである。さもなければコンパイラが文句を言う。言語が、プログラマに正しいことをするように強制している。これは煩わしいことだろうか? もしあなたがNoneを使った醜いハックに慣れているのであれば、煩わしく感じられることだろう。しかし、ちょっと経験すれば、怠惰のためのあなたの性癖よりも利点の方がずっと上回っていることにすぐに納得するだろう。ところで、Haskellはまさにあなたの怠惰さを支援する。その値の型がMaybeであると一度言ってしまいさえすれば、HaskellはNothingについて気をつけるべき箇所すべてをあなたのために見つけてくれる。C, Java, Python, perlは、ずっとだまっていて、あなた自身がNULLやnullやNoneやundefを使って犯したミスであなたを苦しませることになる。

Other languages that let you have the data type like Haskell’s Maybe are ML and Ocaml because they have sum types. Pascal, Modula-2 and C have broken sum types because they require the programmer to handle the tag by hand.

HaskellのMaybeのようなデータ型を持つほかの言語は、MLやOcalmlである。なぜならこれらには「sum types」があるから。PascalやModula-2やCは壊れた「sum types」を持っている。なぜならそれらはプログラマにそのタグを手で操作することを強制するからだ*1

あらゆるものがオブジェクト(またはリストや配列)――Everything is an object (or list, or array)

Many languages are advertised as “simple” because in them everything is expressed with just a couple of basic concepts. Lisp and scheme programmers proudly represent all sorts of data with conses and lists. Fortran programmers implement linked lists and trees with arrays. In Java and Python “everything is an object”, more or less.

多くの言語は、その言語ではすべてが少数の基本的な概念で表現できるからシンプルである、と宣伝されている。Lispとschemeのプログラマは誇らしげにあらゆる種類のデータをコンスセルとリストで表現する。Fortranのプログラマは連結リストやツリーを配列で実装する。JavaやPythonでは、「あらゆるものがオブジェクト」だ(まあだいたいは)。

It is good to have a simple language, but it is not good to sacrifice its expressiveness to the point where most of the time the programmer has to encode the concepts that he really needs indirectly with those available in the language. Programmers cannot do such things reliably, and the compiler cannot help them with the task because it does not know what is in programmer’s head.

シンプルな言語を持つのは良いことだ。しかし、表現力を犠牲にし、プログラマが本当に必要とする概念をコード化しようとする際に、言語で可能な機能では間接的にしか表現できないのなら、それはよくないことだ。プログラマはそんなことを確実に行うことはできないし、コンパイラもそれを助けることはできない。なぜならコンパイラプログラマの頭の中にあることを知らないからだ。

Let us look at a typical example in scheme. Suppose we would like to represent binary trees in which the nodes are labeled with integers. In scheme we might do this by representing the empty tree as (), and use a three-element list (k l r) to represent a tree whose root is labeled by k, the left subtree is l, and the right subtree is r. A quick search on Google shows that this is a popular way of implementing trees in scheme. It’s simple, it’s cool, it’s easy to explain to the students, but scheme will have no idea whatsoever what you’re doing. There are a number of trivial mistakes which can be made with such a representation, and scheme won’t detect them (at best you will get a runtime error): you might write (l k r) instead of (k l r), you might mistakenly pass a four-element list to a function expecting a tree, you might mistakenly think that the integer 42 is a valid representation of the tree (42 () ()), you might mistakenly try to compute the left subtree of the empty tree, etc. And remember, the programmer will make all these mistakes.

schemeにおける典型的な例を示す。我々が、各ノードが整数でラベル付けされる二分木を表現したかったとする。schemeでは、我々はこう表現するだろう。空の木は()とし、そして3つの要素を持つリスト(k l r)により、そのルートがkでラベルされ、左の部分木をl、右の部分木をrとする木を表現する。Googleでちょっと検索してみると、これはschemeにおける木の実装としてポピュラーなものだ。シンプルで、クールで、学生に説明しやすい。しかし、schemeは、あなたがやろうとしていることについて何一つ知らない。この表現方法には、犯しうる些細な間違いがたくさんあり、schemeはそれを検出できない(よくてもあなたはランタイムエラーを受け取るだけだろう)。あなたは(k l r)の代わりに(l k r)と書いてしまうかもしれないし、ツリーを期待している関数に間違って4つの要素のリストを渡してしまうかもしれないし、整数の42が、(42 () ())という木の正しい表現だと思ってしまうかもしれないし、間違って空のツリーの左の部分木を計算しようとするかもしれないし、等々。そして、思い出そう。プログラマとはこれらのあらゆる間違いを犯してしまうものなのだ。

With objects the situation is somewhat better. In Java we would define a class Tree with three attributes root, left, and right. It will be impossible to build a tree with a missing attribute, or too many attributes. But we will hit another problem: how to represent the empty tree? There are several choices none of which is ideal:

オブジェクトがあれば状況はいくぶんよくなる。Javaでは、我々はroot, left, rightの3つの属性を持つTreeクラスを定義するだろう。欠けた属性、多すぎる属性を持つ持つ木は作れない。しかし、我々は別の問題に突き当たる。空の木はどう表現すべきだろうか? どれも理想的ではないが、いくつかの方法がある。

  1. the empty tree is null: this is the worst solution, as any Java programmer knows
  2. we define a class Tree and subclasses EmptyTree and NodeTree represent the two different kinds of tree
  3. we add a fourth attribute empty of type boolean which tells us whether the tree is empty
  1. 空の木はnullとする:これは、どんなJavaプログラマでも知っているように、最悪の解決方法である。
  2. Treeクラスと、2種の異なる木(のノード)を表現するため、サブクラスEmptyTreeとNodeTreeを定義する。
  3. 4つ目のboolean型の属性emptyを追加し、それにより木が空であることを示す。

There are probably other options. The first solution is horrible, as every Java programmer knows, because it leads to many NullPointerExceptions. The second solution is probably the most “object-orientedly correct” but people find it impractical, as it spreads code around in two classes. When I taught java I lectured the third solution, but that one has the big disadvantage that the programmer is responsible for checking every time whether a tree is empty or not.

おそらく他にも選択肢はあるだろう。最初の解決方法は、すべてのJavaプログラマが知っているようにひどいものだ。なぜなら多くのNullPointerExceptionを引き起こすから。2番目の解決方法はおそらくもっとも「オブジェクト指向的に正しい」が、人々はそれが実用的でないことを知っている。コードが2箇所に分散してしまうからだ。私がJavaを教えたときは3番目の解決方法を教えたが、この方法は、プログラマが毎回木が空かそうでないかを判定する責任を負うという大きな欠点がある。

A decent programming language should help with the following common problems regarding binary trees:

まともなプログラミング言語は、二分木に対する以下のよくある問題について、何らかの助けをするべきだ。

  1. Prevent the construction of an invalid tree, such as one with missing parts, or dangling pointers.
  2. Prevent at compile time access to a component which is not there. For example, the compiler should detect the fact that the programmer is trying to access the left subtree of the empty tree.
  3. Make sure the programmer never forgets to consider both possibilities - the empty tree and the non-empty tree.
  1. 不正な木を作ってしまうことを防止する。部分が欠けていたり、ダングリングポインタのような。
  2. 存在しない要素に対するアクセスを、コンパイル時に防止する。たとえば、コンパイラプログラマが空の木の左の部分木にアクセスしようとしていることを検出すべきである。
  3. プログラマが、空の木、空でない木の両方の可能性について考慮することを決して忘れないようにする。

The above scheme representation does not help with the first problem. A C implementation with pointers would allow dangling pointers. An object-oriented solution typically won’t help with the second and the third problems.

上のschemeの表現は、最初の問題について助けになってくれない。Cのポインタによる実装はダングリングポインタを許す。オブジェクト指向の解決法は2番目と3番目の問題について通常は助けになってくれない。

You might wonder what it is that I want. The answer is that the programming language should have built-in inductive data types, because that’s what binary trees are. In Haskell, which has inductive data types, trees are defined directly in terms of their structure:

data Tree = Empty | Node Int Tree Tree

あなたは私が欲しているものが何か不思議に思うかもしれない。回答は、プログラミング言語は組み込みのinductive data型を持つべきだ。なぜなら二分木がそうであるから。inductive data型を持つHaskellなら、木は言語の機構を使って直接に定義できる*2

data Tree = Empty | Node Int Tree Tree

This expresses the definition of trees directly: a tree is either empty or a node composed of an integer and two trees. Haskell will be able to catch all the common problems listed above. Other languages supporting this sort of definition are ML, Ocaml, F#, and interestingly Visual Prolog (I am told by Wikipedia).

これは、木の定義を直接に表現している。木は、空または整数とふたつの木から成るかもしれない。Haskellは上であげたすべてのよくある問題を検出できる。このような定義をサポートするほかの言語はMLとOcamlとF#、そして面白いことにVisual Prologである(私はこれをWikipediaで知った)。

We might ask for more. Suppose we wanted to implement binary search trees. Then we would require that the left subtree only contains nodes that are smaller than the root, and the right subtree only nodes that are larger than the root. Can a programming language be designed so that this property is guaranteed? Yes, for example the compiler could insert suitable checks into the code so that anomalies are detected during execution as soon as they occur. This might be nice for debugging purposes, but what is production code supposed to do if it discovers an anomalous data structure during its execution? Ignore it? Raise an exception? It is much more useful to know before the program is run that the data structure will never be corrupted. Here we hit against a law of nature: there is no algorithm that would analyze an arbitrary piece of code and determine whether it will only produce valid search trees. It is a fact of life. If you really want to check that your programs are correct you will have to help the compiler. There are excellent tools for doing that, such as Coq and Agda―have a look to see how programmers might develop their code in the future.

我々はさらに求めるだろう。二分探索木が欲しいとする。すると我々は左の部分木はルートより小さなノードだけを含み、右の部分木はルートよりも大きいノードだけを含むことを要求する。プログラミング言語を、この特性が保証されるように設計することはできるだろうか? イエス。たとえば、コンパイラは、実行中、異常が発生後即座に検出できるよう、チェックするコードを挿入することができる。これはデバッグ目的に良いかもしれないが、製品版のコードでは、実行中に異常なデータ構造を発見したらどうすべきだろうか? 無視する? 例外を投げる? 実行前に知ることができ、データ構造が決して壊れなければ、そのほうがずっと役に立つ。ここで、我々は、自然の法則に突き当たる。ある任意のコードを分析して、それが必ず正しい探索木しか生成しないのかどうかを決定するアルゴリズムは存在しない。これは動かすことのできない現実である。もしあなたが本当に自分のプログラムが正しいことをチェックしたかったら、あなたがコンパイラの手助けをしなければならない。それをするためのすばらしいツールがある――CoqとかAgdaとか。見て、プログラマが将来どうコードを開発するようになるかを見るといい。

定義と変数の混同――Confusing definitions and variables

A definition binds an identifier to a particular fixed value. A variable or a mutable value is a memory location which holds a value that can be read and changed. These two notions should not be confused. Unfortunately, traditional programming languages only provide variables, so many programmers don’t even understand what definitions are. Java tries to fix this with the final declaration, and C++ with the const declaration, but these are not used by programmers as much as they could be (which is a typical sign of dubious design decisions).

定義は、ある識別子を特定の決まった値に束縛する。変数すなわち変更可能な値は、読み書きできる値を保持したメモリ領域である。このふたつの概念は混同すべきではない。不幸にして、伝統的なプログラミング言語は変数だけを提供してきた。そのため、多くのプログラマは定義とは何であるかを理解さえしていない。Javaはfinal宣言でこれを修正しようとし、C++はconst宣言でそうしようとしたが、それは、実際にそれが使える箇所ほどには使われていない(これは疑わしい設計上の決定の典型的な兆候である)。

Using variables instead of definitions is wrong for a number of reasons. First, if the compiler knows which identifiers are bound to immutable values it can optimize the code better. It can, for example, decide to store the value in a register, or to keep around several copies without worrying about synchronization between them (think threaded applications). Second, if we allow the programmer to change a value which is supposed to be constant, then he will do so.

定義の代わりに変数を使うのはいくつもの理由で間違いだ。第一に、コンパイラがどの識別子がimmutableな値となるかを知っていれば、コードをより最適化できる。たとえば、値をレジスタに格納したり、同期を気にすることなくいくつかのコピーを作ることができるだろう(スレッド分割されたアプリケーションを考えよ)。第二に、もし我々が、本来定数であるはずだった値を変更することを許してしまったら、プログラマはきっとそれをやってしまうだろう。

If you observe how variables are typically used, you will see several distinct uses:

もしあなたが変数が典型的にどう使われるのかを観察したら、いくつかのまったく異なる用途を見ることになるだろう。

  • often a variable is only assigned to once and is used as an (immutable) definition
  • a variable in a loop or list comprehension ranges over the elements of a list, or a collection of objects
  • a variable stores the current state and is genuinely mutable
  • しばしば変数は1回しか代入されず、(immutableな)定義として使用される。
  • ループやリストの中の変数は、リストの各要素やオブジェクトのコレクションの中を動き回る。
  • ある変数は、現在の状態を保持し、それが真のmutableだ。

Should loop counters be mutable? I think not. Code that changes the loop counter in the body of the loop is confusing and error prone. If you want to fiddle with counters, use the while loop instead. So in two out of three cases we want our variables to be immutable, but the popular programming languages only give us variables. That’s silly. We should design the language so that the default case is an immutable value. If the programmer wants a mutable value, he should say so explicitly. This is just the opposite of what Java and C++ do. An example of a language that is designed this way is ML and ocaml. In Haskell you have to jump through hoops to get mutable values (now I am going to hear it from a monad aficionado, please spare me an unnecessary burst of anger).

ループカウンタはmutableであるべきか? 私はそうは思わない。ループカウンタをループのボディの中で変更するようなコードは混乱の元だしエラーを起こしやすい。もしあなたがループカウンタをいじくりたいのなら、代わりにwhileループを使うべきだ。よって、3つのうち2つのケースでは、我々は変数をimmutableとして使いたいのだ。なのにポピュラーなプログラミング言語は変数しか与えてくれない。これはおろかなことだ。我々は、デフォルトがimmutableになるように言語を設計すべきなのだ。もしプログラマが変更可能な値が欲しければ、明示的に指定すべきだ。これはJavaC++がやっていることと反対である。この方法で設計されている言語の例は、MLとocamlだ。Haskellでは、あなたは変更可能な値を売るにはタガを飛び越えなければならない。(今からmonadの熱烈なファンからそれを聞くつもりなので、不要な怒りの炸裂は勘弁して欲しい)。

スコープ外の変数――Out of scope variables

I thought I would not have to explain why undefined identifiers are a bad a idea, but the reader in comment 27 explicitly asked about this.

私は、未定義の変数が悪いアイディアであるということは説明不要だと考えたが、コメント27の読者は明示的にそれについて尋ねてきた。

If a programmer refers to an undefined name then an error should be reported. Concretely, I claimed that Python should complain about the following definition:

def f(n): return i + n

プログラマが未定義の名前を参照したら、エラーが報告されるべきだ。具体的には、私はPythonは以下の定義に文句を言うべきだと主張しているのだ。

def f(n): return i + n

What is i? Pythonists will quickly point out that i will be defined later, and how deferred definitions are useful because they allows us to define mutually recursive functions. Indeed, Java and Haskell also accept mutually recursive definitions. But unlike Python they make sure that nothing is missing at the time of definition, whereas Python will only complain when the above function f is used. To be honest, Python kindly displays the correct error message showing that the trouble is with the definition of f. But why should this be a runtime error when the mistake can easily be detected at compile time? Actually, this question leads to a more general question, which I consider next.

iって何だ? Pythonistは即座に、iは後で定義されるし、遅延した定義が相互に再帰する関数を定義することを許すのでいかに役に立つか、ということを指摘するだろう。実際にはJavaHaskellも相互に再帰する定義を許す。しかし、Pythonとは異なり、それらは定義の時点で何も欠けていないことを確認する。それに反してPythonは上の関数fが使用される時にしか文句を言わない。正直なところ、Pythonは、親切にも問題がfの定義にあるという正しいエラーメッセージを表示する。しかし、間違いが簡単にコンパイル時に検出できるとき、なぜランタイムエラーにするべきなのか? この疑問は、より一般的な疑問につながる。私は次にそれについて考える。

ミスはいつ発見されるべきか?――When should mistakes be discovered?

Should programming bugs be discovered by the programmer or by the user? The answer seems pretty clear. Therefore, a language should be designed so that as many programming errors as possible are discovered early on, that is before the program is sent to the user. In fact, in order to speed up development (remember that the development time is expensive) the programmer should be told about errors without having to run the program and directing its execution to the place where the latest code change actually gets executed.

プログラミングバグは、プログラマかユーザか、どちらによって発見されるべきか? この答えは実に明白に見える。よって、言語は可能な限りたくさんのプログラミングエラーが早期に発見されるようにデザインされているべきである。プログラムがユーザの手元に送られる前に。実際には、開発をスピードアップするには(開発時間が高価であったことを思い出そう)、プログラマは、プログラムを実行する必要なしにエラーについて教えられるべきであり、その実行は最後のコード修正がまさに実行される場所に向けられるべきだ。

This philosophy leads to the design of statically checked languages. A typical feature of such a language is that all the types are known at compile time. In contrast, a dynamically typed languages checks the types during runtime. Java, Haskell and ocaml are of the former kind, scheme, javascript and Python of the latter.

この哲学は、静的チェックのある言語の設計につながる。このような言語の典型的特徴は、コンパイル時にすべての型がわかっていることである。対照的に、動的型付け言語は型を実行時にチェックする。Haskellやocamlは前者であり、schemeやjavascriptやPythonは後者だ。

There are situations in which a statically checked language is better, for example if you’re writing a program that will control a laser during eye surgery. But there are also situations in which maximum flexibility is required of a program, for example programs that are embedded in web pages. The web as we know it would not exist if every javascript error caused the browser to reject the entire web page (try finding a page on a major web site that does not have any javascript errors).

静的にチェックされる言語の方が望ましい状況は存在する。たとえば、あなたが目の手術に使うレーザーを制御するプログラムを書いている場合だ。しかし、プログラムに最大限の柔軟性が要求される状況もある。たとえばWebページに埋め込まれているプログラムだ。あなたも知っているように、あらゆるjavascriptエラーが起きるたびにWebページ全体をブラウザが拒否していたら(javascriptのエラーをまったく持たないメジャーなWebサイトを見つけてみたまえ)、Webページは存在していないことだろう。

Let me also point out that testing cannot replace good language design. Testing is very important, but it should be used to discover problems that cannot be discovered earlier in the development cycle.

また、テストが良い言語設計を代用することはできないということも指摘させてほしい。テストはとても重要だが、それは、開発サイクル上、より早く発見することができない問題を発見するために使われるべきだ。

I used to think that statically checked languages are better for teaching because they prevent the students from doing obviously stupid things. About two years ago I changed my mind. The students learn much better by doing stupid things than by being told by an oppressive compiler that their programs are stupid. So this year I switched to Python. The students are happier, and so am I (because I don’t have to explain that code must be properly indented). Python does not whine all the time. Instead it lets them explore the possibilities, and by discovering which ones crash their programs they seem to understand better how the machine works.

私は、静的型付け言語は、学生が明らかに馬鹿なことをしでかすことを防ぐので、教えるのによいと以前は思っていた。2年ほど前私は考えを変えた。学生は、やたらと厳しいコンパイラに自分のプログラムが馬鹿だと言われることよりも、実際に馬鹿なことをやらかすことからずっと多くのことを学ぶ。そこで今年から私はPythonに切り替えた。学生はより幸福になり、私もまたそうだ(なぜならコードを適切にインデントしなければならないということを説明する必要がないので)。Pythonはのべつ幕なしに文句を言ったりしない。代わりに彼らに可能性を探らせてくれる。何が彼らのプログラムをクラッシュさせたのかを発見することで、彼らはマシンがどう動くのかをよりよく理解しているようだ。

*1:「sum types」が何かわかりませんでした…… えらいひと教えてください(_o_)

*2:inductive data typeもわかりませんでした……orz。こちらについてもえらいひと教えてください(_o_)