Пример рефакторинга «извлечение функции» для валидации дат в трёх полях формы


Исходный код

var req_date = document.getElementById('requested_date').value.split('-');
if (req_date.length!=3 || new Number(req_date[0])<1990 || new Number(req_date[0])>2100
|| new Number(req_date[1])<1 || new Number(req_date[1])>12 || new Number(req_date[2])<1) {
alert('Requested date is not valid, please re-enter.');
return false;
}

var ef_date = document.getElementById('effective_date').value.split('-');
if (ef_date.length!=3 || new Number(ef_date[0])<1990 || new Number(ef_date[0])>2100
|| new Number(ef_date[1])<1 || new Number(ef_date[1])>12 || new Number(ef_date[2])<1) {
alert('Effective date is not valid, please re-enter.');
return false;
}

var ex_date = document.getElementById('expiration_date').value.split('-');
if (ex_date.length!=3 || new Number(ex_date[0])<1990 || new Number(ex_date[0])>2100
|| new Number(ex_date[1])<1 || new Number(ex_date[1])>12 || new Number(ex_date[2])<1) {
alert('Expiration date is not valid, please re-enter.');
return false;
}


        var month_days = new Array(31,29,31,30,31,30,31,31,30,31,30,31);

if (new Number(req_date[0])%4) {
month_days[1] = 28;
}

if ((new Number(req_date[2])>month_days[new Number(req_date[1])-1]) ) {
alert('Requested date is not valid, please re-enter.');
return false;
}

if (new Number(ef_date[0])%4) {
month_days[1] = 28;
}
if ((new Number(ef_date[2])>month_days[new Number(ef_date[1])-1]) ) {
alert('Effective date is not valid, please re-enter.');
return false;
}

if (new Number(ex_date[0])%4) {
month_days[1] = 28;
}
if ((new Number(ex_date[2])>month_days[new Number(ex_date[1])-1]) ) {
alert('Expiration date is not valid, please re-enter.');
return false;
}

Как можно отрефакторить и улучшить исходный код

Разделение ответственностей

Здесь один фрагмент кода выполняет сразу много обязанностей (судя по операторам return это код функции):

  • получает введённое пользователем значение даты
  • применяет валидацию к введённым датам
  • проверяет валидность даты
  • выводит сообщения об ошибках
  • сопоставляет машинное и человеческое названия полей

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

function validate(errors, field_name, value, method) {
    const field_errors = method(value);
    if (field_errors) {
        errors.push({
            name: field_name,
            errors: field_errors,
        });
    }
}

function validateDate(date) {
    // ...
}

function reportValidationErrors(errors) {
    let messages = [];
    for (const field of errors) {
        for (const field_error of field.errors) {
            messages.push(`${field.name}: ${field_error}`);
        }
    }
    alert(messages.join("\n"));
}

let validation_errors = [];
validate(validation_errors, 'requested_date', document.getElementById('requested_date').value, validateDate);
validate(validation_errors, 'effective_date', document.getElementById('effective_date').value, validateDate);
validate(validation_errors, 'expiration_date', document.getElementById('expiration_date').value, validateDate);
reportValidationErrors(validation_errors);

Теперь верхнеуровневый код знает только о том, как получить значения полей и как в целом организовать процесс валидации, остальные ответственности разбежались по отдельным функциям:

  • как валидировать поле формы (без привязки к типу)
  • как валидировать дату (без привязки источника: поле или не поле)
  • как показать пользователю ошибки валидации

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

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

Рефакторинг алгоритма валидации даты

Проверка валидности состоит из двух шагов:

  • проверить, что введённая дата в принципе валидна
  • проверить, что дата находится в требуемом диапазоне (от 1990 до 2100 года)

В исходном коде эти шаги перемешаны между собой.

Очевидно, что для первого шага нет смысла писать какой-либо код, когда существует функция Date.parse.

function validateDate(date_string) {
    const date = Date.parse(date_string);
    if (isNaN(date)) {
        return ['Неправильная дата'];
    }
    const year = date.getFullYear();
    if (year < 1990 || year > 2100) {
        return ['Год должен находиться в диапазоне от 1990 до 2100'];
    }
    return null;
}

Теория