Rails デバッグ・エラー処理について整理してみた


先日Railsのデバッグ中にあれこれ疑問に思った事について整理してみました。

ざっくり書くと、以下の内容です。

- 想定外の入力に対する備え
- ログ出力を動的に変更するには
- pry-byebugのブレークポイントの設定方法
- RSpecで特定のテストケースを行うには

# 1. 想定外の入力に備える

Railsを使っていて入力に対するチェックが甘くなるのは、私だけでしょうか？
つい例外に頼ってしまったりとか...

想定外の入力が発生した時点で、その入力に対して適切に処理を行う事で、エラーを抑止できたり、エラーの原因特定が早まる可能性があります。

```rb
def foo(name)
  model = Model.where(name: name).first
  puts model.name # modelがnilなら例外.
end
```

```rb
def foo(name)
  model = Model.where(name: name).first
  if model
    puts model.name
  else
    logger.warn("#{name} is not found.") # ログファイルに情報を残しておく.
  end
end
```

## 1.1 防御的プログラミング

CODE COMPLETE 防御的プログラミングの章には、以下の点が述べられています。

- 外部ソース（ネットワークやDB、ユーザー)からの入力データ、メソッドへの入力データが妥当である(想定内の範囲か、nilでないかなど）かを確認する。
- 想定外の入力に対して、どのように対処するかの指針。

以下は、基本に立ち返り、今後取り入れようとしている3つの指針です。

### a. assertの利用.

ミッションクリティカルなシステムでは、入力チェックは必須ですが、Webアプリケーションでも、エラーの原因を早期に特定したり、エラーそのものに発生させないために有効だと思います。

rubyでは言語仕様にassertは無い様なので、raiseの利用を検討しています。
(Test::Unitを利用する方法もあるようです。([Ruby/assertしたい](http://tobysoft.net/wiki/index.php?Ruby%2Fassert%A4%B7%A4%BF%A4%A4)))


```rb
def foo(name)
  raise "name is nil."  unless name
end
```

- assertの指針
 - 発生してはならない状況に対して、アサーションを利用する
 - assertには実行コード（副作用のある処理,状態が変化する処理）を組み込まない

### b. エラー発生時の指針

- ログファイルにエラー内容を書き込む。
- エラーコード(又は例外)を返す

### c. バリゲード

- パブリックメソッドで受け取るデータは安全でないと見なし、データを検査し、適切なデータに変換する。入力データは、入力された時に適切な型に変換する。

----

# 2. ログを適切に記録する

エラー時や状態変化時にログを出力する事で、デバッガに頼らずとも、早期に原因が特定できる場合があります。

## 2.1 Railsでのログレベル

Railsの場合は、以下のレベルがあります。

|ログレベル|説明|
|:--------------|:---|
|unknown|不明なエラー|
|fatal|致命的なエラー|
|error|エラー|
|warn|警告|
|info|通知|
|debug|デバック情報|

どのような事象に対して、どのログレベルを設定するかを、チームで決定しておく必要があります。

## 2.2. 出力レベルの設定

config/environments/#{RAILS_ENV}.rbのファイルの以下で変更できます。


```rb
# config/environments/development.rb

config.log_level = :debug
```

## 2.3. 出力レベルの動的変更

実行中のRailsに対して、ログレベルを動的には変更できないようなので、
productionでデバッグログを出力したい場合は、動的に変更できるようにします。

参考:
http://rails.hatenadiary.jp/entry/2013/02/06/181430

## 2.4. Rails標準以外のクラスに対するログ出力

modelやcontrollerからは、loggerでログ出力できますが、独自クラスの場合は
Rails.loggerにします。

```rb
logger.info('info')
```

```rb
Rails.logger.info('info')
```

## 2.5. ログローテーション

ログファイルを１つにすると、ファイルの表示に時間がかかったり、ディスクスペースも圧迫してしまうので、ローテーションするようにします。

参考:
http://rails.hatenadiary.jp/entry/2013/02/06/201302


# 3. 例外

準備中

# 4. binding.pry(pry-byebug)を効果的に使う

pry-byebugによるデバッグはものすごく便利です。しかしデバッガに頼りすぎると、動作確認に時間がかかる事もあるため、ログ等で事前に原因を把握できた方が良いと思います。マルチスレッド環境の場合、タイミングに依存する事もあるため、デバッガでの原因特定が難しい事もあります。けどやっぱりデバッガは便利です。

[Railsの開発効率をあげる - Pryを使ってRailsのコンソールをパワーアップ & デバッグをする](http://ruby-rails.hatenadiary.com/entry/20141024/1414081224)

## 4.1 pryの入力の効率化（ちょっとだけ）

以下の設定をしておくと、改行で直前のコマンドが実行でき少しだけ楽ができます。s,s,s,s,...とかしなくてもs, 改行, 改行,改行で同じ事ができるようになります。

```
# ~/.pryrc

# Hit Enter to repeat last command
Pry::Commands.command /^$/, "repeat last command" do
  _pry_.run_command Pry.history.to_a.last
end

```

参考:
https://github.com/deivid-rodriguez/pry-byebug

# 4.2 処理停止中のブレークポイントの設定

pry中にブレークポイントを設定するには次のようにします。

```rb
# pry中

> break 51 # 51行目にブレークポイント設定
> break --delete 2 # 2番目のブレークポイントを削除
> break app/models/users.rb:15 指定したファイルの15行目にブレークポイント
> break --condition 2 x > 2 # 2番目のブレークポイントに条件設定
> break --condition 2 str != \"abc\" # 文字列の場合は要エスケープ
> break --condition 2 # 2番目のブレークポイントの条件解除
> break --show 2 # 2番目のブレークポイント確認
> break --disable-all # 削除.
```
参考:https://github.com/deivid-rodriguez/pry-byebug

# 5. 単体テストを作成する

参考:
[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](http://qiita.com/jnchito/items/42193d066bd61c740612)

参考:
[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)

# 5.1 specでの無駄なテストは省く

## 5.2. 特定のファイルのみ試験する

```sh
rake spec SPEC="spec/path/hoge_spec.rb"
```

参考:http://qiita.com/snaka/items/e0808f9cfbad87c49b4e

## 5.3. 特定のテストケースのみ試験する 1

```sh
rake spec SPEC="spec/path/hoge_spec.rb:39" # 39行目のメソッドをテストする
```

参考:http://labs.timedia.co.jp/2013/03/rspec.html

## 5.4. 特定のテストケースのみ試験する 2

```rb
# spec_helper.rb
  RSpec.configure do |config|
    config.filter_run :focus => true
    config.run_all_when_everything_filtered = true
  end
```

```rb
it "testcase", focus: true do # focus:trueのみ試験される.
end
```

参考:http://qiita.com/snaka/items/e0808f9cfbad87c49b4e

## 5.5 DatabaseCleaner利用時にデータの再投入の無駄を省く

DatabaseCleanerを利用している場合、次のように設定すると、マスターデータの内容を削除せず試験をする事ができます。


```rb
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseClean.erclean_with :truncation, { except: %w(m_table1 m_table2) }
```

参考: http://blog.inouetakuya.info/entry/2013/11/10/201040