Java: использование RuntimeException для выхода из Visitor

Я сильно соблазняюсь использовать непроверенное исключение в качестве конструкции потока управления короткого замыкания в программе Java. Я надеюсь, что кто-то здесь может посоветовать мне лучший, более чистый способ решения этой проблемы.

Идея состоит в том, что я хочу прервать рекурсивное исследование поддеревьев посетителем без необходимости проверять флаг «стоп» при каждом вызове метода. В частности, я строю граф потока управления с помощью посетителя над абстрактным синтаксическим деревом. returnЗаявление в AST следует прекратить исследование суб-дерева и отправить посетителя обратно в ближайшей ограждающих если / то или блок цикла.

VisitorСуперкласс (из библиотеки XTC ) определяет

Object dispatch(Node n)

который перезванивает через методы отражения формы

Object visitNodeSubtype(Node n)

dispatch не объявлено, чтобы выбрасывать какие-либо исключения, поэтому я объявил закрытый класс, который расширяет RuntimeException

private static class ReturnException extends RuntimeException {
}

Теперь метод посетителя для оператора return выглядит так

Object visitReturnStatement(Node n) {
    // handle return value assignment...
    // add flow edge to exit node...
    throw new ReturnException();
}

и каждый составной оператор должен обрабатывать ReturnException

Object visitIfElseStatement(Node n) {
  Node test = n.getChild(0);
  Node ifPart = n.getChild(1);
  Node elsePart = n.getChild(2);

  // add flow edges to if/else... 

  try{ dispatch(ifPart); } catch( ReturnException e ) { }
  try{ dispatch(elsePart); } catch( ReturnException e ) { }
}

Это все отлично работает, кроме:

  1. Я могу забыть поймать ReturnExceptionгде-нибудь, и компилятор не предупредит меня.
  2. Я чувствую себя грязным

Есть лучший способ сделать это? Есть ли шаблон Java, о котором я не знаю, чтобы реализовать этот нелокальный поток управления?

[ОБНОВЛЕНИЕ] Этот конкретный пример оказывается несколько недопустимым: Visitorсуперкласс перехватывает и переносит исключения (даже RuntimeExceptions), поэтому создание исключений на самом деле не помогает. Я реализовал предложение вернуть enumтип из visitReturnStatement. К счастью, это нужно проверить только в небольшом количестве мест (например, visitCompoundStatement), так что на самом деле это немного меньше хлопот, чем выдача исключений.

В общем, я думаю, что это все еще актуальный вопрос. Хотя, возможно, если вы не привязаны к сторонней библиотеке, всей этой проблемы можно избежать с помощью разумного дизайна.

15.12.2008 19:32:16
Ваша точка № 2 заставила меня посмеяться. Хорошие инстинкты на этого парня ...
Paul Brinkley 15.12.2008 19:39:35
7 ОТВЕТОВ

Есть ли причина, по которой вы не просто возвращаете значение? Например, NULL, если вы действительно хотите ничего не возвращать? Это было бы намного проще, и не рисковало бы генерировать неконтролируемое исключение времени выполнения.

0
15.12.2008 19:35:57
Я не думаю, что возвращение значения решит проблему. Метод, генерирующий исключение, не возвращает NULL. Вам все еще нужно проверить ранний возврат на каждом узле.
Chris Conway 15.12.2008 19:43:18
Ой, подожди. Может быть, вы имеете в виду что-то большее, как № 2 Пол Бринкли?
Chris Conway 15.12.2008 19:50:45

Я вижу следующие варианты для вас:

  1. Идите вперед и определите этот RuntimeExceptionподкласс. Проверьте наличие серьезных проблем, поймав ваше исключение в самом общем вызове dispatchи сообщив об этом, если оно зашло так далеко.
  2. Попросите код обработки узла вернуть специальный объект, если он считает, что поиск должен внезапно завершиться. Это по-прежнему вынуждает вас проверять возвращаемые значения, а не перехватывать исключения, но вам может понравиться внешний вид кода таким образом.
  3. Если обход дерева должен быть остановлен каким-либо внешним фактором, сделайте все это внутри подпотока и установите синхронизированное поле в этом объекте, чтобы указать потоку преждевременно остановиться.
0
15.12.2008 19:47:44

Я думаю, что это разумный подход по нескольким причинам:

  • Вы используете стороннее устройство и не можете добавить проверенное исключение
  • Проверка возвращаемых значений повсюду в большом количестве посетителей, когда это необходимо только для нескольких, является ненужным бременем

Кроме того, есть такие, которые утверждают, что непроверенные исключения не так уж и плохи . Ваше использование напоминает мне об Eclipse OperationCanceledException, который используется для выдувания из длительных фоновых задач.

Это не идеально, но, если хорошо задокументировано, мне кажется, это нормально.

4
15.12.2008 19:56:42
Заставляет меня чувствовать себя немного чище ... ;-)
Chris Conway 15.12.2008 19:58:07
Ты все еще в грязи, но, возможно, только по пояс :)
Dave Ray 15.12.2008 23:15:07

Почему вы возвращаете значение от вашего посетителя? Соответствующий метод посетителя вызывается посещаемыми классами. Вся проделанная работа инкапсулирована в самом классе посетителя, он не должен ничего возвращать и обрабатывать собственные ошибки. Единственное обязательство, которое требуется от вызывающего класса, - это вызвать соответствующий метод visitXXX, не более того. (Это предполагает, что вы используете перегруженные методы, как в вашем примере, в отличие от переопределения одного и того же метода visit () для каждого типа).

Посещаемый класс не должен быть изменен посетителем или иметь какие-либо знания о том, что он делает, за исключением того, что позволяет посещение. Возврат значения или создание исключения нарушит это.

Шаблон посетителя

0
15.12.2008 20:11:13
Если вы собираетесь проголосовать вниз, оставьте комментарий, пожалуйста. Это помогает нам всем.
Robin 15.12.2008 20:18:59
Вы не ответили на вопрос, вы ошибочно заявили, что посетители никогда не должны изменять структуру данных или возвращать значение, в вопросе не упоминалась обработка ошибок, но вы упомянули об этом. Я не думаю, что вы поняли вопрос или действительные проблемы спрашивающего.
Steven Huwig 15.12.2008 20:46:15

Вам нужно использовать Visitor от XTC? Это довольно тривиальный интерфейс, и вы можете реализовать свой собственный, который может генерировать проверенный ReturnException, который вы не забудете перехватить там, где это необходимо.

0
15.12.2008 20:46:49

Я не использовал упомянутую вами библиотеку XTC. Как это обеспечивает дополнительную часть шаблона посетителя - accept(visitor)метод на узлах? Даже если это диспетчер, основанный на отражениях, должно быть что-то, что обрабатывает рекурсию в синтаксическом дереве?

Если этот структурный итерационный код легкодоступен, и вы еще не используете возвращаемое значение из ваших visitXxx(node)методов, можете ли вы использовать простое перечисляемое возвращаемое значение или даже логический флаг, говорящий о том, чтобы accept(visitor)не возвращаться в дочерние узлы?

Если:

  • accept(visitor) не реализуется явно узлами (происходит некоторое отражение поля или средства доступа, или узлы просто реализуют дочерний интерфейс для некоторой стандартной логики потока управления или по любой другой причине ...), и

  • Вы не хотите связываться со структурной итерационной частью библиотеки, или она недоступна, или это не стоит усилий ...

тогда, в крайнем случае, я предполагаю, что исключения могут быть вашим единственным вариантом, все еще используя библиотеку vanilla XTC.

Хотя интересная проблема, и я могу понять, почему поток управления на основе исключений заставляет вас чувствовать себя грязным ...

0
15.12.2008 22:04:05

Бросать исключение времени выполнения в качестве логики управления, безусловно, плохая идея. Причина, по которой вы чувствуете себя грязной, заключается в том, что вы обходите систему типов, то есть возвращаемый тип ваших методов - ложь.

У вас есть несколько вариантов, которые значительно более чисты.

1. Функтор исключений

Хорошая техника для использования, когда вы ограничены в исключениях, которые вы можете выбросить, если вы не можете выбросить проверенное исключение, верните объект, который вызовет проверенное исключение. Например, java.util.concurrent.Callable является экземпляром этого функтора.

Смотрите здесь для подробного объяснения этой техники.

Например, вместо этого:

public Something visit(Node n) {
  if (n.someting())
     return new Something();
  else
     throw new Error("Remember to catch me!");
}

Сделай это:

public Callable<Something> visit(final Node n) {
  return new Callable<Something>() {
    public Something call() throws Exception {
      if (n.something())
         return new Something();
      else
         throw new Exception("Unforgettable!");
    }
  };
}

2. Несвязанный Союз (или Бифунктор Либо)

Этот метод позволяет вам вернуть один из двух разных типов из одного и того же метода. Это немного похоже на Tuple<A, B>технику, с которой большинство людей знакомо для возврата более одного значения из метода. Однако вместо того, чтобы возвращать значения обоих типов A и B, это предполагает возвращение одного значения типа A или B.

Например, учитывая перечисление Fail, которое может перечислять применимые коды ошибок, пример становится ...

public Either<Fail, Something> visit(final Node n) {
  if (n.something())
    return Either.<Fail, Something>right(new Something());
  else
    return Either.<Fail, Something>left(Fail.DONE);
}

Выполнение вызова теперь намного чище, потому что вам не нужно пытаться / ловить:

Either<Fail, Something> x = node.dispatch(visitor);
for (Something s : x.rightProjection()) {
  // Do something with Something
}
for (Fail f : x.leftProjection()) {
  // Handle failure
}

Класс Either не очень сложен в написании, но полнофункциональная реализация обеспечивается библиотекой Functional Java .

3. Вариант Монада

Немного похоже на типобезопасный ноль, это хороший метод, который нужно использовать, когда вы не хотите возвращать значение для некоторых входных данных, но вам не нужны исключения или коды ошибок. Обычно люди возвращают так называемую «дозорную ценность», но Option значительно чище.

Теперь у вас есть ...

public Option<Something> visit(final Node n) {
  if (n.something())
    return Option.some(new Something());
  else
    return Option.<Something>none();
}    

Звонок приятный и чистый:

Option<Something> s = node.dispatch(visitor));
if (s.isSome()) {
  Something x = s.some();
  // Do something with x.
}
else {
  // Handle None.
}

И тот факт, что это монада, позволяет вам связывать вызовы без обработки специального значения None:

public Option<Something> visit(final Node n) {
  return dispatch(getIfPart(n).orElse(dispatch(getElsePart(n)));
}    

Класс Option написать даже легче, чем Either, но, опять же, полнофункциональная реализация предоставляется библиотекой Functional Java .

Смотрите здесь для подробного обсуждения Option и Either.

4
23.05.2017 12:09:11