Что-то может пойти не так, но это не исключение

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

Например:

Я разрабатываю монопольную игру. У класса Bank есть метод buyHouse и поле, которое подсчитывает количество оставшихся домов (в монополии 32 дома). Что-то, что может пойти не так, это игрок, покупающий дом, когда осталось 0. Как я должен справиться с этим. Вот три подхода, которые я могу придумать.

1. public void buyHouse(Player player, PropertyValue propertyValue)
{
    if(houseCount < 0) throw new someException;
    ....
    //Not really an exceptional situation
}

2. public boolean buyHouse(Player player, PropertyValue propertyValue)
{
    if(houseCount < 0) return false;
    ....
    //This I think is the most normal approach but changing something
    //and returning if it was a success seems bad practice to me.
}

3. public boolean housesLeft()
{
    if(houseCount > 0) return true;

    return false;

    //Introducing a new method. But now I expect the client to call this method
    //first before calling buyHouse(). 
}

Что бы вы сделали?

Ответ 1

Я бы сделал 3 и 1 вместе. Правильное использование API - это проверить, остались ли дома перед покупкой. Если, однако, разработчик забыл это сделать, то выкиньте исключение во время выполнения.

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

Ответ 2

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

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

Итак, я бы предоставил метод проверки наличия домов и ожидал, что люди его вызовут и вернут true/false. В случае, если они не вызывают этот метод или игнорируют возвращаемое значение, я бы выбрал исключение, чтобы игра не попала в плохое состояние.

Ответ 3

Я нахожу значение "исключительного" весьма субъективным. Это означает, что вы хотите, чтобы это означало. Вы разрабатываете интерфейс для функции, вы можете решить, что является исключительным, а что нет.

Если вы не ожидаете, что buyHouse будет вызываться, когда houseCount равен <= 0, то здесь исключение будет прекрасным. Даже если вы ожидаете, что он будет вызван, вы можете поймать исключение в вызывающем абоненте для обработки этой ситуации.

Ответ 4

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

Учитывая ситуацию, которую вы описываете, я думаю, что использование исключений не подходит, так как когда-то продано 32 дома, банк будет оставаться вне их (это новое "нормальное" состояние), а обработка исключений на самом деле довольно медленный в Java по сравнению с обычной обработкой.

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

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

public House buy(Player player, PropertyValue propertyValue) {
  House propertyHouse = null;
  if (houseCount > 0) {
    propertyHouse = new House(player, propertyValue);
    houseCount--;
  }

  return propertyHouse;
}

Это также позволит вам добавить поведение к объекту House и сделать поток для запроса/покупки дома немного более естественным. Если домов нет, вы не получите их.

Ответ 5

Либо (1), либо (2) является приемлемым, в зависимости от того, считаете ли вы "дома не для покупки" обычным результатом или исключительным условием.

(3) - плохая идея по нескольким причинам:

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

Ответ 6

Пара других вариантов:

  • Ваш метод может принять несколько заданных параметров домов и вернуть количество фактически купленных домов, после проверки доступного баланса игрока и количества доступных домов. Возвратный ноль был бы вполне приемлемой возможностью. Разумеется, вы полагаетесь на вызов кода, чтобы проверить, сколько домов оно действительно вернуло. (это вариант при возврате логического значения, где true/false указывают, конечно, 1 или нулевые дома, купленные, конечно)

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

  • Ваш метод может вернуть объект HousePurchaseTransaction, который сам был запрошен для определения успеха или неудачи транзакции, ее фактической стоимости и т.д.

  • Более богатым вариантом этой темы может быть создание абстрактного абзаца HousePurchaseTransaction и получение двух дочерних классов: SuccessfulHousePurchase и FailedHousePurchase, поэтому вы можете связать другое поведение с двумя условиями результата. Установка дома на улицу может потребовать передачи объекта "SuccessfulHousePurchase" для продолжения. (Это исключает опасность возвращения нуль будучи корень более поздней эталонной нулевой ошибки, и вариант на нулевой шаблон объекта)

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

Ответ 7

Я бы сделал что-то вроде этого:

public boolean BuyHouse(Player player, PropertyValue propertyValue) {
      // Get houseCount
      if(houseCount <= 0) {
       // Log this to your message queue that you want to show 
       // to the user (if it has a UI)
       return false;
      }
      // Do other stuff if houses are left
}

PS: Я не знаком с java, я использую С#

Ответ 8

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

Ответ 9

Вы выбираете правило и исключение для пользователей, которые используют ваши API/методы:

housesLeft() можно вызвать для проверки количество домов, оставшихся до buyHouse(). призвание buyHouse() всякий раз, когда количество исключение составляет ноль.

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

Итак, это должно выглядеть так:

if (housesLeft() > 0) buyHouse(...);

похож на

for (int i=0; i < arrayList.length; i++) System.out.println(arrayList[i]);

Ответ 10

Помните, что вы можете использовать

return houseCount > 0;

а не

if(houseCount > 0) return true;

return false;