Ответ
Разработка нового кода с расчетом на долгосрочную поддержку строится на комбинации принципов проектирования, стандартов кода и инженерных практик.
1. Принципы проектирования (SOLID, DRY, YAGNI):
// Пример соблюдения SRP (Single Responsibility) и Dependency Injection
@Service
public class OrderService {
private final PaymentProcessor paymentProcessor; // Зависимость через интерфейс
private final NotificationService notificationService;
// Внедрение зависимостей через конструктор
public OrderService(PaymentProcessor processor, NotificationService notifier) {
this.paymentProcessor = processor;
this.notificationService = notifier;
}
public OrderResult placeOrder(OrderRequest request) {
// Каждый сервис отвечает за одну зону ответственности
validate(request);
PaymentReceipt receipt = paymentProcessor.charge(request);
notificationService.sendOrderConfirmation(request.getUserEmail(), receipt);
return new OrderResult(receipt);
}
}
2. Инженерные практики:
- Test-Driven Development (TDD): Сначала пишется падающий тест, затем — минимальная реализация, потом рефакторинг.
-
Покрытие unit-тестами: Использование JUnit 5, Mockito для изоляции тестируемого модуля.
@Test void placeOrder_ShouldChargePaymentAndSendNotification() { // Arrange PaymentProcessor mockProcessor = mock(PaymentProcessor.class); NotificationService mockNotifier = mock(NotificationService.class); OrderService service = new OrderService(mockProcessor, mockNotifier); OrderRequest testRequest = new OrderRequest("test@mail.com", 100.0); when(mockProcessor.charge(testRequest)).thenReturn(new PaymentReceipt("123")); // Act OrderResult result = service.placeOrder(testRequest); // Assert verify(mockProcessor).charge(testRequest); verify(mockNotifier).sendOrderConfirmation("test@mail.com", any()); assertThat(result.getReceiptId()).isEqualTo("123"); } - Согласованный стиль кода: Следование правилам именования, форматирования (используя Checkstyle, Spotless).
- Ревью кода: Обязательный peer review перед мержем для обмена знаниями и контроля качества.
Ответ 18+ 🔞
А, ну вот, опять про эту вашу долгосрочную поддержку, блядь. Как будто кто-то из нас, грешных, реально думает, что этот код проживёт дольше, чем кофе в кружке на утреннем стендапе. Но ладно, давайте по порядку, а то потом охуеем, когда через полгода придётся в эту жебучую систему лезть.
1. Эти ваши принципы, SOLID там всякие.
Ну, типа, чтобы не получилась одна большая функция-монстр, которая делает всё: и деньги берёт, и письма шлёт, и в БД пишет, и тебе кофе варит. Это пиздец как неудобно потом, когда надо поменять, как именно деньги берут. Представь, блядь, приходит новый закон, и надо не просто списать, а ещё и в налоговую отчёт высрать. А у тебя всё в одном методе placeOrder, который уже на 500 строк раздуло. Ты его тронешь — всё развалится, как карточный домик от чиха.
Вот смотри, в примере-то всё красиво разложено. OrderService только оркестрирует процесс. Платежи — это отдельная сущность, PaymentProcessor. Уведомления — это NotificationService. Каждый, сука, отвечает за свой огород. Это и есть SRP — принцип единственной ответственности. Не пытайся быть и швецом, и жнецом, а то получишь пиздопроебибну архитектуру.
И смотри, как зависимости внедряются — через конструктор. Это называется Dependency Injection. Раньше бы написали new PaymentProcessor() прямо внутри класса, и привязались бы намертво к одной конкретной реализации. А теперь мы говорим: «Да мне похуй, какой именно процессор, лишь бы он интерфейс PaymentProcessor реализовывал». Захотел поменять платёжку с Stripe на PayPal? Подсунул другую реализацию, и основной бизнес-логины даже не чихнул. Красота, ёпта.
// Пример соблюдения SRP (Single Responsibility) и Dependency Injection
@Service
public class OrderService {
private final PaymentProcessor paymentProcessor; // Зависимость через интерфейс
private final NotificationService notificationService;
// Внедрение зависимостей через конструктор
public OrderService(PaymentProcessor processor, NotificationService notifier) {
this.paymentProcessor = processor;
this.notificationService = notifier;
}
public OrderResult placeOrder(OrderRequest request) {
// Каждый сервис отвечает за одну зону ответственности
validate(request);
PaymentReceipt receipt = paymentProcessor.charge(request);
notificationService.sendOrderConfirmation(request.getUserEmail(), receipt);
return new OrderResult(receipt);
}
}
2. Инженерные практики, без которых — просто пиздец.
- TDD (Test-Driven Development). Это когда ты сначала пишешь тест на то, чего ещё нет. Звучит, как бред шизофреника, но, блядь, работает. Ты описываешь, как система должна себя вести. Потом запускаешь тест — он, естественно, падает, потому что кода нет. А потом пишешь самый простой и тупой код, чтобы тест прошёл. И только потом рефакторишь, делаешь красиво. Это заставляет думать о дизайне API сразу, а не как обычно: накодил гору, а потом охуеваешь, как это тестировать.
- Покрытие unit-тестами. Это святое. Без этого — ты ходишь по охуенно тонкому льду с грузовиком говна за спиной. Каждый чих в коде — и ты не знаешь, что сломалось. Вот смотри, как тестируется наш сервис:
@Test
void placeOrder_ShouldChargePaymentAndSendNotification() {
// Arrange (Подготовка)
// Создаём моки — заглушки для зависимостей. Это не реальные процессоры, а куклы.
PaymentProcessor mockProcessor = mock(PaymentProcessor.class);
NotificationService mockNotifier = mock(NotificationService.class);
// Подсовываем эти моки в наш сервис.
OrderService service = new OrderService(mockProcessor, mockNotifier);
OrderRequest testRequest = new OrderRequest("test@mail.com", 100.0);
// Говорим моку процессора: «Когда у тебя вызовут charge с этим request, верни вот такой ресипт».
when(mockProcessor.charge(testRequest)).thenReturn(new PaymentReceipt("123"));
// Act (Действие)
// Вызываем метод, который тестируем.
OrderResult result = service.placeOrder(testRequest);
// Assert (Проверка)
// Проверяем, что мок процессора действительно был вызван с нужными аргументами.
verify(mockProcessor).charge(testRequest);
// Проверяем, что мок нотификатора тоже вызвали.
verify(mockNotifier).sendOrderConfirmation("test@mail.com", any());
// И проверяем результат.
assertThat(result.getReceiptId()).isEqualTo("123");
}
Видишь магию? Мы не поднимаем всю банду — базу данных, почтовый сервер, платёжный шлюз. Мы тестируем только логику OrderService: что он правильно координирует вызовы других сервисов. Это быстро, это надёжно, это изолированно.
- Стиль кода. Да, это скучно, как хуй в пальто, но необходимо. Один пишет с отступами в 2 пробела, другой — в 4, третий табами. В итоге в гите одна сплошная война изменений, где половина коммитов — это «поправил форматирование». Настраиваешь Checkstyle или Spotless, и пусть машина сама приводит всё к одному виду. Читаемость, блядь, важна.
- Ревью кода. Это, пожалуй, главное. Один человек — это слепая зона. Он мог накосячить в самом неожиданном месте, а сам даже не заметит. Вторые, третьи глаза видят то, что ты уже перестал замечать. Плюс, это обмен знаниями: все в команде понимают, что и как пишется. Без ревью мержить — это как играть в русскую рулетку с автоматом Калашникова. Рано или поздно прилетит так, что мало не покажется.