Связь между слоями в приложении

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

public String updateSomething()
{
   try
   {
      //Begin transaction here
      dataLayer.do1();
      dataLayer.do2();
      dataLayer.doN();
      //Commit transaction code here
   }
   catch(Exception exc)
   {
      //Rollback transaction code here
      return exc.message;
   }

   return "";
 }

Является ли это хорошей практикой или я должен бросить другое исключение в улове (тогда метод будет недействительным)?


person Petar Minchev    schedule 27.03.2010    source источник


Ответы (4)


Немного необычно возвращать такую ​​строку (но для этого нет настоящей причины). Более обычными методами будут:

  • вернуть логическое значение и иметь какой-либо метод установки сообщения об ошибке, либо путем его регистрации, установки некоторого глобального значения «последней ошибки», либо с помощью указателя на конструкцию ошибки, переданную вашему методу, который вы обновляете;

  • иметь метод void, который генерирует исключение при сбое и обрабатывает его в вызывающем коде (как вы предлагаете)

Я видел, как оба вышеперечисленных широко используются. Трудно сказать, что "лучше". Старайтесь соответствовать идиомам и соглашениям языка, на котором вы работаете, и/или существующему кодовому набору/библиотекам, с которыми вы работаете, если таковые имеются.

person Nick Moore    schedule 27.03.2010
comment
+1, я люблю первый способ. Я лично ненавижу беспорядок try-catch. Разве не здорово писать if (updateSomething()) { } вместо try { updateSomething(); } поймать (исключение) { }? - person Petar Minchev; 27.03.2010
comment
Мне также нравится прямолинейность техники if. Но у техники исключений есть свои преимущества, например, когда вам нужно вызывать множество методов последовательно, вы можете написать: try { something(); somethingElse(); anotherThing(); blah(); } catch(...) { } finally { alwaysRunThis(); } вместо проверки каждого оператора if по пути. Лошади на курсы... - person Nick Moore; 27.03.2010

Мне нравится возвращать стандартный контракт на мой уровень пользовательского интерфейса из моего бизнес-уровня.

Это выглядит так:

public class ServiceOperationResult<T>
{

    public bool Successful
    {
        get; 
        set;
    }

    public ServiceErrorType ErrorType
    {
        get;
        set;
    }

    public string ErrorMessage
    {
        get;
        set;
    }

    public T ReturnData
    {
        get;
        set;
    }
}

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

Например, бизнес-ошибки отображаются с красной меткой ошибки, а внутренние ошибки перенаправляются на страницу ошибок (в веб-приложении) или закрывают форму (в приложении Windows).

Я ненавижу видеть красную метку на веб-сайте (где я ожидаю увидеть ошибки проверки) и видеть что-то вроде «Сервер базы данных отклонил ваше соединение». Это риск, которому вы подвергаетесь, используя только строку для возврата данных об ошибке.

person Joon    schedule 27.03.2010
comment
+1, у меня тоже была эта идея, полезная, если вы хотите избежать использования try catch везде... - person Rushino; 06.12.2011

Лучший способ - обернуть исключение в какой-то более общий тип и повторно выдать его. Таким образом, updateSomething() должен объявить, что он может генерировать какое-то исключение (например: UpdateFailedException), а в блоке catch вы должны обернуть исключение.

public String updateSomething() {
  try {
    [...]
  } catch ( SQLException e ) {
    // rollback;
    throw new UpdateFailedException(e);
  }
}

Но ловить абстрактный тип Exception не очень хорошая идея. Вы должны обернуть только те вещи, семантику которых вы знаете. Например: SQLException, DataAccessException (Spring DAO) и т. д.

Если вы обертываете Exception, вы легко можете обернуть InterruptedException из NullPointerException. И это может сломать ваше приложение.

person Denis Bazhenov    schedule 27.03.2010
comment
Вы абсолютно правы. Я просто сомневаюсь, что в конечном итоге я закончу с помощью try, поймать все в пользовательском интерфейсе, который вызывает бизнес-уровень. Это проблема? - person Petar Minchev; 27.03.2010
comment
Я так думаю. Есть некоторые ситуации, когда вы не можете продолжить и должны остановить приложение. Нет особого смысла ловить OutOfMemoryError или что-то в этом роде. Итак, главное — отделить ошибки, которые можно восстановить, от ошибок, которые восстановить нельзя. - person Denis Bazhenov; 27.03.2010
comment
Я по-прежнему считаю, что trycatch уродлив и его следует размещать только на бизнес-уровнях и выше. - person Rushino; 06.12.2011

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

  1. вы получите лучший модульный подход для обработки исключения.
  2. обслуживание кода будет легким, когда сложность вашего кода увеличится
  3. у вас будет больше контроля над сценариями исключений

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

public string DummyFunction
{
   try
   {

   }
   catch(Exception ex)
   {
      throw new businessException();
   }
}
person sandeephu    schedule 28.07.2013