Я пытаюсь понять, как правильно определять интерфейсы в Java в отношении того, как исключения обрабатываются на языке (и времени выполнения).
ПРИМЕЧАНИЕ. Возможно, этот вопрос уже задан и получил адекватный ответ, но мой поиск SO (и других сайтов) не выявил ничего, что непосредственно касалось проектного вопроса и компромиссов, которые я представляю. Я с радостью удалю этот вопрос, если можно найти аналогичный ответ, который отвечает на мой вопрос. Я заранее извиняюсь за давний вопрос, но я хотел как можно более четко заявить о проблеме. Я также знаю об спорах вокруг отмеченных исключений, но поскольку я не в состоянии изменить язык и все еще работать в нем, Я хотел бы выбрать подход (ы), который вызовет наименьшую боль в долгосрочной перспективе.
Повторяющаяся проблема, с которой я сталкиваюсь, - это когда я пытаюсь определить свои собственные интерфейсы. Я столкнулся с выбором того, как указать, какие (если есть) исключения допускаются к членам интерфейса. Беда в том, что у меня нет способа узнать, какие исключения имеют смысл, поскольку я не знаю заранее, какие могут быть все возможные реализации для моего интерфейса. Для примера предположим, что у нас есть следующий интерфейс:
// purely hypothetical interface meant to illustrate the example...
public interface SegmentPartition {
// option 1
bool hasSegment(Segment s);
// option 2
void addSegment(Segment s) throws Exception;
// option 3
bool tryAddSegment(Segment s) throws TimeoutException, IOException, ParseException, XmlException, SQLException;
// option 4
void optimizeSegments() throws SegmentPartitionException;
}
Какие типы исключений должны объявлять этот интерфейс заранее? Возможные варианты:
- не объявлять никаких исключений, реализации могут только бросать
RuntimeException
- объявить каждый метод как метать
Exception
- попытайтесь предвидеть все возможные типы исключений, которые имеют смысл для этого интерфейса и объявляют их как бросающиеся исключения.
- создайте свой собственный тип исключения (например,
SegmentException
) и потребуйте, чтобы он был добавлен с внутренним исключением, если это необходимо для дополнительных подробностей.
Есть проблемы с каждым из этих подходов, и это не совсем ясно, каковы будут все компромиссы, я упомянул несколько для каждого случая. В идеале я не хочу, чтобы исключения были исключены из реализации, но это не всегда практическое ограничение.
Для параметра 1 возникает проблема, что многие типы исключений, которые могут быть выбраны, не выводятся из RuntimeException (например, IOException или TimeoutException). Я мог бы, конечно, добавить эти конкретные исключения в интерфейс, а затем разрешить другие RuntimeExceptions, но как насчет пользовательских исключений? Это кажется плохим вариантом.
С опцией 2 мы получим интерфейс, который, похоже, может привести к каким-либо исключениям (что, я полагаю, истинно) и не дает указания разработчикам о том, что на самом деле нужно выбросить. Интерфейс уже не является самостоятельным документированием. Хуже того, каждый сайт вызова этого интерфейса должен либо быть объявлен как бросающий Exception, либо обернуть каждый вызов методам этого интерфейса в try{} catch(Exception)
, либо создать блок, который ловит исключение, и пытается выяснить, была ли она реализована в результате реализации интерфейса или что-то еще в этом блоке. Тьфу. В качестве альтернативы, код, который использует этот интерфейс, может поймать конкретные исключения, которые могут быть реализованы реализацией, но затем он нарушает цель начала интерфейса (в том, что он требует, чтобы вызывающий пользователь знал тип выполнения реализации). На практике это вариант, наиболее близкий к тому, что большинство других языков, которые я знаю, относятся к исключениям (С++, С#, Scala и т.д.).
С опцией 3 я должен заранее предвидеть потенциальные реализации, которые могут быть определены для моего интерфейса, и объявлять исключения, наиболее подходящие для них. Поэтому, если я ожидаю, что мой интерфейс может быть реализован через удаленное, ненадежное соединение, я бы добавил TimeoutException, если я ожидаю, что он может быть реализован с использованием внешнего хранилища, я бы включил IOException и т.д. Я почти наверняка ошибаюсь... это означает, что я собираюсь постоянно выводить интерфейс в качестве новой поверхности парадигм реализации (что, разумеется, нарушает все существующие реализации и требует, чтобы они объявляли нечувствительные исключения как бросаемые). Еще одна проблема с этим подходом заключается в том, что результаты в уродливом, повторяющемся коде... особенно для больших интерфейсов со многими методами (да, иногда это необходимо), которые затем необходимо повторять в каждой точке реализации.
С опцией 4Я должен создать свой собственный тип исключения вместе со всем багажом, который сопровождает это (сериализуемость, вложенность и внутренние исключения, подходящая иерархия и т.д.). Кажется, что здесь неизбежно происходит то, что вы получаете один тип исключения для каждого типа интерфейса: InterfaceFoo + InterfaceFooException. Помимо раздувания кода это влечет за собой настоящую проблему: тип исключения ничего не значит... это всего лишь тип, используемый для объединения всех условий ошибки для конкретного интерфейса. В некоторых случаях, возможно, вы можете создать несколько подклассов типа исключения, чтобы указать конкретные режимы отказа, но даже это сомнительно.
Итак, что здесь делать, в общем? Я понимаю, что не может быть сурового и сухого правила, но мне интересно, что делают опытные разработчики Java. p >
Спасибо.