Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

EDIT:

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

например.

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

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

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

Ответ 4

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

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