← Назад к вопросам

С чем не хотел сталкиваться

1.0 Junior🔥 151 комментариев
#Soft Skills и карьера

Комментарии (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;
    }
}

Что я ценю в проектах

  1. Чистый код — SOLID принципы, DRY, KISS
  2. Хорошее тестовое покрытие — 80%+ с быстрыми тестами
  3. Документация — JavaDoc, архитектурные решения описаны
  4. CI/CD — полная автоматизация
  5. Мониторинг — метрики, логи, алерты
  6. Code Review культура — улучшение качества через обсуждение
  7. Разумная архитектура — масштабируемая, тестируемая, поддерживаемая

Основной принцип: код пишется один раз, а читается и поддерживается много раз. Инвестируй в качество с самого начала!

С чем не хотел сталкиваться | PrepBro