Как внедрить принципы SOLID в существующий проект
Я прошу прощения за субъективность этого вопроса, но я немного застрял, и я был бы признателен за некоторые рекомендации и советы от всех, кто имел дело с этой проблемой ранее:
У меня есть (что стало) очень большой проект API RESTful, написанный на C# 2.0, и некоторые из моих классов стали чудовищными. Мой основной класс API является примером этого - с несколькими десятками членов и методов (вероятно, приближающихся к сотням). Как вы можете себе представить, это становится маленьким кошмаром не только для того, чтобы поддерживать этот код, но даже просто для навигации по нему стало рутиной.
Я достаточно новичок в принципах SOLID, и я большой поклонник шаблонов проектирования (но я все еще на той стадии, когда я могу их реализовать, но не достаточно, чтобы знать, когда их использовать - в ситуациях, когда это не так очевидно),
Мне нужно разбить мои классы по размеру, но я не знаю, как лучше это делать. Могут ли мои коллеги по Stackru предложить способы использования существующих монолитов кода и их уменьшения в размере?
5 ответов
Принцип единой ответственности - класс должен иметь только одну причину для изменения. Если у вас есть монолитный класс, то у него, вероятно, есть несколько причин для изменения. Просто определите свою единственную причину, чтобы измениться, и будьте настолько детализированы, насколько разумны Я бы предложил начать "большой". Переведите одну треть кода в другой класс. Как только вы это получите, начните все сначала с вашего нового класса. Переход от одного класса к 20 слишком сложен.
Открытый / закрытый принцип - класс должен быть открыт для расширения, но закрыт для изменения. Там, где это целесообразно, отметьте своих членов и методы как виртуальные или абстрактные. Каждый элемент должен быть относительно небольшим по своей природе и давать вам базовую функциональность или определение поведения. Однако, если вам потребуется изменить функциональность позже, вы сможете добавлять код, а не изменять код для введения новых / других функций.
Принцип замещения Лискова - класс должен быть замещаемым для своего базового класса. Ключевым моментом здесь, на мой взгляд, является правильное наследование. Если у вас огромный оператор case или две страницы операторов if, которые проверяют производный тип объекта, то вы нарушаете этот принцип и вам необходимо переосмыслить свой подход.
Принцип разделения интерфейса - На мой взгляд, этот принцип очень похож на принцип единой ответственности. Это относится только к высокоуровневому (или зрелому) классу / интерфейсу. Один из способов использовать этот принцип в большом классе - заставить ваш класс реализовать пустой интерфейс. Затем измените все типы, которые используют ваш класс, чтобы быть типом интерфейса. Это сломает ваш код. Тем не менее, он укажет, как именно вы потребляете свой класс. Если у вас есть три экземпляра, каждый из которых использует свое собственное подмножество методов и свойств, то теперь вы знаете, что вам нужно три разных интерфейса. Каждый интерфейс представляет собой совокупный набор функций и одну причину для изменения.
Принцип обращения зависимостей - Аллегория родителей / детей позволила мне понять это. Подумайте о родительском классе. Он определяет поведение, но не касается грязных деталей. Это надежно. Дочерний класс, однако, все о деталях, и на него нельзя положиться, потому что он часто меняется. Вы всегда хотите зависеть от родителей, ответственных классов, а не наоборот. Если у вас есть родительский класс, зависящий от дочернего класса, вы получите неожиданное поведение при изменении чего-либо. На мой взгляд, это то же самое мышление SOA. Контракт на обслуживание определяет входные, выходные данные и поведение без каких-либо подробностей.
Конечно, мое мнение и понимание могут быть неполными или неправильными. Я бы предложил учиться у людей, которые освоили эти принципы, таких как дядя Боб. Хорошей отправной точкой для меня была его книга " Гибкие принципы, шаблоны и практики на C#". Другим хорошим ресурсом был дядя Боб на Hanselminutes.
Конечно, как указывали Джоэл и Джефф, это принципы, а не правила. Они должны быть инструментами, которые помогут вам вести, а не закон страны.
РЕДАКТИРОВАТЬ:
Я только нашел эти ТВЕРДЫЕ скринкасты, которые выглядят действительно интересными. Каждый длится примерно 10-15 минут.
Есть классическая книга Мартина Фаулера "Рефакторинг: улучшение дизайна существующего кода".
Там он предоставляет набор методов проектирования и пример решений, чтобы сделать вашу существующую кодовую базу более управляемой и поддерживаемой (и это то, что представляют собой принципы SOLID). Хотя в рефакторинге есть несколько стандартных процедур, это очень индивидуальный процесс, и одно решение не может быть применено ко всем проектам.
Модульное тестирование является одним из краеугольных камней успеха этого процесса. Вам действительно необходимо покрыть существующую кодовую базу достаточным покрытием кода, чтобы вы могли быть уверены, что ничего не сломаете при его изменении. На самом деле использование современных фреймворков для модульного тестирования с поддержкой насмешек приведет вас к лучшему дизайну.
Есть инструменты, такие как ReSharper (мой любимый) и CodeRush, чтобы помочь с утомительными изменениями кода. Но это, как правило, тривиальные механические вещи, принятие проектных решений является гораздо более сложным процессом, и инструментальной поддержки не так уж много. Использование диаграмм классов и UML помогает. Это то, с чего я бы начал, на самом деле. Постарайтесь понять, что уже есть, и привнести в него некоторую структуру. Затем вы можете принимать решения о декомпозиции и отношениях между различными компонентами и соответствующим образом изменять свой код.
Надеюсь, это поможет и счастливого рефакторинга!
Это будет трудоемкий процесс. Вам необходимо прочитать код и определить части, которые не соответствуют принципам SOLID, и преобразовать их в новые классы. Использование надстройки VS, такой как Resharper ( http://www.jetbrains.com/), поможет в процессе рефакторинга.
В идеале у вас должно быть хорошее покрытие автоматических модульных тестов, чтобы вы могли убедиться, что ваши изменения не создают проблем с кодом.
Дополнительная информация
В основном классе API вам нужно идентифицировать методы, которые связаны друг с другом, и создать класс, который более конкретно представляет, какие действия выполняет метод.
например
Допустим, у меня был класс Address с отдельными переменными, содержащими номер улицы, название и т. Д. Этот класс отвечает за вставку, обновление, удаление и т. Д. Если бы мне также нужно было отформатировать адрес определенным образом для почтового адреса, я мог бы иметь метод с именем GetFormattedPostalAddress(), который возвращает отформатированный адрес.
В качестве альтернативы я мог бы преобразовать этот метод в класс AddressFormatter, который принимает в нем конструктор Address и имеет свойство Get с именем PostalAddress, которое возвращает отформатированный адрес.
Идея состоит в том, чтобы разделить разные обязанности на отдельные классы.
То, что я сделал, когда мне представили подобные вещи (и я с готовностью признаю, что раньше я не использовал принципы SOLID, но из того, что я мало знаю о них, они звучат хорошо), это взгляд на существующую кодовую базу из точка зрения подключения. По сути, взглянув на систему, вы сможете найти некоторое подмножество функций, которые внутренне сильно связаны (много частых взаимодействий), но внешне слабо связаны (мало нечастых взаимодействий). Обычно в любой большой кодовой базе есть несколько таких частей; они кандидаты на удаление. По сути, после того, как вы определили своих кандидатов, вы должны перечислить точки, в которых они внешне связаны с системой в целом. Это должно дать вам хорошее представление об уровне взаимозависимости. Обычно в этом есть доля взаимозависимости. Оценить подмножества и их точки подключения для рефакторинга; часто (но не всегда) в конечном итоге возникает пара четких структурных рефакторингов, которые могут увеличить разделение. Принимая во внимание эти рефакторинги, используйте существующие связи для определения минимального интерфейса, необходимого для работы подсистемы с остальной частью системы. Ищите общие черты в этих интерфейсах (часто вы найдете больше, чем ожидаете!). И наконец, внедрите те изменения, которые вы определили.
Процесс звучит ужасно, но на практике он довольно простой. Имейте в виду, что это не путь к созданию полностью идеально спроектированной системы (для этого вам придется начинать с нуля), но это, безусловно, уменьшит сложность системы в целом и повысит понятность кода.
SOLID является частью OOD - объектно-ориентированного дизайна
Принцип единой ответственности - SRP - представил дядя Боб. Метод, класс, модуль отвечают только за выполнение одной задачи (одной задачи)
Принцип открытости / закрытости - OCP - представил Бертран Мейер. Метод, класс, модуль открыты для расширения и закрыты для модификации. Используйте силу наследования, абстракции, полиморфизма
Принцип замены Лискова - LSP - представлен Барбарой Лисков и Жаннетт Винг. [О программе] - Подтип может заменить suoertype без побочных эффектов.
Принцип разделения интерфейса - ISP - представил дядя Боб. Ваш интерфейс должен быть как можно меньше
Принцип инверсии зависимостей - DIP - правило зависимости - введен дядей Бобом. Внутренний класс, слой не должен зависеть от внешнего класса, слоя. Например, когда у вас есть
aggregation
<sup>[О]</sup> зависимости, вам лучше использовать некоторую абстракцию
Внедрение зависимости
Поиск услуг
Заводской метод
IoC-контейнер фреймворк, который его реализует