Глянь мой новый курс по Git! Привет! Глянь мой новый курс по Git! Привет! Глянь мой новый курс по Git на GitByBit.com! Привет! Хочешь круто подтянуть Git? Глянь мой новый курс на GitByBit.com!

Введение проверки утверждения

Также известен как: Introduce Assertion

Здесь и далее под проверками подразумеваются вызовы функции assert.

Проблема

Корректная работа участка кода предполагает наличие каких-то определённых условий или значений.

Решение

Замените эти предположения конкретными проверками.

До
double getExpenseLimit() {
  // Should have either expense limit or
  // a primary project.
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit :
    primaryProject.getMemberExpenseLimit();
}
После
double getExpenseLimit() {
  Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);

  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}
До
double GetExpenseLimit() 
{
  // Should have either expense limit or
  // a primary project.
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit :
    primaryProject.GetMemberExpenseLimit();
}
После
double GetExpenseLimit() 
{
  Assert.IsTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);

  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.GetMemberExpenseLimit();
}
До
function getExpenseLimit() {
  // Should have either expense limit or
  // a primary project.
  return ($this->expenseLimit !== NULL_EXPENSE) ?
    $this->expenseLimit:
    $this->primaryProject->getMemberExpenseLimit();
}
После
function getExpenseLimit() {
  assert($this->expenseLimit !== NULL_EXPENSE || isset($this->primaryProject));

  return ($this->expenseLimit !== NULL_EXPENSE) ?
    $this->expenseLimit:
    $this->primaryProject->getMemberExpenseLimit();
}
До
def getExpenseLimit(self):
    # Should have either expense limit or
    # a primary project.
    return self.expenseLimit if self.expenseLimit != NULL_EXPENSE else \
        self.primaryProject.getMemberExpenseLimit()
После
def getExpenseLimit(self):
    assert (self.expenseLimit != NULL_EXPENSE) or (self.primaryProject != None)

    return self.expenseLimit if (self.expenseLimit != NULL_EXPENSE) else \
        self.primaryProject.getMemberExpenseLimit()
До
getExpenseLimit(): number {
  // Should have either expense limit or
  // a primary project.
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}
После
getExpenseLimit(): number {
  // TypeScript and JS doesn't have built-in assertions, so we'll use
  // good-old console.error(). You can always extract this into a
  // designated assertion function.
  if (!(expenseLimit != NULL_EXPENSE ||
       (typeof primaryProject !== 'undefined' && primaryProject))) {
      console.error("Assertion failed: getExpenseLimit()");
  }

  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}

Причины рефакторинга

Участок кода создает предположения о чем-то (например, о текущем состоянии объекта, значении параметра или локальной переменной). Обычно это предположение никогда не будет нарушено разве что при наличии какой-то ошибки.

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

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

Достоинства

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

Недостатки

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

  • В каких случаях исключение лучше простой проверки? Если к нему может привести деятельность пользователя или системы, и вы можете обработать это исключение. С другой стороны, обычные безымянные необрабатываемые исключения равносильны простым проверкам — вы их не обрабатываете, они могут быть вызваны только как результат бага в программе, который никогда не должен был возникнуть.

Порядок рефакторинга

Когда вы видите, что предполагается какое-то условие, добавьте проверку этого условия, чтобы проверить его наверняка.

Добавление проверки не должно изменять поведение программы.

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