Структура и обработка ошибок — Примеры

Пример 1 — Сбой, дошедший до клиента нулём

Функция ниже списывает деньги с карты, а когда шлюз отказывает, переходит к нулевой сумме. Трассировка — то, что увидел оператор. Скажите, что получил клиент, затем перепишите путь отказа так, чтобы причина сохранилась.

async function charge(order) {
  try {
    return await gateway.charge(order);
  } catch (e) {
    console.log("charge failed");
    return { total: 0, status: "ok" };
  }
}

trace:
  gateway.charge(order) -> 502 от провайдера
  строка журнала: "charge failed"
  вызывающий: заказ #4471 подтверждён на 0.00

Решение

async function charge(order) {
  try {
    return await gateway.charge(order);
  } catch (cause) {
    throw new PaymentFailed(`charge failed for order ${order.id}`, { cause });
  }
}

Нулевая сумма со статусом ok — правдоподобное значение для сломанной операции. Сбой из-за этого продолжает маскироваться под данные и всплывает через две системы позже неверным счётом. Если перебросить ошибку, прикрепив к ней id заказа и исходную причину, детали сохранятся там, где ими владеет только этот слой, а вызывающему это позволит решить, повторять или отказываться. Ловушка в том, что catch написали, чтобы удержать запрос живым: намерением была устойчивость, а результатом стало то, что сбой выглядит как успех.

Пример 2 — Защитные условия вместо четырёх уровней вложенности

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

function discountFor(customer, order) {
  if (customer) {
    if (customer.active) {
      if (order && order.total > 0) {
        if (customer.years > 1) {
          return order.total * customer.years * 0.01;
        }
      }
    }
  }
  return 0;
}

Решение

function discountFor(customer, order) {
  if (!customer || !customer.active) return 0;
  if (!order || order.total <= 0) return 0;
  if (customer.years <= 1) return 0;
  return order.total * customer.years * 0.01;
}

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