Advertisement
NLinker

Почему сhecked exceptions в Java не работают

Apr 24th, 2015
344
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scala 10.92 KB | None | 0 0
  1. # Почему сhecked exceptions в Java не работают
  2. ... и какая альтернатива им в Scala
  3.  
  4. ## Когда они нужны
  5. Один человек достаточно ёмко [объяснил](http://stackoverflow.com/a/19061110) случай, когда их имеет смысл использовать
  6. > Checked Exceptions should be used to declare for __expected__, but __unpreventable__ errors that are __reasonable to recover from__.
  7.  
  8. Увы, это достаточно узкий случай, который волей случая натянулся на создание вообще любого софта в Java.
  9.  
  10. ## В чём проблема?
  11.  
  12. 1. Это экспериментальная фича, и java - единственный язык, который имеет её. В то время никто ещё не думал, что java будет везде, и была иллюзия, что если что-то окажется плохо, то фичу можно будет быстренько выпилить.
  13.  
  14. 1. Исключения становятся частью интерфейса, что неправильно - разные реализации могут кардинально различаться по поводу того, какие исключения они будут бросать.
  15.  
  16. 1. Необходимо постоянно оборачивать библиотечные исключения в исключения своего приложения, что раздувает количество рутинного кода.
  17.  
  18. 1. throws Exception просачиваются везде в стандартном API, что фактически не даёт никакого профита, а только засоряет как само API, так и будущие реализации (Например, AutoCloseable, Callable):
  19.     ```java
  20.     public interface AutoCloseable {
  21.         void close() throws *Exception*;
  22.     }
  23.     public interface Callable<V> {
  24.         V call() throws *Exception*;
  25.     }
  26.     ```
  27.     Аналогично, весь код jdbc насквозь пронизан java.sql.SQLException, что не даёт ничего, кроме необходимости создавать рутинный код.
  28.  
  29. 1. Невозможно написать правильный throws для функций высшего порядка (не годится даже бесполезный Exception):
  30.     ```java
  31.         interface Func<T, R> { R apply(T t) throws *???*; }
  32.  
  33.         List<R> map(List<T> list, Func<T, R> f) throws *???* {}
  34.     ```
  35.     Если `f` аннотирован исключением, то это же исключение должно быть и у `map` (снова `Exception`?) и вдобавок семантика `map` становится намного сложнее - ведь процесс уже может прерваться в любой момент, что вызывает вопрос "А как тогда корректно обработать это исключение?" порождая вопросы вроде доступа к уже обработанным и ещё не обработанным элементам и прочее. Непоследний момент ещё и в том, что `map` становится непараллелизуемой. Если же аннотации различаются, то данные функции (`map` и `f.apply`) вообще невозможно сочетать. Единственный выход - избавиться вообще от `throws`, и сделать функцию `map` чистой, а исключительные ситуации возложить на код где-то наверху.
  36.  
  37. 1. Характерен пример с интерфейсом Appendable в JDK:
  38.     ```java
  39.     Appendable.append(CharSequence csq) throws IOException;
  40.     ```
  41.     Что говорит этот `throws`? Каждый раз, когда я добавляю строку к чему-нибудь (билдеру, логам, консоли) я должен ловить это `IOException`. Почему? Потому что эта операция _теоретически_ может совершать IO (`Writer` тоже реализует `Appendable`). Следовательно в каждом месте я должен делать
  42.     ```java
  43.     try {
  44.       log.append(message)
  45.     }
  46.     catch (IOException e) {
  47.       // swallow it
  48.     }
  49.     ```
  50.     Да, придётся проглатывать исключение, потому что никакого другого разумного способа его обработать нет.
  51.  
  52. 1. Аналогичная проблема вообще с любым кодом общего назначения. Например, коллбэки также невозможно аннотировать нужным исключением:
  53.     ```java
  54.         class Component implements Listenable {
  55.             Listener getListener() {...}
  56.             interface Listener {
  57.                 void changed(Component c) throws *???*;
  58.             }
  59.         }
  60.  
  61.         /** библиотека общего назначения, предполагается,
  62.             что она будет использоваться для кучи проектов */
  63.         class Library {
  64.             Component c = ...;
  65.             void genericMethod() throws *???* {
  66.                 if (...) с.getListener().changed(c);
  67.             }
  68.         }
  69.     ```
  70.     Никакого разумного исключения, кроме `Exception`, вместо *???* поставить не получится, но если воткнуть `Exception`, то снова получим необходимость создания большого количества рутинного кода.
  71.  
  72. 1. Если для кого-то имеют значения мнения авторитетов, то вот:
  73.  
  74.    [Bruce Eckel](http://www.mindview.net/Etc/Discussions/CheckedExceptions)
  75. > Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result – decreased productivity and little or no increase in code quality.
  76.  
  77.    [Rod Waldhoff](http://radio-weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html)
  78. > Checked exceptions are pretty much disastrous for the connecting parts of an application's architecture however. These middle-level APIs don't need or generally want to know about the specific types of failure that might occur at lower levels, and are too general purpose to be able to adequately respond to the exceptional conditions that might occur.
  79.  
  80.    [Anders Hejlsberg](http://www.artima.com/intv/handcuffs.html)
  81. > I see two big issues with checked exceptions: scalability and versionability.
  82.  
  83. ## Как правильно?
  84.  
  85. В Java все исключения обернуть в RuntimeException и создать общий код где-то наверху, который корректно и полно обрабатывает все исключения. Так сделано в Tapestry, Spring, Joda-time и наверное ещё многих других фреймворках.
  86.  
  87. В Скале можно использовать scala.util.Try, сделать алгебраический тип, аннотировать метод исключениями или (есть совершенно фатальные причины) использовать соответствующий плагин для Scala.
  88.  
  89. ### Использовать Try
  90.  
  91. ```scala
  92. def perform(input: String): Try[...] = {
  93.   for {
  94.     a <- Try(operation1(input))
  95.     b <- Try(operation2(a))
  96.     c <- Try(operation3(b))
  97.   } yield doSomethingWith(a, b, c)
  98. }
  99. ```
  100. Если какая-нибудь из operation1..3 кинет исключение, оно будет обёрнуто в Failure, если же всё будет нормально, то вызов doSomethingWith(a, b, c) будет обёрнут в Success. И потом это значение можно использовать как обычное значение, например, преобразовать в нужный json-чик.
  101.  
  102. ### Создать свой тип, и разбирать его на каждом шаге
  103.  
  104. ```scala
  105.   sealed trait MyResult
  106.   case class  Success(value: Int) extends MyResult
  107.   case class  FileNotFound(file: java.io.File) extends MyResult
  108.   case object IllegalState extends MyResult
  109.  
  110.   def perform(input: String): Int = {
  111.     val r = operation1(input) match {
  112.       case Success(a) => operation2(a) match {
  113.         case Success(b) => operation3(b) match {
  114.           case Success(c) => doSomethingWith(a, b, c)
  115.           case failure ⇒ failure
  116.         }
  117.         case failure ⇒ failure
  118.       }
  119.       case failure ⇒ failure
  120.     }
  121.     r match {
  122.       case Success(a) ⇒ a
  123.       case FileNotFound(file)
  124.         Console.println(s"File $file not found")
  125.         0  // default value
  126.       case IllegalState ⇒
  127.         Console.println(s"The illegal state encountered")
  128.         0  // default value
  129.     }
  130.  
  131.   }
  132.  
  133.   def operation1(input: String): MyResult = ???
  134.   def operation2(a: Int): MyResult = ???
  135.   def operation3(b: Int): MyResult = ???
  136.   def doSomethingWith(a: Int, b: Int, c: Int): MyResult = ???
  137. ```
  138.  
  139. ### Аннотировать (эта возможность сделана для совместимости с Java):
  140.  
  141. ```scala
  142.     @throws(classOf[IOException])
  143.     @throws(classOf[LineUnavailableException])
  144.     @throws(classOf[UnsupportedAudioFileException])
  145.     def playSoundFileWithJavaAudio {
  146.       // exception throwing code here ...
  147.     }
  148. ```
  149. Вызов этого метода из Java будет неотличим от throws аннотации в Java-методах.
  150.  
  151. ### Для Scala есть наконец плагин для компилятора, который реализует checked exceptions:
  152. [No Exceptions: Checked Exceptions for Scala](https://opensource.plausible.coop/src/projects/SNX/repos/nx/browse)
  153. ----
  154.  
  155. Источники:
  156. http://java.dzone.com/articles/tragedy-checked-exceptions
  157. http://radio-weblogs.com/0122027/stories/2003/04/01/JavasCheckedExceptionsWereAMistake.html
  158. http://stackoverflow.com/questions/10818427/is-either-the-equivalent-to-checked-exceptions
  159. http://stackoverflow.com/questions/27578/when-to-choose-checked-and-unchecked-exceptions
  160. http://programmers.stackexchange.com/questions/177806/decision-for-unchecked-exceptions-in-scala
  161. http://stackoverflow.com/questions/613954/the-case-against-checked-exceptions
  162. https://opensource.plausible.coop/src/projects/SNX/repos/nx/browse
  163.  
  164.  
  165. --
  166. Best regards,
  167. Nick Linker, Scala & Haskell adept
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement