Elixirで試しに何か書いてみる(その5) - 失敗したらやり直す

今回は例外処理について[^1]。

[^1]: "Let it crash"のコンセプトからすると、例外で落っこちたら再度プロセスを立ち上げる、方がElixirらしいのかもしれませんが…

[Elixirで試しに何か書いてみる(その4) - Taskを使って簡単にする](http://qiita.com/HirofumiTamori/items/ed131104494f4bd2f695)を書いたあと、**Task**のタイムアウト時間を修正したりパースするタグをいじったりと修正を繰り返していたのですが「そういえばいちいちどっちのタイプとか指定したくないなあ」と思いました。

どっちのタイプ、というのはgithubのリリースページを調べてみて

簡単にリリースバージョンだけ並んでるこういうやつを `:type1` 
![jquery_github.png](https://qiita-image-store.s3.amazonaws.com/0/46294/c2aa3d94-8fc4-d486-66d9-ed06916d4871.png)

リリースノートなどが混じって書かれているのを `:type2`
![TypeScript_guthub.png](https://qiita-image-store.s3.amazonaws.com/0/46294/8cb57849-8a97-1be5-bf5e-58c0dede5a04.png)

と分類してソースの中のURLとともにこんな感じで

```ex
    urls = [ #{ URL, type}
      {"https://github.com/jquery/jquery/releases", :type1},
      {"https://github.com/angular/angular/releases", :type1},
      {"https://github.com/facebook/react/releases", :type2},
      {"https://github.com/PuerkitoBio/goquery/releases", :type1},
      {"https://github.com/revel/revel/releases", :type2},
      {"https://github.com/lhorie/mithril.js/releases", :type1},
      {"https://github.com/riot/riot/releases", :type1},
      {"https://github.com/atom/atom/releases", :type2},
      {"https://github.com/Microsoft/TypeScript/releases", :type2},
      {"https://github.com/docker/docker/releases", :type2},
      {"https://github.com/JuliaLang/julia/releases", :type2},
      {"https://github.com/nim-lang/Nim/releases", :type1},
      {"https://github.com/elixir-lang/elixir/releases", :type2},
      {"https://github.com/philss/floki/releases", :type1},
      {"https://github.com/takscape/elixir-array/releases", :type2},
    ]
```

記述していたんですね、今まで。

でもこれだと予めどっちのタイプか判断して書いておかないといけないのでイマイチです。どっちのタイプかその場で判断する、という関数を考えるという手もあるのでしょうが…。

これまでのプログラムだとタイプが合っていないと

```ex
  def fetch_content(param) do
    {{url, type}, i} = param
    #IO.puts "URL = #{url}"
    #IO.puts "i = #{i}"
    ret = HTTPoison.get!( url )

    %HTTPoison.Response{status_code: 200, body: body} = ret

    {_,_,n} = Floki.find(body, ".container strong a") |> List.first
    {_, d} = Floki.find(body, "time") |> Floki.attribute("datetime")
                                      |> List.first
                                      |> Timex.DateFormat.parse("{ISOz}")
    if(type == :type1) do
      {_,[{_,_}],x} = Floki.find(body, ".tag-name") |> List.first
    else
      {_,[{_,_}],x} = Floki.find(body, ".release-title a") |> List.first
    end
    d |> Timex.Date.Convert.to_erlang_datetime
      |> Timex.Date.from "Asia/Tokyo"
      #IO.inspect n
    {:ok, {hd(n),hd(x),d,i}}
  end

```

の` {_,[{_,_}],x} = Floki.find(body, ".tag-name") |> List.first`
のあたりで

```iex
** (EXIT from #PID<0.172.0>) an exception was raised:
    ** (MatchError) no match of right hand side value: nil
        (vercheckex) lib/vercheckex.ex:29: VercheckEx.fetch_content/1
```

になって`MatchError`で失敗していました。

### 変えたところ

このパターンマッチしてるところで失敗したら(少なくとも今のところ２タイプしかないので)もう片方を試せばいいのですね。

そこで、`try` ～ `rescue` による例外処理を入れて以下のようにしてみました。

まず、上記のパターンマッチを2つのプライベート関数に分けます。

```ex
  defp parse_title(body, :type1) do
    try do
      {_,[{_,_}],x} = Floki.find(body, ".tag-name") |> List.first
      {:ok, x}
    rescue
      what -> {:error, what}
    end
  end

  defp parse_title(body, :type2) do
    try do
      {_,[{_,_}],x} = Floki.find(body, ".release-title a") |> List.first
      {:ok, x}
    rescue
      what -> {:error, what}
    end
  end
```

関数`parse_title`自体も引数パターンマッチで`:type1, :type2`の処理を振り分けるようにしました。

**Elixir**の例外処理は**エラー**を拾う場合は

```ex
try do
  {:ok, 結果の値} # エラーが起きない場合の式(戻り値)
rescue
  e -> {:error, e} # エラーが起きた時の式(戻り値)
end
```

のような形を取ります。呼び出す側で

```ex
    # まずは:type1として呼び出す
    {result,x} = parse_title(body, :type1)
    # Match Errorが起きたらもう一方のタイプでやり直す
    if result == :error do
      {_,x} = parse_title(body, :type2)
      x
    end
```

としておけば例外はちゃんとハンドルされてダメな場合はもう片方のタイプとしてパースし直します。

ソースは[github](https://github.com/HirofumiTamori/vercheckex)にあります。

### まとめ

だいぶすっきりしてきました。