例外の勉強会をやるので、是非にというお話を頂いて、 LOG.debug(“nice catch!”) というイベントで発表させていただきました。 当日の資料はこちらでご覧になれます。

エラー処理の抽象化

昨年末頃に社内セミナーで発表した エラー処理を書いてはいけない をもうちょっと抽象的に、あるいは具体的な話を入れて焼き直したような内容です。

今回は java-ja さんの勉強会という事で、 なにやらモヒカンとかマサカリとかが飛んでくるらしい、 とんでもないところに来てしまったとビクビクしていましたが、 この難局も何とか乗り切りました。はい。

皆さんとても真面目にソフトウェアエンジニアリングについて議論していて、楽しかったです。 僕は普段Javaでコードは書かないのですが、 Javaだと普通こうする的なのが垣間見えて、 いろいろJavaのバッドノウハウ(あるいはグッドプラクティス)が学べました。 Javaは大変だと思いました。


発表後、幾つかご意見・質問・フィードバックをいただきましたので、 ここで回答させていただこうと思います。

  • エラーフレームワーク、どれがデファクトなのか?

mtlMonadErrorは事実上の標準で、 サポートも割りと手厚いですが、機能が貧弱かもしれません。 でもこれで十分なことも多いです。

monad-control/ lifted-baseはモナド変換子に対してきちんとしたサポートがある、新しめのライブラリです。 WebフレームワークYesodでも使われています。

  • n回再試行するやつ、失敗するごとにログに出したいような場合は?純粋なHaskellでは難しいのではないか。

元のコードはこれです。たしかにこのままではIOができないのでログは出せません。

tryN :: (MonadError e m) => Int -> m a -> m a
tryN n m = go n where
  go 1 = m
  go i = m `catchError` (\e -> go (i-1))

そういう時は私なら、コンテクスト (上のコードでは、(MonadError e m) =>)に このモナドはIOできますよという MonadIO を追加して解決します。

tryN :: (MonadError e m, MonadIO m) => Int -> m a -> m a
tryN n m = go n where
  go 1 = m
  go i = m `catchError` (\e -> liftIO (outputLog $ show e) >> go (i-1))

m が MonadIO のインスタンスになることにより、このモナドは中にIOを含むことができるようになります。

  • (46ページ~49ページのくだり)このあと組み合わせたコードが来るんですよね? → 来ない!

すいません、一番美味しいところを忘れていました! 当日は宿題とか言いましたが、追記しておきました。

  • 全人類が読むべき本第2位は?
  • 他のやり方を知らないと、そういうものだと思ってしまう。 そういうものだと思ってしまっていると、 頑張って漏れ無く手でエラー処理コードを書くことは素晴らしいことだ、 あまつさえ優れたプログラマである、そんな風に思われているフシもある

はい、残念なことです。 正規表現を使って置換すれば1分で終わる仕事を、 何時間もかけてコツコツ手で編集してるのと同じ構図の話です。 手でやれば時間もかかるし、間違いも起こります。 正規表現を使うとズルだと言われるとか、そういう話を笑えません。 プログラマは怠惰でありたいものです。