Ruby初級者向けレッスン 45回 ─例外─
第57回 Ruby/Rails勉強会@関西での初級者向けレッスンの資料を公開します。*1
エラーメッセージ
require 'open-uri' open 'http://github.com/rubykansai/workshops/wiki'
.../open-uri.rb:223:in `open_loop': redirection forbidden: http://github.com/rubykansai/workshops/wiki -> https://github.com/rubykansai/workshops/wiki (RuntimeError) from .../open-uri.rb:149:in `open_uri' from .../open-uri.rb:688:in `open' from .../open-uri.rb:34:in `open' from ex.rb:2:in `<main>'
- 上 2行が Ruby のコード。
- 実行すると、下のエラーメッセージが出た。
- Ruby のエラーメッセージは、いんちき英語でも読める。
- 「...(RuntimeError)」までが 1行
- 後は「from ...」から行末までが 1行
- 最初の行がエラーの出た場所。
- 最後の行が呼び出し元。
- 「in `<main>'」は実行を開始したオブジェクト。
- 下から 2行目「in `open'」は <main> から open メソッドが呼ばれたという意味。
- 各行がメソッドの呼び出しを表している。*2
- 結局、英語のメッセージを読む必要がある ><
- 「redirection forbidden」理由は分からんが http から https へのリダイレクトが禁止されてるらしい。
- たぶん url を https://... に直せば動く。
- 「redirection forbidden」理由は分からんが http から https へのリダイレクトが禁止されてるらしい。
- 最初の行の行末「(RuntimeError)」は RuntimeError 例外が発生したという意味。
例外いろいろ TypeError
"1" + 1 # ~> ex.rb:1:in `+': can't convert Fixnum into String (TypeError) # ~> from ex.rb:1:in `<main>'
- 下のコメントは実行した際のエラーメッセージ*3
- String の "1" に Fixnum の 1 は足せない。
例外いろいろ NoMethodError
1.to_sym # ~> ex.rb:1:in `<main>': undefined method `to_sym' for 1:Fixnum (NoMethodError)
- Fixnum の 1 に to_sym というメソッドはない。
例外いろいろ NameError
n.times{puts 'Ruby!'} # ~> ex.rb:1:in `<main>': undefined local variable or method `n' for main:Object (NameError)
- n という変数やメソッドはない。
例外いろいろ NoMethodError (再び)
n = ARGV.first.to_i unless ARGV.empty? n.times{puts 'Ruby!'} # ~> ex.rb:2:in `<main>': undefined method `times' for nil:NilClass (NoMethodError)
- Rubyには、他の言語のように NullPointerException のようなものはない。
例外いろいろ Errno::ENOENT
open('nothing.txt') # ~> ex.rb:1:in `initialize': No such file or directory - nothing.txt (Errno::ENOENT) # ~> from ex.rb:1:in `open' # ~> from ex.rb:1:in `<main>'
- 存在しないファイルを open しようとした。
- 「in `initialize'」は File.open メソッドが Fileクラスのインスタンスを生成しようとしている。
- Errno::Exxx の例外は OS の errno に対応した例外。
例外いろいろ SyntaxError
1 def even?(n) 2 if n % 2 == 0 3 true 4 else 5 false 6 end 7 8 puts even?(0) 9 puts even?(1) # ~> -:9: syntax error, unexpected end-of-input, expecting keyword_end
- if-else-end の end を忘れている。
Intelligence と Wisdom
雨が降ってきて…
- Intelligence
- 雨だ!
- Wisdom
- 傘を差そう
- 雨宿りしよう
- それが雨だと認識できる ─── Intelligence (知識)
- 濡れないためには、傘を差すか、雨宿りしないと ─── Wisdom (知恵)
例外も同じで、
- エラーを検知したものが例外を起こす (Intelligence)
- 対処方法を知っているものが例外を捕捉する (Wisdom)
例えば、アプリケーションがファイルを読もうとして、
- open できない!
- read できない!
File ライブラリはエラーを検知できる。(Intelligence)
- 既定値を使うから、読めなくてもよい。
- 別のファイルから読みなおしたい。
- etc...
これはアプリケーションの仕様。(Wisdom)
ライブラリには分かりようがない。
例外を捕捉する (コード例1)
files = %w[file.txt file1.txt file2.txt] files.each do |fn| begin open(fn, 'w'){|f| f.puts "Ruby!!"} break rescue => ex $stderr.puts "#{ex} (#{ex.class})" end end
- ファイルに "Ruby!!" と書き込みたい。
- ファイル名は file.txt
- もし書けなければ、代わりに file1.txt に書こう。
- それでも書けなければ、file2.txt に書こう。
- それでダメなら諦める。
コード解説
- ファイル名 "file.txt", "file1.txt", "file2.txt" という Array を用意。
- 各ファイル名に対して処理を繰り返す。─── files.each ...
やってみる
$ ls -Fdl file* drwxr-xr-x 2 mas staff 68 4 12 23:37 file.txt/ -r--r--r-- 1 mas staff 0 4 12 23:37 file1.txt # file.txt はディレクトリ # file1.txt は書き込み権なし $ ruby retry.rb Is a directory - file.txt (Errno::EISDIR) Permission denied - file1.txt (Errno::EACCES) $ ls -Fdl file* drwxr-xr-x 2 mas staff 68 4 12 23:37 file.txt/ -r--r--r-- 1 mas staff 0 4 12 23:37 file1.txt -rw-r--r-- 1 mas staff 7 4 12 23:39 file2.txt # file2.txt ができた! $ cat file2.txt Ruby!!
例外を捕捉する
begin 式1… [rescue [型1[, 型2]…][=> 変数][then] 式2…]… [else 式3…] [ensure 式4…] end
- [ ... ] 内は省略可能。
- begin - end で例外を捕捉する準備。
- 式1 が例外を捕捉したい処理 (複数書ける)
- rescue節で例外を捕捉。(rescue は複数書ける)
- 「型」に例外クラスを指定すると、特定の例外だけを捕捉できる。(型は複数書ける)
- 式2 が例外対処のための処理 (複数書ける)
- 発生した例外に対応する rescue節がなければ、捕捉できない。
- ensure節は begin - end のブロックを抜ける際の後始末。
- 例外が起きても、起きなくても実行される。
- 式4 が後始末の処理 (複数書ける)
- else節は、上記 式1 で例外が起きなかった際に実行される。
- rescue節で指定しなかった例外を捕捉する処理ではない。
- 式3 で起きた例外は、このブロックでは捕捉しない。
上記のコードを省略なしで書くと、3通りの実行パターンがある。
- 式1→式3→式4 (例外なし)
- 式1→式2→式4 (例外を捕捉)
- 式1→式4 (例外が発生するが、捕捉できない)
例外を捕捉する (コード例2)
require './factorial' def fact(n) n.factorial rescue ArgumentError "1以上の整数を指定してください" rescue NoMethodError "整数を指定してください" end fact 3 # => 6 fact 4 # => 24 fact 5 # => 120 fact 0 # => "1以上の整数を指定してください" fact 2.5 # => "整数を指定してください" fact "2" # => "整数を指定してください"
- ハチドリ本のサンプル
- メソッド定義の def - end にも rescue, else, ensure が書ける。*4
例外の種類
puts NoMethodError.ancestors # >> NoMethodError # >> NameError # >> StandardError # >> Exception # >> Object # >> Kernel # >> BasicObject
- 一覧はるりま参照。
- NoMethodError は NameError のサブクラス
- NameError は StandardError のサブクラス
- StandardError は Exception のサブクラス
- Exception は例外の基底クラス
rescue修飾子
require './factorial' 3.factorial rescue 0 # => 6 4.factorial rescue 0 # => 24 5.factorial rescue 0 # => 120 0.factorial rescue 0 # => 0 2.5.factorial rescue 0 # => 0 "2".factorial rescue 0 # => 0
- rescue の後置。
- まず rescue の左辺を評価。
- 例外が起きると rescue の右辺を評価。
- 例外が起きたら、結果は 0 でいい。という大雑把なコード。
- 捕捉する例外クラスを指定できない。
- 例外オブジェクトを指す変数を指定できない。
例外を起こす
raise "simple" # ~> ex.rb:1:in `<main>': simple (RuntimeError)
- 例外を起こすには Kernel#raise メソッドを使う。
- RuntimeError が発生する。
例外を起こす (クラス指定)
raise ArgumentError, "bad argument" # ~> ex.rb:1:in `<main>': bad argument (ArgumentError)
- 例外クラスを指定して raise できる。
例外を起こす (オブジェクト)
raise TypeError.new("can't convert...") # ~> ex.rb:1:in `<main>': can't convert... (TypeError)
- 例外オブジェクトを raise することができる。*5
例外を起こす (独自の例外クラス)
class MyError < StandardError; end raise MyError, 'original' # ~> ex.rb:3:in `<main>': original (MyError)
サンプルコード
- https://github.com/higaki/learn_ruby_kansai_57
- 演習問題 1, 2, 3 の回答例も上げてます。
スライド、コードに間違いがあれば、ご指摘ください。