Безопасные шаги рефакторинга — Примеры

Пример 1 — Один коммит, который рефакторит и меняет поведение

Разработчик приводит в порядок функцию расчёта цены и заодно меняет округление. Тесты, которые он прогнал после этого, стали красными. Определите, чем на самом деле было изменение, и разделите его.

// до
function calc(v) { return Math.floor(v.reduce((s, i) => s + i.price, 0)); }

// после — переименовано, выделено и теперь округляет вверх
function totalPrice(items) {
  return Math.round(sumPrices(items));
}

Решение

// шаг 1: только структура — тесты не менялись и зелёные
function calcTotal(items) { return Math.floor(sumPrices(items)); }

// шаг 2: только поведение — со своим новым тестом
test("округляет до ближайшего пенни", () => {
  expect(calcTotal([{ price: 1.005 }])).toBe(1.01);
});

Прогоните тесты после шага 1: если они провалятся, виновато переименование или выделение. Затем прогоните их после шага 2: провал теперь может быть только из-за округления. Одна шляпа за раз — вот что делает прогон тестов информативным; смешав две задачи, вы оставляете открытыми обе гипотезы, и проверяющий не может сказать, какая половина диффа должна была сохранять поведение.

Пример 2 — Перестройка кода, у которого нет тестов

Вас попросили разбить одну большую функцию устаревшего форматтера на три, а тестов у неё нет. Покажите, как заслужить право двигать код прежде, чем его двигать.

function formatLegacy(input) {
  // 80 строк: обрезает, дополняет, приводит к верхнему регистру, добавляет суффикс
}
// тестов нет; никто не знает, какие части намеренные

Решение

// три характеризующих теста, записанных с текущего кода
test("дополняет короткое значение", () => {
  expect(formatLegacy("ab")).toBe("ab  ");
});
test("обрабатывает пустое значение", () => {
  expect(formatLegacy("")).toBe("  ");
});
test("приводит к верхнему регистру до дополнения", () => {
  expect(formatLegacy("a b")).toBe("A B ");
});

// затем, когда сетка на месте: выделите одну ответственность, прогоните тесты

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