Я работаю в команде, лидер которой является ярым сторонником принципов разработки SOLID. Однако ему не хватает большого опыта в получении сложного программного обеспечения на открытом воздухе.
У нас возникла ситуация, когда он применил SRP к тому, что уже представляло собой довольно сложную кодовую базу, которая теперь стала очень сильно фрагментированной, трудной для понимания и отладки.
Теперь у нас есть проблема не только с фрагментацией кода, но и с инкапсуляцией, поскольку методы внутри класса, которые могли быть частными или защищенными, были оценены как «причина для изменения» и были извлечены для открытых или внутренних классов и интерфейсов, которые не соответствует целям инкапсуляции приложения.
У нас есть конструкторы классов, которые принимают более 20 параметров интерфейса, поэтому наша регистрация и разрешение IoC сами по себе становятся монстрами.
Я хочу знать, есть ли какой-либо подход «рефакторинг от SRP», который мы могли бы использовать, чтобы помочь решить некоторые из этих проблем. Я читал, что это не нарушает SOLID, если я создаю несколько пустых классов общего назначения, которые «обертывают» ряд тесно связанных классов, чтобы обеспечить единую точку доступа к сумме их функциональных возможностей (т.е. имитировать менее реализация класса чрезмерно SRP).
Кроме того, я не могу придумать решение, которое позволит нам прагматично продолжать наши усилия по развитию, сохраняя при этом всех счастливыми.
Какие-либо предложения ?
ISomething
). ИМХО, с этими подходами гораздо легче справиться, чем с внедрением зависимостей, и они дают более читаемый кодОтветы:
Если у вашего класса в конструкторе 20 параметров, это не похоже на то, что ваша команда знает, что такое SRP. Если у вас есть класс, который делает только одно, как у него 20 зависимостей? Это все равно, что отправиться на рыбалку и взять с собой удочку, ящик для снастей, стегальные принадлежности, шар для боулинга, нунчаки, огнемет и т. Д. Если вам все это нужно, чтобы отправиться на рыбалку, вы не просто отправляетесь на рыбалку.
Тем не менее, SRP, как и большинство принципов там, может быть применен чрезмерно. Если вы создадите новый класс для приращения целых чисел, то да, это может быть единственной обязанностью, но давай. Это нелепо. Мы склонны забывать, что такие вещи, как принципы SOLID, существуют для определенной цели. ТВЕРДЫЕ - это средство для достижения цели, а не самоцель. Конец ремонтопригодности . Если вы собираетесь получить это гранулированное с принципом единой ответственности, это показатель того, что рвение к SOLID ослепило команду к цели SOLID.
Итак, я думаю, что я говорю ... SRP не ваша проблема. Это либо неправильное понимание SRP, либо невероятно детальное его применение. Постарайся заставить свою команду держать главное главное. И главное - ремонтопригодность.
РЕДАКТИРОВАТЬ
Заставьте людей разрабатывать модули таким образом, чтобы это облегчало их использование. Думайте о каждом классе как о мини-API. Сначала подумайте: «Как бы я хотел использовать этот класс», а затем реализовать его. Не просто думайте: «Что должен делать этот класс?» SRP действительно имеет тенденцию усложнять использование классов, если вы не уделяете много внимания удобству использования.
РЕДАКТИРОВАТЬ 2
Если вы ищете советы по рефакторингу, вы можете начать делать то, что вы предлагали - создавать более грубые классы, чтобы обернуть несколько других. Убедитесь, что более грубый класс все еще придерживается SRP , но на более высоком уровне. Тогда у вас есть две альтернативы:
Когда вы закончите рефакторинг (но до фиксации в репозитории), просмотрите свою работу и спросите себя, действительно ли ваш рефакторинг улучшил удобство обслуживания и простоту использования.
источник
Customer
класс, и иметь более понятный код. Смотрите примеры здесь: codemonkeyism.com/...Я думаю, что в Рефакторинге Мартина Фаулера я однажды прочитал контр-правило для SRP, определяя, куда оно заходит слишком далеко. Есть второй вопрос, такой же важный, как «у каждого класса есть только одна причина для изменения?» и то, что "каждое изменение затрагивает только один класс?"
Если ответ на первый вопрос в каждом случае «да», а второй вопрос «даже не близко», то вам нужно еще раз взглянуть на то, как вы реализуете SRP.
Например, если добавление одного поля в таблицу означает, что вы должны изменить DTO и класс валидатора, а также класс персистентности и объект модели представления и т. Д., То вы создали проблему. Возможно, вам следует переосмыслить, как вы реализовали SRP.
Возможно, вы сказали, что добавление поля является причиной изменения объекта Customer, но изменение уровня постоянства (скажем, из файла XML в базу данных) - еще одна причина для изменения объекта Customer. Таким образом, вы решили создать объект CustomerPersistence. Но если вы делаете это так, что добавление поля STILL требует изменения объекта CustomerPersisitence, тогда какой смысл? У вас все еще есть объект с двумя причинами для изменения - он просто больше не Клиент.
Однако, если вы введете ORM, вполне возможно, что вы сможете заставить классы работать так, что если вы добавите поле в DTO, оно автоматически изменит SQL, используемый для чтения этих данных. Тогда у вас есть веская причина разделить две проблемы.
Таким образом, вот что я склонен делать: если есть грубый баланс между количеством раз, когда я говорю «нет, есть более чем одна причина изменить этот объект» и количеством раз, когда я говорю «нет, это изменение будет затрагивать более одного объекта ", тогда я думаю, что у меня есть правильный баланс между SRP и фрагментацией. Но если оба они все еще высоки, я начинаю задумываться, а можно ли по-другому разделить проблемы?
источник
То, что система сложна, не означает, что вы должны усложнять ее . Если у вас есть класс, который имеет слишком много зависимостей (или коллабораторов), как это:
... тогда это стало слишком сложно, и ты не следишь за SRP , не так ли? Могу поспорить, если бы вы записали, что
MyAwesomeClass
на карточке CRC она не помещается на карточку, или вы должны писать очень мелкими неразборчивыми буквами.Здесь у вас есть то, что ваши ребята следовали только принципу разделения интерфейса и, возможно, довели его до крайности, но это совсем другая история. Можно утверждать, что зависимости являются объектами домена (что происходит), однако наличие класса, который одновременно обрабатывает 20 объектов домена, слишком растягивает его.
TDD даст вам хороший показатель того, сколько делает класс. Прямо поставил; если у тестового метода есть код установки, на запись которого уходит вечность (даже если вы реорганизуете тесты), то у вас,
MyAwesomeClass
вероятно, слишком много дел.Итак, как вы решаете эту головоломку? Вы перемещаете обязанности в другие классы. Есть несколько шагов, которые вы можете предпринять для класса с такой проблемой:
Абстрактный пример рефакторинга ответственности
Пусть
C
будет класс , который имеет несколько зависимостейD1
,D2
,D3
,D4
что вам нужно реорганизовать , чтобы использовать меньше. Когда мы определяем, какие методыC
вызывают зависимости, мы можем составить простой список:D1
-performA(D2)
,performB()
D2
-performD(D1)
D3
-performE()
D4
-performF(D3)
Глядя на список, мы видим это
D1
иD2
связаны друг с другом, так как класс так или иначе нуждается в них. Мы также можем видеть, чтоD4
необходимоD3
. Итак, у нас есть две группировки:Group 1
-D1
<->D2
Group 2
-D4
->D3
Группировка является показателем того, что у класса теперь есть две обязанности.
Group 1
- Один для обработки вызова двух объектов, которые нужны друг другу. Возможно, вы можете позволить своему классуC
устранить необходимость обработки обеих зависимостей и оставить одну из них для обработки этих вызовов. В этой группе очевидно, чтоD1
может иметь ссылку наD2
.Group 2
- Другая ответственность нуждается в одном объекте, чтобы вызвать другой. Не можетеD4
справитьсяD3
вместо вашего класса? Тогда мы, вероятно, можем исключитьD3
из классаC
, разрешивD4
вместо этого делать вызовы.Не принимайте мой ответ, изложенный в камне, так как пример очень абстрактный и содержит много предположений. Я почти уверен, что есть и другие способы реорганизовать это, но, по крайней мере, эти шаги могут помочь вам создать какой-то процесс перемещения обязанностей вместо разделения классов.
Редактировать:
Среди комментариев @ Эммад Карем говорит:
Это правда, что объекты DAO имеют тенденцию иметь много параметров, которые вы должны установить в своем конструкторе, и параметры, как правило, являются простыми типами, такими как string. Однако в примере с
Customer
классом вы можете сгруппировать его свойства внутри других классов, чтобы упростить задачу. Например, наличиеAddress
класса с улицами иZipcode
класса, который содержит почтовый индекс и будет обрабатывать бизнес-логику, такую как проверка данных:Эта тема обсуждается далее в сообщении блога «Никогда, никогда, никогда не используйте String в Java (или, по крайней мере, часто)» . В качестве альтернативы использования конструкторов или статических методов для облегчения создания подобъектов вы можете использовать шаблон построителя флюидов .
источник
Я согласен со всеми ответами о SRP и о том, как можно зайти слишком далеко. В своем посте вы упоминаете, что из-за «чрезмерного рефакторинга» для соблюдения SRP вы обнаружили нарушение инкапсуляции или изменение. Единственное, что сработало для меня, - это всегда придерживаться основ и делать именно то, что требуется для достижения цели.
При работе с системами Legacy «энтузиазм» исправления всего, что делает его лучше, обычно достаточно велик в Team Leads, особенно тех, кто новичок в этой роли. SOLID, просто нет SRP - это просто S. Убедитесь, что если вы следуете SOLID, вы также не забудете OLID.
Я сейчас работаю над системой Legacy, и в начале мы начали идти по тому же пути. Для нас сработало коллективное решение команды сделать лучшее из обоих миров - SOLID и KISS (Keep It Simple Stupid). Мы коллективно обсудили основные изменения в структуре кода и применили здравый смысл при применении различных принципов разработки. Они великолепны в качестве руководства, а не «Законов развития ПО». Команда - это не только командный лидер, а все разработчики в команде. То, что всегда работало для меня, - это собрать всех в комнате и выработать общий набор правил, которым вся команда соглашается следовать.
Что касается того, как исправить текущую ситуацию, если вы используете VCS и не добавили слишком много новых функций в свое приложение, вы всегда можете вернуться к версии кода, которую вся команда считает понятной, читаемой и поддерживаемой. Да! Я прошу вас отказаться от работы и начать с нуля. Это лучше, чем пытаться «починить» что-то, что было сломано, и вернуть его к тому, что уже существовало.
источник
Ответ - удобство обслуживания и ясность кода превыше всего. Для меня это значит писать меньше кода , а не больше. Меньше абстракций, меньше интерфейсов, меньше опций, меньше параметров.
Всякий раз, когда я оцениваю реструктуризацию кода или добавляю новую функцию, я думаю о том, сколько шаблонов потребуется по сравнению с реальной логикой. Если ответ превышает 50%, это, вероятно, означает, что я слишком обдумываю это.
Помимо SRP, есть много других стилей разработки. В вашем случае это звучит как YAGNI, безусловно, не хватает.
источник
Многие ответы здесь действительно хороши, но они сосредоточены на технической стороне этого вопроса. Я просто добавлю, что это звучит как попытки разработчика следовать SRP, как будто они действительно нарушают SRP.
Вы можете увидеть блог Боба здесь об этой ситуации, но он утверждает , что если ответственность размазывается по нескольким классам , то ответственность SRP нарушается , поскольку эти классы меняются параллельно. Я подозреваю, что вашему разработчику действительно понравится дизайн в верхней части блога Боба, и он может быть немного разочарован, увидев его разорванным на части. В частности, потому что это нарушает «Общий принцип закрытия» - вещи, которые меняются вместе, остаются вместе.
Помните, что SRP относится к «причине изменения», а не «делать что-то одно», и что вам не нужно заботиться о причине изменения до тех пор, пока изменение не произойдет. Второй парень платит за абстракцию.
Теперь есть вторая проблема - «яростный сторонник разработки SOLID». Конечно, не похоже, что у вас хорошие отношения с этим разработчиком, поэтому любые попытки убедить его / ее в проблемах в кодовой базе поставлены в тупик. Вам нужно будет восстановить отношения, чтобы вы могли по-настоящему обсудить проблемы. Я бы порекомендовал это пиво.
Нет, серьезно - если вы не пьете голову в кафе. Выйдите из офиса и где-нибудь спокойно, где вы можете поговорить об этом неформально. Вместо того, чтобы пытаться выиграть спор на собрании, чего вы не сделаете, поговорите где-нибудь весело. Попытайтесь понять, что этот разработчик, который сводит вас с ума, является действующим человеком, который пытается вытащить программное обеспечение "за дверью" и не хочет отправлять дерьмо. Поскольку вы, вероятно, разделяете эту точку соприкосновения, вы можете начать обсуждать, как улучшить дизайн, в то же время придерживаясь SRP.
Если вы оба можете признать, что SRP - это хорошо, то, что вы просто по-разному интерпретируете аспекты, вы, вероятно, можете начать продуктивную беседу.
источник
Я согласен с вашим решением [update = 2012.05.31] о том, что SRP обычно является хорошей идеей. Но я полностью согласен с комментарием @ Spoike -s о том, что конструктор с 20 аргументами интерфейса - это очень много. [/ Update]:
Введение SRP с IoC перемещает сложность от одного «класса с множественной ответственностью» ко многим классам srp и намного более сложной инициализации в пользу
Боюсь, вы не можете уменьшить фрагментацию кода, не жертвуя srp.
Но вы можете «облегчить боль» при инициализации кода, внедрив синтаксический класс сахара, который скрывает сложность инициализации в конструкторе.
источник