Как я могу избежать использования исключений для управления потоком?
Мне был назначен проект по разработке набора классов, которые действуют как интерфейс к системе хранения. Требование состоит в том, что класс поддерживает метод get со следующей сигнатурой:
public CustomObject get(String key, Date ifModifiedSince)
В принципе, предполагается, что метод возвращает CustomObject
, связанный с key
, если и только если объект был изменен после ifModifiedSince
. Если система хранения не содержит key
, тогда метод должен вернуть значение null.
Моя проблема такова:
Как обрабатывать сценарий, в котором существует ключ, но объект имеет not?
Это важно, потому что некоторые приложения, которые используют этот класс, будут веб-службами и веб-приложениями. Этим приложениям необходимо будет знать, нужно ли возвращать 404 (не найдено), 304 (не изменено) или 200 (ОК, здесь данные).
Решения, которые я взвешиваю:
- Выбросить пользовательское исключение, если
система хранения не содержит
key
- Выбросить пользовательское исключение, если
ifModifiedSince
не работает.
- Добавить свойство status в CustomObject. Требовать вызова для проверки свойства.
Я не доволен ни одним из этих трех вариантов. Мне не нравятся варианты 1 и 2, потому что я не люблю использовать исключения для управления потоком. Мне также не нравится возвращать значение, когда я намерен указать, что было no value.
Тем не менее, я склоняюсь к варианту 3.
Есть ли вариант, который я не рассматриваю? Кто-нибудь имеет сильные чувства по поводу любого из этих трех вариантов?
Ответы на этот вопрос, парафраз:
- Предоставьте
contains
метод и требует вызова вызывающего абонента
перед вызовом get(key,
ifModifiedSince)
, бросьте
исключение, если ключ не существует,
return null, если объект не был
модифицировано.
- Оберните ответ и данные (если есть)
в составном объекте.
- Используйте предопределенную константу для обозначения некоторого состояния (
UNMODIFIED, KEY_DOES_NOT_EXIST
).
- Caller реализует интерфейс, который будет
используется как обратные вызовы.
- Дизайн отстой.
Почему я не могу выбрать ответ № 1
Я согласен, что это идеальное решение, но это был тот, который я (неохотно) отклонил. Проблема с этим подходом заключается в том, что в большинстве случаев, когда эти классы будут использоваться, бэкэнд-система хранения будет сторонней удаленной системой, такой как Amazon S3. Это означает, что для метода contains
требуется обратная поездка в систему хранения, которая в большинстве случаев будет сопровождаться другой поездкой туда и обратно. Поскольку это будет стоить как времени, так и денег, это не вариант.
Если бы не это ограничение, это был бы лучший подход.
(Я понимаю, что я не упомянул об этом важном элементе в вопросе, но я старался держать его в курсе. Очевидно, это было актуально.)
Вывод:
После прочтения всех ответов я пришел к выводу, что обертка - лучший подход в этом случае. По сути, я буду подражать HTTP, с метаданными (заголовками), включая код ответа, и тело контента (сообщение).
Ответы
Ответ 1
Похоже, вы действительно хотите вернуть два элемента: код ответа и найденный объект. Вы можете подумать о создании облегченной обертки, которая удерживает их и возвращает их вместе.
public class Pair<K,V>{
public K first;
public V second;
}
Затем вы можете создать новую пару, содержащую ваш код ответа и данные. В качестве побочного эффекта использования дженериков вы можете повторно использовать эту оболочку для любой пары, в которой вы действительно нуждаетесь.
Кроме того, если данные еще не истекли, вы все равно можете вернуть их, но дайте ему код 303, чтобы сообщить им, что он не изменился. Серия 4xx будет сопряжена с null
.
Ответ 2
С данным требованием вы не можете этого сделать.
Если вы разработали контракт, добавьте условие и вызовите вызывающего абонента
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 (key, date) возвращает только null или object]
Так что не беспокойтесь об этом. Поговорите с вашим менеджером и сообщите ему об этом решении. Комментировать код с этим решением тоже. И если у вас есть архитектор в руке, подтвердите обоснование этого странного ограничения.
Скорее всего, вы не видели этого, и они могут изменить контракт после вашего предложения.
Иногда, когда вы хотите действовать правильно, мы можем пойти не так и поставить под угрозу безопасность нашего приложения.
Общайтесь с вашей командой.
Ответ 3
Вы можете создать специальный окончательный CustomObject как "маркер", чтобы указать неизмененный:
static public final CustomObject UNCHANGED=new CustomObject();
и проверьте соответствие с "==" вместо .equals().
Может также работать, чтобы возвращать значение null без изменений и исключение исключения не существует? Если бы мне пришлось выбрать одно из ваших 3, я бы выбрал 1, потому что это кажется самым исключительным случаем.
Ответ 4
Поиск объекта, который не существует, кажется мне исключительным случаем. В сочетании с методом, который позволяет вызывающему пользователю определить, существует ли объект, я думаю, что было бы нормально выбросить исключение, если это не так.
public bool exists( String key ) { ... }
Caller мог бы сделать:
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) { ... }
Ответ 5
Проблема с исключениями заключается в том, что они предназначены для сигнализации сценария "с быстрым" (т.е. если не обрабатывается, исключение будет останавливать приложение) из-за исключительного и ненормального поведения.
Я не думаю, что "сценарий, в котором ключ существует, но объект не был изменен" является исключительным, конечно, не ненормальным.
Следовательно, я бы не использовал исключение, но я бы документировал действие, которое должен выполнить вызывающий объект, чтобы правильно интерпретировать результат (свойство или специальный объект).
Ответ 6
Насколько строго требование для этой сигнатуры метода?
Кажется, что вы работаете над проектом, который все еще продолжается. Если потребители вашего класса - другие разработчики, можете ли вы убедить их, что подпись метода, которую они запросили, недостаточна? Возможно, они еще не поняли, что должны быть два уникальных режима отказа (ключ не существует, и объект не был изменен).
Я бы обсуждал это с вашим супервизором, если это вариант.
Ответ 7
Я все равно вернул бы нуль.
Цель свойства - вернуть объект, который был изменен после указанной даты. Если возвращать значение null для какого-либо объекта в порядке, то, конечно, возврат значения null для немодифицированного объекта тоже око.
Я лично вернул бы null для немодифицированного объекта и выбросил исключение для несуществующего объекта. Это кажется более естественным.
Вы совершенно правы, чтобы не использовать исключения для управления потоком BTW, поэтому, если у вас есть только эти 3 варианта, ваш инстинкт кишки прав.
Ответ 8
Вы можете следовать шаблону библиотеки .Net и иметь общедоступное статическое поле readonly в настраиваемом объекте с именем CustomObject.Empty, который имеет тип CustomObject (например, строка. Empty и Guid.Empty). Вы можете вернуть это, если объект не модифицирован (потребитель функции должен будет сравнить с ним).
Изменить: я только заметил, что вы работаете на Java, но принцип по-прежнему применяется
Это дает вам возможность следующего
-
Возвращает null, если ключ не существует.
-
Возвращает CustomObject.Empty, если ключ существует, но объект не был изменен.
Недостаток заключается в том, что потребитель должен знать разницу между возвращаемым значением null и возвращаемым значением CustomObject.Empty.
Возможно, свойство будет более точно названо CustomObject.NotModified, поскольку Empty действительно предназначен для типов значений, поскольку они не могут быть нулевыми. Кроме того, NotModified будет более легко передавать значение поля потребителю.
Ответ 9
(предполагаемый) интерфейс в отношении требований серьезно нарушен. Вы пытаетесь делать несвязанные вещи в рамках одного метода. Это путь к адскому программному обеспечению.
Ответ 10
Предоставить обратный вызов в качестве аргумента, в котором класс обратного вызова может быть вызван событиями или управляться с помощью setter.
У вас есть интерфейс класса, который определяет различные ошибки, которые могут произойти, передавая в CustomObject как параметр для события, если это необходимо.
public interface Callback {
public void keyDoesNotExist();
public void notModified(CustomObject c);
public void isNewlyModified(CustomObject c);
.
.
.
}
Таким образом, вы позволяете разработчику интерфейса обратного вызова определять, что делать, когда происходит событие, и вы можете выбирать через интерфейс, независимо от того, требует ли это условий для получения восстановленного объекта. Наконец, это уменьшает сложность логики при возврате. Ваш метод делает это один раз. Разработчикам API не требуется делать это вообще, как это сделано для них.
Ответ 11
Если это приемлемо, вы можете вернуть усиленный CustomObject (обертку), который содержит значения, которые представляли объект и его состояние модификации, если таковые имеются и т.д.