Autumn SALE

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

Також відомий як: 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();
}

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

Ділянка коду створює припущення про щось (наприклад, про поточний стан об’єкта, значення параметра або локальної змінної). Зазвичай це припущення ніколи не буде порушено хіба що за наявності якоїсь помилки.

Зробіть ці припущення явними, додавши відповідні перевірки. Як і підказки типів в параметрах методів, ці перевірки можуть грати роль живої документації коду.

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

Переваги

  • Якщо якесь припущення невірне і в результаті код дає також невірний результат, краще відразу зупинити виконання, поки це не привело до фатальних наслідків і псування даних. Крім того, це означає, що ви забули написати якийсь тест в наборі тестів вашої програми.

Недоліки

  • Іноді замість простої перевірки краще вставити виключення. При цьому ви можете вибрати необхідний клас виключення і дати іншому коду можливість коректно його обробити.

  • В яких випадках виключення краще простої перевірки? Якщо до нього може привести діяльність користувача або системи, і ви можете обробити це виключення. З іншого боку, звичайні безіменні необроблювані виключення роблять теж саме, що й перевірки — ви їх також не обробляєте, вони можуть бути викликані тільки як результат бага в програмі, який ніколи не повинен був виникнути.

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

Коли ви розумієте, що передбачається якась умова, додайте перевірку цієї умови, щоби перевірити її напевно.

Додавання перевірки не повинне змінювати поведінку програми.

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