SOLID vs. Уникнення передчасної абстракції


27

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

  • Хочу уникнути передчасного абстрагування. З мого досвіду, малювання абстракційних ліній без конкретних випадків використання (таких, які існують зараз або в осяжному майбутньому) призводить до того, що вони малюються в неправильних місцях. Коли я намагаюся змінити такий код, лінії абстрагування заважають, а не допомагають. Тому я схильний помилятися на тому, що не малюю жодних абстракційних ліній, поки не буду добре уявляти, де вони були б корисні.

  • Мені важко виправдати посилення модульності заради себе, якщо це робить мій код більш багатослівним, складнішим для розуміння тощо і не усуває жодного дублювання. Я вважаю, що простий, щільно пов'язаний процедурний або боговий об'єктний код іноді простіше зрозуміти, ніж дуже добре розроблений код равіолі, оскільки потік простий і лінійний. Так само набагато простіше писати.

З іншого боку, такий спосіб мислення часто призводить до об'єктів Бога. Я, як правило, реконструюю це консервативно, додаючи чіткі лінії абстракції лише тоді, коли бачу чіткі закономірності. Що, якщо що, не так з об'єктами Бога і щільно зв'язаний код, якщо вам явно не потрібна більше модульність, не маєте значного дублювання і код читається?

EDIT: Що стосується окремих принципів SOLID, я мав наголосити, що Заміна Ліскова - це ІМХО - формалізація здорового глузду, і його слід застосовувати всюди, оскільки абстракції не мають сенсу, якщо це не так. Крім того, кожен клас повинен нести єдину відповідальність на певному рівні абстракції, хоча це може бути дуже високий рівень з деталями реалізації, всі вони набиті на один величезний клас 2000 ліній. В основному, ваші абстракції повинні мати сенс, де ви вирішите зробити конспект. Принципи, які я сумніваюсь у випадках, коли модульність не є однозначно корисною, є відкрита-закрита, сегрегація інтерфейсу і особливо інверсія залежності, оскільки мова йде про модульність, а не просто абстрагування має сенс.


10
@ S.Lott: Ключове слово там "більше". Це саме та річ, яку придумали ЯГНІ. Збільшення модульності або зменшення з'єднання просто заради себе - це просто культаве програмування.
Мейсон Уілер

3
@ S.Lott: Це повинно бути очевидно з контексту. Як я читаю, принаймні, "більше, ніж зараз існує у відповідному коді".
Мейсон Уілер

3
@ S.Lott: Якщо ви наполягаєте на педантичності, "більше" посилається на більше, ніж існує зараз. Сенс полягає в тому, що щось, чи то код, чи груба конструкція, вже існує, і ви думаєте про те, як це переробити.
dimimcha

5
@ back2dos: Правильно, але я скептично налаштовую щось на розширення, не маючи чіткого уявлення про те, як це, ймовірно, буде розширено. Без цієї інформації ваші лінії абстрагування, ймовірно, опиняться у всіх неправильних місцях. Якщо ви не знаєте, де рядки абстракції, ймовірно, будуть корисні, я пропоную вам написати лише найкоротший код, який працює і є читабельним, навіть якщо у вас закинуто кілька об'єктів Бога.
dimimcha

2
@dsimcha: Вам пощастило, якщо вимоги зміниться після того, як ви написали фрагмент коду, оскільки зазвичай вони змінюються, коли ви це робите. Ось чому реалізація знімків не підходить для переживання фактичного життєвого циклу програмного забезпечення. Крім того, SOLID не вимагає від вас сліпого абстрагування. Контекст абстракції визначається за допомогою інверсії залежності. Ви зводите залежність кожного модуля до найпростішої і зрозумілої абстракції інших служб, на які він покладається. Це не довільно, штучно чи складно, але чітко визначено, правдоподібно та просто.
back2dos

Відповіді:


8

Нижче наведено прості принципи, які ви можете застосувати, щоб допомогти вам зрозуміти, як збалансувати дизайн системи:

  • Принцип єдиної відповідальності: SSOLID. Ви можете мати дуже великий об’єкт за кількістю методів або кількістю даних і все одно дотримуватися цього принципу. Візьмемо для прикладу об’єкт Ruby String. Цей об’єкт має більше методів, ніж можна потиснути палицю, але він все ще несе лише одну відповідальність: утримуйте рядок тексту для програми. Як тільки ваш об’єкт починає брати на себе нову відповідальність, починайте замислюватися над цим. Проблема з технічним обслуговуванням задає себе питанням: "де я очікую знайти цей код, якщо у мене виникли проблеми з ним пізніше?"
  • Простота рахується: Альберт Ейнштейн сказав: "Зробіть все максимально просто, але не простіше". Вам справді потрібен цей новий метод? Чи можете ви виконати те, що вам потрібно, з існуючим функціоналом? Якщо ви дійсно думаєте, що має бути новий метод, чи можете ви змінити існуючий метод, щоб задовольнити все необхідне? По суті рефактор, коли ви створюєте нові речі.

По суті, ви намагаєтесь робити все, що можете, щоб не стріляти собі в ногу, коли настає час підтримувати програмне забезпечення. Якщо ваші великі об'єкти є розумними абстракціями, то є мало причин розділяти їх лише тому, що хтось придумав метрику, яка каже, що клас повинен бути не більше X рядків / методів / властивостей / тощо. Якщо що, це - керівні принципи, а не жорсткі та швидкі правила.


3
Хороший коментар. Як обговорюється в інших відповідях, одна проблема "єдиної відповідальності" полягає в тому, що відповідальність класу може бути описана на різних рівнях абстрагування.
dimimcha

5

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

Як і у всіх інших творчих дисциплінах, немає абсолютів - просто компроміси. Чим більше ви це робите, тим "простіше" стає вирішувати, коли цього достатньо для вашої поточної проблеми або вимог.

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


5
I want to avoid premature abstraction.In my experience drawing abstraction lines without concrete use cases... 

Це частково правильно і частково неправильно.

Неправильно
Це не є причиною заважати застосувати принципи ОО / СОЛІД. Це лише заважає застосовувати їх занадто рано.

Який є...

Право
Не змінюйте код рефактора, поки він не буде відновлений; коли це вимоги завершені. Або коли "випадки використання" все є, як ви кажете.

I find simple, tightly coupled procedural or God object code is sometimes easier to understand than very well-factored...

Під час роботи в самоті або в силосному середовищі
Проблема з невиконанням ООС не відразу очевидна. Після написання першої версії програми:

Code is fresh in your head
Code is familiar
Code is easy to mentally compartmentalize
You wrote it

3/4 цих добрих речей швидко вмирають за короткий час.

Нарешті, ви визнаєте, що у вас є об'єкти Бога (Об'єкти з багатьма функціями), тому ви розпізнаєте окремі функції. Інкапсулюйте. Покладіть їх у папки. Використовуйте інтерфейси раз за часом. Зробіть собі прихильність до тривалого обслуговування. Тим більше, що нереставровані методи прагнуть нескінченно розквітати і ставати методами Бога.

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


Правильно. Я розпізнаю туманно, що мої об’єкти роблять більше однієї речі, але я не розумію конкретніше, як корисно їх розбити, щоб лінії абстрагування знаходилися в потрібних місцях, коли потребує обслуговування. Це здається марною витратою часу на рефактор, якщо програмісту технічного обслуговування, мабуть, доведеться зробити набагато більше рефакторингу, щоб так чи інакше потрапити лінії абстрагування в потрібне місце.
dimimcha

Інкапсуляція та абстракція - це два різних поняття. Інкапсуляція - це основна концепція ОО, яка в основному означає, що у вас є заняття. Заняття забезпечують модульність і читабельність самих по собі. Це корисно.
P.Brian.Mackey

При правильному застосуванні абстракція може зменшити технічне обслуговування. При неправильному застосуванні це може збільшити технічне обслуговування. Знання, коли і де проводити межі, вимагає чіткого розуміння бізнесу, рамок, замовника, крім основних технічних аспектів того, як саме "застосовувати заводський зразок".
P.Brian.Mackey

3

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

Вільна муфта та невеликі, прості предмети, як правило, дають певні переваги у багатьох поширених випадках, тому загалом їх корисно використовувати. Проблема полягає в людях, які не розуміють обґрунтування принципів, що сліпо намагаються застосовувати їх універсально, навіть там, де вони не застосовуються.


1
+1. Ще потрібно добре визначити, що означає "підходящий".
jprete

2
@jprete: Чому? Спроба застосувати абсолютні визначення є частиною причини концептуальних заворушень , як це. Тут є багато майстерності, яка займається створенням хорошого програмного забезпечення. Що дійсно потрібно ІМО - це досвід та хороші оцінки.
Мейсон Уілер

4
Ну так, але широке визначення якогось роду все ж корисне.
jprete

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

1
Я віддаю перевагу, щоб хтось наосліп писав розв'язаний код, а не сліпо писав код спагетті :)
Борис Янков

2

Я схильний до цього більше підходити з точки зору You Ain't Gonna Need It. Ця публікація буде зосереджена зокрема на одному пункті: спадщині.

У своєму початковому дизайні я створюю ієрархію речей, за якими я знаю, що їх буде дві або більше. Цілком ймовірно, що їм знадобиться багато одного і того ж коду, тому варто розробити його з самого початку. Після того, як початковий код встановлений , і мені потрібно додати більше можливостей / функціональності, я дивлюся на те, що я маю, і думаю: "Чи щось я вже реалізував таку ж чи подібну функцію?" Якщо так, то, найімовірніше, буде випущена нова абстракція.

Хорошим прикладом цього є рамка MVC. Починаючи з нуля, ви створюєте одну велику сторінку з кодом позаду. Але тоді ви хочете додати ще одну сторінку. Однак один код, який знаходиться позаду, вже реалізує багато логіки, необхідної новій сторінці. Отже, ви абстрагуєте Controllerклас / інтерфейс, який буде реалізовувати логіку, характерну для цієї сторінки, залишаючи загальні речі у вихідному "боговому" коді позаду.


1

Якщо ви знаєте як розробник, коли НЕ застосовувати принципи через майбутнє коду, то добре для вас.

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

Що ще важливіше, врахуйте, що більшість розробників займаються денною роботою і не хвилюються так само, як ви. Якщо ви спочатку виклали довгі методи роботи, то які інші розробники, які входять до коду, думають, коли вони прийдуть його підтримувати чи розширювати? ... Так, ви здогадалися, BIGGER функції, LONGER запущені класи та БІЛЬШЕ Бог об'єкти, і вони не мають принципів на увазі, щоб мати можливість зловити зростаючий код і правильно закріпити його. Ви "читабельний" код тепер стає гнильним безладом.

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

Я особливо не проти глобальної "карти реєстру" або "об'єкта Бога", якщо вони прості. Це дійсно залежить від дизайну системи, іноді ви можете піти від неї і залишатися простою, іноді не можете.

Не забуваймо також, що великі функції та об'єкти Бога можуть бути надзвичайно важкими для перевірки. Якщо ви хочете залишатися спритними та відчувати себе "безпечно" при повторному факторингу та зміні коду, вам потрібні тести. Тести важко записати, коли функції чи об’єкти роблять багато речей.


1
В основному хороша відповідь. Моя одна переконання полягає в тому, що якщо якийсь розробник технічного обслуговування приходить і заплутається, це його вина, що не рефакторинг, якщо тільки такі зміни не здаються досить ймовірними для передбачення, щоб виправдати їх планування або код був настільки погано задокументований / протестований / тощо . що рефакторинг був би формою неосудності.
dimimcha

Це справжня дімча. Просто я подумав, що в системі, де розробник створює папку "RequestHandlers", і кожен клас у цій папці - це обробник запитів, потім наступний розробник, який приходить разом і думає: "Мені потрібно поставити новий обробник запитів", нинішня інфраструктура майже змушує його спускатися з маршруту слідувати конвенції. Я думаю, це те, що я намагався сказати. Встановлення очевидної конвенції з самого початку може доручити навіть самим недосвідченим розробникам продовжувати схему.
Мартін Блор

1

Проблема з об'єктом бога полягає в тому, що зазвичай можна вигідно розбити його на частини. За визначенням, це робить більше ніж одне. Вся мета його розбиття на менші класи полягає в тому, щоб ви могли мати клас, який добре спрацьовує одну справу (і ви також не повинні розтягувати визначення "одна річ"). Це означає, що для будь-якого класу, коли ви дізнаєтесь одну річ, яку він повинен робити, ви зможете прочитати його досить легко і сказати, чи робить це одна справа правильно.

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


"Чи має більше ніж одне" може бути важко визначити, залежно від того, на якому рівні абстракції ви працюєте. Можна стверджувати, що будь-який метод з двома або більше рядками коду робить більше ніж одне. На протилежному кінці спектра щось складне, як сценарій сценарію, потребує цілого ряду методів, за допомогою яких усі роблять окремі "речі", які безпосередньо не пов'язані між собою, але кожен складається із важливої ​​частини "мета- річ ", яка змушує ваші сценарії працювати належним чином, а розбиття є лише стільки, що можна зробити, не порушивши механізм сценарію.
Мейсон Уілер

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

ІМХО найкращий рівень абстракції, на який потрібно подумати, - це рівень, на який ви очікуєте, що споживачі цього об'єкта будуть використовувати. Скажімо, у вас є об'єкт, який називається, Queryerякий оптимізує запити до бази даних, запитує RDBMS та аналізує результати на об'єкти для повернення. Якщо у вашому додатку Queryerє лише один спосіб зробити це і герметично зафіксовано та інкапсульовано одним способом, це робить лише одне. Якщо існує декілька способів зробити це, і хтось може подбати про деталі однієї його частини, то він робить багато речей, і ви можете захотіти розділити їх.
dimimcha

1

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

Крім того, слід пам’ятати про ТОЛИЙ, але скоріше як провід, ніж правило.


0

Що, якщо що, не так з об'єктами Бога та щільно зв'язаним кодом, якщо вам явно не потрібна більше модульність, не маєте значного дублювання і код читається?

Якщо додаток досить малий, все можна ремонтувати. У великих програмах об'єкти Бога швидко стають проблемою. Врешті-решт реалізація нової функції вимагає зміни 17 об'єктів Бога. Люди не дуже добре дотримуються 17-крокових процедур. Об'єкти Бога постійно змінюються декількома розробниками, і ці зміни доводиться повторно об'єднувати. Ви не хочете туди їхати.


0

Я поділяю Ваші занепокоєння щодо надмірної та невідповідної абстракції, але я не обов'язково так переймаюся передчасною абстракцією.

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

Це означає, що в коді є деяка ступінь прототипування, експериментування та зворотного відстеження.

Однак, не існує простого детермінованого правила, яке слід вирішити, щоб вирішити всі проблеми. Досвід - це багато, але ви повинні набути цього досвіду, помиляючись. І завжди є більше помилок, щоб зробити попередні помилки вас не підготували.

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

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


0

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

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

SOLID - це модульність, модульність - це ремонтопридатність, а ремонтопридатність - це дозволяє вам дотримуватися гуманного робочого графіка, як тільки програмне забезпечення досягне виробництва та клієнт почне помічати помилки, яких у вас немає.

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

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


0

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


1
Просимо розширити свою відповідь. Як видно, ви об'єднуєте дві ортогональні концепції, і не ясно, як результат вирішує проблеми ОП.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.