Чи може / чи повинен застосовуватися Принцип єдиної відповідальності до нового коду?


20

Принцип визначається як модулі, які мають одну причину зміни . Моє запитання: напевно ці причини зміни не відомі, поки код фактично не почне змінюватися ?? Насправді, кожен фрагмент коду має численні причини, чому він міг би змінитися, але, безумовно, спроба передбачити все це та спроектувати ваш код, маючи на увазі, це призведе до дуже поганого коду. Хіба не краще ідеї почати застосовувати SRP лише тоді, коли починають надходити запити про зміну коду? Більш конкретно, коли фрагмент коду неодноразово змінювався з декількох причин, таким чином, підтверджуючи, що він має більше, ніж одну причину. Дуже протилежним виглядає спроба вгадати причини змін.

Прикладом може бути фрагмент коду, який друкує документ. Надходить запит, щоб змінити його для друку у PDF, а потім другий запит, щоб змінити його, щоб застосувати до документа певне форматування. На даний момент у вас є доказ більш ніж однієї причини зміни (і порушення SRP), і ви повинні зробити відповідне рефакторинг.


6
@Frank - це насправді зазвичай так визначено - див., Наприклад, en.wikipedia.org/wiki/Single_responsibility_principle
Joris Timmermans

1
Те, як ви це фразуєте, - це не те, як я розумію визначення SRP.
Пітер Б

2
Кожен рядок коду має (принаймні) дві причини для зміни: Це сприяє появі помилки або втручається в нову вимогу.
Барт ван Інген Шенау

1
@BartvanIngenSchenau: LOL ;-) Якщо ви бачите це таким чином, SRP не можна застосовувати ніде.
Док Браун

1
@DocBrown: Ви можете, якщо не з'єднати SRP зі зміною вихідного коду. Наприклад, якщо ви інтерпретуєте SRP як можливість дати повне уявлення про те, що клас / функція виконує в одному реченні, не вживаючи слова та (і жодних формулювань про те, щоб обійти це обмеження).
Барт ван Інген Шенау

Відповіді:


27

Звичайно, принцип YAGNI підкаже вам застосовувати SRP не раніше, ніж вам це справді потрібно. Але питання, яке ви повинні задати собі, таке: чи потрібно мені спочатку застосовувати SRP і лише тоді, коли мені потрібно фактично змінити код?

На мій досвід, застосування SRP дає вам перевагу набагато раніше: коли ви повинні з’ясувати, де і як застосувати конкретну зміну вашого коду. Для цього завдання ви повинні прочитати та зрозуміти існуючі функції та класи. Це стає набагато простіше, коли всі ваші функції та заняття несуть конкретну відповідальність. Таким чином, IMHO слід застосовувати SRP, коли він полегшує читання коду, коли він робить ваші функції меншими та більш самостійними. Тому відповідь " так" , має сенс застосовувати SRP навіть для нового коду.

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

 void RunPrintWorkflow()
 {
     var document = ReadDocument();
     var formattedDocument = FormatDocument(document);
     PrintDocumentToScreen(formattedDocument);
 }

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

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

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


Я, як правило, завжди розвиваюся з TDD, тому в моєму прикладі я фізично не зміг би зберегти всю цю логіку в одному модулі, оскільки це було б неможливо перевірити. Це лише побічний продукт TDD, а не тому, що я навмисно застосовую SRP. Мій приклад мав досить чіткі, окремі обов'язки, то, можливо, не вдалий приклад. Я думаю, що я прошу, чи можете ви написати який-небудь новий фрагмент коду і однозначно сказати, так, це не порушує SRP? Чи не «причини змінитись» по суті не визначені бізнесом?
SeeNoWeevil

3
@thecapsaicinkid: так, можна (принаймні, негайним рефакторингом). Але ви отримаєте дуже-дуже маленькі функції - і це не кожному програмісту подобається. Дивіться цей приклад: sites.google.com/site/unclebobconsultingllc/…
Doc Brown

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

1
@thecapsaicinkid: див. мою редакцію. Основна відповідальність зовнішньої функції - не друкувати на певному пристрої чи форматувати документ - це інтегрувати робочий процес друку. І коли цей робочий процес змінюється, це єдина і єдина причина, чому функція зміниться
Doc Brown

Ваш коментар щодо дотримання правильного рівня абстракції, здається, те, чого я бракував. Наприклад, у мене є клас, який я би описав як "Створює структури даних з масиву JSON". Звучить як одна відповідальність для мене. Проводиться через об'єкти в масиві JSON і відображає їх у POJO. Якщо я дотримуюсь такого ж рівня абстрагування, як і в моєму описі, важко стверджувати, що у нього є кілька причин змінити, тобто "Як JSON відображає об'єкт". Будучи менш абстрактним, я можу стверджувати, що це має більш ніж одну причину, наприклад, як я відображую зміни полей дати, як числові значення відображаються на дні тощо
SeeNoWeevil

7

Я думаю, ти нерозумієш СРП.

Єдина причина зміни - НЕ в зміні коду, а в тому, що робить ваш код.


3

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

Один з найкращих підказок щодо подібних речей - це коли ви вибираєте назви класів чи функцій. Якщо не відразу зрозуміло, який клас повинен бути названий, або ім'я особливо довге / складне, або ім'я використовує загальні терміни, такі як "менеджер" або "корисність", то це, ймовірно, порушує SRP. Аналогічно, документуючи API, це повинно стати очевидним, якщо ви порушуєте SRP, виходячи з того, яку функціональність ви описуєте.

Звичайно, у НРП є нюанси, про які ви не зможете дізнатися, як пізніше в проекті, - як здавалося, одна відповідальність склала два-три. Це випадки, коли для впровадження SRP вам доведеться переробляти рефактори. Але це не означає, що SRP слід ігнорувати, поки ви не отримаєте запит на зміну; що перемагає мету СРП!

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


3

Прикладом може бути фрагмент коду, який друкує документ. Надходить запит, щоб змінити його для друку у PDF, а потім другий запит, щоб змінити його, щоб застосувати до документа певне форматування. На даний момент у вас є доказ більш ніж однієї причини зміни (і порушення SRP), і ви повинні зробити відповідне рефакторинг.

Я так багато разів стріляв собі в ногу, витрачаючи занадто багато часу на адаптацію коду до відповідних змін. Замість того, щоб просто надрукувати проклятий дурний PDF.

Рефактор для зменшення коду

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

Створіть Bullseye

Схема одноразового використання призначена для зменшення вихідного коду та покращення використання коду. Він мав на меті створити спеціалізацію та конкретні реалізації. Сорт bullseyeу вихідному коді для вас go to specific tasks. Коли виникла проблема з друком, ви точно знали, куди слід її виправити.

Одноразове використання не означає неоднозначне розбиття

Так, у вас є код, який вже друкує документ. Так, тепер ви повинні змінити код для друку PDF-файлів. Так, тепер потрібно змінити форматування документа.

Ви впевнені, що usageзміни суттєво змінилися?

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

Чи зможе новий хлопець швидко зрозуміти це?

Завжди підтримуйте свій вихідний код у найпростішій для розуміння організації.

Не будьте спостерігачем

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

введіть тут опис зображення


2

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

Оскільки ви знаєте специфікацію та середовище, коли пишете компонент, ви можете застосувати принцип.

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

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


2

Флюп прямує в правильному напрямку. Первісно застосовувався до процедур "принцип єдиної відповідальності". Наприклад, Денніс Річі сказав, що функція повинна виконувати одне і робити це добре. Тоді, в C ++, Bjarne Stroustrup сказав, що клас повинен робити одне і робити це добре.

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

Сучасні (тобто гнучкі та DDD) реалізації більше зосереджуються на тому, що важливо для бізнесу, ніж на тому, що може виражати мова програмування. Дивовижна частина полягає в тому, що мови програмування ще не наздогнали. Старі мови, подібні до FORTRAN, охоплюють обов'язки, що відповідають основним концептуальним моделям того часу: процеси, застосовані до кожної картки під час проходження через зчитувач карт, або (як у С) обробка, що супроводжувала кожне переривання. Потім з'явилися мови ADT, які дозріли до того, щоб зрозуміти, що DDD люди згодом переосмислили як важливі (хоча Джим Сусіди більшу частину цього з'ясував, опублікував та використовував до 1968 року): що ми сьогодні називаємо класами . (Вони НЕ модулі.)

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

Що загубилося - це системні обов'язки . Ми не продаємо елементи DDD. І ми не дуже добре класифікуємо методи. Ми продаємо системні обов'язки. На якомусь рівні вам потрібно розробити свою систему на основі єдиного принципу відповідальності.

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

Ось чому я захоплююсь архітектурою DCI Тригве Реенскауга - про це описано в книзі Lean Architecture нагорі. Це, нарешті, надає деякого реального ставлення до того, що раніше було довільним і містичним підкоренням "єдиної відповідальності" - як це виявляється у більшості аргументацій, наведених вище. Цей зріст стосується ментальних моделей людини: кінцеві споживачі перші І програмісти другі. Це стосується проблем бізнесу. І, майже випадково, вона інкапсулює зміни, коли флюп кидає нам виклик.

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


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

1
Ах, ти один із таких, як один із моїх старих менеджерів. "Ми не хочемо цього розуміти: ми хочемо вдосконалити це!" Основне тематичне питання тут є одним із принципів: це "P" у "SRP". Можливо, я б прямо відповів на питання, якби це було правильним питанням: це не було. Ви можете вирішити це з тим, хто коли-небудь ставив це питання.
Копі

Тут хороший відповідь похований десь. Я думаю ...
RubberDuck

0

Так, до нового коду слід застосувати Принцип єдиної відповідальності.

Але! Що таке відповідальність?

Чи "друкує звіт відповідальність"? Я вважаю, що відповідь "Можливо".

Спробуємо використати визначення SRP як "маючи лише одну причину для зміни".

Припустимо, у вас є функція, яка друкує звіти. Якщо у вас дві зміни:

  1. змінити цю функцію, оскільки ваш звіт повинен мати чорний фон
  2. змінити цю функцію, тому що вам потрібно роздрукувати у форматі PDF

Тоді перша зміна - «змінити стиль звіту», інша - «змінити формат виводу звіту», і тепер ви повинні поставити їх у двох різних функціях, оскільки це різні речі.

Але якби ваша друга зміна була б:

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

Я б сказав, що обидві зміни - це "змінити стиль звіту", і вони можуть залишитися в одній функції.

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


0

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

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

Чудово читайте про ці речі: Lean Architecture by Coplien


0

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

Це системна відповідальність. Він реалізований як механізм - MVC - який включає принтер (Вид), надруковану річ (Модуль) та запит та параметри принтера (від Контролера).

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


0

Хіба не краще ідеї починати застосовувати SRP лише тоді, коли починають надходити запити про зміну коду?

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

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

Прикладом може бути фрагмент коду, який друкує документ. Надходить запит, щоб змінити його для друку у PDF, а потім другий запит, щоб змінити його, щоб застосувати до документа певне форматування. На даний момент у вас є доказ більш ніж однієї причини зміни (і порушення SRP), і ви повинні зробити відповідне рефакторинг.

Це строго свідчить про те, що загальна відповідальність - «друк» коду - несе відповідальність, і її слід розділити на частини. Це не є порушенням SRP як такого, а скоріше вказівкою на те, що поділ (можливо, на підзадачі «форматування» та «надання»), ймовірно, потрібен. Чи можете ви чітко описати ці обов'язки, щоб ви могли зрозуміти, що відбувається в межах підзадач, не дивлячись на їх виконання? Якщо ви можете, вони, ймовірно, будуть розумні розбиття.

Це також може бути зрозумілішим, якщо ми подивимось на простий реальний приклад. Розглянемо sort()корисний метод в java.util.Arrays. Що це робить? Він сортує масив, і це все, що він робить. Він не роздруковує елементи, не знаходить самого морально придатного члена, не свистить Діксі . Він просто сортує масив. Вам також не потрібно знати. Сортування - це лише відповідальність цього методу. (Насправді, у Java існує багато методів сортування з дещо негарних технічних причин, пов’язаних із примітивними типами; вам не потрібно приділяти цьому уваги, оскільки всі вони мають рівнозначні обов'язки.)

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

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