Критерии простоты

в 23:54, , рубрики: .net, C#, ооп, Программирование, Проектирование и рефакторинг, простота

Львиная доля программистов с чистой совестью заявит, что предпочитает решать задачи просто, руководствуясь прежде всего здравым смыслом. Вот только это "просто" у каждого свое и как правило отличное от других. После одного долгого и неконструтивного спора с коллегой я решил изложить, что именно считаю простым сам и почему. Это не привело к немедленному согласию, но позволило понять логику друг друга и свести к минимуму лишние дискуссии.

Первый критерий

Особенности мозга человека таковы, что он плохо хранит и отличает более 7-9 элементов в одном списке при оптимальном их количестве 1-3.

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

Второй критерий

Самое важное — в первых двух строках любого класса.

  1. Имя, параметры типа, реализованные интерфейсы.
  2. Параметры конструктора.

В результате мы знаем о классе все, что необходимо для его использования (имя, параметры, входы, выходы), даже не заглядывая в оставшийся код.
Разумеется, это работает при условии ограничений:

  1. Избегайте наследования реализаций
  2. Избегайте внедрения зависимостей иначе, чем через параметры конструктора.

И этот критерий может быть реализован средствами статического анализа.

Третий критерий

Один тип — одна задача. Контракты — интерфейсам, реализации — классам, алгоритмы — методам!
Мультитулы хороши только если ничего другого под рукой вообще нет. Благодаря первым двум критериям писать такие типы несложно. Да, это принцип единственной ответственностиразделения интерфейсов до кучи)
А вот здесь статический анализ придется доверить человеку.

Четвертый критерий

Ограничения — такая же легальная часть контракта, как и сигнатуры методов.
И следовательно...

  1. Комментарии в контракте — почти всегда полезны, комментарии в коде реализации почти всегда вредны.
  2. DTO — полноценные объекты, чья примитивность поведения вознаграждается автоматической сериализацией.
  3. Неизменяемые объекты — вознаграждают удобством валидации, отсутствием гонок и лишнего копирования.
  4. Статический метод — полноценный класс без состояния, все плюшки от неизменяемых объектов плюс меньший трафик памяти.
  5. Анонимные делегаты и лямбды — заменяют связку интерфейс-класс с одним методом, позволяя выкинуть два лишних типа и продемонстрировать сигнатуру вызова прямо в коде.
  6. Добавьте остальные "ненастоящие объекты" по вашему вкусу.

Статический анализ тут может помочь лишь рекомендациями по использованию более простых вариантов типов, если их увидит.

Пятый критерий

Максимальное удобство для повторного использования.
Применяйте наследование интерфейсов, избегайте наследования реализаций и получите сразу три источника повторного использования кода

  1. Повторное использование контрактов — очень полезно, даже если ни одна готовая реализация не подошла.
  2. Повторное использование реализаций — везде где принимается контракт можно задействовать уже написанную реализацию.
  3. Повторное использование клиентского кода — весь код, работающий с интерфейсом (включая тесты), можно переиспользовать с каждой новой реализацией.

Да, это принцип подстановки Лисков в максимально простой и практичной форме.
Статический анализатор может сообщать об использовании нежелательного наследования реализации.
Также не стоит прятать типы или методы "на всякий случай". Публичным должно быть все, кроме того, что необходимо спрятать как детали реализации.

Шестой критерий

Ваши типы должны быть более простыми для композиции, декорирования, адаптации, организации фасада и т.п., чем для изменения. При соблюдении предыдущих критериев этого добиться несложно.
Это позволит как можно больше изменений свести к добавлению нового кода вместо переписывания текущего, что уменьшит регрессию и необходимость править еще и старые тесты.
Идеальный пример — методы расширения интерфейсов, добавляющие функциональность с одной стороны и никак не меняющие исходные контракт и реализацию с другой.
Да, это принцип открытости-закрытости.
Нет, статический анализ тут мало чем поможет

Седьмой критерий

Типы параметров конструктора должны как можно больше говорить о том, как надо использовать их значение.
Например:

  1. IService — служба, необходимая для реализации
  2. Lazy<IService> — служба, которой может и не быть при старте, для начала использования надо прочитать свойство Value, при первом обращении возможна пауза.
  3. Task<IResource> — ресурс, который получается асинхронно
  4. Func<IArgument, IResource> — параметризованная фабрика ресурсов
  5. Usable<IResource> — ресурс, который нужен до определенного момента, об окончании использования можно сообщить, вызвав метод Dispose.

Увы, статический анализ мало чем тут поможет.

Восьмой критерий

Маленькие типы лучше больших, так как даже плохо спроектированные или реализованные маленькие типы намного проще исправить, переписать или удалить по сравнению с большими.
Большое количество маленьких типов не является само по себе фатальной проблемой, так как типы — это не список, а граф. В голове одновременно достаточно держать связи текущего типа, а их количество ограничивается первым критерием.

Девятый критерий

Зависимости типов между собой необходимо ограничивать

  1. Циклов в графе типов надо избегать
  2. Также надо избегать прямых ссылок реализаций друг на друга.
  3. Крайне желательно использовать паттерны разбиения типов на слои, добавляя ограничение на зависимости между типами разных слоев друг на друга.

Целью является максимальное упрощение графа зависимостей типов. Это помогает как при навигации по коду, так и при определении возможного аффекта от тех или иных изменений.
Да, в этот критерий входит принцип инверсии зависимостей.
Да, тут может помочь статический анализ.

Десятый критерий

То же, что и девятый критерий, но для сборок. Сильно упрощает жизнь и ускоряет сборку, особенно если сразу проектировать с его учетом. Для сборок с контрактами проще поддерживать обратную совместимость (опубликованные контракты не меняются). Сборки же с реализациями можно заменять целиком — они все равно друг от друга напрямую не зависят.
Средствами статического анализа можно воспретить одним сборкам ссылаться на другие.

Одиннадцатый критерий

Все предыдущие критерии могут быть нарушены при необходимости оптимизации. Но одно, на мой взгляд работает всегда:
Проще оптимизировать корректный код, чем корректировать оптимизированный.

Итоги

Письменное изложение собственных представлений многое прояснило для меня самого.
Хотелось бы увидеть ваши критерии простоты в комментариях.
Упоминания статического анализа означают возможность реализации, а не ее наличие на текущий момент.
Дополнения и критика традиционно приветствуются.

Автор: Bonart

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js