Let It Crashとは何か

Dec 21, 2016

この記事はRecruit Engineers Advent Calendar 2016 - Adventarの21日目の記事です。

昨日はktrysmtさんのChrome便利拡張のお話 ググったあとワンクリックで期間指定ができるChrome拡張を作った - Qiita でした。


“Let It Crash”ってなかなかのパワーフレーズじゃないですか?

ちょこちょこといろいろな場所で見かけるものの、(letitcrash.comとか…)その定義や、それがどこで出てきたフレーズなのかまでに触れている記事は少ないのでは、と思います。

この記事では、”Let It Crash”が初めて登場したと思われる、Erlang設計者の1人であるJoe Armstrong氏の論文「Making reliable distributed systems in the presence of software errors」の内容を紹介します。

この記事のまとめ

記事が長くなってしまったので先にまとめを書きます。

  • Let It Crashが初めて登場したのは Making reliable distributed systems in the presence of software errors と思われます
    • Let It Crashは、Erlangにおけるエラー処理の方針をあらわしているフレーズです
    • Let It Crashという言葉が独り歩きしているように思えたら、上記論文の「4.1 Let It Crash」を参照するとよいのでは、と思います
  • 実践的な内容を知りたい方は書籍やQiitaの記事を参照してください。この記事では扱っていません

Let It Crashとかいうパワーフレーズはいつ誕生したのか?

“Let It Crash”というパワーフレーズがどこで使われ始めているのか調べたところ、これはどうやら2003年11月最終更新の Making reliable distributed systems in the presence of software errors という論文において初めて登場したフレーズのようでした。

このドキュメントは、Erlang設計者の1人でもあり、”Programming Erlang“(和訳「プログラミングErlang」)著者のJoe Armstrong氏が書いたものです。

内容は平易な英語で書かれており、読みやすいです。 プログラミングで起こり得る問題と、それに対してErlang/OTPでどういう解決策を取れるのか、ということを書いています。

Erlangに興味がある人は読むと面白いと思います。ただ、長いというのと、すごいE本プログラミングErlangほど実践的な内容ではないので、目次から興味のあるところを選んで読んでいくのがよさそうかなと思いました。

Erlangでのエラーハンドリングの方針

Armstrong氏は、前述した論文中の「4.3 Error handling philosophy」で、Erlangのエラーハンドリングを以下の方針で行うとしています:

  • エラーからの復旧は他のプロセスにやらせる
  • 何もやることができなければ死のう
  • クラッシュさせておこう(Let It Crash)
  • 防御的プログラミング(Deffensive Programming)をしない

この記事では、論文から上記のポイントを抜粋して紹介していきます。

エラーからの復旧は他のプロセスにやらせる

Making reliable distributed systems in the presence of software errors の 4.3.1 からの引用です。

分散システム上でエラーからの復旧を行うには、複数のコンピュータを用意してreplicationする必要があります。 コンピュータAがクラッシュしたら、コンピュータBにコンピュータAがクラッシュしたことが通知します。コンピュータBはエラーを復旧しようとすることができます。

replication

Erlangでは、これと同様のことを「軽量プロセス」単位で行います。 また、Erlangアプリケーションは複数のノード上で稼働させることができ、ノードA上の軽量プロセスAのエラーを、ノードB上の軽量プロセスBに通知することができます。 (※コードサンプルはこの記事では扱いません。実際にどのようにプログラムを書くかはErlangの書籍やドキュメンテーションを参照してみてください)

replication

このような仕組みを持つと、ハードウェア上のエラーもソフトウェア上のエラーと同様に見ることができます。 そして、エラーがどのレイヤで発生したのかを気にせず、エラーが発生した箇所とは別の場所でエラーからの復旧を行うことができます。

並列志向ではないプログラミング言語では、エラーが発生した箇所(スレッド)で処理する必要があります。 また、例外を処理するための構造を設計する必要があります。(try-catchの構造がイメージできるかと思います)

replication

すべてのコードは、エラー処理のための構造によって包まれているような書き方になります。

Erlangのように、エラー処理を別の場所で実行できる仕組みがあると以下のメリットがあります:

  1. エラー処理のコードは、エラーが発生したスレッドとは別の場所で実行することができる
  2. メイン処理を実行するコードは、例外処理を行うコードによって煩雑にならない
  3. メソッドは分散環境で実行できるので、1つのノードのシステムから分散環境へ移行しても、エラー処理のコードに対する変更は少なくて済む
  4. システムは1つのノードで実行とテストを行うことができる。また、コードに大きな変更を加えることなく、複数のノードを使った分散環境に移行できる

クラッシュさせておこう(Let It Crash)

Making reliable distributed systems in the presence of software errors の 4.4 からの引用です。

How does our philosophy of handling errors fit in with coding practices? What kind of code must the programmer write when they find an error? The philosophy is let some other process fix the error,

ここを読むと、Armstrong氏の提唱するLet It Crashとは「エラーを処理するための1方針」であることがわかります。

let some other process fix the error

前提として、前セクションで書いたように、Erlangではエラーの復旧処理を、エラーが発生した軽量プロセスとは別の軽量プロセスに行わせる、という方針をとっています。

そして、エラーが発生した軽量プロセスは

let it crash

クラッシュさせておく、と書いています。

また、ここでは「例外」と「エラー」の定義についても触れています。

but what does this mean for their code? The answer is let it crash. By this I mean that in the event of an error, then the program should just crash. But what is an error? For programming purpose we can say that:

  • exceptions occur when the run-time system does not know what to do.
  • errors occur when the programmer doesn’t know what to do.

例外・エラーの定義として以下を挙げています:

  1. run-time systemが何をしていいかわからない「例外
  2. プログラマが何をしていいかわからない「エラー

1は、プログラマがどう状態を回復できるのかわかっている問題を指しています。

例えば、存在しないファイルを開こうとすると例外が発生します。しかし、これはプログラマが対処できます。 プログラマはこの例外をcatchして必要な正しい処理をするコードを書くことができるので、エラーではありません。

2は、エラーというのはプログラマが何をしたらよいかわからない問題を指しています。

そして、Erlangではエラーに対する防御的プログラミングを推奨していません。次のセクションで例を挙げます。

防御的プログラミングをしない

例として、マイクロプロセッサのコードを書いている場合を考えてみます。 仕様では、loadという操作はコード1を返し、storeという操作はコード2を返す、とされています。 プログラマはこの仕様を以下の実装に落とすでしょう。

asm(load)  -> 1;
asm(store) -> 2.

それでは、asm(jump)を評価しようとしたときどうなるべきでしょうか。 防御的プログラミング(Deffensive Programming)に慣れていたら以下のように書くでしょう。

asm(load)  -> 1;
asm(store) -> 2;
asm(X)     -> ??????

ここで、??????はどう書くべきでしょうか。 これは実行環境が処理できない状況に遭った場合と同じような状況をあらわしています。(例えば、0の割り算を行わせる、など)

これは、「エラー」です。 何か賢明なコードをここで書くことはできないだろうと思われます。

この場合、できることは終了することのみでしょう。

asm(load)  -> 1;
asm(store) -> 2;
asm(X)     -> exit({oops,i,did,it,again,in,asm,X})

でもこれは煩わしいコードです。 Erlangでは、「エラー」に対する操作を書きません。以下のコードは

asm(load)  -> 1;
asm(store) -> 2.

以下と同義です。

asm(load)  -> 1;
asm(store) -> 2;
asm(X)     -> exit({bad_arg, asm, X}).

そして、軽量プロセスによるエラー発生の情報は、別の軽量プロセスに通知されます。 エラーからの復旧処理は、別の軽量プロセスによって行います。

この論文では、防御的プログラミング(Defensive programming)は「純粋な分岐の記述を損ない、読み手を混乱させる」と主張しています。 コンピュータからエラー情報を受け取った箇所で、そのエラー情報以上の何らかの診断は殆どできない、とも書いています。

Let It Crashに対するありそうな誤解ポイント

前項で紹介しましたが、論文では以下のように例外的状況には

  • 例外
  • エラー

の2種類があり、このうち「エラー」を

let it crash

させるのだ、という主張をしていると読み取れます。

例外的状況において、なんでもかんでもcrashさせておくという話ではありません。あくまで「エラー」に対する処理の話をしています。

自分が書いているソフトウェアにおいて、何が(この論文における)「例外」「エラー」なのかを設計しておくことは、他のプログラミング言語でも同じように必要なことだと思います。


次回22日目はneko-nekoさんの記事です!ワイワイ!

Retrun to top