Принцип єдиної відповідальності - чи я його надто зловживаю?


13

Для довідки - http://en.wikipedia.org/wiki/Single_responsibility_principle

У мене є тестовий сценарій, коли один модуль програми відповідає за створення записів у головній книзі. Є три основні завдання, які можна було б виконати -

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

(На кожній сторінці є ще кілька операцій / перевірок, але заради стислості я обмежуся цим)

Тому я вирішив створити три різні класи -

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

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

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


2
IMHO немає сенсу намагатися відповісти на ваше запитання взагалі, не знаючи двох речей: Наскільки великі ці класи (за кількістю методів та LOC)? І наскільки великі / складніші вони, як очікується, зростуть в осяжному майбутньому?
Péter Török

2
10 методів кожен є IMHO чіткою ознакою не ставити код в один клас з 30 методами.
Doc Brown

6
Це прикордонна територія: клас з 100 LOC та 10 методів не надто малий, а один із 300 LOC не надто великий. Однак 30 методів в одному класі мені занадто багато звучать. В цілому я схильний погоджуватися з @Doc тим, що менше ризиків мати багато крихітних класів, ніж мати один клас зайвої ваги. Особливо враховуючи, що заняття, природно, набирають вагу з часом ...
Péter Török

1
@ PéterTörök - Я погоджуюся, якщо код не вдасться відновити до одного класу, який можна використовувати інтуїтивно, який повторно використовує код, а не дублює функціональність, як я вважаю, що в ОП.
SoylentGray

1
Можливо, застосування MVC очистить це все: три види, одна модель (Ledger) і, ймовірно, три контролери.
Кевін Клайн

Відповіді:


19

Посилаючись на @YannisRizos тут :

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

Я не згоден, принаймні зараз, приблизно через 30 років досвіду програмування. Менші класи ІМХО краще підтримувати майже у кожному випадку, про який я можу подумати у таких випадках. Чим більше функцій ви вкладете в один клас, тим менше будете мати структури, а якість вашого коду погіршується - щодня дещо потроху. Коли я правильно зрозумів Таруна, кожну з цих 3 операцій

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

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

Якщо у вас є спільні функціональні можливості між цими 3 класами, вони належать або до загального базового класу, до Ledgerсамого класу (я припускаю, що у вас є, принаймні, для операцій CRUD), або до окремого допоміжного класу.

Якщо вам потрібно більше аргументів для створення безлічі менших класів, рекомендую ознайомитися з книгою Роберта Мартіна «Чистий код» .


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

2
"Менші класи краще підтримувати майже в кожному випадку, про який я думаю." Я не думаю, що навіть Чистий кодекс піде так далеко. Чи дійсно ви б заперечували, що у шаблоні MVC має бути один контролер на кожній сторінці (або у випадку використання, як ви сказали)? У Refactoring у Фаулера є два запахи коду разом: один стосується наявності багатьох причин зміни класу (SRP), а другий - про необхідність редагування багатьох класів для однієї зміни.
pdr

@pdr, ОП заявляє, що між цими класами немає загальної функціональності, тому (для мене) важко уявити ситуацію, коли вам потрібно торкнутися більше одного, щоб зробити одну зміну.
Péter Török

@pdr: Якщо у вас є занадто багато класів для редагування для однієї зміни, це здебільшого знак того, що ви не перетворювали загальну функціональність на окреме місце. Але у наведеному вище прикладі це, мабуть, призведе до четвертого класу, а не лише одного.
Doc Brown

2
@pdr: BTW, ви насправді читали "Чистий код"? Боб Мартін дуже далеко розбиває код на більш дрібні одиниці.
Doc Brown

11

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

Як пише @Karpie у попередній відповіді :

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

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

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

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


1
Наскільки я розумію, SRP насправді не є дизайнерською схемою.
Doc Brown

@DocBrown Звичайно, не дизайнерський зразок, але обговорення цього питання надзвичайно актуальне ... Хороший улов, проте я оновив відповідь
yannis

4

Розглянемо ці класи:

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

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

  • CreateLedgerEntryPage: Якщо у вас ViewLedgerEntryPageє повна можливість редагування, відповідальність цього класу, можливо, може бути виконана шляхом редагування "пустого запису", що може або не має сенсу.

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


Існує таке поняття, як надмірне використання SRP : Якщо для читання вмісту файлу вам потрібен десяток класів, можна вважати, що ви робите саме це.

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


Гадаю, навіть я не міг би описати відповідальність цих класів краще, ніж ви.
Тарун

2

Чи мають ці заняття лише одну причину змінити?

Якщо ви цього ще не знаєте, то, можливо, ви натрапили на спекулятивний дизайн / над інженерною пасткою (занадто багато класів, застосовано занадто складні шаблони дизайну). Ви не задовольнили YAGNI . На ранніх етапах життєвого циклу програмного забезпечення (деякі) вимоги можуть бути незрозумілими. Таким чином, напрямок змін для вас зараз не видно. Якщо ви це усвідомлюєте, зберігайте це в одному або мінімальній кількості класів. Однак це пов'язано з відповідальністю: Як тільки вимоги та напрямок змін стануть зрозумілішими, вам потрібно переосмислити поточну конструкцію та зробити рефакторинг, щоб задовольнити причину зміни.

Якщо ви вже знаєте, що існують три різні причини змін і вони відображають ці три класи, то ви просто застосували потрібну дозу SRP. Знову це пов’язано з певною відповідальністю: якщо ви помиляєтесь або майбутні вимоги несподівано змінюються, вам потрібно переосмислити поточну конструкцію та зробити рефакторинг, щоб задовольнити причину зміни.

Вся справа в тому, що:

  • Слідкуйте за драйверами щодо змін.
  • Вибрали гнучку конструкцію, яку можна реконструювати.
  • Будьте готові до рефакторного коду.

Якщо у вас є такий спосіб мислення, ви сформуєте код без особливого страху спекулятивного дизайну / над технікою.

Однак завжди є факторний час: для багатьох змін у вас не буде потрібного часу. Реконструкція коштує часу та зусиль. Я часто стикався з ситуаціями, коли я знав, що мені потрібно змінити дизайн, але через часові обмеження мені довелося відкласти його. Це станеться і цього не уникнути. Я виявив, що рефакторинг під інженерним кодом легше, ніж над інженерним кодом. YAGNI дає мені гарне керівництво: Якщо ви сумніваєтесь, зведіть до мінімуму кількість класів та застосування дизайнерських моделей.


1

Є три причини, щоб викликати SRP як причину поділу класу:

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

Є три причини відмовитись від розділення класу:

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

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

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


приємно бачити іншу точку зору
Тарун

0

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


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

0

ІМХО вам взагалі не слід використовувати заняття. Тут немає даних абстракцій. Книги є конкретним типом даних. Методами маніпулювання та їх відображення є функції та процедури. Звичайно, буде багато спільно використовуваних функцій, таких як форматування дати. Немає місця для абстрагування та приховування будь-яких даних у класі в режимах відображення та вибору.

Існує певний випадок приховування стану в класі для редагування книги.

Незалежно від того, зловживаєте ви SRP чи ні, ви зловживаєте чимось більш фундаментальним: ви зловживаєте абстракцією. Це типова проблема, породжена міфічним поняттям OO - це парадигма здорового розвитку.

Програмування конкретно на 90%: використання абстрагування даних має бути рідкісним і ретельно продуманим. З іншого боку, функціональна абстракція повинна бути більш поширеною, оскільки мовні деталі та вибір алгоритмів потрібно відокремлювати від семантики операції.


-1

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


2
Для мене клас "... менеджер", як правило, показує, що ви не можете потурбуватися про його подальше розбиття. Що таке управління класом? Презентація, наполегливість?
sebastiangeiger

-1. Внесення 3 або більше випадків використання в 1 клас - це саме те, чого намагається уникнути СРП - і з поважних причин.
Док Браун

@sebastiangeiger Отже, чим займаються три класи ОП? Презентація чи наполегливість?
sevenseacat

@Karpie Я чесно сподіваюся на презентацію. Зрозуміло, що приклад насправді не дуже хороший, але я би сподівався, що "... Сторінка" на веб-сайті робить щось подібне до точки зору.
sebastiangeiger

2
Насправді, вам потрібен лише один клас для всієї програми, з єдиною відповідальністю зробити користувачів щасливими ;-)
Péter Török
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.