Приводит ли SOLID к написанию фреймворка поверх стека технологий?

70

Мне нравится SOLID, и я стараюсь изо всех сил использовать и применять его при разработке. Но я не могу не чувствовать, что подход SOLID превращает ваш код в код «фреймворка» - то есть код, который вы разработали бы, если бы создавали фреймворк или библиотеку для использования другими разработчиками.

Я обычно практиковал 2 режима программирования - создание более или менее точно того, что задают с помощью требований и KISS (типичное программирование), или создание очень общей и многократно используемой логики, сервисов и т. Д., Которые обеспечивают гибкость, которая может понадобиться другим разработчикам (программирование фреймворка) ,

Если пользователь действительно просто хочет, чтобы приложение делало x и y, имеет ли смысл следовать SOLID и добавлять целую кучу точек входа абстракции, когда вы даже не знаете, является ли это допустимой проблемой для начала с участием? Если вы добавляете эти точки входа абстракции, действительно ли вы удовлетворяете требованиям пользователей, или вы создаете инфраструктуру поверх существующей инфраструктуры и стека технологий, чтобы облегчить будущие добавления? В каком случае вы служите интересам заказчика или разработчика?

Это то, что кажется распространенным в мире Java Enterprise, когда кажется, что вы разрабатываете свою собственную среду на основе J2EE или Spring, так что это лучший UX для разработчика, вместо того, чтобы сосредоточиться на UX для пользователя?

Igneous01
источник
12
Проблема большинства кратких правил программирования заключается в том, что они подлежат интерпретации, крайним случаям, и иногда определения слов в таких правилах неясны при ближайшем рассмотрении. По сути, они могут означать самые разные вещи для разных людей. Наличие неидеологического прагматизма обычно позволяет принимать более мудрые решения.
Марк Роджерс
1
Вы говорите так, будто следование принципам SOLID так или иначе подразумевает большие инвестиции, много дополнительной работы. Это не так, это практически бесплатно. И это, вероятно, сэкономит вам или кому-то еще большие инвестиции в будущее, потому что это облегчает поддержку и расширение вашего кода. Вы продолжаете задавать вопросы типа: «Должны ли мы делать домашнее задание или сделать клиента счастливым?» Это не компромиссы.
Мартин Маат
1
@MartinMaat Я думаю, что более экстремальные формы SOLID подразумевают большие инвестиции. То есть. Корпоративное программное обеспечение. Вне корпоративного программного обеспечения у вас будет очень мало причин абстрагировать свой ORM, технологический стек или базу данных, потому что есть очень хороший шанс, что вы будете придерживаться выбранного вами стека. В том же смысле, привязывая себя к определенной среде, базе данных или ORM, вы нарушаете принципы SOLID, потому что вы связаны со своим стеком. Такой уровень гибкости от SOLID не требуется на большинстве рабочих мест.
Igneous01
1
Смотрите также эффект внутренней платформы .
Макс. Мин.
1
Превращение большей части кода в нечто вроде фреймворка звучит совсем не страшно. Это становится только ужасным, если это становится чрезмерно спроектированным. Но рамки могут быть минимальными и самоуверенными. Я не уверен, будет ли это неизбежным следствием использования SOLID, но это определенно возможное последствие, которое, я думаю, вам следует принять.
Конрад Рудольф

Ответы:

84

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

Это компромисс, и ему нужен некоторый опыт для принятия правильных решений о том, когда обобщать, а когда нет. Возможный подход к этому состоит в том, чтобы придерживаться принципа YAGNI - не делайте ваш код SOLID «на всякий случай» - или, если использовать ваши слова: не

обеспечить гибкость, которая может понадобиться другим разработчикам

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

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

Конечно, этот способ работы всегда будет требовать некоторого рефакторинга в существующей, работающей кодовой базе. Вот почему автоматические тесты здесь важны. Поэтому с самого начала сделать ваш код достаточно твердым, чтобы его можно было тестировать на уровне модулей, - это не пустая трата времени, и это не противоречит YAGNI. Автоматические тесты являются допустимым случаем «повторного использования кода», так как рассматриваемый код используется как из производственного кода, так и из тестов. Но имейте в виду, просто добавьте гибкость, которая вам действительно нужна для того, чтобы тесты работали, не меньше и не больше.

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

Док Браун
источник
23
Дополнительные спорные моменты: подождите, пока у вас не будет 3 варианта использования, в которых вы видите ту же логику, прежде чем рефакторинг кода для повторного использования. Если вы начнете рефакторинг с 2-мя частями, легко оказаться в ситуации, когда изменение требований или новый сценарий использования нарушают созданную вами абстракцию. Кроме того, рефакторы должны быть ограничены вещами с одним и тем же сценарием использования: 2 компонента могут иметь один и тот же код, но выполнять совершенно разные вещи, и если вы объедините эти компоненты, вы в конечном итоге свяжете эту логику, что может впоследствии вызвать проблемы.
января
8
Я в целом согласен с этим, но чувствую, что он слишком сосредоточен на «одноразовых» приложениях: вы пишете код, он работает, хорошо. Тем не менее, есть много приложений с «долгой поддержкой». Вы можете написать некоторый код, и через 2 года бизнес-требования изменятся, поэтому вам придется скорректировать код. К тому времени от него может зависеть много другого кода - в этом случае принципы SOLID облегчат изменение.
Р. Шмитц
3
«Прежде чем мы попытаемся написать повторно используемый код, мы должны написать полезный код» - очень мудро!
Грэм
10
Вероятно, стоит отметить, что ожидание фактического варианта использования сделает ваш код SOLID лучше , потому что работать в гипотетических условиях очень сложно, и вы, вероятно, ошибочно догадаетесь, что будет в будущем. В нашем проекте есть несколько случаев, когда вещи были спроектированы, чтобы быть ТВЕРДЫМИ и гибкими для будущих нужд ... за исключением того, что будущие потребности оказались вещами, о которых никто не думал в то время, поэтому нам обоим нужно было провести рефакторинг, и у нас была дополнительная гибкость. все еще не нуждался - что либо нужно было поддерживать перед лицом рефакторинга, либо списывать.
KRyan
2
также обычно вам все равно нужно писать тестируемый код, что обычно означает наличие первого уровня абстракции, чтобы иметь возможность переключаться с конкретной реализации на тестовую.
Вальфрат,
49

Исходя из моего опыта, при написании приложения у вас есть три варианта:

  1. Написать код исключительно для выполнения требований,
  2. Написать общий код, который предвидит будущие требования, а также выполняет текущие требования,
  3. Напишите код, который соответствует только текущим требованиям, но его можно легко изменить позже, чтобы удовлетворить другие потребности.

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

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

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

Единственный аспект SOLID, который на самом деле здесь не применим, - это принцип открытия / закрытия. Для библиотек и фреймворков это важно. Для автономного приложения не так уж и много. На самом деле это случай написания кода, который следует за « SLID »: легко писать (и читать), легко тестировать и легко обслуживать.

Дэвид Арно
источник
один из моих любимых ответов на этом сайте!
TheCatWhisperer
Я не уверен, как вы пришли к выводу, что 1) сложнее проверить, чем 3). Сложнее вносить изменения, конечно, но почему вы не можете проверить? Во всяком случае, целеустремленное программное обеспечение легче проверить на соответствие требованиям, чем более общее.
Мистер Листер
@MrLister Две стороны идут рука об руку, 1. труднее тестировать, чем 3. потому что определение подразумевает, что оно не написано «таким образом, что его легко изменить позже, чтобы удовлетворить другие потребности».
Марк Бут
1
+0; IMVHO вы неверно истолковываете (хотя и обычным образом) способ работы 'O' (открытый-закрытый). См., Например, codeblog.jonskeet.uk/2013/03/15/… - даже в небольших кодовых базах это больше о наличии автономных единиц кода (например, классов, модулей, пакетов и т. Д.), Которые можно тестировать изолированно и добавлять / удаляется по мере необходимости. Одним из таких примеров может быть пакет утилитарных методов - независимо от того, как вы их объединяете, они должны быть «закрытыми», то есть автономными, и «открытыми», то есть расширяемыми каким-либо образом.
vaxquis
Кстати, даже дядя Боб идет по этому пути в один момент: «Что [открыто-закрыто] означает, что вы должны стремиться привести свой код в такое положение, чтобы, когда поведение меняется ожидаемым образом, вам не приходилось делать развертки Изменения во всех модулях системы. В идеале вы сможете добавить новое поведение, добавив новый код и мало или совсем не изменив старый код ». <- это, очевидно, применимо даже к небольшим приложениям, если они предназначены для изменения или исправления (и, IMVHO, это обычно бывает, особенно , когда в отношении исправления. посмеиваться )
vaxquis
8

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

  • Рамки больше по объему, чем небольшие проекты.
  • С плохой практикой значительно сложнее справиться в больших кодовых базах.
  • Создание фреймворка (в среднем) требует более опытного разработчика, чем создание небольшого проекта.
  • Лучшие разработчики больше следуют хорошей практике (SOLID).
  • В результате фреймворки имеют более высокую потребность в хорошей практике и, как правило, создаются разработчиками, которые более тесно знакомы с хорошей практикой.

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

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

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


2 режима программирования - создание более или менее точно того, что задают с помощью требований и KISS (типичное программирование), или создание очень общей и многократно используемой логики, сервисов и т. Д., Которые обеспечивают гибкость, которая может понадобиться другим разработчикам (программирование фреймворка)

Вы несколько подтверждаете это здесь. Подумайте, что такое основа. Это не приложение. Это обобщенный «шаблон», который другие могут использовать для создания всевозможных приложений. По логике это означает, что фреймворк построен в гораздо более абстрактной логике, чтобы его могли использовать все.

Разработчики фреймворков не могут использовать ярлыки, потому что они даже не знают, каковы требования последующих приложений. Создание фреймворка по своей сути стимулирует их сделать свой код пригодным для использования другими.

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

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

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


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

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

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

Допустим, вы можете создать приложение за 4 недели, а за 6 недель - правильно. С первого взгляда, к сожалению, здание кажется лучше. Заказчик получает свое приложение быстрее, и компании приходится тратить меньше времени на заработную плату разработчика. Победа / победа, верно?

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

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

А также есть тенденция тратить время на охоту за ошибками. Это часто имеет место в проектах, где ведение журнала было проигнорировано из-за нехватки времени или явного нежелания его реализовать, потому что вы рассеянно работаете в предположении, что конечный продукт будет работать так, как ожидалось.

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

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

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

Flater
источник
1
Это хороший ответ, но я должен уточнить, что я не говорю, что мы отказываемся от хороших практик, но какой уровень «хороших практик» мы преследуем? Является ли хорошей практикой абстрагирование вашего ORM в каждом проекте, потому что вам «может понадобиться» поменять его на другой позже? Я так не думаю, есть определенные уровни связывания, которые я готов принять (т. Е. Я привязан к выбранной структуре, языку, ORM, базе данных). Если мы следуем SOLID до экстремистской степени, действительно ли мы просто реализуем нашу собственную структуру поверх выбранного стека?
Igneous01
Вы отрицаете опыт ОП как «заблуждение». Не конструктивно.
max630
@ max630 Я не отрицаю это. Я потратил большую часть ответа, объясняя, почему наблюдения OP являются действительными.
Флэтер
1
@ Igneous01 SOLID не является основой. SOLID - это абстракция, которая чаще встречается в фреймворке. При реализации любого вида абстракции (включая SOLID) всегда есть линия разумности. Вы не можете просто абстрагироваться ради абстракции, вы потратили бы целую вечность, придумывая код, который настолько чрезмерно обобщен, что за ним трудно следовать. Только абстрактное, что вы разумно подозреваете, будет полезно для вас в будущем. Тем не менее, не попадайтесь в ловушку, предполагая, что вы привязаны, например, к вашему текущему серверу базы данных. Вы никогда не знаете, какая новая база данных будет выпущена завтра.
Флэтер
@ Igneous01 Другими словами, у вас есть правильная идея, если вы не хотите абстрагироваться от всего, но у меня возникает ощущение, что вы слишком немного склоняетесь в этом направлении. Разработчики очень часто предполагают, что текущие требования изложены в камне, а затем принимают архитектурные решения на основе этого (желаемого) предположения.
Флэтер
7

Как SOLID превращает простой код в код фреймворка? Я не стану любителем SOLID, но на самом деле не совсем понятно, что вы имеете в виду.

  • ПОЦЕЛУЙ является сутью S Ingle ответственности Принцип.
  • Там нет ничего в O перо / Closed принцип (по крайней мере , как я понимаю, вижу Джон тарелочкам ) , что идет вразрез с написанием кода , чтобы сделать одну вещь хорошо. (На самом деле, чем более сфокусирован код, тем важнее становится «закрытая» часть.)
  • L Иськов принцип замещения не говорит , вы должны позволить людям подкласс классы. В нем говорится, что если вы создаете подклассы для ваших классов, ваши подклассы должны выполнять контракт своих суперклассов. Это просто хороший ОО дизайн. (И если у вас нет подклассов, это не применяется.)
  • ПОЦЕЛУЙ также суть I nterface Сегрегация принцип.
  • D ependency Принцип инверсии является только один я могу видеть удаленно применять, но я думаю , что это широко неправильно и раздуто. Это не значит, что вам нужно вводить все с помощью Guice или Spring. Это просто означает, что вы должны абстрагироваться, где это уместно, и не зависеть от деталей реализации.

Я признаю, что я не думаю, что в терминах SOLID сам, потому что я прошел через школу программирования Gang of Four и Josh Bloch , а не школу Bob Martin. Но я действительно думаю, что если вы думаете «SOLID» = «добавление большего количества слоев в технологический стек», вы читаете это неправильно.


PS Не продавайте преимущества «лучшего UX для разработчика». Код проводит большую часть своей жизни в обслуживании. Разработчик - это ты .

Дэвид Моулз
источник
1
Что касается SRP - можно утверждать, что любой класс с конструктором нарушает SRP, потому что вы можете переложить эту ответственность на фабрику. Что касается OCP - это действительно проблема уровня структуры, потому что, когда вы публикуете интерфейс для использования извне, вы не можете его изменять. Если интерфейс используется только внутри вашего проекта, тогда можно изменить контракт, потому что у вас есть возможность изменить контракт в вашем собственном коде. Что касается ISP - можно утверждать, что интерфейс должен быть определен для каждого отдельного действия (таким образом сохраняя SRP), и это касается внешних пользователей.
Igneous01
3
1) можно, но я сомневаюсь, что кто-либо стоит слушать. 2) вы можете быть удивлены, насколько быстро один проект может вырасти до такого размера, что свободная модификация внутренних интерфейсов становится плохой идеей. 3) см. Как 1), так и 2). Достаточно сказать, что я думаю, что вы слишком много читаете по всем трем принципам. Но комментарии на самом деле не место для рассмотрения этих аргументов; Я предлагаю вам задать каждый из них как отдельный вопрос и посмотреть, какие ответы вы получите.
Дэвид Моулз
4
@ Igneous01 Используя эту логику, вы также можете отказаться от геттеров и сеттеров, так как вы можете создать отдельный класс для каждого сеттера переменных и один класс для каждого геттера. IE: class A{ int X; int Y; } class A_setX{ f(A a, int N) { a.X = N; }} class A_getX{ int f(A a) { return X; }} class A_setY ... etc.я думаю, что вы смотрите на это слишком мета с точки зрения вашей фабричной претензии. Инициализация не является аспектом проблемы домена.
Аарон
@ Аарон Это. Люди могут использовать SOLID, чтобы выдвигать плохие аргументы, но это не значит, что делать плохие вещи = «следовать SOLID».
Дэвид Моулз