Как я могу избежать использования исключений для управления потоком? - PullRequest
9 голосов
/ 06 декабря 2008

Мне был поручен проект по разработке набора классов, которые действуют как интерфейс к системе хранения. Требование заключается в том, чтобы класс поддерживал метод get со следующей подписью:

public CustomObject get(String key, Date ifModifiedSince)

По сути, метод должен возвращать CustomObject, связанный с key тогда и только тогда, когда объект был изменен после ifModifiedSince. Если система хранения не содержит key, метод должен возвращать ноль.

Моя проблема заключается в следующем:

Как мне обработать сценарий, в котором ключ существует, но объект не был изменен?

Это важно, потому что некоторые приложения, которые используют этот класс, будут веб-службами и веб-приложениями. Этим приложениям необходимо знать, возвращать ли 404 (не найдено), 304 (не изменено) или 200 (хорошо, вот данные).

Решения, которые я взвешиваю:

  1. Создайте пользовательское исключение, когда система хранения не содержит key
  2. Создайте пользовательское исключение, когда ifModifiedSince терпит неудачу.
  3. Добавить свойство статуса в CustomObject. Требовать звонящего, чтобы проверить собственность.

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

Тем не менее, я склоняюсь к варианту 3.

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


Ответы на этот вопрос, перефразированный:

  1. Обеспечить contains метод и требуют, чтобы вызывающий вызывающий его перед звонком get(key, ifModifiedSince), киньте исключение, если ключ не существует, вернуть ноль, если объект не был модифицировано.
  2. Обернуть ответ и данные (если есть) в составном объекте.
  3. Используйте предопределенную константу для обозначения некоторого состояния (UNMODIFIED, KEY_DOES_NOT_EXIST).
  4. Абонент реализует интерфейс для используется в качестве обратных вызовов.
  5. Дизайн отстой.

Почему я не могу выбрать ответ № 1

Я согласен, что это идеальное решение, но это я уже (неохотно) отменил. Проблема этого подхода заключается в том, что в большинстве случаев, когда эти классы будут использоваться, внутренняя система хранения данных будет удаленной системой стороннего производителя, например Amazon S3. Это означает, что метод contains потребует обратной передачи в систему хранения, за которой в большинстве случаев последует повторная передача в обе стороны. Поскольку это будет стоить и времени, и денег , это не вариант.

Если бы не это ограничение, это был бы наилучший подход.

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


Вывод:

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

Ответы [ 11 ]

7 голосов
/ 06 декабря 2008

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

public class Pair<K,V>{
  public K first;
  public V second;
}

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

Кроме того, если срок действия данных еще не истек, вы все равно можете их вернуть, но укажите код 303, чтобы сообщить им, что они не изменились. Серия 4xx будет в паре с null.

5 голосов
/ 06 декабря 2008

С данным требованием вы не можете сделать это.

Если вы разработали контракт , добавьте условие и вызовите вызывающего абонента

exists(key): bool

Реализация сервиса выглядит следующим образом:

if (exists(key)) {
    CustomObject o = get(key, ifModifiedSince);
    if (o == null) { 
      setResponseCode(302);
    } else {
      setResponseCode(200);
      push(o);
   }

} else {
      setResponseCode(400);
}

Клиент остается неизменным и никогда не заметит, что вы предварительно подтвердили.

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

Тогда вам следует придерживаться спецификаций и действовать следующим образом:

 CustomObject o = get(key, ifModifiedSince);

 if (o != null) {
     setResponseCode(200);
     push(o);
  } else {
     setResponseCode(404); // either not found or not modified.
  }

Хорошо, в этом случае вы не посылаете 302, но, вероятно, именно так оно и было задумано.

Я имею в виду, по соображениям безопасности, сервер не должен возвращать больше информации, чем это [зонд get (ключ, дата) возвращает только нуль или объект]

Так что не беспокойся об этом. Поговорите с вашим менеджером и сообщите ему это решение. Прокомментируйте код с этим решением тоже. И если у вас в руках архитектор, подтвердите обоснование этого странного ограничения.

Скорее всего, вы не ожидали этого, и они могут изменить договор после вашего предложения.

Иногда, желая действовать правильно, мы можем действовать неправильно и ставить под угрозу безопасность нашего приложения.

Общайтесь с вашей командой.

3 голосов
/ 06 декабря 2008

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

public bool exists( String key ) { ... }

Звонящий может сделать:

if (exists(key)) {
   CustomObject modified = get(key,DateTime.Today.AddDays(-1));
   if (modified != null) { ... }
}

or

try {
    CustomObject modified = get(key,DateTime.Today.AddDays(-1));
}
catch (NotFoundException) { ... }
3 голосов
/ 06 декабря 2008

Вы можете создать специальный конечный CustomObject в качестве «маркера» для указания неизменного:

static public final CustomObject UNCHANGED=new CustomObject();

и проверьте соответствие "==" вместо .equals ().

Может также сработать, чтобы вернуть null в неизмененном виде и выдать исключение для не существует? Если бы мне пришлось выбрать один из ваших 3, я бы выбрал 1, потому что это кажется самым исключительным случаем.

2 голосов
/ 06 декабря 2008

Проблема с исключениями заключается в том, что они должны сигнализировать о сценарии «быстрого отказа» (т. Е. Если не обработано, исключение остановит приложение) из-за исключительного и ненормальное поведение.

Я не думаю, что «сценарий, в котором ключ существует, но объект не был изменен», является исключительным, безусловно, не ненормальным.

Следовательно, я бы не использовал исключение, а скорее задокументировал бы действие, которое должен сделать вызывающий, чтобы правильно интерпретировать результат (свойство или специальный объект).

1 голос
/ 08 декабря 2008

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

Интерфейс вашего класса определяет различные ошибки, которые могут возникнуть, передавая CustomObject в качестве параметра для события, если это необходимо.

public interface Callback {
  public void keyDoesNotExist();
  public void notModified(CustomObject c);
  public void isNewlyModified(CustomObject c);
  .
  .
  .
}

Таким образом, вы позволяете разработчику интерфейса Callback определять, что делать, когда происходит событие, и вы можете выбрать через интерфейс, требует ли это условие прохождения извлеченного объекта. Наконец, это уменьшает сложность логики возврата. Ваш метод делает это один раз. Разработчики API вообще не требуют этого, как это делается для них.

1 голос
/ 06 декабря 2008

(Предполагаемый) интерфейс в отношении требований серьезно нарушен. Вы пытаетесь делать несвязанные вещи в рамках одного метода. Это дорога в адский софт.

1 голос
/ 06 декабря 2008

Вы можете следовать шаблону библиотеки .Net и иметь открытое статическое поле только для чтения в пользовательском объекте с именем CustomObject.Empty типа CustomObject (например, string.Empty и Guid.Empty). Вы можете вернуть это, если объект не изменен (потребитель функции должен будет сравнить с ним).
Редактировать: Я только что заметил, что вы работаете в Java, но принцип все еще применяется

Это дает вам возможность следующего

  • Возвращает ноль, если ключ не существует.

  • Возврат CustomObject.Empty , если ключ существует, но объект не был изменен.

Недостатком является то, что потребителю необходимо знать разницу между нулевым возвращаемым значением и возвращаемым значением CustomObject.Empty.

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

1 голос
/ 06 декабря 2008

Я бы все равно вернул ноль.

Назначение свойства - вернуть объект, который был изменен после указанной даты. Если возвращение нулевого значения для какого-либо объекта не является нормальным, тогда, конечно, возвращение нулевого значения для неизмененного объекта также является приемлемым.

Лично я бы вернул null для неизмененного объекта и сгенерировал бы исключение для несуществующего объекта. Это кажется более естественным.

Вы совершенно правы, что не используете исключения для управления потоком, поэтому, если у вас есть только эти 3 варианта, ваш инстинкт инстинктивен правильно.

1 голос
/ 06 декабря 2008

Насколько строгим является требование для подписи этого метода?

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

Я бы обсудил это с вашим руководителем, если это возможно.

...