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

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

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

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

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

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


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

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

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

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

def register_user(email, age):
if email is None:
raise ValueError("email обязателен")
if not isinstance(email, str):
raise TypeError("email должен быть строкой")
if not email.strip():
raise ValueError("email не может быть пустым")

if not isinstance(age, int):
raise TypeError("age должен быть int")
if age < 0 or age > 150:
raise ValueError("age вне допустимого диапазона")

# дальше — сохранение в базу
return {"email": email.strip(), "age": age}

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

JavaScript

function registerUser(email, age) {
if (email == null) {
throw new Error("email обязателен");
}
if (typeof email !== "string") {
throw new TypeError("email должен быть строкой");
}
if (!email.trim()) {
throw new Error("email не может быть пустым");
}

if (typeof age !== "number" || !Number.isInteger(age)) {
throw new TypeError("age должен быть целым числом");
}
if (age < 0 || age > 150) {
throw new RangeError("age вне допустимого диапазона");
}

return { email: email.trim(), age };
}

Сравнение 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
Какие правила домена?Диапазоны, длины, белые списки кодов
Где сообщать об ошибке?Пользователь, лог, метрика — разные тексты и уровни
Повторяются ли одни проверки?Вынести в функцию, тип или слой валидации