Roll With IT

tamakiのIT日記

チェリー本輪読会 第14週目まとめ

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

チェリー本輪読会の第14週目のエントリーになります。

輪読会の概要については第1週目にまとめています。

🍒 輪読会 第14週目まとめ

第9章9.1.1〜第9章9.7まで

期間:2021年08月23日〜2021年08月27日

エラーとは

プログラムの処理中に発生するエラーにはいくつか種類があります。

  • データのエラー((広い意味で)データの誤り。構造上の間違いなど)
  • システムのエラー(ハードディスクが故障、回線が切断など)
  • プログラミングのエラー(メソッド名、引数が存在しない、間違っているなど)

上記のようなさまざま状況で発生するエラーですが、プログラムを正常に戻し処理を続けるには、エラーの原因を取り除く必要があります。その際に役に立つのが「例外」という仕組みです。

Rubyには、エラー処理をサポートするための「例外処理」の仕組みが備わっています。

例外(Exception)とは

wikipediaには以下のように書いています。

プログラミングでは、プログラムがある処理を実行している途中で生じ得る、設計から逸脱した状態を「例外」という。

例外 - Wikipedia

ここでは、地震、落雷により急にパソコンのハードウエアが壊れてエラーが起きるような、想定が困難な状況ではなく、「メソッド名の定義がおかしい。変数に誤った値が格納されている、タイポしている等」予め発生する可能性があると思われる、想定しているエラーのことを指しています。通常、例外は何らかの理由により処理が中断され、その状況に関する情報が通常とは異なる経路で送出されます。

例外処理とは

エラーが起きたときに行う処理のことを例外処理と呼んでいます。プログラムの実行中にエラーが起こると、プログラムは一時中断し「例外処理」を探しそれを実行します。

仮に、以下のファイルを用意し実行してみました。敢えて、行末のメソッドで呼び出すことができない引数を渡しています。

# fizz_buzz.rb

def fizz_buzz(n)
  if n % 15 == 0
    'Fizz Buzz'
  elsif n % 3 == 0
    'Fizz'
  elsif n % 5 == 0
    'Buzz'
  else
    n.to_s
  end
end

fizz_buzz(foobarbaz)

結果、プログラムが一時中断し「例外処理」が出力されます。

❯ ruby fizz_buzz.rb
fizz_buzz.rb:13:in `<main>': undefined local variable or method `foobarbaz' for main:Object (NameError)

例外処理もオブジェクト

上記の例外処理では、NameErrorとなり、「定義されていないローカル変数またはメソッドなので、エラーですよ〜」と怒られています。この例外処理は、NameErrorクラスのオブジェクトになります。

NameError(未定義のローカル変数や定数を使用したときに発生します。)

class NameError (Ruby 3.0.0 リファレンスマニュアル)

例外クラス(るりま)

例外も例外クラスのインスタンス(オブジェクト)なので、るりまに情報がまとめられています。

library _builtin (Ruby 3.0.0 リファレンスマニュアル)

例外処理が発生した場合、るりまを読んでも具体的な解決策がわかないことが多いです。その際は、適切なキーワードを元に(エラー文など)解決策をググって見つけた方が早いです。(個人的意見)

例外を捕捉して処理を実行する

何らかの理由で例外が発生してもプログラムを続行したい場合は、begein 〜 rescue 〜 end と、記述することで、プログラムを続行させることができます。

# 構文

begein
    # 例外が起きうる処理
rescue
  # 例外が発生した場合の処理
end

実際に試してみました。

puts '演奏開始'

module BassGuitar
    def play(bassist)
        puts "#{bassist}: ブン!ブン!"
    end
end

begin
    bass_guitar = BassGuitar.new('ジャコ・パストリアス')
rescue
    puts '機材トラブルが発生したが、このまま演奏を続行する'
end

puts '演奏終了'

この処理を実行してみると...

# 処理結果
演奏開始
機材トラブルが発生したが、このまま演奏を続行する
演奏終了

「演奏開始」を出力し、その後、begin以下で「例外が起きうる処理」を書いていますので、rescue以下で、「機材トラブルが発生したが、このまま演奏を続行する」がputsで出力されました。その後の「演奏終了」まで無事出力されたので、最初から最後までプログラムを実行できたことになります。

例外オブジェクトから情報を取得する

例外オブジェクトから情報を取得することも可能です。

# 構文

begin
    # 例外が起きうる処理
rescue => 例外オブジェクトを格納する変数
    # 例外が発生した場合の処理
end

rescue => e として、変数に例外オブジェクトを格納することができます。変数eは、exception(例外)から来ています。

例外オブジェクトへメソッドを使うことで、エラーメッセージmessageメソッドを返したり、バックトレース情報backtraceメソッドを返したりすることができます。

module BassGuitar
    def play(bassist)
        puts "#{bassist}: Bom!Bom!"
    end
end

begin
    bass_guitar = BassGuitar.new('ジャコ・パストリアス')
rescue => e
    puts "エラークラス: #{e.class}"
    puts "エラーメッセージ: #{e.message}"
    puts "バックトレース: #{e.backtrace}"
    puts "---------------------------------"
end

この処理を実行してみると...

# 処理結果
エラークラス: NoMethodError
エラーメッセージ: undefined method `new' for BassGuitar:Module
バックトレース: ["(irb):27:in `<main>'", "/Users/shiro/.rbenv/versions/3.0.2/lib........(省略)
---------------------------------

変数eへ格納した例外オブジェクトの情報を出力することができます。

意図的に例外を発生させる

例外は捕捉するだけではなく、コードの中で意図的に発生させることもできます。

その際に使用するのが、raiseメソッドです。

チェリー本のプログラム例を試してみました。(9.3 「意図的に例外を発生させる」チェリー本341頁)

def currency_of(country)
 case country
    when :japan
      'yen'
    when :us
      'dollar'
    when :india
      'rupee'
    else
      raise
  end
end

# 実行結果
currency_of(:italy)
(irb):24:in `currency_of': unhandled exception
  from (irb):27:in `<main>'
  from /Users/shiro/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /Users/shiro/.rbenv/versions/3.0.2/bin/irb:23:in `load'
  from /Users/shiro/.rbenv/versions/3.0.2/bin/irb:23:in `<main>'

???

エラーメッセージにunhandled exception と出てしまいました。

チェリー本とるりまには、特定の例外クラスに該当しないエラーが起こった場合はRuntimeErrorが発生すると書いてあるが、なぜ?

質問してみました

伊藤さんから回答いただきました。

どこかのタイミングで表示が変わったようです。以下のように記述すると、RuntimeErrorを出すことができました。

def currency_of(country)
  case country
  when :japan
    'yen'
  when :us
    'dollar'
  when :india
    'rupee'
  else
    raise
  end
end

begin
  currency_of(:italy)
rescue => e
  p e
end

# 実行結果
RuntimeError

例外処理のベストプラクティス

  • 例外処理のセオリー
    • 例外が発生したら即座に異常終了させよう
    • フレームワークの共通事項に全部丸投げしよう

安易にrescueせずに、フレームワークに備わっている例外処理の仕組みに処理を委ねるのがセオリーです。実際にrescueすべきケースに遭遇した時、今回の件を改めて見直し対応を検討するのが、初心者の内はベストプラクティスだと思いました。

RubyMine入門

zenn.dev

フィヨルドブートキャンプ受講生の@ikumatdkrさんが、Zennで出版されたRubyMineの入門書です。

RubyMineを使い始めたばかりの方、基本操作は覚えたんだけどさらなる便利機能を覚えたい方、ぜひとも一度読んでいただきたい内容です。

僕もRubyMineを使い初めて3ヶ月目ごろに読みましたが、知らない内容や知ってはいたが使いこなせていない内容が盛りだくさん、かつ親切丁寧に説明されており、非常に勉強になりました。

RubyMineは、ググっても詳しく丁寧に説明されている情報があまりありません。また公式ページも細かい説明までは載っていなく、RubyMine初心者にとっては、まず最初に読むべきベストな一冊だと思います!!

しかも無料!@ikumatdkrさん、ありがとうございます〜!

参考書籍

🍒 まとめ

今週は例外処理編でした。

例外処理のベストプラクティスに書いたように、まずはフレームワークに従うのが良いとは思いますが、裏側で起こっている処理の仕組みを知ることができ勉強になりました。

次は、yield、Procについて書きたいと思います。

では、また来週!(次回、第15週目)