私は自分のエラー処理に一貫性を持たせるための練習をしており、自分が書いたコードが縮小し始めることを期待し続けています。しかし、私はドメインを意味する永続化関数を構築しました。モナド処理とカスタム エラー処理を行うためだけに書かなければならなかったコードの量は驚くべきものでした。
- 「プログラミングエラー」の場合は、呼び出すだけです
error "assertion blown"
- 本当にありふれたものについては、Nothing を返します (要求されたオブジェクトが存在しません)。
- 処理する必要があるエラーについては、それを処理するインスタンスを
Either E V
作成することにより、または同等のものを返しています。Control.Monad.Error
アプリケーションにはプリミティブと呼ばれる複数の関数がありますが、それらは特定のエラーをキャッチし、DBError 型の値をスローしてエラーを発生させます。だから、私はそれらを次のように定義しました:
data DBError = ConversionError ConvertError
| SaveError String
| OtherError String
deriving (Show, Eq)
instance Error DBError where
noMsg = OtherError "No message found"
strMsg s = OtherError s
type DBMonad = ErrorT DBError IO
selectWorkoutByID :: IConnection a => UUID -> a -> DBMonad (Maybe SetRepWorkout)
insertWorkout :: IConnection a => SetRepWorkout -> a -> DBMonad ()
呼び出し側アプリケーションのレベルでは、Workout はデータベースに保持される一意のオブジェクトであるため、アプリケーションは saveWorkout のみを呼び出します。これは、selectWorkoutByID、insertWorkout、および updateWorkout を期待どおりに使用します。
saveWorkout :: IConnection a => SetRepWorkout -> a -> DBMonad ()
saveWorkout workout conn =
r <- liftIO $ withTransaction conn $ \conn -> runErrorT $ do
w_res <- selectWorkoutByID (uuid workout) conn
case w_res of
Just w -> updateWorkout workout conn >> return ()
Nothing -> insertWorkout workout conn >> return ()
case r of
Right _ -> return ()
Left err -> throwError err
これは醜いです。DBMonad を実行してラップを解除し、それを IO モナドで実行し、IO を DBMonad に戻してから、結果をチェックして DBMonad で結果を再ラップする必要があります。
より少ない、読みやすいコードでこれを行うにはどうすればよいですか?
カスタム アプリケーション モナドを使用して回復可能なエラーを処理すると、記述しなければならないコードの量を減らすことができると期待していますが、これは逆です!
追加の質問を次に示します。
- アプリケーションのセマンティック エラーを構築するためのより良い方法はありますか?
- 代わりに Control.Exception を使用する必要がありますか?