はじめに
例外処理について軽く触れていた際に、「作るだけではだめで、こうした特殊な場合や想定していない場合にも対処できるエンジニアにならなきゃ!!」と思い立ち、簡単にアウトプットとして記事にまとめました。
例外処理とは
Rspec等などで「テストして問題を先に見つけたり、エラーが起これば修正」ということは、過去に何度もやってきましたが、様々な環境下ユーザの予期せぬ入力や動作に対応するということはあまりしてきませんでした。例外処理とは、こうした事象(プログラムで意図した結果が得られない)ときに行う処理です。
RubyおよびRubyonRailsでは、Exceptionというクラスを継承する形で様々な例外が定義されています。例外が発生した場合は、それ以降の処理は全て中止され、実行されません。
こうした例外処理は、緊急時の早期復旧だったり、大きな問題発生への抑止に必要不可欠なわけです。
#例外処理の実装
例外処理の実装には色々なものがありますが、簡単なものでbeginブロックの中にrescueを記述する方法があります。
###begin
使い方は簡単で、例外が起こりそうな場所を囲ってやります
begin
#例外が起きそうな処理
end
ただし、このままではどのタイミングで例外を行うか判断できません。そこでresucueを使います。
###rescue
resucueは、発生した例外を捉えて、例外が起きたらこうしてねという条件節です。
基本的にはbeginの中で記述して使います。
begin
1/0 #0で割るというタブーを起こしてみる
rescue
puts "こんなことできねーよ!!!!!!" #怒る!笑
end
※beginブロックではif~else~endのようなインデントは必要ありません。
上記のプログラムでは、1/0を行い、ZeroDivisionErrorという例外を発生させます。このとき、例外が発生するとresucue以下の処理を行います。
while等のような繰り返し処理でも例外が発生した場合は、そこで処理を止め、次のループには実行されません。ただしm繰り返し処理の中に例外処理を記述すると例外が発生しても次のループに移ります。
簡単なRakeタスクを実装
begin,resucueを用いてRakeタスクを実装してみよう。
なお、Rakeタスクについてわからないという人は、簡単なまとめを後述してありますのでそちらをご覧ください。
今回はあるユーザが、限界値のりんごの数を持っていたとして、さらにそこから10個増やす場合について考えます。
namespace :distribute_apple do
desc "全ユーザーのappleをrescueしながら10増加させる"
task rescue: :environment do
User.find_each do |user|
begin
user.increment!(:apple, 10)
rescue => e
Rails.logger.debug e.message
end
end
end
end
find_eachメソッドは、利用するモデルに紐づくテーブルの全てのレコードに対して、eachメソッドのように繰り返し処理を行ます。
rescue => eという記述は、発生した例外をrescue ~ end間の処理内でeという変数に入れて扱う、という意味です。続くRails.logger.debug e.messageで、発生した例外をログに記録しています。
#例外を自分で発生させる場合
さきほどは、例外が発生するようにあえて限界値を持ったユーザがいたとして〜〜と仮定(設定)しましたが、自ら発生させる方法があります。
さきほど行ったような方法は、「不具合の原因となる箇所で例外を明示して、処理を止めたいとき」などのために行うものです。
しかし、場合によっては問題のある値が登場する箇所で例外を発生させた方が、原因の特定が早くなり修正もしやすくなります。
###raise
raiseは例外を発生させたい時に使用するものです。
raise 発生させたい例外クラス,"エラーメッセージ"
第一引数に発生させたい例外クラス、第二引数にエラーメッセージを記述して使用します。
さきほど行った例外処理をraiseで記述すると以下のようになります。
namespace :distribute_apple do
desc "全ユーザーのappleをrescueしながら10増加させる"
task rescue: :environment do
User.find_each do |user|
begin
RangeError,"これ以上、りんご持てません!!!!!"
rescue => e
Rails.logger.debug e.message
end
end
end
end
#トランザクション
ここで少し応用的な部分の話もまとめます。
いままで行ってきた、resucue等の例外処理は、例外が発生したら処理を止めてしまいます。しかし、その場合途中まではある処理をして→例外発生→それ以降はなにも処理されないとうことになります。りんごの場合「途中までりんご10個もらえたけど、限界値を持っている人以降にいるユーザはもらえない」ということになります。
そうした状況を避けるために、トランザクションを利用します、
ActiveRecord::Base.transaction do
# 処理1
# 処理2
# ...
end
これをrakeタスクに入れ込みます。
namespace :distribute_apple do
desc "全ユーザーのappleをトランザクションで10増加させる"
task transact: :environment do
ActiveRecord::Base.transaction do
User.find_each do |user|
user.increment!(:apple, 10)
end
end
end
end
このゆにすることで、例外が発生しても一旦は全体に平等な処理を実装できます。
参考:Rakeタスク(おまけ)
ついでに、今回Rakeタスクについても学んだのでこの辺もまとめておきます。
Rakeタスクとは、ターミナルなどのコマンドライン上から
アプリケーションを実行できる機能の一つです。
以下のコマンドでファイルの作成ができます。
rails g task タスクファイル名
Rakeファイルの書き方
処理の記述は以下のように書きます。
namespace :ここにグループ名を記述する do
desc "ここに処理の説明を記述する"
task タスク名: :environment do
ここに実際のタスクを記述する
end
end
先に簡単な基礎的メソッドについてまとめました。
以下をご参照ください
メソッド | 意味 |
---|---|
namespace do ~end | 複数のタスクのグループを一つに分けたもの |
desc | 処理の説明 |
task do~end | タスクの内容説明 |
###environmentメソッド
environmentメソッドについて解説します。これは「タスクの処理をアプリケーション環境に依存させた上で実行する」ということを可能にしてくれます。今回の場合は、Rails環境になります。このメソッドのおかげでRails上に設定したモデルの情報を取り扱うことが可能となります。
さて、おおざっぱにメソッドの確認をしたところで、実行方法のコマンドも知っておきましょう
rails namespaceの名前:taskの名前
###incrementメソッド
incrementメソッドは、カラム名と数字を引数に取り、引数の数だけカラムの値を増加させます。ターミナルから、このタスクを実行することで、あるテーブルにある全レコードの指定カラムの値が10増加します。
モデル.increment!(カラム名,値)
#例)user.increment!(:apple,10)
#おわりに
お疲れ様でした。
例外処理についてざっくりとですがまとめました。ユーザ様に不具合なく使ってもらうためにも、こうした処理方法は学ばないわけにはいきませんね。
もっと勉強して、有益な情報を発信していければと思います!!!