Рефакторьте следующий Java-метод, нарушающий SRP и тестируемость: "public void processOrder(Order o){ validate(o); saveToDb(o); sendEmail(o); log(o); }" — опишите необходимые изменения, интерфейсы и тестовую стратегию

3 Июн в 08:27
15 +1
0
Ответы
1
Кратко и по делу — что менять, примеры интерфейсов/классов и стратегия тестирования.
1) Проблема
Исходный метод смешивает 4 ответственности: валидация, персистенция, отправка почты и логирование. Это мешает тестируемости и расширению.
2) Что сделать (основные шаги)
- Вынести каждую ответственность в отдельный интерфейс.
- Внедрить зависимости через конструктор (инверсия зависимостей).
- Возвращать результат или бросать специализированные исключения вместо «молчаливого» void, чтобы упростить проверку в тестах.
- Логика оркестрации должна оставаться в новом классе (OrderProcessor) и быть максимально простая.
- Избежать статики и прямых вызовов внешних ресурсов в коде (использовать абстракции), для тестов — мокать/фейкать реализации.
3) Пример интерфейсов
public interface OrderValidator {
void validate(Order order) throws ValidationException;
}
public interface OrderRepository {
Order save(Order order) throws RepositoryException;
}
public interface EmailService {
boolean sendOrderConfirmation(Order order) throws EmailException;
}
public interface AuditLogger {
void info(String msg);
void warn(String msg);
void error(String msg, Throwable t);
}
4) Рефакторинг: оркестратор + результат
public final class OrderProcessor {
private final OrderValidator validator;
private final OrderRepository repository;
private final EmailService emailService;
private final AuditLogger logger;
public OrderProcessor(OrderValidator validator,
OrderRepository repository,
EmailService emailService,
AuditLogger logger) {
this.validator = validator;
this.repository = repository;
this.emailService = emailService;
this.logger = logger;
}
public ProcessResult process(Order order) {
try {
validator.validate(order);
} catch (ValidationException ve) {
logger.warn("Validation failed: " + ve.getMessage());
return ProcessResult.failure(Stage.VALIDATION, ve.getMessage());
}
Order saved;
try {
saved = repository.save(order);
} catch (RepositoryException re) {
logger.error("Save failed", re);
return ProcessResult.failure(Stage.PERSISTENCE, re.getMessage());
}
boolean emailSent;
try {
emailSent = emailService.sendOrderConfirmation(saved);
if (!emailSent) {
logger.warn("Email service returned false");
return ProcessResult.partialSuccess(saved, Stage.EMAIL);
}
} catch (EmailException ee) {
logger.error("Email failed", ee);
return ProcessResult.partialSuccess(saved, Stage.EMAIL, ee.getMessage());
}
logger.info("Order processed: " + saved.getId());
return ProcessResult.success(saved);
}
}
public final class ProcessResult {
public enum Status { SUCCESS, PARTIAL_SUCCESS, FAILURE }
public enum Stage { VALIDATION, PERSISTENCE, EMAIL }
private final Status status;
private final Stage failedStage; // nullable
private final String message; // nullable
private final Order order; // nullable
// фабричные методы: success, partialSuccess, failure + геттеры
}
5) Дополнительные улучшения (по необходимости)
- Асинхронная отправка почты (через очередь) — если email не должен блокировать save.
- Retry/идемпотентность для сетевых операций.
- Транзакции: оборачивать save + другие критичные действия в транзакцию, если нужно атомарно.
- Сделать Order immutable / DTO для передачи между слоями.
6) Тестовая стратегия
Unit tests (быстро, изолировано)
- Мокать все зависимости (Mockito / MockK / ручные фейки).
- Тесты:
- Успешный сценарий: validator.validate() не кидает, repository.save() возвращает объект, emailService.send... возвращает true; assert: результат SUCCESS, verify(repository).save(order), verify(emailService).send...
- Валидция провалилась: validator бросает ValidationException; assert: ProcessResult.failure со Stage.VALIDATION; verify(repository, never()).save(...).
- Сохранение упало: repository.save() бросает; assert: FAILURE/PERSISTENCE, verify(emailService, never()).
- Email провалился (false или исключение): assert: PARTIAL_SUCCESS, сохранение произошло, emailService вызван.
- Логирование можно проверить через мок AuditLogger (verify вызовов).
Integration tests (медленнее)
- Поднять реальную (или in-memory) БД (H2 / Testcontainers) и реальный/локальный SMTP (GreenMail) и проверить end-to-end: request -> запись в БД -> фактическая отправка письма.
- Тесты транзакций, консистенции данных.
Contract / Component tests
- Проверять поведение concrete EmailService (например, retry) отдельно.
- Проверять repository на реальные сценарии.
End-to-end / Acceptance
- Полная система с реальным внешним окружением (или staging): проверка бизнес-правил и уведомлений.
7) Что проверять в тестах (ключевые ассерты)
- Правильные взаимодействия (verify вызовов).
- Корректные возвращаемые статусы/сообщения в ProcessResult.
- Что при ошибках ненужные побочные эффекты не происходят (напр., при провальной валидации — нет сохранения/письма).
- Поведение при исключениях (правильная обработка/логирование, транзакционная консистентность).
8) Заключение (одно предложение)
Разделив обязанности и введя интерфейсы + DI, вы получите легко тестируемый, расширяемый и надёжный код: unit-тесты мокают зависимости и проверяют оркестрацию, integration/acceptance тесты покрывают реальные реализации.
3 Июн в 08:34
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир