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

Были ли случаи, когда принимал неверное решение

1.3 Junior🔥 81 комментариев
#Soft Skills

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Были ли случаи, когда принимал неверное решение?

Да, конечно. И это нормально. Ошибки — лучшая школа разработчика. Расскажу про несколько значимых:

Ошибка 1: Выбор неправильной архитектуры (Monolith vs Microservices)

Контекст: Проект стартапа достиг 10 разработчиков. Я решил, что пора переходить на микросервисы и начал разбивать монолит на сервисы без четкого плана. Потратил 2 месяца, а результат был печален.

Проблемы:

  • Появились сложнейшие баги с синхронизацией данных между сервисами
  • Distributed transactions стали кошмаром (нет ACID гарантий)
  • Было сложнее дебажить проблемы
  • Простой монолит был бы на 80% эффективнее для нашего размера

Что я сделал:

  • Признал ошибку на ретроспективе команде
  • Вернулся на монолит (больно, но необходимо)
  • Выучил правило: Monolith First, потом микросервисы только если реально нужны

Вывод: Теперь я руководствуюсь правилом: Используй простую архитектуру пока не упёрёшься в её потолок. Микросервисы — это не премиум, это решение определённых проблем (масштабируемость, независимые deploys, разные технологии). Без явной боли — не нужны.

Ошибка 2: Оптимизация, которая не требовалась (Premature Optimization)

Контекст: Я потратил целую неделю на оптимизацию БД запросов, которые и так выполнялись за 20ms. Результат: код стал сложнее на 30%, выигрыш был 2ms.

# ❌ Чрезмерная оптимизация
class UserRepository:
    def get_users_optimized_v1(self):
        # Используем select_in_load
        users = session.query(User).options(
            selectinload(User.posts).selectinload(Post.comments)
        ).all()
        return users
    
    def get_users_optimized_v2(self):
        # Кеширую промежуточные результаты
        cache_key = "users:all"
        cached = redis.get(cache_key)
        if cached:
            return json.loads(cached)
        
        users = session.query(User).all()
        redis.setex(cache_key, 3600, json.dumps(users))
        return users
    
    def get_users_optimized_v3(self):
        # Даже более сложная оптимизация с denormalization
        # ...

В то же время, другая часть кода работала 5 секунд, но я на неё не смотрел!

Правильный подход:

# Сначала мерим (profiling)
import cProfile
import pstats

cProfile.run('get_users()', 'output.prof')
stats = pstats.Stats('output.prof')
stats.sort_stats('cumulative')
stats.print_stats(10)  # Top 10 функций

# Потом оптимизируем то, что реально медленно

Вывод: "Premature optimization is the root of all evil" — эта цитата Donald Knuth истинна. Сначала правильный код, потом профилирование, потом оптимизация.

Ошибка 3: Игнорирование техдолга

Контекст: Мы писали код быстро без тестов. "Потом напишем тесты". Через полгода:

  • Не могли менять старый код без страха что-то сломать
  • Каждый баг фикс занимал 3 часа вместо 30 минут
  • Новые разработчики боялись трогать старый код
  • Скорость разработки упала в 3 раза

Что я сделал: Итерировали 3 месяца, писали тесты. Да, это было болезненно, но результат:

  • Уверенность в коде вернулась
  • Скорость разработки вернулась в норму
  • Новичкам было легче влиться
# ✅ Правильный подход: TDD
# Сначала тест
def test_user_registration():
    user_service = UserService()
    user = user_service.register("test@example.com", "password123")
    
    assert user.email == "test@example.com"
    assert user.password != "password123"  # Хеширован
    assert user.created_at is not None

# Потом код
class UserService:
    def register(self, email: str, password: str) -> User:
        user = User(email=email, password=bcrypt.hash(password))
        db.add(user)
        db.commit()
        return user

Вывод: Тесты — это не бюрократия, это инвестиция в будущую скорость. Да, медленнее в начале, но быстрее потом.

Ошибка 4: Неправильная обработка ошибок (Silent Failures)

Контекст: В продакшене случилась ошибка БД, но код её проглотил и продолжил работать. Баг жил в production 3 дня, пока не привёл к потере данных.

# ❌ Плохо: игнорируем ошибки
try:
    save_user_to_db(user)
except Exception as e:
    pass  # Молча пропускаем

# ❌ Плохо: логируем, но не бросаем
try:
    save_user_to_db(user)
except Exception as e:
    logger.error(f"Failed to save user: {e}")
    # Продолжаем как будто ничего не произошло

# ✅ Правильно: логируем и бросаем
try:
    save_user_to_db(user)
except DatabaseError as e:
    logger.error(f"Failed to save user: {e}", exc_info=True)
    raise UserSaveError("Could not save user to database") from e

# ✅ Правильно: обрабатываем специфичные ошибки
try:
    save_user_to_db(user)
except IntegrityError:  # Email уже существует
    raise UserAlreadyExistsError(user.email)
except DatabaseError:
    raise UserSaveError("Database is unavailable")

Вывод: Записное правило: Fail Fast, Fail Loud. Ошибка должна быть видна сразу, не в глубине логов.

Ошибка 5: Игнорирование масштабируемости

Контекст: Я написал простую в коде систему рассылки, которая обрабатывала 100 писем параллельно. Когда дошли до 10,000 писем — система упала.

# ❌ Плохо: всё в памяти
def send_newsletter(user_ids: list[int]):
    for user_id in user_ids:
        user = get_user(user_id)
        send_email(user.email, "Newsletter")
        # Если user_ids содержит 10,000 — будет 10,000 одновременных операций
        # Память переполнится, БД упадёт

# ✅ Правильно: батчи + очереди
from celery import group

def send_newsletter(user_ids: list[int]):
    batch_size = 100
    
    for i in range(0, len(user_ids), batch_size):
        batch = user_ids[i:i+batch_size]
        # Отправляем батч в очередь Celery
        tasks = [send_email_task.s(uid) for uid in batch]
        group(tasks).apply_async()
        
        # Rate limiting
        time.sleep(1)  # Не перегружаем систему

# Или используем stream processing
from kafka import KafkaProducer, KafkaConsumer

# Producer: создаём события
producer = KafkaProducer(bootstrap_servers=['localhost:9092'])
for user_id in user_ids:
    producer.send('send_email', {'user_id': user_id})

# Consumer: обрабатываем батчами
consumer = KafkaConsumer('send_email', group_id='email_service')
for message in consumer:
    user_id = message['user_id']
    send_email(user_id)

Вывод: Любой код должен уметь масштабироваться. Не обязательно до экстрема, но хотя бы x10. Очереди (Celery, RabbitMQ, Kafka) — твой друг.

Как я теперь принимаю решения

  1. Пауза перед действием

    • Не спешу реализовать первую идею
    • Думаю про варианты
  2. Обсуждение с командой

    • Code review не только для кода
    • Архитектурные решения тоже обсуждаю
  3. Данные, не мнения

    # Профилирование
    import timeit
    
    approach1 = lambda: sorted(items)
    approach2 = lambda: heapq.nlargest(k, items)
    
    time1 = timeit.timeit(approach1, number=10000)
    time2 = timeit.timeit(approach2, number=10000)
    
    # Выбираем на основе данных
    
  4. Минимальная жизнеспособность (MVP)

    • Сначала простейшее решение
    • Потом итерирую на основе опыта
  5. Постмортем после ошибок

    • Разбираюсь, что пошло не так
    • Документирую вывод
    • Делюсь с командой

Самое важное

Ошибки — это нормально. Ненормально — это:

  • Повторять одну ошибку дважды
  • Скрывать ошибку от команды
  • Не учиться на своих ошибках
  • Обвинять других

Хороший разработчик — это не тот, кто не делает ошибок. Это тот, кто быстро их находит, быстро учится и не повторяет их дважды.