ガード句でRubyの防御力を上げる！gem Contract

今回紹介するのはRubyで型を縛る方法です！
(phperの方はごめんなさい！)

<span style="font-size: 120%"><b>〜 読んでいただきたい人 〜</b></span>

+ ジェンガコードに怯えている方  
+ 人とコミュニケーションが取りづらい方  
+ 人の入れ替わりが激しいプロジェクトにいる方  
+ 適当なコメントやコメントが無い事に悩まされる方  
+ エンジニアの皆さん  

# 型を縛る？？とは
<b><span style="font-size: 100%">今回はRubyのgem [Contracts](https://github.com/egonSchiele/contracts.ruby)を使用します。
このgemは「メソッドの引数と戻り値が、ある条件を満たしているかをチェック」できるものです。</span></b>

## 使用方法
### インストール
まずはgemfileに記述します。

``` ruby:Gemfile
gem 'contracts'
```

### ファイルへの記述
Contractsをclassやmoduleにincludeします。

``` ruby:sample.rb
class Sample
  include Contracts
```

これで使用できる準備ができました！
## 簡単な使用例
さっそく例に移ります!
まずは成功例からです。

``` ruby
class MyCalculate
  include Contracts

  Contract Num, Num => Num
  def self.add(a, b)
    a + b
  end
end

puts MyCalculate.add(1, 1)
```

解説です。
MyCalculateClassのaddメソッドの引数として2つの引数Numeric型を指定して出力しています。  

Contractsの設定は 
<b>1.MyCalculateClassのaddメソッドの引数が 2つともNumeric型 であること
2.MyCalculateClassのaddメソッドの戻り値が Numeric型 であること</b>
を確認しています。
ここでは問題なく成功したのでなにもおこりません。

こちらは失敗例です。

```  ruby
class MyCalculate
  include Contracts

  Contract Num, Num => Num
  def self.add(a, b)
    a + b
  end
end

puts MyCalculate.add(1, "good!!")

```
解説です。
MyCalculateClassのaddメソッドの引数として Numeric型・String型　を指定して出力しています。 

先ほどと同様でContractの設定は
<b>1.MyCalculateClassのaddメソッドの引数が 2つともNumeric型 であること 
2.MyCalculateClassのaddメソッドの戻り値が Numeric型 であること</b>
を確認しています。    

しかしNumeric型を期待していたのにString型が指定されました。
そのため、これを実行すると以下のように例外が発生します。    

```  ruby
./contracts.rb:4:in `failure_callback': Contract violation: (RuntimeError)
    Expected: Num,
    Actual: "good!!"
    Value guarded in: Object::add
    With Contract: Num, Num    
```
解説です。
<b>1.`Expected: Num`で期待(予想)している引数はNumeric型であることがわかります。 
2.`Actual: "good!!"`で実際に引数で指定した値を出力しています。 </b>
Numeric型が来るはずなのに値がNumeric型じゃ無いですよ！と出力されています。

## 値の型チェック
### 例1)
```  ruby
Contract String => nil
def greet(name)
  puts "hello, #{name}!"
end
```
解説です。  
Contractsの設定は
<b>1.greetメソッドの引数がString型であること 
2.greetメソッドの戻り値がnilであること </b>
を確認しています。 

先ほどと違うところはメソッドの戻り値が何もない場合
nilを指定できるところです。  

### 例2)
``` ruby
Contract ArrayOf[Num] => Num
def count_total_numbers(nums)
  nums.inject(:+)
end
```
解説です。 
Contractsの設定は
<b>1.count_total_numbersメソッドの引数が Array型 で配列の中は Numeric型 はあること 
2.count_total_numbersメソッドの戻り値が Numeric型 であること</b> 
を確認しています。  


この場合、配列の中まで確認することができます。  
成功 : `count_total_numbers([1, 2, 3, 4]`
失敗 : `count_total_numbers([1, 2, 3, "foo"])` 

そのほか、Contractsに標準で組み込まれている様々な型チェックが使用できます。  
実際に業務でContractsを使用した際に、頻出した型チェック(のリスト)です。

| 型      　　　  | 確認できる型 |
|:------------------|:------------|
| <b>Bool</b>             |真偽値(true or false)をチェックできます。|
|<b> Or </b>               |型の条件が2つ存在する場合にどちらか一方の型をチェックできます。|
| <b>HashOf</b>        |Hash型の時に使用します。例)HashOf[Symbol,String] Hash値の中の型まで見ることができます。|
| <b>Maybe</b>         |型にnilが来る場合に使用します。　Or[String,nil]と同じ意味です。|
| <b>Any</b>              |型を見ずに何かしらの値が来ていることをチェックできます。<br>例えばHashが入れ子になりすぎている状態だと長すぎて邪魔なためAnyで省略したり<br>rubyでは最後に評価した値が戻り値になるので<br>引数や戻り値を見る必要がない場合などに使います。|

もっと詳しい情報は[こちら](http://egonschiele.github.io/contracts.ruby/)のリファレンスページに記載されています。
## 自作のContracts
型自体が標準で組み込まれていない、開発者側が定義した型が存在する場合 
自分で型チェックを作ります!σﾟﾛﾟ)σ

Contractの標準で組み込まれているメソッドはこのようになっています。

``` ruby
class Num
  def self.valid? val
    val.is_a? Numeric
  end
end
```
解説です。  

classメソッドに`valid?`をつけることによってバリデーションを使用します。 
引数で渡ってきた値を`is_a?`メソッドで型の判定をしています。 

標準メソッドを応用して新たにチェックするメソッドを作成します。 
railsのlibディレクトリの中にCustomContractsディレクトリを作成し 
CustomContracts配下にUserファイルを作成しました。 
  
``` ruby
module CustomContracts
  class User
    def self.valid? val
      val.is_a?(::User)
    end
  end
end  
```
呼び出し

``` ruby
Contracts CustomContracts::User => String
```

解説です。
基本的に標準メソッドと同じですがここでチェックしている型は
User(UserClass)型です。
呼び出し側はクラスメソッドになっているので`CustomContracts::User`
で呼び出せます。

`is_a?(::User)`は`is_a?(User)`にしてしまうとCustomContractsのUserクラス
を見てしまうのでこのようにしています。
## 最後に
最後までご覧いただきありがとうございます！ 

実際にContractsをプロジェクトに導入すると
メソッドの引数に制限が加えられたので、想定していない型で引数が
セットされることがなくなりました。
つまり、<span style="font-size: 130%"><span style="color: #ff0000"><b>型の不一致によるバグを事前に防ぐ</b></span></span>ことが出来ます！

皆さんも是非使ってみてはいかがでしょうか？

ネオキャリアのエンジニアブログでも同じ記事を投稿しています。
[こちら](https://github.com/egonSchiele/contracts.ruby)のブログも随時更新していきますので宜しくお願いします。