Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
С чем не хотел бы сталкиваться
Честный ответ
В своей карьере я встречал множество антипаттернов и архитектурных ошибок, которые превращали проекты в кошмары для разработки и поддержки. Честно скажу, что нет ничего, чего я бы абсолютно боялся, но есть вещи, которые я предпочел бы избежать.
1. Legacy Code без Tests
Худшее, что может быть — это большая кодовая база без тестов, особенно с циклическими зависимостями и тесно связанными компонентами.
// ❌ Кошмар: Тесты невозможны
public class UserServiceOld {
private static UserServiceOld instance;
private static Database db = new Database(); // Статик!
private static EmailService email = new EmailService(); // Статик!
public static UserServiceOld getInstance() {
if (instance == null) {
instance = new UserServiceOld();
}
return instance;
}
public void registerUser(String email) {
// Жёстко привязано к БД и Email сервису
User user = db.createUser(email);
email.sendWelcomeEmail(user); // Невозможно замокировать!
}
}
// ✅ Правильно: Тестируемо через DI
public class UserService {
private final Database db;
private final EmailService emailService;
public UserService(Database db, EmailService emailService) {
this.db = db;
this.emailService = emailService;
}
public void registerUser(String email) {
User user = db.createUser(email);
emailService.sendWelcomeEmail(user);
}
}
// Тесты просто пишутся
@Test
void testRegisterUser() {
Database mockDb = mock(Database.class);
EmailService mockEmail = mock(EmailService.class);
UserService service = new UserService(mockDb, mockEmail);
service.registerUser("test@example.com");
verify(mockEmail).sendWelcomeEmail(any());
}
2. Монолитный ад
Единый JAR размером в 500+ МБ, где все зависит от всего:
// ❌ Типичная картина в монолите
// pom.xml содержит 200+ зависимостей
// Время сборки: 15 минут
// Каждое изменение требует полного перезапуска
// Невозможно разделить команды по разным компонентам
public class MainApplication {
// Всё загружается при запуске
private UserService userService;
private OrderService orderService;
private ReportService reportService;
private PaymentService paymentService;
private NotificationService notificationService;
private AnalyticsService analyticsService;
// ... ещё 50 сервисов
}
// ✅ Правильно: Microservices или модульный монолит
// Каждый сервис — отдельный JAR
// Независимые базы данных
// Асинхронная коммуникация через Event Bus
3. Null Pointer Exception Hell
// ❌ Кошмар: NullPointerException везде
public class OrderProcessor {
public void processOrder(Order order) {
User user = getUser(order.getUserId());
Address address = user.getAddress(); // Может быть null!
String city = address.getCity(); // NPE!
}
}
// ✅ Правильно: Optional или не-null контракты
public class OrderProcessor {
public void processOrder(Order order) {
Optional<User> user = getUser(order.getUserId());
Optional<String> city = user
.flatMap(User::getAddress)
.map(Address::getCity);
city.ifPresent(c -> System.out.println("City: " + c));
}
}
// Или с @NotNull аннотациями
public class OrderProcessor {
public void processOrder(@NotNull Order order) {
@NotNull User user = getUser(order.getUserId());
@NotNull Address address = user.getAddress();
String city = address.getCity();
}
}
4. No Logging или Over-Logging
// ❌ Плохо: Нет информации при проблемах
public void processPayment(Long orderId) {
try {
paymentGateway.charge(orderId, amount);
} catch (PaymentException e) {
// Ничего не логируем! Как потом дебаговать?
throw new RuntimeException();
}
}
// ❌ Плохо: Спам в логах
public void processPayment(Long orderId) {
logger.info("Starting processPayment");
logger.info("orderId: " + orderId);
logger.info("Getting order...");
Order order = getOrder(orderId);
logger.info("Order retrieved: " + order);
logger.info("Preparing payment...");
// ... ещё 100 log.info
}
// ✅ Правильно: Структурированное логирование
public void processPayment(Long orderId) {
logger.info("Processing payment",
Map.of(
"orderId", orderId,
"timestamp", Instant.now(),
"userId", userId
)
);
try {
paymentGateway.charge(orderId, amount);
logger.info("Payment processed successfully",
Map.of("orderId", orderId)
);
} catch (PaymentException e) {
logger.error("Payment processing failed",
Map.of("orderId", orderId, "error", e.getMessage())
, e);
throw new PaymentFailedException(e);
}
}
5. Отсутствие CI/CD
// ❌ Ужас: Ручное развёртывание
// Процесс:
// 1. Разработчик компилирует на своей машине
// 2. FTP'ит JAR на сервер
// 3. Вручную рестартит приложение
// 4. Если что-то сломалось, никто не знает почему
// 5. Откат делается переписыванием файла
// ✅ Правильно: Полная автоматизация
// GitHub Actions:
// 1. Push -> Запуск unit тестов
// 2. Тесты passed -> Запуск интеграционных тестов
// 3. Все passed -> Сборка Docker образа
// 4. Push в Registry -> Развёртывание в K8s
// 5. Все шаги логируются и можно откатить одной кнопкой
6. Магические числа и строки
// ❌ Кошмар
public class SubscriptionValidator {
public boolean isValid(Subscription sub) {
return sub.getStatus() == 1 && // Что это?
sub.getDaysRemaining() > 3 && // Откуда 3?
sub.getAmount() > 999.99; // Почему 999.99?
}
}
// ✅ Правильно
public class SubscriptionValidator {
private static final int ACTIVE_STATUS = 1;
private static final int MIN_DAYS_VALID = 3;
private static final BigDecimal MIN_AMOUNT = new BigDecimal("999.99");
public boolean isValid(Subscription sub) {
return sub.getStatus() == ACTIVE_STATUS &&
sub.getDaysRemaining() > MIN_DAYS_VALID &&
sub.getAmount().compareTo(MIN_AMOUNT) > 0;
}
}
7. Exception Handling как try-catch везде
// ❌ Плохо: Подавление ошибок
public void processData() {
try {
readFile();
parseData();
saveToDatabase();
} catch (Exception e) {
// Молча игнорируем ошибку
e.printStackTrace(); // Только в консоль
}
}
// ✅ Правильно: Специфичные исключения
public void processData() {
try {
readFile();
parseData();
saveToDatabase();
} catch (FileNotFoundException e) {
logger.error("File not found", e);
throw new DataProcessingException("Unable to read input file", e);
} catch (DataParseException e) {
logger.error("Data parsing failed", e);
throw new DataProcessingException("Invalid data format", e);
} catch (DatabaseException e) {
logger.error("Database operation failed", e);
// Retry logic или graceful degradation
throw new DataProcessingException("Unable to persist data", e);
}
}
8. Отсутствие documentation
// ❌ Что это делает? Никто не знает
public List<User> getFU(Long id, Integer status, Boolean active) {
return users.stream()
.filter(u -> u.getOrgId() == id)
.filter(u -> u.getStatus() == status)
.filter(u -> u.isActive() == active)
.collect(Collectors.toList());
}
// ✅ Правильно: JavaDoc + примеры
/**
* Получает активных пользователей организации с заданным статусом.
*
* @param organizationId ID организации (не может быть null)
* @param status статус пользователя (0=pending, 1=active, 2=suspended)
* @param active фильтр по активности (true=активные, false=заморозённые)
* @return список пользователей, отсортированный по createdAt DESC
* @throws IllegalArgumentException если organizationId <= 0
*
* Пример:
* <pre>
* List<User> activeUsers = userService.getActiveUsersByStatus(123L, 1, true);
* </pre>
*/
public List<User> getActiveUsersByStatus(
@NotNull Long organizationId,
int status,
boolean active) {
return users.stream()
.filter(u -> u.getOrgId().equals(organizationId))
.filter(u -> u.getStatus() == status)
.filter(u -> u.isActive() == active)
.sorted(Comparator.comparing(User::getCreatedAt).reversed())
.collect(Collectors.toList());
}
9. Race Conditions и потокобезопасность
// ❌ Кошмар: Race condition в production
public class Counter {
private int count = 0; // Не потокобезопасно!
public void increment() {
count++; // Не атомарно!
}
}
// ✅ Правильно
public class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
}
// Или если требуется более сложная логика
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
10. Отсутствие мониторинга
// ❌ Приложение упало в production, никто ничего не знает
public void processOrder(Order order) {
// ... код
// Ошибка
// Но никто об этом не знает!
}
// ✅ Правильно: Полный мониторинг
public void processOrder(Order order) {
long start = System.currentTimeMillis();
try {
// ... код
meterRegistry.timer("order.processing.duration").record(
System.currentTimeMillis() - start,
TimeUnit.MILLISECONDS
);
meterRegistry.counter("order.processing.success").increment();
} catch (Exception e) {
meterRegistry.counter("order.processing.error").increment();
logger.error("Order processing failed",
Map.of("orderId", order.getId()), e);
// Отправляем alert
alertingService.alert("Order processing failed: " + e.getMessage());
throw e;
}
}
Что я ценю в проектах
- Чистый код — SOLID принципы, DRY, KISS
- Хорошее тестовое покрытие — 80%+ с быстрыми тестами
- Документация — JavaDoc, архитектурные решения описаны
- CI/CD — полная автоматизация
- Мониторинг — метрики, логи, алерты
- Code Review культура — улучшение качества через обсуждение
- Разумная архитектура — масштабируемая, тестируемая, поддерживаемая
Основной принцип: код пишется один раз, а читается и поддерживается много раз. Инвестируй в качество с самого начала!