Как реализовать принципы SOLID в существующем проекте

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

У меня (что стало) очень большой проект RESTful API, написанный на C # 2.0, и некоторые из моих классов стали чудовищными. Мой основной класс API является примером этого - с несколькими десятками членов и методов (вероятно, приближающихся к сотням). Как вы понимаете, это становится маленьким кошмаром, ведь не только поддерживать этот код, но даже просто перемещаться по коду превратилось в рутинную работу.

Я достаточно новичок в принципах SOLID, и я большой поклонник шаблонов проектирования (но я все еще нахожусь на той стадии, когда я могу реализовать их, но недостаточно, чтобы знать, когда использовать их - в ситуациях, когда это не так очевидно).

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


person Ash    schedule 23.04.2009    source источник


Ответы (5)


Принцип единой ответственности. У класса должна быть только одна причина для изменения. Если у вас монолитный класс, то у него, вероятно, есть несколько причин для изменения. Просто определите единственную причину для изменения и будьте настолько подробны, насколько разумны. Я бы посоветовал начать с большого. Выполните рефакторинг одной трети кода в другой класс. Как только вы его получите, начните заново со своим новым классом. Перейти сразу с одного класса до 20 - это слишком сложно.

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

Принцип замещения Лискова. Класс должен быть заменяемым для своего базового класса. Ключевым моментом здесь, на мой взгляд, является правильное наследование. Если у вас есть большой оператор case или две страницы операторов if, которые проверяют производный тип объекта, значит, вы нарушаете этот принцип и вам необходимо переосмыслить свой подход.

Принцип разделения интерфейса. На мой взгляд, этот принцип очень похож на принцип единой ответственности. Это просто применимо конкретно к высокоуровневому (или зрелому) классу / интерфейсу. Один из способов использования этого принципа в большом классе - заставить ваш класс реализовать пустой интерфейс. Затем измените все типы, которые используют ваш класс, на тип интерфейса. Это нарушит ваш код. Однако он точно укажет, как вы потребляете свой класс. Если у вас есть три экземпляра, каждый из которых использует собственное подмножество методов и свойств, то теперь вы знаете, что вам нужны три разных интерфейса. Каждый интерфейс представляет собой совокупный набор функций и одну причину для изменения.

Принцип инверсии зависимостей. Аллегория родитель / ребенок заставила меня понять это. Подумайте о родительском классе. Он определяет поведение, но не касается грязных деталей. Это надежно. Однако дочерний класс - это все о деталях, и на него нельзя полагаться, потому что он часто меняется. Вы всегда хотите зависеть от родительских, ответственных классов, а не наоборот. Если у вас есть родительский класс, зависящий от дочернего класса, вы получите неожиданное поведение, когда что-то измените. На мой взгляд, это то же самое мышление, что и SOA. Контракт на обслуживание определяет входы, выходы и поведение без каких-либо подробностей.

Конечно, мои мнения и понимание могут быть неполными или ошибочными. Я бы посоветовал поучиться у людей, усвоивших эти принципы, например у дяди Боба. Хорошей отправной точкой для меня была его книга Agile Principles, Patterns и практики в C #. Еще один хороший ресурс - Дядя Боб на Hanselminutes.

Конечно, как отметили Джоэл и Джефф, это принципы, а не правила. Они должны быть инструментами, которые помогут вам направлять вас, а не законом страны.

РЕДАКТИРОВАТЬ:

Я только что нашел эти SOLID скринкасты, которые выглядят действительно интересно. Каждый длится примерно 10-15 минут.

person Aaron Daniels    schedule 23.04.2009

Есть классическая книга Мартина Фаулера - Рефакторинг: улучшение дизайна Существующий код.

Там он предоставляет набор методов проектирования и примеры решений, чтобы сделать вашу существующую кодовую базу более управляемой и удобной в обслуживании (и в этом суть принципалов SOLID). Несмотря на то, что существует несколько стандартных процедур рефакторинга, это очень нестандартный процесс, и нельзя применить одно решение ко всем проектам.

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

Существуют такие инструменты, как ReSharper (мой любимый) и CodeRush, которые помогают с утомительными изменениями кода. Но обычно это тривиальные механические вещи, принятие проектных решений - гораздо более сложный процесс, и здесь не так много инструментов поддержки. Помогает использование диаграмм классов и UML. Собственно, с этого я и начал бы. Постарайтесь разобраться в том, что уже есть, и внести в это некоторую структуру. Затем оттуда вы можете принимать решения о декомпозиции и отношениях между различными компонентами и соответствующим образом изменять свой код.

Надеюсь, это поможет и удачного рефакторинга!

person bychkov    schedule 24.04.2009

Это займет много времени. Вам необходимо прочитать код и определить части, которые не соответствуют принципам SOLID, и провести рефакторинг в новые классы. Использование надстройки VS, например Resharper (http://www.jetbrains.com), поможет с рефакторингом. процесс.

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

Дополнительная информация

В основном классе API вам необходимо определить методы, которые связаны друг с другом, и создать класс, который более конкретно представляет, какие действия выполняет метод.

e.g.

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

В качестве альтернативы я мог бы преобразовать этот метод в класс AddressFormatter, который принимает в своем конструкторе Address и имеет свойство Get с именем PostalAddress, которое возвращает отформатированный адрес.

Идея состоит в том, чтобы разделить разные обязанности на отдельные классы.

person Anthony    schedule 23.04.2009
comment
Я большой поклонник Resharper, использую его долгое время, и у меня есть некоторое покрытие для модульных тестов, но его недостаточно. Есть ли что-то более конкретное, о чем следует подумать при проведении рефакторинга? - person Ash; 24.04.2009

Что я сделал, когда представил такой тип вещей (и я с готовностью признаю, что раньше я не использовал принципы SOLID, но из того немногого, что я о них знаю, они звучат хорошо), я взглянул на существующую кодовую базу из точка зрения связности. По сути, глядя на систему, вы должны быть в состоянии найти некоторое подмножество функций, которые внутренне сильно связаны (много частых взаимодействий), но внешне слабо связаны (мало редких взаимодействий). Обычно в любой большой кодовой базе есть несколько таких частей; они кандидаты на удаление. По сути, после того, как вы определили своих кандидатов, вы должны перечислить точки, в которых они внешне связаны с системой в целом. Это должно дать вам хорошее представление об уровне вовлеченной взаимозависимости. Обычно здесь присутствует изрядная доля взаимозависимости. Оценить подмножества и их точки подключения для рефакторинга; часто (но не всегда) возникает пара четких структурных рефакторингов, которые могут увеличить разделение. С учетом этих рефакторингов используйте существующие связи для определения минимального интерфейса, необходимого для того, чтобы подсистема могла работать с остальной системой. Ищите общие черты в этих интерфейсах (часто вы найдете больше, чем ожидаете!). И, наконец, внесите эти изменения, которые вы определили.

Звучит ужасно, но на практике все довольно просто. Имейте в виду, что это не путь к созданию полностью идеально спроектированной системы (для этого вам нужно будет начать с нуля), но он, безусловно, снизит сложность системы в целом и повысит понятность кода.

person Paul Sonier    schedule 23.04.2009

ТВЕРДЫЙ

SOLID является частью OOD - объектно-ориентированного дизайна

Принцип единой ответственности - SRP - представил дядя Боб. Метод, класс, модуль отвечают только за выполнение одной задачи (одной задачи).

Принцип открытости / закрытости - OCP - представил Бертран Мейер. Метод, класс, модуль открыты для расширения и закрыты для модификации. Используйте силу наследования, абстракции, полиморфизма

[Принцип замены Лискова] - LSP, представленный Барбарой Лисков и Жаннетт Уинг. Подтип может заменить супертип без побочных эффектов

Принцип разделения интерфейса - ISP - представил дядя Боб. Ваш интерфейс должен быть как можно меньше

[Принцип инверсии зависимостей (DIP)] - DIP - представил дядя Боб. Внутренний класс, слой не должен зависеть от внешнего класса, слоя. Например, если у вас есть зависимость aggregation [About], вам лучше использовать некоторую абстракцию / интерфейсы

[DIP против DI против IoC]

person yoAlex5    schedule 07.02.2021