かとじゅんの技術日誌

技術の話をするところ

チェックされる例外とチェックされない例外について

例外の扱いについてあれこれと調べてみました。

まず、既存のエントリから。ふむふむ。ごもっとも。

Error callerによるリカバリは不能(原因はcalleeにある or 特定不能)である為。
RuntimeException リカバリの可否はcalleeに判断不能である為。callerはリカバリ可能ならばcatchすれば良いし、不可能ならばスルーすれば良い。
Exception(非RTE) 第三者異常を想定することは必須。callerにも同様の想定を強制させ、リカバリ処理を書かせなければならない為。

特にAPI設計者として、気を使うのはどういう場合にリカバリ可能とするかどうかですね。リカバリというのをどう定義するかってところ。

で、先人たちはどう例外を扱っているんだろうということで、Seasar2とかSpringを調べてみる。
Seasar2はRuntimeExceptionですね。2004年ぐらいからのフレームワークはRTEをスローしていると思いますよって、ひがさんから情報。
Springをのぞいてみたら混在。

RuntimeException
L org.springframework.context.NoSuchMessageException
L org.springframework.core.NestedRuntimeException
 L org.springframework.aop.AopInvocationException
 L org.springframework.aop.framework.AopConfigException
 L org.springframework.beans.BeansException.java
 L org.springframework.core.task.TaskRejectedException
 L org.springframework.dao.DataAccessException
 L org.springframework.ejb.access.EjbAccessException
 L org.springframework.jms.JmsException
 L org.springframework.jmx.JmxException
 L org.springframework.jndi.JndiLookupFailureException
 L org.springframework.mail.MailException
 L org.springframework.remoting.RemoteAccessException
 L org.springframework.scheduling.quartz.JobMethodInvocationFailedException
 L org.springframework.scheduling.SchedulingException
 L org.springframework.scripting.ScriptCompilationException
 L org.springframework.transaction.TransactionException
 L org.springframework.web.multipart.MultipartException

Exception
L org.springframework.core.NestedCheckedException
 L org.springframework.jdbc.support.MetaDataAccessException (RTEにラップしなおしている。。。)
 L org.springframework.validation.BindException
L ServletException
 L org.springframework.web.HttpRequestMethodNotSupportedException 
 L org.springframework.web.HttpSessionRequiredException
 L org.springframework.web.servlet.ModelAndViewDefiningException
 L org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException
 L org.springframework.web.util.NestedServletException
  L org.springframework.web.bind.ServletRequestBindingException

うーん、どういう場合に使い分ければよいか?もう少し掘り下げてみようと思い、そこでEffective Javaを手にしてみたわけですよ。例外の章を改めて読み直してみる。

まず、【項目58 回復可能な状態にはチェックされる例外をプログラミングエラーには実行時例外を使用する】ですね。

まとめると、回復可能な状態にはチェックされる例外を使用して、プログラミングエラーには実行時例外を使用してください。もちろん、その状況が必ずしも白黒つけられるわけではありません。

回復可能かどうかは、API設計者が判断することです。状態が回復可能であると信じるのであればチェックされる例外を使用してください。もし、回復可能かどうかが明白でない場合には、おそらくチェックされない例外を使用するのがよいでしょう。

やはり、フォースの加護を信じるってことですかね。。。API設計者の作るAPI仕様によって、それを回復可能とするか、プログラミングエラーとするか、変わってきますね。

項目58の終わりのほうで、「ギフトカードで購入しようとして、そのカードに十分な残金がなくて失敗した時に、チェックされる例外がスローされる」という例があるのですが、これもチェックされる例外の事例としてわかりやすくて、なるほどと思いました。

また、【項目59 チェックされる例外を不必要に使用するのを避ける】で

そうは言っても、チェックされる例外を過剰に使用すると、APIをかなり使いにくいものにします。メソッドが1つ以上の例外をスローするならば、そのメソッドを呼び出すコードは、1つ以上のcatchブロックでそれらの例外を処理しなければなりません。あるいは、それら例外をスローすると宣言して、例外を外側に伝播させなければなりません。どちらの方法でも、プログラマに対して多少の負荷を課します。

APIの適切な使用では例外状態を防ぐことができなく、かつ、そのAPIを使用しているプログラマが例外に直面した時に何らかの有用な処理をできる場合に、その負荷は正当化されます。この両方がない限り、チェックされない例外の方が適切です。

なるほど。項目59はチェックされる例外か、チェックされない例外か、迷うときに一つの指針になりそうな気がします。
チェックされる例外を扱う場合は回復可能な手段を提供すべきで、この場合、APIユーザに対して例外から不足している残高値を取得し、残高を追加できるAPIも提供するべきと説いてありました。チェックされる例外を使用するなら回復のためのAPI仕様を念頭におくということが重要になってきますね。回復手段も存在しないのに、チェック例外を使用するのは論理的に好ましくないですね。

ということで、チェックされる例外はリカバリAPIも含めて考えるというのがポイントですね。