Как внедрить принципы SOLID в существующий проект - PullRequest
18 голосов
/ 24 апреля 2009

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

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

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

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

Ответы [ 4 ]

24 голосов
/ 24 апреля 2009

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

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

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

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

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

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

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

EDIT:

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

4 голосов
/ 24 апреля 2009

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

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

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

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

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

3 голосов
/ 24 апреля 2009

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

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

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

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

, например

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

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

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

2 голосов
/ 24 апреля 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...