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

Какую полезную ошибку совершал?

2.0 Middle🔥 151 комментариев
#Soft Skills и рабочие процессы

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

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

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

Ошибка как путь к глубине: «Чистая» абстракция vs прагматичный код

Одна из самых полезных ошибок в моей карьере — это ранняя и чрезмерная попытка абстрагировать все и создать «идеальную», универсальную архитектуру на Frontend. Я стремился строить системы, максимально независимые от конкретных бизнес-задач, полагая, что это путь к долгосрочной устойчивости и скорости разработки. Эта ошибка научила меня критическому балансу между абстракцией и прагматизмом.

Симптомы ошибки: абстракция ради абстракции

В начале работы над сложным SPA (Single Page Application) я создавал слои абстракций, которые часто были более сложными, чем сами бизнес-процессы.

  • Избыточные фабрики и DI (Dependency Injection): Я внедрял паттерны, подобные инверсии управления, даже для простых UI компонентов, где прямой импорт был более эффективным.
  • Генерализация вместо специализации: Попытка создать один UniversalDataFetcher, который должен был обрабатывать все возможные типы API запросов (REST, GraphQL, WebSocket), вместо нескольких простых, специализированных модулей.
  • Абстрактные интерфейсы для конкретных сущностей: Определение интерфейсов IUser, IProduct с десятками свойств, когда в текущем модуле использовалось только 3-4 поля.

Пример проблемного кода (TypeScript):

// Сложная абстракция для простой задачи
class AbstractDataService<T> {
  constructor(private adapter: IDataAdapter<T>, private validator: IValidator<T>) {}
  
  async fetch(url: string): Promise<T> {
    const raw = await this.adapter.fetchRaw(url);
    const validated = this.validator.validate(raw);
    return this.adapter.normalize(validated);
  }
}

// Для использования нужно создать кучу вспомогательных классов
const userAdapter = new UserAdapter();
const userValidator = new UserValidator();
const userService = new AbstractDataService<User>(userAdapter, userValidator);
const user = await userService.fetch('/api/user/1');

// В то время как прямая реализация была в 10 строк:
async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/user/${id}`);
  const data = await response.json();
  // Простая, локальная валидация
  if (!data.id || !data.name) throw new Error('Invalid user data');
  return { id: data.id, name: data.name };
}

Чем это было вредно?

  1. Снижение скорости разработки: Новому разработчику требовалось изучать не бизнес-логику, а мою абстрактную систему. Простая задача занимала вдвое больше времени.
  2. Рост сложности (Cognitive Load): Код становился «умным», но не понятным. Каждая связь требовала просмотра 3-4 файлов.
  3. Ошибка предвидения: Я абстрагировал возможности, которые никогда не требовались в проекте. Продукт менялся, и многие мои «универсальные» решения оказывались негодными для новых реальных требований.
  4. Тестирование превращалось в ритуал: Чтобы протестировать один компонент, нужно было мокировать целую цепочку абстрактных зависимостей.

Корень ошибки и переосмысление

Я действовал под влиянием книг и статей о «чистой архитектуре» в backend, не учитывая контекст Frontend:

  • Frontend часто имеет императивную, а не декларативную логику: Многие задачи — это «получи данные -> отобрази здесь -> обработай клик».
  • Бизнес-правила на Frontend менее стабильны: Они меняются с каждым обновлением дизайна или новым фичей.
  • Критическая важность DX (Developer Experience): Скорость и ясность для команды часто важнее теоретической гибкости системы.

Правильный подход, который я выработал

Я начал применять принцип «Абстрагируй, когда болишь, а не заранее» (Abstract when you hurt):

  1. Сначала конкретная реализация: Пишу код, решающий непосредственную задачу, максимально прямо и ясно.
  2. Рефакторинг при повторении: Если я вижу, что аналогичную логику нужно использовать в третьем месте — это сигнал к абстракции. Но не раньше.
  3. Абстракция должна быть ближе к доменной области: Вместо UniversalFetcher создаю ProductCatalogAPI и UserProfileAPI. Они могут иметь схожие части, которые потом выделяю.
  4. Использование языковых и фреймворковых средств: Часто вместо своего DI-контейнера достаточно React Context или композиции функций. Вместо фабрики — фабричная функция (factory function).

Пример здорового, постепенного подхода:

// Шаг 1: Конкретная функция для задачи
async function getProductList(categoryId: string): Promise<Product[]> {
  const res = await fetch(`/api/categories/${categoryId}/products`);
  return res.json();
}

// Шаг 2: Появилась похожая потребность (получение продуктов по фильтру)
async function getFilteredProducts(filters: FilterParams): Promise<Product[]> {
  const query = new URLSearchParams(filters);
  const res = await fetch(`/api/products/search?${query}`);
  return res.json();
}

// Шаг 3: Рефакторинг и абстракция ОБЩЕЙ части (теперь она оправдана!)
// Выделяем общую логику подготовки и выполнения запроса
class ProductApiClient {
  private async call(endpoint: string, params?: Record<string, string>): Promise<Product[]> {
    const url = params ? `${endpoint}?${new URLSearchParams(params)}` : endpoint;
    const res = await fetch(url);
    // Здесь может быть общая обработка ошибок, логирование
    if (!res.ok) throw new Error(`Product API error: ${res.status}`);
    return res.json();
  }

  // Конкретные, доменные методы остаются простыми
  getByCategory(categoryId: string) {
    return this.call(`/api/categories/${categoryId}/products`);
  }
  
  getByFilters(filters: FilterParams) {
    return this.call(`/api/products/search`, filters);
  }
}

Вывод и урок

Эта ошибка научила меня, что сильная архитектура — это не та, что покрывает все возможные случаи, а та, что оптимально решает текущие задачи и позволяет адаптироваться к будущим без избыточных затрат. Сейчас я оцениваю необходимость абстракции по стоимости ее отсутствия: если дублирование кода или его сложность уже вызывают проблемы — абстрагирую. Если нет — пишу простой, даже «немного повторяющийся» код. Этот прагматичный подход делает систему более живой, понятной для всей команды и, как ни парадоксально, часто более готовой к реальным, а не гипотетическим изменениям.