LoginSignup
5
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-11-30

今回は例外処理について1

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

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

簡単にリリースバージョンだけ並んでるこういうやつを :type1
jquery_github.png

リリースノートなどが混じって書かれているのを :type2
TypeScript_guthub.png

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

    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},
    ]

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

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

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

  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
のあたりで

** (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で失敗していました。

変えたところ

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

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

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

  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の例外処理はエラーを拾う場合は

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

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

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

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

ソースはgithubにあります。

まとめ

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


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

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4