Перейти к основному содержимому

Проверка и валидация

Разработчику Аналитику Тестировщику Архитектору Инженеру

Проверка и валидация​

Как устроены параметры и аргументы функций — Функции — параметры и аргументы.

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


Входные данные — параметры и аргументы​

Любая функция или метод работает с входными данными. В объявлении функции они называются параметрами; при вызове в скобки передаются аргументы — конкретные значения на этот запуск. Подробнее — в главе Функции.

Учебные примеры часто выглядят так:

def discount(price, percent):
return price * (100 - percent) / 100

discount(1000, 10) # аргументы — литералы 1000 и 10

Здесь оба аргумента — литералы: числа, записанные прямо в коде. Разработчик полностью контролирует, что передаётся.

В реальных системах аргументами чаще становятся неконтролируемые значения:

  • строка из поля формы или query-параметра URL;
  • JSON-тело HTTP-запроса от другого сервиса;
  • строка, прочитанная из файла или очереди сообщений;
  • запись из базы данных, созданная когда-то другим модулем или сторонней системой;
  • результат вызова внешнего API;
  • значение, сгенерированное другой программой или скриптом.

Внешние источники не дают гарантии, что данные полные, свежие и согласованные с вашей логикой. Файл мог повредиться при копировании. Пользователь мог оставить поле пустым. API мог вернуть null вместо объекта. В базе могла остаться строка там, где код уже ожидает число. Иногда приходит неопределённое значение (undefined в JavaScript) или явное отсутствие (null, None).

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


Когда "плохие" данные роняют программу​

Самый частый сценарий — обращение к полю или методу у отсутствующего значения.

Java — компилируемый язык с NullPointerException​

public class ProfileService {
public static String displayName(User user) {
return user.getName().trim(); // NPE, если user или getName() вернул null
}
}

// Вызов с данными "из базы", где пользователь не найден:
ProfileService.displayName(null);

Компилятор Java не запретит передать null в параметр ссылочного типа, если аннотации и nullable-типы не настроены. На этапе выполнения JVM выбросит NullPointerException — программа прервёт текущий поток, если исключение не перехватят.

JavaScript — интерпретируемый язык с TypeError​

function displayName(user) {
return user.name.trim(); // TypeError, если user — null или undefined
}

// Ответ API без поля user:
displayName(undefined);

JavaScript не падает на этапе "компиляции" в браузере: ошибка проявится во время вызова, когда движок попытается прочитать свойство name у undefined. Сообщение будет вроде Cannot read properties of undefined (reading 'name').

Один смысл — разные ошибки
Java — NullPointerException, JavaScript — TypeError, Python — AttributeError: 'NoneType' object has no attribute …. Механизм разный, причина одна — код предположил, что значение есть, а его не было.

Подробнее про null, undefined, None и приёмы защиты — в статье Обработка значения null.


Проверка и валидация — что это​

Проверка (check) — установить истинность условия — "значение есть?", "это число?", "строка не пустая?". Результат — да или нет; дальше код решает, продолжать работу, вернуть ошибку или подставить значение по умолчанию.

Валидация (validation) — сопоставить данные с набором правил предметной области — формат email, диапазон возраста, допустимые символы в логине, согласованность полей ("дата окончания позже даты начала"). Итог — данные валидны или невалидны.

ТерминСмысл
ВалидностьДанные удовлетворяют всем правилам для данной операции
НевалидностьХотя бы одно правило нарушено; иногда в документации встречают синоним "инвалидность данных" в том же техническом смысле
Валидные данныеМожно безопасно передавать в бизнес-логику
Невалидные данныеНужно отклонить, исправить запрос или запросить повторный ввод

На практике слова "проверка" и "валидация" часто пересекаются: проверка на null — минимальное правило валидации; полная валидация формы включает десятки отдельных проверок.

Типичный порядок при приёме аргументов:

  1. Присутствие — значение не null / не undefined / не пустая ссылка.
  2. Тип — это число, строка, список, а не "что-то неожиданное".
  3. Ограничения — диапазон, длина, формат, перечисление допустимых кодов.
  4. Согласованность — связь нескольких полей друг с другом.

Ниже — та же логика в виде шпаргалки: что именно проверять и с чем сравнивать фактическое значение.

На что проверять и с чем сравнивать​

Вид проверкиС чем сравниваемЗачемПример
Существование ссылкизначение не null, не undefined, не Noneможно безопасно обращаться к полям и методамif (user != null), if (user && user.id)
Наличие содержимогоне пустая строка, не пустой список, не "только пробелы"отличить "поле не заполнили" от "поле заполнили пробелами"email.trim() !== "", items.length > 0
Соответствие типуожидаемый тип данныхотсечь "25" там, где нужен number, или объект вместо строкиtypeof age === "number", isinstance(age, int)
Конкретное значениелитерал, код из белого списка, enumтолько разрешённые состоянияstatus === "active", role in ("user", "admin")
Диапазонминимум и максимум, границы интервалачисла и даты в допустимых пределахage >= 0 && age <= 150, price > 0
Длина или размерлимит символов, элементов, байтUX, лимиты БД, защита от слишком больших payloadpassword.length >= 8, len(title) <= 200
Формат / шаблонregexp, маска, структура (email, UUID, дата ISO)строка "похожа" на нужный формат@ в email, RegEx
Согласованность полейдругое поле того же запроса или моделиправила между несколькими значениямиendDate > startDate, confirm === password
Существование сущностизапись в БД, файл на диске, ключ в кэшеid передан, но объект мог быть удалёнuser = repo.findById(id); if (user is None) …
Уникальностьотсутствие дубликата в хранилищерегистрация email, slug, номер документаnot repo.existsByEmail(email)
Контекст и праватекущий пользователь, роль, владелец ресурсаоперация разрешена именно этому субъектуorder.userId === currentUser.id
Состояние процессадопустимый переход статусанельзя "отменить" уже доставленный заказorder.status === "pending" перед оплатой

Truthiness — не подмена проверок
Конструкция if (user && user.name) отсекает null и undefined, но не заменяет проверку типа и диапазона. Значения 0, "" и false в JavaScript ложны в условии — их нельзя отбрасывать, если они могут быть корректными данными (возраст 0, флаг false, пустой комментарий как осознанный ввод).

Порядок строк в таблице близок к реальному коду на границе модуля — сначала "есть ли объект", затем тип и содержимое, потом бизнес-правила, в конце — существование связанных записей и права.


Пример — регистрация пользователя на четырёх языках​

Один сценарий: функция принимает email и возраст. Email обязателен, возраст — целое число от 0 до 150.

Python​

Код ITЗагрузка примера кода…

is None отделяет отсутствие значения от пустой строки "". isinstance проверяет тип во время выполнения — Python допускает передать любой объект.

JavaScript​

Код ITЗагрузка примера кода…

Сравнение email == null отлавливает и null, и undefined — типичный приём на границе API.

C#​

public static User RegisterUser(string email, int age)
{
if (email is null)
throw new ArgumentNullException(nameof(email));
if (string.IsNullOrWhiteSpace(email))
throw new ArgumentException("email не может быть пустым", nameof(email));
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(age), "age вне допустимого диапазона");

return new User(email.Trim(), age);
}

Тип int в C# уже отсекает нечисловые аргументы на этапе компиляции; nullable int? потребовал бы отдельной проверки .HasValue. Для строк ArgumentNullException и ArgumentException — стандартные типы ошибок контракта метода.

Java​

public static User registerUser(String email, int age) {
if (email == null) {
throw new IllegalArgumentException("email обязателен");
}
if (email.isBlank()) {
throw new IllegalArgumentException("email не может быть пустым");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("age вне допустимого диапазона");
}
return new User(email.trim(), age);
}

Примитивный int в Java не бывает null; если возраст опционален, используют Integer и проверяют его отдельно.


Уровни защиты в одном проекте​

УровеньЧто проверяютПример
UI / клиентБазовый UX — не отправлять пустую формуHTML required, маска телефона
Граница сервераПолный набор правил, безопасностьREST-контроллер, десериализация JSON
ДоменИнварианты модели"Счёт не может быть отрицательным"
ИнфраструктураЦелостность храненияUNIQUE в БД, NOT NULL

Клиентские проверки удобны, но не заменяют серверные: запрос можно отправить в обход браузера.

Guard clause — ранний выход
Сначала отсечь невалидные случаи и выйти из функции (return, throw), затем писать "счастливый путь" без глубокой вложенности if. Тот же приём описан для null в Обработка значения null.


Что делать с невалидными данными​

Выбор зависит от слоя и контракта:

  • Исключение (throw, raise) — ошибка программиста или нарушение контракта внутреннего API.
  • Код ошибки / Result — ожидаемый отказ для внешнего вызова ("логин занят", "неверный формат").
  • Сообщение пользователю — список полей с пояснением, без внутренних деталей стека.
  • Значение по умолчанию — только если бизнес явно разрешает подстановку; иначе это скрывает проблему.

Сильная типизация и маленькие типы (value objects) переносят часть проверок в конструктор типа Email или Age: один раз валидировали — дальше по коду передаётся уже гарантированно корректное значение.


Текстовые форматы и регулярные выражения​

Проверка "похож ли email на email" часто сочетает простые правила (не пусто, есть @) с регулярными выражениями. RegEx удобен для формата, но не заменяет проверку смысла (существует ли такой пользователь в базе).


Связь с тестированием​

Каждое правило валидации — отдельный тестовый сценарий — null, пустая строка, отрицательное число, слишком длинное значение, неверный тип. Тестировщик и разработчик составляют одну и ту же матрицу "вход → ожидаемый результат". Обзор процесса на уровне проекта — в разделе Тестирование.


Краткий чек-лист​

ВопросДействие
Откуда пришло значение?Определить, контролируемый это источник или внешний
Может ли быть null / пусто?Явная проверка до . / -> / индексации
Известен ли тип?typeof, isinstance, сигнатура метода, схема JSON
Какие правила домена?Диапазоны, длины, белые списки кодов
Где сообщать об ошибке?Пользователь, лог, метрика — разные тексты и уровни
Повторяются ли одни проверки?Вынести в функцию, тип или слой валидации