トラック
/
Julia
Julia
/
演習
/
工場のセンサー
工場のセンサー

工場のセンサー

学習演習

はじめに

プログラマーはふつう、完璧なソフトウェアを書こうとします。そして、たいていは失敗します。

物事は思いがけないときにうまくいかなくなるもので、それに対処できる必要があります。

言語の設計者の中には、できるだけ早くエラーを検出し、デバッグの助けになる情報を含むメッセージとともに実行を終了させることを優先すべきだと考える人もいます。

データサイエンス向けの言語は、もっと細やかなアプローチを取る傾向があります。即座に終了させなければならないほど深刻なエラーもありますが、多くの場合、問題を後で対処すべきものとして記録しておき、実行を続けたほうがよいのです。

Nothingnessコンセプトで見たように、Juliaには問題のある値を表すさまざまなプレースホルダーが用意されています。missing、NaN、Infです。こうしたものが、特定の状況でプログラムを終了させるよりよいアプローチかどうかは、プログラマーが判断することです。

_用語についての注意_を先に述べておきます。細かな話に入る前に。Juliaのドキュメントでは、「error」と「exception」という言葉をほぼ同じ意味で扱っています。以下の内容も同じくらい一貫していないかもしれません。

標準のエラー型

シラバスのここまで進めば、Juliaからたくさんのエラーメッセージを見てきたはずです。たとえば、次のようなものです。

julia> Int(3.14)
ERROR: InexactError: Int64(3.14)

小数を整数にキャストしようとすると精度が失われるので、InexactErrorが発生します。

InexactErrorは型のひとつで、Juliaに標準で組み込まれているいくつかの型(現在は25種類)のうちの1つです。これらはすべてExceptionのサブタイプです。

julia> supertype(InexactError)
Exception

throw()

標準のエラー型の中には、自分のコードで発生させると役に立つものもあります。

すべての具象型と同じように、エラーにもコンストラクターがあります。引数はさまざまなので、使いたいものについてはドキュメントを確認してください。

julia> DomainError(42, "out of range")
DomainError(42, "out of range")

エラーを使うには、コンストラクターをthrow()関数で包みます。

julia> throw(DomainError(42, "out of range"))
ERROR: DomainError with 42:
out of range

error()

手っ取り早く済ませたいときは、error()関数が便利です。引数として文字列(または文字列を構成する部品)を取ります。

julia> happy = false;
julia> happy || error("😞 something went wrong")
ERROR: 😞 something went wrong

カスタムエラー

新しいエラー型を作るのは、原理的にはとても簡単です。Exceptionのサブタイプをもう1つ追加するだけです。

julia> struct MyError <: Exception end

julia> throw(MyError)
ERROR: MyError

アサーション

アサーションの基本的な考え方は、「この文は真であるはずなので、偽なら大声で警告する」というものです。これが役に立つのは主にデバッグ中で、本番のコードではアサーションが失敗してはいけません。

Typesコンセプトで見たように、型アサーションを追加できます。たとえば、関数の戻り値を確認するのに使います。

julia> 42::Number
42

julia> "two"::Number
ERROR: TypeError: in typeassert, expected Number, got a value of type String

より一般的には、@assertマクロを使うと、真偽値に評価される任意の式を検査できます。

julia> n = 22;
julia> @assert isodd(n) "n must be odd"
ERROR: AssertionError: n must be odd

try...catch

どうしても致命的になるエラーもありますが、多くの場合、プログラムがうまく復帰してくれることを期待します。

デフォルトでは、エラーが起きると現在の関数は即座に終了し、そのエラー(情報を含むメッセージがあればそれも)は呼び出し元の関数に渡されます。

これはコールスタックをさかのぼって続き、最終的にトップレベルのコードがエラーメッセージとともに終了します。

どの段階でも、エラーを処理しようとするtry...catchブロックでそのエラーを捕まえることができます。

julia> n = -1;
julia> try
           log_n = log(n)
       catch problem
           if problem isa DomainError # number out of range
               # See next section for more on @warn and @info
               @warn "you may have supplied a negative real number: $n"
               @info "trying with complex argument"
               log_n = log(Complex(n))  # fallback calculation

           elseif problem isa MethodError # no idea what n is
               @error "please supply a valid argument"
 
           else
              rethrow() # the error could be anything else
           end
      end
┌ Warning: you may have supplied a negative real number: -1
└ @ Main REPL[3]:5
[ Info: trying with complex argument
0.0 + 3.141592653589793im  # success

上の例では、log(n)はnが正の実数値か、任意の複素数値であることを必要とします。try ... catchは負の実数値による問題を捕まえ、数学的表記でいう正しい複素数解iπを返します。

たとえば文字列を引数として渡した場合、ユーザーに修正してもらう以外に復帰する方法はありません。

最後の受け皿として、DomainErrorでもMethodErrorでもないものすべてに対してrethrow()を追加しました。

注意: try...catchが必要なこともありますが、使いすぎは避けてください。代わりにif...elseブロックを使えるなら、例外を捕まえるよりずっと性能がよくなります。

ロギング

上で説明したerror()関数と、@errorマクロを混同しないようにしましょう。

関数のほうは例外を発生させ、捕まえられない限りコールスタックをさかのぼって渡されていきます。

@errorマクロは、その仲間である@debug、@info、@warnとともにLoggingモジュールの一部で、プログラムの流れを変えずに情報を伝えるメッセージを生成するためのものです。

出力はデフォルトでターミナルに送られます(重要度に応じて色分けされます)が、実際のアプリケーションではほかにも多くの選択肢があります。

julia> @warn "Something looks not quite right"
┌ Warning: Something looks not quite right
└ @ Main REPL[55]:1

julia> @error "Panic!"
┌ Error: Panic!
└ @ Main REPL[56]:1

try...catchのところにある前の例も参照してください。

説明

エレナは新聞工場の新しい品質管理責任者です。 ちょうど入社したばかりなので、工場のいくつかの工程を見直して、改善できるところがないか確かめることにしました。 調べてみると、技術者たちが多くの品質チェックを手作業で行っていることがわかります。自動化の好機だと感じたエレナは、フリーランスのエンジニアであるあなたに、いくつかの機械を監視するソフトウェアの開発を頼みます。

1. 部屋の湿度レベルを確認する

最初のミッションは、生産室の湿度レベルを監視するソフトウェアを書くことです。すでにセンサーが会社のソフトウェアに接続されていて、部屋の湿度のパーセントを定期的に返してくれます。

湿度が高すぎる場合にエラーを投げる関数を、ソフトウェアに実装する必要があります。 湿度が許容範囲内であれば、Infoログが追加されます。 関数の名前はhumiditycheckとし、湿度のパーセントを引数として受け取ります。

パーセントが70%を超える場合は、ErrorExceptionで停止してください(メッセージの内容そのものは重要ではありませんが、測定された湿度を含める必要があります)。 そうでなければ、メッセージ"humidity level check passed: h%"のInfoログを追加してください。ここでhは湿度のパーセントです。

julia> humiditycheck(60)
[ Info: humidity level check passed: 60%
julia> humiditycheck(100)
ERROR: humidity check failed: 100%

2. 過熱をチェックする

エレナは最初の仕事にとても満足していて、次は機械の温度の監視を任せたいと考えています。 技術者のグレッグと雑談していると、機械の温度が500°Cを超えると技術者たちは過熱を心配し始める、と教えてくれます。

機械には、内部温度を測るセンサーが付いています。 このセンサーはとても繊細で、よく壊れてしまうことを知っておいてください。 壊れた場合は、技術者が交換する必要があります。

あなたの仕事は、温度を引数に取る関数temperaturecheckを実装することです。この関数は、問題がなければログを追加し、センサーが壊れている場合や機械が過熱し始めた場合にはエラーを投げます。 あとでエラーの種類に応じて違う対応をする必要があるので、2種類のエラーを区別する仕組みが要ります。

  • センサーが壊れていると、温度はnothingになります。 この場合は、ArgumentErrorで停止してください(メッセージは重要ではありません)。
  • センサーが正常に動いているとき、温度が500°Cを超えていれば、測定された温度を含むDomainErrorを投げてください。
  • それ以外は問題がないので、メッセージ"temperature check passed: t °C"のInfoログを追加してください。ここでtは温度です。
julia> temperaturecheck(nothing)
ERROR: ArgumentError: sensor is broken

julia> temperaturecheck(800)
ERROR: DomainError with 800:
"overheating detected"

julia> temperaturecheck(500)
[ Info: temperature check passed: 500 °C

3. 独自のエラーを定義する

次のタスクでは、より一般的な、どんなエラーも受け止めるエラーを定義する必要があります。 エラーであることと、名前がMachineErrorであること以外に、実装の詳細は重要ではありません。 フィールドやメッセージは、役に立つと思うものを自由に含めてかまいません。

4. 機械を監視する

機械がエラーを検知できるようになり、独自のマシンエラーも定義できたので、全体がどう動いているかを報告するラッパー関数を追加します。 このラッパーは、これまでの関数からのログを返すだけでなく、発生した不具合の種類に応じてログを追加する必要もあります。

  • 湿度と温度を確認します。
  • 湿度チェックがErrorExceptionを投げた場合は、メッセージ"humidity level check failed: h%"のErrorログを追加します。ここでhは湿度のパーセントです。
  • 温度チェックがArgumentErrorを投げた場合は、メッセージ"sensor is broken"のWarnログを追加します。
  • 温度チェックがDomainErrorを投げた場合は、メッセージ"overheating detected: t °C"のErrorログを追加します。ここでtは温度です。
  • どちらか、または両方のチェックが失敗した場合は、ログを追加したあとにMachineErrorを1つ投げます。
  • すべて問題がなければ、humiditycheckとtemperaturecheckからのログだけが追加されます。

湿度と温度を引数に取る関数machinemonitor()を実装してください。

julia> machinemonitor(42, 450)
[ Info: humidity level check passed: 42%
[ Info: temperature check passed: 450 °C

julia> machinemonitor(42, 550)
[ Info: humidity level check passed: 42%
┌ Error: overheating detected: 550 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(82, 521)
┌ Error: humidity level check failed: 82%
└ @ Main # output truncated
┌ Error: overheating detected: 521 °C
└ @ Main # output truncated

Error: MachineError

julia> machinemonitor(42, nothing)
[ Info: humidity level check passed: 42%
┌ Warning: sensor is broken
└ @ Main # output truncated

Error: MachineError
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Julia Exercism

工場のセンサーを始める準備はできましたか?

Exercismに登録すれば、35個のコンセプト128個の演習、そして本物の人間によるメンタリングとともに、Juliaを学んでマスターできます。すべて無料です。