Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyプログラムを正しく終了する方法 ── exit・abort・at_exitの基本と終了ステータス

開発者の私たちは、プログラムを「動かす」ことに時間を費やすあまり、そのプログラムがどのように「終了する」かを見落としがちです。しかし、終了処理は非常に重要です。プログラムが適切に終了できれば、運用や管理が格段に楽になり、標準的なDevOpsツールともスムーズに連携できるようになります。

Rubyにはプログラムを終了させるための方法がいくつかあります。以下に代表的な例を示します。本記事ではそれぞれの詳細を解説し、あなたのアプリを「行儀の良いUnix市民」にするための活用方法を紹介していきます。

# メソッドを呼び出して終了する
exit
exit!
abort("She cannot take any more of this, Captain!")

# ...あるいは例外を捕捉せずに終了する
raise("Destroyed...")
fail

# ...あるいはプログラムを自然に完走させて終了する :)

終了コード(Exit Code)とは何か

LinuxやmacOS上で実行されるすべてのプログラムは、実行終了時に「終了ステータスコード」を返します。普段は意識しませんが、OSはこの値を裏側で利用し、プログラムが正常に終了したのか、それともエラーが発生したのかを判断しています。

bashを使っているなら、直前に実行したプログラムの終了コードを環境変数 $? で確認できます。以下の例は、失敗時と成功時の終了コードを示したものです。

% ls this-file-doesnt-exist
ls: this-file-doesnt-exist: No such file or directory
blog% echo $?
1
% ls .
bm.rb 
% echo $?
0

Unix系システムでは一般的に、「0 = 成功」「0以外 = 失敗」というルールになっています。

bashで && 演算子を使ったことがあるなら、すでに終了コードを活用していることになります。次の例では、プログラムAが成功した場合にのみプログラムBを実行するようbashに指示しています。

% a && b

このテクニックはさまざまな場面で応用できます。たとえば、アセットをプリコンパイルしてからCDNへアップロードしたいケースなどが挙げられます。

blog% rake assets:precompile && rake cdn:upload_assets

Rubyで終了ステータスを指定する

Rubyは終了ステータス周りの処理を自動的に担ってくれます。プログラムが正常に終了すれば「成功」ステータスが返り、捕捉されていない例外で失敗すれば「失敗」ステータスが返ります。以下は、例外を発生させたスクリプトの終了コード例です。

% ruby err.rb
err.rb:1:in `<main>': goodbye (RuntimeError)
tmp% echo $?
1

ところで、未捕捉の例外を出したくはないけれど、きれいに終了しつつ「失敗」のコードを返したい場合はどうすればよいでしょうか。さらに言えば、独自のエラーコードをエラーメッセージと一緒に返したい場合はどうでしょう。

幸い、これはとても簡単です。exitメソッドに引数を渡すだけです。引数には真偽値または整数を指定でき、真偽値の場合はtrueが成功を、整数の場合は0が成功を意味します。

exit(true) # 「成功」コードで終了
exit(0)    # 「成功」コードで終了

exit(false) # 「失敗」コードで終了
exit(1)     # 「失敗」コードで終了

exit(436)   # 独自の失敗エラーコード

exitの内部動作の仕組み

実はexitメソッドは、内部的にSystemExit例外を発生させているだけです。この例外が捕捉されなければ、他の未捕捉例外と同じようにプログラムが中断されます。興味深い仕組みですが、ここからいくつか面白い帰結が導かれます。

もしSystemExit例外をすべて握りつぶしてしまうと、exitメソッドは機能しなくなります。以下の例でその様子を確認できます。

begin
  exit 100
rescue SystemExit => e
  puts "Tried to exit with status #{ e.status }"
end

puts "...but it never exited, because we swallowed the exception"

# 出力:
# Tried to exit with status 100
# ...but it never exited, because we swallowed the exception

これこそが、「決してExceptionをrescueしてはいけない」という鉄則のもう一つの理由です。SystemExitExceptionを継承しているため、Exceptionを丸ごと握りつぶすとexitまで壊してしまうのです。

begin
  exit
rescue Exception # 絶対にやってはいけない書き方
  puts "I just broke the exit function!"
end

人間が読めるエラーメッセージを出力する

終了コードはマシンにとって便利な仕組みですが、私たち人間には多少の説明文があったほうが嬉しいものです。幸い、OSにはエラーメッセージ向けの出力ストリームが用意されています。そう、STDERRのことです。

STDERRへの書き出しは、他のIOオブジェクトとまったく同じように行えます。以下の例では、エラーメッセージを出力したうえで「エラー」ステータスで終了しています。

STDERR.puts("ABORTED! You forgot to BAR the BAZ")
exit(false)

Rubyらしく、もっと簡潔な書き方もあります。abortメソッドを使えば、STDERRへのメッセージ出力とエラー終了を一行で実現できます。

# STDERRにメッセージを書き出し、エラーステータスコードで終了する。
abort("ABORTED! You forgot to BAR the BAZ")

at_exitによる終了時コールバック

Rubyでは、プログラム終了時に呼び出されるハンドラを登録できます。ハンドラは複数登録可能で、呼び出される順序は登録した順番とは逆順になる点に注意しましょう。実際の動作は次のとおりです。

at_exit do 
  puts "handler 1"
end

at_exit do 
  puts "handler 2"
end

# 終了時に "handler2\nhandler1" と出力される

さらに、終了ハンドラの中でexitを呼び出せば、終了コードを上書きすることもできます。一見無限ループになりそうですが、実際にはなりません。:)

at_exit do 
  exit 100
end

exit 0

# このプログラムは終了コード100で終了する

コールバックを一切呼び出さずに終了したい場合は、exit!メソッドを使用します。ただし、依存しているgemなどサードパーティのコードがそれらのコールバックに頼っている場合、問題が発生する可能性がある点は覚えておきましょう。

おまけ:意外なat_exitの活用例

実は多くのライブラリが、at_exitを独創的──ひねくれた使い方だと評する人もいますが──に活用しています。たとえばSinatraは、Webアプリの起動にat_exitフックを利用しています。これにより、サーバーの起動前にすべてのコードが確実にロードされていることを保証できるのです。実際のコードは次のようになっています。

module Sinatra
  class Application < Base

   ...

  at_exit { Application.run! if $!.nil? && Application.run? }
end

このトピックについては、Arkencyブログの「Are we abusing at_exit?」という記事が参考になります。興味のある方は、ぜひ原文もチェックしてみてください。

  1. Rubyのマトリックス(行列)とは?配列・Matrixクラス・Daruを使いこなす完全ガイド

    マトリックス(行列)とは、表計算ソフトのようなデータを格納・操作するための2次元(2D)配列のことです。 マトリックスが活躍する場面: チェスやオセロなど、テーブルゲームの盤面を表現する 統計処理やデータ分析を行う グラフやプロットを生成する マトリックスは非常に強力なデータ構造なので、その使い方をマスターしておくと開発の幅が大きく広がります。この記事では、Rubyでマトリックスを扱う3つの方法——配列、Matrixクラス、そしてDaru gem——を順番に解説していきます。 Rubyでマトリックスを作成する方法 最もシンプルな方法は、配列の入れ子(ネスト)を使ってマトリックスを作ることで

  2. RubyのStructとOpenStructの使い方を徹底解説!値オブジェクトを簡単に作る方法

    Rubyには、関連する属性をまとめて保持する「値オブジェクト」を手軽に作れるStructという組み込みクラスが用意されています。本記事では、Structの基本的な使い方から注意点、そしてOpenStructとの違いまで詳しく解説します。 そもそもStructとは何か? StructはRubyに組み込まれたクラスで、新しいクラスを動的に生成し、値オブジェクト(Value Object)を作成するために使われます。値オブジェクトとは、関連性のある複数の属性をひとまとめにして扱うためのオブジェクトです。 具体例を挙げてみましょう。 2つの座標(xとy)を持つPointというデータを表したい場合、この