Beautiful Error Handling
2012年夏のプログラミング・シンポジウム
2012年夏のプログラミング・シンポジウム
田中英行 (@tanakh , http://tanakh.jp )
(株)Preferred Infrastructure勤務のプログラマ
Haskell 愛好家
「すごいHaskellたのしく学ぼう!」 (Learn You a Haskell for Great Good! の和訳)
エラー処理に美しさを!
エラー処理の抽象化
Haskellでのアプローチ
エラー処理は醜くなりがち
なんで汚くなるのか?
なぜそういう状況になっているのか?
大きな原因の一つはプログラミング言語の記述力の問題
fine-grained な処理の抽象化、リッチな型システム、etc...
int foo(...)
{
int fd = open(...);
if (fd < 0) return -1; // <- error handling
...
}
int foo(...)
{
int fd = open(...);
if (fd < 0) return -1; // <- error handling
int fe = open(...);
if (fe < 0) { // <- error handling
close(fd); // <- error handling
return -1; // <- error handling
} // <- error handling
}
int foo(...)
{
int fd = open(...);
if (fd < 0) return -1; // <- error handling
int fe = open(...);
if (fe < 0) { // <- error handling
close(fd); // <- error handling
return -1; // <- error handling
} // <- error handling
int ff = open(...);
if (ff < 0) { // <- error handling
close(fe); // <- error handling
close(fd); // <- error handling
return -1; // <- error handling
} // <- error handling
...
}
そういう背景からか、
エラー処理を Ugly に、愚直に書いてあるプログラムが良いプログラムであ る
Ugly なエラー処理を、きちんともれなく書けるプログラマが良いプログラ マである
という風潮も
→ プログラムに美しさが必要だから
→ なぜプログラムに美しさが必要なのか?
→ 多分、竹内先生が語って下さっているはず
美しいプログラムは
完結で、理解しやすい
変更しやすい
つまり
(あくまでテクニカルに)
プログラムを書くということを職人芸にしたくない
例えば、優れたプログラマだからといって
なぜか?
ではエラー処理は…?
エラー処理の抽象化が必要である
抽象化されていれば
という理想の世界
プログラムの実行中に発生するエラーに対して、 正しく回復処理・あるいは報告を行い、 何事もなかったかのように動き続ける堅牢なソフトウェアを書くための処理
報告して終了するだけで十分な場合もある
実際のところ一長一短
返り値にエンコードするタイプは容易にチェック忘れ
例外を用いるタイプは処理系のサポートが必要
いずれの場合にも同様の安全性は必要
どのエラー通知を使うかは、言語デザインにも関わる
Javaなど、例外とGCをあわせて提供する
Goなど、例外をサポートしないという選択肢
例外はコストが大きい
例外はコントロールフローがわからなくなる
複数のエラーを組み合わせて使うことが難しい
なぜ複数のエラー通知手段を用いるのが難しいのか?
{
try {
Hoge h = new Hoge(foo);
Moge i = h.find(x);
if (i == null) {
...
}
}
catch(HogeException e) {
...
}
}
Java は全般的に例外でエラーを通知する
Map.get()など、null で返すインターフェースもある
そもそも、何をエラーとして扱うかという話でもある
実際、使い分けたほうが綺麗になることもある
そのために必要なもの
エラーの抽象化
メモリ管理との対比
GCなんか使えない(昔)
神経すり減らしてエラー処理を書くべし(今)
エラー処理の抜けを検出できること
エラーに関する情報が型に現れること
Composableであること
プログラムを組み合わせ可能であるということ
正しいとは(ここでは)
特定のプロパティ
これを組み合わせたプログラムにおいても保持する
cf. 例外は抽象化の一例ではあるが、Composableではない
class Hoge {
public void foo() {
try {
DB db = new DB(...);
try {
if (db.getRow(...)) {
...
}
}
catch(...) {
...
}
}
catch(...) {
db.release();
}
}
}
モナドを用いるアプローチ
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
エラーの送信(throwError)、エラーの受信(catchError)
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
よくあるパターンを抽象化できるようになる
これらを組み合わせて、
ign :: MonadError e m => m () -> m ()
ign m = m `catchError` (\e -> return ())
受け取ったエラーを無視するだけのコード
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))
失敗したらカウンタを減らして再度実行
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"
あんなコードやこんなコードも自由自在!
Composableであっても(正しくても)、 記述力が十分とは限らない。
これに対する解答が最近ようやく確立
class MonadTrans t => MonadTransControl t where
data StT t :: * -> *Source
liftWith :: Monad m => (Run t -> m a) -> t m aSource
-- liftWith is similar to lift in that it lifts a computation from the argument monad to the constructed monad.
-- Instances should satisfy similar laws as the MonadTrans laws:
-- restoreT :: Monad m => m (StT t a) -> t m a
type Run t = forall n b. Monad n => t n b -> n (StT t b)
ともかく、それなりに課題もある
Verification
Effect Analysis
エラー処理に美しさを!
エラーハンドラの抽象化