今回は例外処理について1。
Elixirで試しに何か書いてみる(その4) - Taskを使って簡単にするを書いたあと、Taskのタイムアウト時間を修正したりパースするタグをいじったりと修正を繰り返していたのですが「そういえばいちいちどっちのタイプとか指定したくないなあ」と思いました。
どっちのタイプ、というのはgithubのリリースページを調べてみて
簡単にリリースバージョンだけ並んでるこういうやつを :type1
と分類してソースの中の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タイプしかないので)もう片方を試せばいいのですね。
そこで、try
~ rescue
による例外処理を入れて以下のようにしてみました。
まず、上記のパターンマッチを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にあります。
まとめ
だいぶすっきりしてきました。
-
"Let it crash"のコンセプトからすると、例外で落っこちたら再度プロセスを立ち上げる、方がElixirらしいのかもしれませんが… ↩