Как лучше всего выполнить набор методов, даже если происходит исключение - PullRequest
7 голосов
/ 25 марта 2009

В текущем проекте Java у нас есть код, подобный следующему примеру:

try {
    doSomeThing(anObject);
}
catch (SameException e) {
    // Do nothing or log, but don't abort current method.
}

try {
    doOtherThing(anObject);
}
catch (SameException e) {
    // Do nothing or log, but don't abort current method.
}

// ... some more calls to different method ...

try {
    finallyDoYetSomethingCompletelyDifferent(anObject);
}
catch (SameException e) {
    // Do nothing or log, but don't abort current method.
}

Как видите, несколько разных методов вызываются с одним и тем же объектом, и для каждого вызова одно и то же исключение перехватывается и обрабатывается одинаковым (или очень похожим) способом. Исключение не генерируется повторно, но может быть зарегистрировано и затем удалено.

Единственная причина, по которой в каждом отдельном методе есть try-catch, состоит в том, чтобы всегда выполнять все методы, независимо от того, завершился ли ранее выполненный метод.

Мне не нравится приведенный выше код вообще. Он занимает много места, очень повторяется (особенно запись в журнале catch; здесь не представлена) и выглядит просто плохо.

Я могу придумать несколько других способов написания этого кода, но они мне тоже не очень нравятся. Мне в голову пришли следующие варианты:

Последовательность петлевого переключателя / парадигма для конкретного случая

(см. Википедия или Ежедневный WTF )

for (int i = 0; i <= 9; i++) {
    try {
        switch (i) {
            case 0:
                doSomeThing(anObject); break;
            case 1:
                doOtherSomeThing(anObject); break;
            // ...More cases...
            case 9:
                doYetSomethingCompletelyDifferent(anObject); break;
        }
    }
    catch (SameException e) {
        // Do nothing or log, but don't abort current method.
    }
}

Это явно плохой код, очень подвержен ошибкам и выглядит дилетантским.

Отражение

Используйте отражение, чтобы получить Method объекты для вызова методов и сохранить их в списке в порядке, в котором они должны быть выполнены. Затем выполните итерацию по этому списку и вызовите метод, используя anObject в качестве единственного параметра. Исключение обрабатывается внутри цикла.

Мне не нравится этот подход, поскольку ошибки (например, опечатки в именах методов) появляются только во время выполнения, а API Reflection немного болтлив.

Functor

Создайте класс Functor следующим образом:

private class Functor
{
    void doStuff(MyObject object) throws SameException;
}

Затем создайте список Functor объектов, которые вызывают методы. Как это:

List<Functor> functors = new ArrayList<Functor>();

functors.add(new Functor() {
    @Override
    public void execute(MyObject anObject) {
        doSomeThing(anObject);
    }
});

functors.add(new Functor() {
    @Override
    public void execute(MyObject anObject) {
        doOtherSomeThing(anObject);
    }
});

Позже повторите этот список и вызовите execute() для каждого Functor объекта. Я могу суммировать мои чувства по поводу этого подхода двумя словами: раздувание кода.


Поскольку мне не очень нравятся все четыре подхода, я хотел бы обсудить эту проблему здесь. Что вы считаете лучшим подходом? Как вы решали подобные проблемы в прошлом? Может быть, есть более простое решение, которое я пропустил полностью?

Ответы [ 8 ]

6 голосов
/ 25 марта 2009

Я бы отстаивал подход рефакторинга (или " Почему мы попали сюда в первую очередь? "):

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

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

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

5 голосов
/ 25 марта 2009

Функторный подход, на мой взгляд, самый хороший - просто жаль, что у Java нет более приятного способа представления замыканий или делегатов. Это в основном то, что вы действительно после, и в C # (и многих других языках) это будет тривиально.

Вы можете несколько сократить физическое раздувание, используя что-то вроде:

Functor[] functors = new Functor[] {
    new Functor() { @Override public void execute(MyObject anObject) {
        doSomeThing(anObject);
    }},
    new Functor() { @Override public void execute(MyObject anObject) {
        doSomeOtherThing(anObject);
    }}
};

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

Лучше начать лоббировать замыкания в Java 8;)

3 голосов
/ 25 марта 2009

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

Однако, если вы уверены, что это именно то, что вы хотите, скажем, возможно, вы работаете с методами из сторонней библиотеки, которые настаивают на выдаче определенных исключений, я бы выбрал следующий подход:

  1. создать интерфейс, содержащий все методы, которые могут быть вызваны:

          
    public interface XyzOperations {
         public void doSomething(Object anObject);
         public void doOtherThing(Object anObject);
         ...
         public void finallyDoYetSomethingCompletelyDifferent(Object anObject);
    
  2. создайте класс реализации по умолчанию для этих методов, соответствующих методов, возможно, рефакторинг их из другого места:

        public class DefaultXyzOperations implements XyzOperations {
        ... 
        }
    
  3. использовать класс Java Proxy для создания динамического прокси на XyzOperations, который делегировал бы все методы на DefaultXyzOperations, но в InvocationHandler * имел бы централизованную обработку исключений 1022 *. Я не скомпилировал следующее, но это основная схема:

      XyzOperations xyz = (XyzOperations)Proxy.newProxyInstance(
            XyzOperations.class.getClassLoader(),
            new Class[] { XyzOperations.class },
            new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                     try {
                          method.invoke(new DefaultXyzOperations(), args);
                     }
                     catch(SameException e) {
                         // desired exception handling
                     }
                }
            });
    
  4. используйте этот прокси-экземпляр с этого момента, просто вызывая нужные методы

В качестве альтернативы вы можете использовать AspectJ или подобное решение AOP, чтобы добавить рекомендации ко всем методам XyzOperations и выполнить там обработку исключений.

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

2 голосов
/ 25 марта 2009

Методы, которые вы вызываете, находятся под вашим контролем? Если это так, то в этом особом случае возврат кодов ошибок (или объектов) может привести к лучшему общему дизайну, чем использование исключений:

handle(doSomething(anObject));
handle(doOtherThing(anObject));
// some more calls to different methods
handle(finallyDoYetSomethingCompletelyDifferent(anObject));

с

private void handle(ErrorCode errorCode) {
  // Do something about it
}

и

private ErrorCode doSomething(Object anObject) {
  // return ErrorCode describing the operation's outcome
}

Это кажется менее многословным, хотя и не СУХИМ.

В качестве альтернативы вы используете некоторый механизм AOP для перехвата вызовов на doSomething, doOtherThing и finallyDoYetSomethingCompletelyDifferent с помощью Around Advice, которая сначала обрабатывает, а затем отбрасывает исключение. Объедините это с RuntimeExceptions и pointcut, основанным на некоторой хорошей описательной аннотации, и вы прекрасно поймете, что, по-видимому, является некой скрытой межсекторной задачей.

Признаюсь, мне нравится подход Функтора, хотя

РЕДАКТИРОВАТЬ : Только что увидел ваш комментарий к одному из ответов. В этом случае я бы, вероятно, использовал подход АОП.

2 голосов
/ 25 марта 2009

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

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

Таким образом, я бы усвоил сообщения регистрации и удалил возникающие исключения.

Как минимум, я думаю, что вам нужно вернуться и заново понять, что пытается сделать код, и какую бизнес-ценность или правила внедряют. Как сказал PeterR, постарайтесь понять, «почему мы сюда попали?» часть кода и что именно означают исключения.

1 голос
/ 25 марта 2009

Оставляя в стороне проблему рефакторинга, AspectJ (или аналогичный) кажется наиболее простым способом прозрачного отслеживания / сообщения об этих исключениях. Вы должны быть в состоянии настроить так, чтобы он переплетал попытки / перехват вызовов вызовов методов, даже если вы добавляете новые (я подозреваю, что приведенный выше блок кода будет довольно хрупким, если кто-то модифицирует этот код без полного понимания обоснование этого)

0 голосов
/ 25 марта 2009

Позвольте мне подробнее остановиться на подходе с функторами ...

В зависимости от сложности вашего приложения, иногда стоит перенести часть вашей бизнес-логики в файл conf, где она может быть выражена более адекватно и кратко. Таким образом, вы отделяете технические детали (создание / вызов функтора, обработка исключений) от бизнес-логики - определение того, какие методы и в каком порядке следует вызывать.

В простейшей форме это может быть что-то вроде этого:

mypackage.Action1
mypackage.Action2
...

где ActionX - класс, реализующий класс Functor (или интерфейс).

0 голосов
/ 25 марта 2009

Если вы идете другим маршрутом стиля кода, я бы придерживался простого:

try { doSomeThing(anObject); } catch (SameException e) { Log(e); }
try { doOtherThing(anObject); } catch (SameException e) { Log(e); }
// ... some more calls to different method ...

Обновление: я не вижу, как использование синтаксиса, подобного подходу Functor, уменьшает какой-либо задействованный код. Как упоминал Джон, java не поддерживает простой синтаксис для его дальнейшего сокращения. Если бы это был C #, есть много вариантов, которые вы можете сделать, в том числе тот факт, что нет особого синтаксиса для составления подобных методов, то есть выражения типа actions. ;

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...