Чи призводить до того, що після SOLID написати рамку поверх технологічного стеку?


70

Мені подобається SOLID, і я намагаюся використовувати його та застосовувати під час розвитку. Але я не можу не відчути, ніби підхід SOLID перетворює ваш код у "рамковий" код - тобто код, який ви створили, якби ви створювали рамки або бібліотеку для інших розробників, які могли використовувати.

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

Якщо користувач дійсно хоче, щоб програма робила речі x і y, чи є сенс слідкувати за SOLID і додавати цілу купу вхідних точок абстракції, коли ви навіть не знаєте, чи це навіть справжня проблема, яку слід почати з? Якщо ви додаєте ці точки введення абстракції, чи дійсно ви виконуюте вимоги користувачів, чи створюєте рамку поверх існуючих фреймворків та технологічних стеків, щоб зробити майбутні доповнення легшими? У якому випадку ви обслуговуєте інтереси замовника чи розробника?

Це те, що здається поширеним у світі Java Enterprise, де здається, що ви розробляєте власну структуру на версії J2EE або Spring, так що це краще UX для розробника, а не орієнтуватися на UX для користувача?


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

1
Вам здається, що слідування принципам SOLID якимось чином означає великі інвестиції, багато зайвої роботи. Це не так, це практично безкоштовно. І це, ймовірно, врятує вас або когось іншого велику інвестицію в майбутньому, оскільки це полегшує ваш код і підтримує його. Ви продовжуєте задавати питання на кшталт "чи варто робити домашнє завдання чи зробити клієнта щасливим?" Це не компроміси.
Мартін Мейт

1
@MartinMaat Я думаю, що більш екстремальні форми SOLID означають великі інвестиції. Тобто Програмне забезпечення підприємства. Поза корпоративним програмним забезпеченням у вас буде дуже мало причин абстрагувати ваш ORM, стек технологій чи базу даних, оскільки є дуже хороший шанс, що ви збираєтесь дотримуватися обраного вами стека. У тому ж сенсі, прив’язуючи себе до певної рамки, бази даних або ORM, ви порушуєте принципи SOLID, оскільки ви з'єднані зі своїм стеком. Цей рівень гнучкості від SOLID не потрібен для більшості робочих місць.
Igneous01


1
Перетворення більшості коду на щось на зразок рамки зовсім не звучить жахливо. Це стає жахливим лише у тому випадку, якщо він стає надмірно розробленим. Але рамки можуть бути мінімальними та впевненими. Я не впевнений, чи це було б неминучим наслідком слідування SOLID, але це, безумовно, можливий наслідок і такий, який, я думаю, ви повинні прийняти.
Конрад Рудольф

Відповіді:


84

Ваше зауваження вірно, принципи SOLID - це IMHO, створений з урахуванням багаторазових бібліотек або рамкового коду. Коли ви просто сліпо слідуєте за всіма ними, не запитуючи, чи має це сенс чи ні, ви ризикуєте надмірно узагальнити і вкласти набагато більше зусиль у свою систему, ніж, мабуть, потрібно.

Це компроміс, і йому потрібен певний досвід для прийняття правильних рішень щодо узагальнення, а коли ні. Можливий підхід до цього полягає в дотриманні принципу YAGNI - не робіть свій код СОЛІД «про всяк випадок» - або, використовуючи свої слова: не робіть

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

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

Отже, коли у вас є одна функція або клас у вашому коді, ви не впевнені, чи можна його повторно використовувати, не вкладайте їх у свої рамки прямо зараз. Зачекайте, поки у вас буде фактичний випадок для повторного використання та рефактора, щоб " ЗАТВОРИТИ досить для цього випадку". Не впроваджуйте більше конфігураційності (після OCP) або вхідних точок абстрагування (за допомогою DIP) у такий клас, який вам справді потрібен для фактичного випадку повторного використання. Додайте наступну гнучкість, коли наступна вимога до повторного використання фактично є.

Звичайно, такий спосіб роботи завжди потребуватиме певної кількості рефакторингу на існуючій робочій кодовій базі. Ось чому тут важливі автоматичні тести. Тож зробити свій код SOLID достатньо з самого початку, щоб його можна було перевірити одиницею - це не витрата часу, і це не суперечить YAGNI. Автоматичні тести є вагомим випадком "повторного використання коду", оскільки код у ставленні використовується як з виробничого коду, так і з тестів. Але майте на увазі, просто додайте гнучкість, яка вам потрібна для того, щоб тести працювали, не менше, не більше.

Це насправді стара мудрість. Задовго до того, як термін SOLID набув популярності, хтось сказав мені, перш ніж спробувати написати повторно корисний код, ми повинні написати корисний код. І я все ще думаю, що це хороша рекомендація.


23
Додаткові суперечки: Зачекайте, поки у вас є 3 випадки використання, коли ви побачите ту саму логіку, перш ніж переробляти код для повторного використання. Якщо ви почнете рефакторинг з двох частин, легко опинитися в ситуації, коли зміна вимог або новий випадок використання закінчуються порушенням зробленої вами абстракції. Також тримайте рефактори обмеженими речами з одним і тим же випадком використання: 2 компоненти можуть мати один і той же код, але робити зовсім інші речі, і якщо ви з’єднаєте ці компоненти, ви закінчите зв’язувати цю логіку, що може спричинити проблеми згодом за лінією.
Nzall

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 тиждень. Можливо, у майбутньому буде багато цих змін.

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

А також є тенденція витрачати час на полювання на клопів. Це часто трапляється в проектах, де реєстрація була проігнорована через часові обмеження або від небажання її впроваджувати, оскільки ви необачно працюєте, припускаючи, що кінцевий продукт буде працювати так, як очікувалося.

Це навіть не потребує значного оновлення. У мого нинішнього роботодавця я бачив кілька проектів, які були побудовані швидко і брудно, і коли найменшу помилку / зміну потрібно було зробити через неправильну комунікацію в вимогах, це призводить до ланцюгової реакції потреби в модулі рефактора після модуля . Деякі з цих проектів закінчилися руйнуватися (і залишили позаду незрозумілий безлад), перш ніж вони навіть випустили свою першу версію.

Рішення швидкого доступу (швидке та брудне програмування) корисні лише в тому випадку, якщо ви можете безумовно гарантувати, що вимоги є абсолютно правильними і ніколи не потребуватимуть змін. З мого досвіду, я ніколи не стикався з проектом, де це правда.

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


1
Це хороша відповідь, але я повинен уточнити, що я не кажу, що ми відмовляємось від належних практик, але який рівень "належних практик" ми проводимо? Чи є гарною практикою абстрагувати свій ORM у кожному проекті, тому що вам може знадобитися замінити його на інший пізніше? Я не думаю, що я готовий прийняти певні рівні зв'язку (тобто я прив'язаний до обраної бази, мови, ORM, бази даних). Якщо ми дотримуємось SOLID до екстремістської міри, чи справді ми просто реалізуємо власну структуру на основі обраного стека?
Igneous01

Ви заперечуєте досвід ОП як "помилковість". Не конструктивна.
max630

@ max630 Я не заперечую це. Я витратив хорошу частину відповіді, пояснюючи, чому спостереження ОП є дійсними.
Flater

1
@ Igneous01 SOLID не є рамкою. SOLID - це абстракція, яка частіше зустрічається в рамках. При здійсненні будь-якого виду абстракції (включаючи SOLID) завжди існує межа обґрунтованості. Ви не можете просто абстрагуватися заради абстракції, ви витратили століття, придумуючи код, який настільки перегенералізований, що важко дотримуватися. Тільки абстрактне те, що ви обгрунтовано підозрюєте, буде корисним вам у майбутньому. Однак не потрапляйте в пастку, якщо припустити, що ви пов'язані, наприклад, з вашим поточним сервером баз даних. Ніколи не знаєш, яка нова база даних вийде завтра.
Flater

@ Igneous01 Іншими словами, у вас є правильна ідея, не хотіти абстрагувати все, але я відчуваю, що ти так злегка нахиляється в цьому напрямку. Для розробників дуже часто припускати, що поточні вимоги встановлені в камені, і потім приймати архітектурні рішення, виходячи з цього (бажаного) припущення.
Flater

7

Як SOLID перетворює простий код у рамковий код? Я не є квартирою для SOLID будь-якими способами, але насправді не очевидно, що ви тут маєте на увазі.

  • KISS - суть принципу відповідальності S ingle.
  • Немає нічого в ручці O / Закритий принцип (принаймні, наскільки я це розумію - див. Джон Скіт ), що суперечить написанню коду, щоб зробити щось добре. (Насправді, чим щільніше зосереджений код, тим важливішою стає "закрита" частина.)
  • Принцип заміщення L iskov не говорить про те, що ви повинні дозволити людям підкласифікувати ваші класи. У ній сказано, що якщо ви підкласируєте свої класи, ваші підкласи повинні виконувати договір про їхні суперкласи. Це просто хороший дизайн OO. (І якщо у вас немає підкласів, це не застосовується.)
  • ПОЦЕЛУЙ також суть I nterface Сегрегація принцип.
  • D ependency Принцип інверсії є тільки один я можу бачити віддалено застосовувати, але я думаю , що це широко неправильно і роздуте. Це не означає, що потрібно вводити все за допомогою Guice або Spring. Це просто означає, що ви повинні абстрагуватися, де це доречно, а не залежати від деталей реалізації.

Зізнаюся, я не думаю про SOLID з точки зору себе, тому що я придумав школу програмування « Банда чотирьох» та « Джош Блок» , а не школу Боб Мартіна. Але я дійсно думаю, що якщо ви думаєте, що "SOLID" = "додавання більше шарів до стеку технологій", ви читаєте це неправильно.


PS Не продайте переваги "кращого UX для розробника" коротко. Кодекс проводить більшу частину свого життя на технічному обслуговуванні. Розробник - це ти .


1
Щодо SRP - можна стверджувати, що будь-який клас із конструктором порушує SRP, оскільки ви можете перекласти цю відповідальність на завод. Що стосується OCP - це справді проблема на базовому рівні, тому що, коли ви публікуєте інтерфейс для споживання для зовнішнього використання, ви не можете його змінити. Якщо інтерфейс використовується лише у вашому проекті, можливо змінити договір, оскільки ви маєте право змінювати контракт у межах власного коду. Щодо ISP - можна стверджувати, що інтерфейс повинен визначатися для кожної окремої дії (таким чином зберігаючи SRP) та стосується сторонніх користувачів.
Igneous01

3
1) можна, але я сумніваюся, хто когось заслуговує слухати. 2) ви можете бути здивовані, як швидко один проект може вирости до розміру, що вільна зміна внутрішніх інтерфейсів стає поганою ідеєю. 3) див. І 1) і 2). Досить сказати, я думаю, ви занадто багато читаєте про всі три принципи. Але коментарі насправді не є місцем для вирішення цих аргументів; Я пропоную вам поставити кожне з них як окреме запитання і подивитися, які відповіді ви отримуєте.
Девід Молес

4
@ Igneous01 Використовуючи цю логіку, ви можете також відмовитися від геттерів і сеттерів, оскільки ви можете скласти окремий клас для кожного сеттера змінної та один для кожного геттера. І.Є .: 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.Я думаю, що ви дивитесь на це з занадто мета-точки зору з фабричною заявою. Ініціалізація не є аспектом проблеми домену.
Аарон

@Aaron Це. Люди можуть використовувати SOLID для поганих аргументів, але це не означає робити погані речі = "слідувати за SOLID".
Девід Молес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.