エラー処理の抽象化

田中英行 <tanaka.hideyuki@gmail.com>

2012/06/26 @LOG.debug("nice catch!")

自己紹介

java-ja …!?

とんでもないところに来てしまったぞ (((´・_・`))) ブルブル

本日のお話

エラー処理とは何なのか

エラー処理は好きですか?






僕は嫌いです

そんなあなたのために

あわせて読みたい:エラー処理を書いてはいけない

エラー処理

時々刻々と発生し続けるエラーに華麗に対処しつつ、 何事もなかったかのように動き続ける 堅牢なソフトウェアを作るための処理。

※ そういうソフトウェアである必要がなければ、 必ずしもきちんとやる必要はない

エラー処理はなんで必要なのか?

プログラムに おかしなものは 憑き物だ
阿良◯木暦

あなたのそばに這い寄るエラー

エラー処理は面倒・難しい

面倒…難しい……!?

!?

そうだね、抽象化だね。

Don't repeat yourself
Andy Hunt, Dave Thomas

エラー処理と例外

エラー処理

エラー処理には次の2つが必要。

エラー通知の手段(代表的なもの)

intでエラーを返す

int foo(...)
{
  int fd = open(...);
  if (fd < 0) return -1;
  int fe = open(...);
  if (fe < 0) {
    close(fd);
    return -1;
  }
  int ff = open(...);
  if (ff < 0) {
    close(fe);
    close(fd);
    return -1;
  }
  ...
}

intでエラーを返す

int foo(...)
{
  int fd = open(...);
  if (fd < 0) return -1;   // <- エラー処理
  int fe = open(...);
  if (fe < 0) {            // <- エラー処理
    close(fd);             // <- エラー処理
    return -1;             // <- エラー処理
  }                        // <- エラー処理
  int ff = open(...);
  if (ff < 0) {            // <- エラー処理
    close(fe);             // <- エラー処理
    close(fd);             // <- エラー処理
    return -1;             // <- エラー処理
  }                        // <- エラー処理
  ...
}

利点と欠点

利点と欠点

例外を用いたエラー処理

class Main {
  public static void main (String[] args) {
    try {
      int x = 1/0;
      ...
    } catch (ArithmeticException e) {
      // error handling...
      ...
    }
  }
}

利点と欠点

例外を用いたエラー処理

http://www.atmarkit.co.jp/fjava/javatips/166java051.html

ん…?

エラー通知として何を利用するか?

プログラミング言語の表現力・思想に影響される

例外使うべからず?

割りと影響力のあるGoogle C++スタイルガイドが 例外を禁止している

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions

例外安全性

例外を用いた正しいコードを書くのは大変。

守るべきガイドラインを、例外安全性としてまとめた

詳しくはこちら → http://togetter.com/li/227690

STLのstack::pop()の返り値がvoidだ問題

全人類が知るべき問題

※注意

一般に、スタックのpopを例外安全に書けないという話ではない

あくまで、C++のセマンティクスで、STLの標準ライブラリの仕様を満たしつつ、 強い例外安全 が実現できないという話

(cf. java.util.Stack<E>)

まあ

コントロールフロー的批判

適材適所

じゃあどうすればいいの (´・_・`)…?

エラー処理の抽象化

エラー送出の抽象化

エラー受信の抽象化

実現に必要なもの

ビルディングブロック

どうやって実現するんですか?

では、お見せしましょう。

Haskellでの例

幾つかのエラー処理フレームワーク

モナドを使えば出来るよ!

おおまかな説明

Error モナド (in mtl)

class (Monad m) => MonadError e m | m -> e where
    -- | Is used within a monadic computation to begin exception processing.
    throwError :: e -> m a

    {- |
    A handler function to handle previous errors and return to normal execution.
    A common idiom is:

    > do { action1; action2; action3 } `catchError` handler

    where the @action@ functions can call 'throwError'.
    Note that @handler@ and the do-block must have the same return type.
    -}
    catchError :: m a -> (e -> m a) -> m a

throwErrorcatchErrorという関数が定義されている)

Error モナド

IO例外を扱えるようにする

instance MonadError IOException IO where
    throwError = ioError
    catchError = catch

Eitherを扱えるようにする

instance Error e => MonadError e (Either e) where
    throwError             = Left
    Left  l `catchError` h = h l
    Right r `catchError` _ = Right r

instance (Monad m, Error e) => MonadError e (ErrorT e m) where
    throwError = ErrorT.throwError
    catchError = ErrorT.catchError

ghci> throwError "hoge" :: Either String ()
Left "hoge"
ghci> throwError "hoge" :: Maybe ()
Nothing

エラーの抽象化 → エラーハンドラパターンの抽象化

よくあるパターンを抽象化できるようになる

これらを組み合わせて、

抽象化:エラー無視

ign :: MonadError e m => m () -> m ()
ign m = m `catchError` (\e -> return ())

受け取ったエラーを無視するだけのコード

抽象化:n回試行

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))

失敗したらカウンタを減らして再度実行

抽象化:aが失敗したらbを実行

or :: MonadError e m => m a -> m a -> m a
or a b = do
  a `catchError` (\_ -> b)

エラーハンドラでbを実行する

組み立てる

main = ign $ tryN 10 $ do
  download "http://xxx/aznn.png" `or`
  download "http://xxx/prpr.png"

あんなコードやこんなコードも自由自在!

extra contents

エラー処理とリソース管理

エラー処理と並行処理

例外マスク

使用例:

mask $ \restore -> do
     x <- acquire
     restore (do_something_with x) `onException` release
     release

引数の処理は非同期例外がマスクされる。 ただし、引数(ここではrestore)を用いれば、 マスクを解除できる。

OO言語での実現

まとめ

まとめ




Thank you for listening!