Unit и integration tests
Unit-тест проверяет маленькую единицу логики изолированно, быстро и детерминированно. Integration-тест проверяет связку компонентов: БД, брокер, HTTP, Spring context.
Пирамида тестов: много быстрых unit-тестов в основании, меньше integration-тестов посередине и совсем немного медленных end-to-end сверху. Перевёрнутая пирамида (всё гоняется через медленные e2e) делает прогон долгим и хрупким.
Структура теста — AAA (Arrange-Act-Assert): подготовьте данные, выполните одно действие, проверьте один результат. Моки заменяют зависимости, чтобы изолировать логику, но мокать стоит свои границы (порты/интерфейсы), а не чужие библиотеки. Хороший набор тестов не мокает всё подряд: контракты с БД, сериализацией и брокером лучше периодически проверять на реальных адаптерах или Testcontainers — иначе тесты зелёные, а интеграция сломана.
Логирование
TRACE и DEBUG — детальная диагностика, INFO — важные штатные события, WARN — подозрительная ситуация, ERROR — сбой, который требует внимания.
В production логи должны помогать расследовать инцидент: correlation id, user/request id и структурированные поля полезнее длинных неструктурированных строк.
log.info("order created: orderId={}, userId={}", orderId, userId);
// не логируйте пароли, токены и персональные данные без необходимости
Singleton
Singleton гарантирует один экземпляр и глобальную точку доступа. Потокобезопасные варианты: enum singleton (самый простой и защищённый, в т.ч. от рефлексии и сериализации), static holder (ленивость через гарантию инициализации класса JVM) или double-checked locking с volatile.
Почему в double-checked locking поле обязано быть volatile — любимый senior-вопрос. instance = new Singleton() не атомарна: это (1) выделить память, (2) вызвать конструктор, (3) присвоить ссылку. Без volatile JVM/процессор вправе переставить (3) перед (2), и другой поток, увидев ненулевую ссылку на первой проверке (вне synchronized), получит наполовину сконструированный объект (half-constructed object) с полями по умолчанию. volatile запрещает такую перестановку и даёт happens-before между записью и чтением ссылки.
Главная ловушка дизайна — глобальное изменяемое состояние. В приложениях со Spring чаще используют singleton scope контейнера и внедрение зависимостей, а не ручной Singleton.
// enum — самый надёжный singleton
enum AppClock {
INSTANCE;
Instant now() { return Instant.now(); }
}
// double-checked locking: volatile обязателен
class Config {
private static volatile Config instance;
static Config get() {
if (instance == null) { // без блокировки
synchronized (Config.class) {
if (instance == null) { // под блокировкой
instance = new Config();
}
}
}
return instance;
}
}
Как отвечать коротко
На интервью начинайте с определения, затем называйте trade-off и типичную ошибку. Такой формат хорошо работает для паттернов, логирования и тестов.
Например: «Unit быстрее и изолированнее, integration дороже, но ловит проблемы wiring/контрактов; ошибка — поднимать весь Spring context для простой функции».