Какие принципы программирования ты применяешь при разработке на Dart/Flutter?

Ответ

В разработке на Flutter я активно применяю принципы SOLID, DRY и KISS, адаптируя их под специфику фреймворка и языка Dart.

SOLID в контексте Flutter/Dart:

  • Принцип единственной ответственности (SRP): Каждый класс или виджет должен решать одну задачу. Например, я разделяю StatefulWidget на:

    • Виджет-экран, который собирает UI.
    • Отдельный класс-логики (ChangeNotifier, Bloc, Cubit), который управляет состоянием и бизнес-логикой.
    • Сервис-класс для работы с API или базой данных.
  • Принцип открытости/закрытости (OCP): Код должен быть открыт для расширения, но закрыт для изменений. Во Flutter это достигается через композицию виджетов, а не наследование. Вместо того чтобы модифицировать сложный виджет, я создаю новый, который оборачивает или комбинирует существующие.

    // Вместо модификации кнопки, создаю новый виджет-обертку.
    class IconButtonWithLabel extends StatelessWidget {
      final IconData icon;
      final String label;
      final VoidCallback onPressed;
    
      const IconButtonWithLabel({...});
    
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            IconButton(icon: Icon(icon), onPressed: onPressed),
            Text(label),
          ],
        );
      }
    }
  • Принцип подстановки Барбары Лисков (LSP): Подклассы должны быть взаимозаменяемы с родительскими классами. В Dart это означает корректное использование @override и соблюдение контрактов абстрактных классов (например, Widget или State).

  • Принцип разделения интерфейса (ISP): Вместо одного "жирного" абстракного класса с множеством методов лучше создать несколько специфичных интерфейсов (в Dart — abstract class). Например, отдельный интерфейс для локального кэша и отдельный — для сетевого клиента.

  • Принцип инверсии зависимостей (DIP): Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. На практике я использую dependency injection (через get_it, riverpod или конструкторы) и завишу от абстракций (Repository, DataSource), а не от их конкретных реализаций (ApiClient, LocalStorage).

DRY (Don't Repeat Yourself): Повторяющуюся логику (валидация форм, форматирование дат, стили виджетов) я выношу в утилитные классы, extensions или общие виджеты-компоненты.

KISS (Keep It Simple, Stupid): Я стремлюсь к простым и понятным решениям. Например, предпочитаю StatelessWidget и StatefulWidget там, где достаточно, и не усложняю архитектуру (например, не внедряю Bloc для простого экрана с одной кнопкой).

Ответ 18+ 🔞

Э, слушай, вот сидишь ты, пишешь на Flutter, и вроде всё летает, но код через месяц самому себе напоминает лапшу, которую кот сука собака навертел. А всё потому, что эти ваши принципы, SOLID там всякие, многие воспринимают как священное писание для Java-дедов, а не как реально полезные штуки. Давай разберём на пальцах, без овердохуища теории.

Ну, SOLID, ёпта. Не для галочки, а чтобы голова не болела.

Принцип единственной ответственности (SRP). Это когда у тебя виджет не пытается быть богом и царём. Он не должен и UI рисовать, и в сеть ходить, и в базу данные пихать. Это пиздопроебибна получается. Отделяй мух от котлет: пусть экран (StatefulWidget) только собирает интерфейс из кубиков, вся бизнес-логика и состояние живут в отдельной сущности — в Cubit, ChangeNotifier или где ты там привык. А за общение с внешним миром отвечает вообще отдельный сервисный класс. Каждый делает своё дело, и когда надо что-то поменять, ты не перелопачиваешь тонны кода, а идёшь в одно конкретное место. Доверия ебать ноль к виджету, который делает всё.

Принцип открытости/закрытости (OCP). Звучит заумно, а на деле — композиция, а не наследование. Не лезь ты внутрь готового сложного виджета, чтобы прикрутить ему какую-то мелкую фичу. Оберни его в другой! Создай новый виджет-обёртку, который будет использовать старый как кирпичик. Вот смотри, простой пример: нужна кнопка с иконкой и подписью.

// Не надо ковырять стандартную IconButton. Просто сделай свою.
class IconButtonWithLabel extends StatelessWidget {
  final IconData icon;
  final String label;
  final VoidCallback onPressed;

  const IconButtonWithLabel({...});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        IconButton(icon: Icon(icon), onPressed: onPressed), // Используем готовое
        Text(label), // Добавляем своё
      ],
    );
  }
}

Всё. Старый код не тронут, новая функциональность есть. Красота.

Принцип подстановки Лисков (LSP). Ну, тут без сюрпризов: если ты наследуешься от какого-то класса или реализуешь интерфейс, будь добр играть по его правилам. Переопределяй методы (@override) так, чтобы твой класс можно было впихнуть куда угодно вместо родительского, и ничего не сломалось. Если твой кастомный Widget ломает всё приложение — ты пидарас шерстяной, а не архитектор.

Принцип разделения интерфейса (ISP). Не создавай интерфейс-монстра, который заставляет класс реализовывать кучу ненужных ему методов. Лучше несколько маленьких и точных. Ну, например, abstract class для работы с кэшем и отдельный — для сетевых запросов. Тогда класс, который только кэширует, не будет вынужден реализовывать метод fetchFromCloud(). Иначе это манда с ушами получается.

Принцип инверсии зависимостей (DIP). Самый, наверное, важный для тестирования и гибкости. Высокоуровневые модули (твой экран, твоя бизнес-логика) не должны знать конкретно, откуда берутся данные: из сети, из базы или из файла. Они должны зависеть от абстракции — от интерфейса Repository. А уже реализацию этого репозитория (ApiRepository, LocalRepository) ты прокидываешь извне, через dependency injection (get_it, riverpod или просто в конструктор). Получается, чтобы подменить настоящий API на мок для тестов, тебе нужно поменять конфигурацию в одном месте, а не переписывать пол-приложения. Волнение ебать сразу спадает.

DRY (Don't Repeat Yourself). Тут всё просто, как три копейки. Видишь, что одну и ту же хуйню (формат даты, проверку email, стиль кнопки) пишешь в десяти местах? Выноси нахуй! Сделай extension для String, вынеси общие стили в отдельный файл app_theme.dart, повторяющуюся логику — в утилитный класс. Иначе потом, когда поменяется дизайн или логика, тебе придётся бегать по всему проекту и править в пятидесяти файлах. Терпения ноль ебать на такое.

KISS (Keep It Simple, Stupid). И, наконец, золотое правило. Не усложняй там, где не надо. Не тащи Bloc с кучей событий и состояний на экран, где одна кнопка, которая меняет цвет. Используй StatefulWidget и не парься. Не создавай семиуровневую абстракцию для задачи из двух действий. Самый простой работающий код — лучший код. А то некоторые так заархитектуривают, что потом сами от своего кода охуевают. Помни: твой код должен быть понятен тебе же через полгода в три часа ночи, когда всё падает.