Наскільки велика норма для класу?


29

Я давно розробник (мені 49), але досить новий в об'єктно-орієнтованому розвитку. Я читав про ОО ще з Ейфеля Бертрана Мейєра, але дуже мало програмував ОО.

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

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

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

Візьмемо для прикладу PurchaseOrderклас, який має кінцеву машину, яка контролює свою поведінку та колекцію PurchaseOrderItem, одним із аргументів на роботі є те, що ми повинні використовувати PurchaseOrderпростий клас з деякими методами (трохи більше, ніж клас даних), і мати PurchaseOrderFSM«експерт клас» , який обробляє кінцевий автомат для PurchaseOrder.

Я б сказав, що це підпадає під класифікацію «Особливості заздрості» або «неналежної інтимності» публікації коду Джеффа Етвуда « Запахи запаху» про кодування жаху. Я б просто назвав це здоровим глуздом. Якщо я можу оформити, затвердити або скасувати своє замовлення в режимі реального покупки, то PurchaseOrderклас повинен мати issuePO, approvePOі cancelPOметоду.

Чи це не стосується принципів "максимальної згуртованості" та "мінімізації зв'язку", які я розумію як наріжні камені ОО?

Крім того, чи це не допомагає дотримуватися ремонтопридатності класу?


17
Немає причин кодувати ім'я типу у назві методу. Тобто , якщо у вас є PurchaseOrder, ви могли б просто назвати свої методи issue, approveі cancel. POСуфікс додає тільки негативне значення.
dash-tom-bang

2
Поки ви тримаєтесь подалі від методів чорної діри , ви повинні бути в порядку. :)
Марк C

1
З моєю поточною роздільною здатністю екрана, я б сказав 145 великих символів.
DavRob60

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

Відповіді:


27

Клас повинен використовувати Принцип єдиної відповідальності. Більшість дуже великих класів, яких я бачив, роблять багато речей, тому вони занадто великі. Подивіться на кожен метод і код вирішуйте, чи повинен він бути в цьому класі або окремий, дублікат коду - підказка. У вас може бути метод problemPO, але чи містить він, наприклад, 10 рядків коду доступу до даних? Такого коду, мабуть, не повинно бути там.


1
Крейг, я не знав принципу єдиної відповідальності, і просто читав про нього. Перше, що мені прийшло в голову, це те, яка сфера відповідальності? для PO, я б сказав, це керування його станом, і це причина для видачіPO, затвердженняPO та методів CancelPO, але також включає взаємодію з класом PurchaseOrderItem, який обробляє стан елемента PO. Тож мені не ясно, чи відповідає цей підхід СРП. Що б ти сказав?
Мігель Велосо

1
+1 за дуже приємну та лаконічну відповідь. Реально не може бути остаточної міри щодо того, яким має бути великий клас. Застосування SRP гарантує, що клас настільки ж великий, як і повинен бути.
Робінз

1
@MiguelVeloso - Затвердження PO, можливо, має іншу логіку та правила, пов'язані з ним, ніж видача PO. Якщо так, то є сенс розділити відповідальність на POApprover та POIssuer. Ідея полягає в тому, що якщо правила змінюються для одного, чому б ви хотіли возитися з класом, який містить інший.
Меттью Флінн

12

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


1
З іншого боку, великий розмір вказує на те, що ці методи, ймовірно, не стосуються відповідного класу.
Біллі ONeal

5
@Billy: Я б сказав, що великі класи - це запах коду, але вони не є поганими автоматично.
Девід Торнлі

@David: Ось чому там слово "напевно" - це важливо :)
Біллі ONeal

Мені б не погодитися ... Я працюю над проектом, який має досить послідовну конвенцію про іменування, і все добре викладено ..., але у мене все ще є клас, який містить 50k рядків коду. Просто занадто велика :)
Джей

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

7

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

Для вашої автомобільної аналогії, якщо клас carзанадто великий, це, мабуть, є свідченням того, що його потрібно розділити на клас engine, клас doors, клас windshields і т.д.


8
І давайте не забувати автомобіль повинен реалізувати інтерфейс автомобіля. :-)
Кріс

7

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

  1. Чи існує багато змінних, які не залежать одна від одної? (Моя особиста толерантність у більшості випадків становить приблизно 10 ~ 20)
  2. Чи існує багато методів, розроблених для кутових справ? (Моя особиста толерантність ... ну, це дуже залежить від мого настрою, я думаю)

Що стосується 1, уявіть, що ви визначаєте розмір свого лобового скла, розмір колеса, модель лампочки заднього світла та 100 інших деталей у вашому класі car. Хоча всі вони "відносяться" до машини, дуже важко простежити поведінку такого класу car. І коли колись ваш колега випадково (або, в іншому випадку, навмисно) змінить розмір лобового скла на основі моделі лампочки задніх ламп, вам доведеться назавжди з’ясувати, чому лобове скло більше не вписується у ваш автомобіль.

Що стосується 2-х, уявіть, що ви намагаєтеся визначити всі поведінки, які може мати автомобіль у класі car, але car::open_roof()вони будуть доступні лише для кабріолетів, car::open_rear_door()але не стосуються цих автомобілів з двома дверима. Хоча кабріолети та 2-дверні машини за визначенням є "машинами", реалізація їх у класі carускладнює клас. Клас стає складнішим у використанні та складнішим у обслуговуванні.

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


7

Скажіть, що вам потрібно в 300 рядках

Примітка. Це правило передбачає, що ви керуєтесь підходом "один клас на файл", який сам по собі є гарною ідеєю ремонту.

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

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

Я виявляю, що я згинаю це правило в Моделі -> ViewModel -> Погляд світу, де я створюю "об'єкт поведінки" для сторінки інтерфейсу користувача, оскільки для опису повної серії поведінки часто потрібно більше 300 рядків сторінки. Однак я все ще новачок у цій моделі програмування і знаходжу хороші способи рефакторингу / повторного використання поведінки між сторінками / моделями перегляду, що поступово зменшує розмір моїх ViewModels.


Моє особисте керівництво також близько 300 рядків. +1
Марсі

Я думаю, що ОП шукає евристику, і це добре.
neontapir

Дякую, корисно використовувати точні цифри як орієнтир.
Буде Шеппард

6

Давайте розберемося назад:

Чи це не стосується принципів "максимальної згуртованості" та "мінімізації зв'язку", які я розумію як наріжні камені ОО?

Насправді, це наріжний камінь програмування, OO - лише одна парадигма.

Я дозволю Антуану де Сент-Екзюпері відповісти на ваше запитання (бо я лінивий;)):

Досконалість досягається не тоді, коли більше нічого не можна додати, а коли не залишається нічого, щоб забрати.

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


3

Я з ОП за класифікацією Feature Envy. Ніщо не дратує мене більше, ніж проведення групи занять з купою методів, які працюють над внутрішніми характеристиками інших класів. OO пропонує моделювати дані та операції над цими даними. Це не означає, що ви ставите операції в інше місце, ніж дані! Він намагається змусити вас зібрати дані та ці методи разом .

З настільки поширеними моделями carта personмоделями, це було б як введення коду для створення електричного підключення або навіть відкручування стартера до personкласу. Я би сподівався, що це, очевидно, буде неправильним для будь-якого досвідченого програміста.

У мене насправді немає ідеального розміру класу чи методу, але я вважаю за краще тримати свої методи та функції на одному екрані, щоб я міг бачити їхню повноту в одному місці. (У мене Vim налаштований відкривати вікно з 60 рядками, що, як правило, відповідає кількості рядків на друкованій сторінці.) Є місця, де це не має великого сенсу, але це працює для мене. Мені подобається дотримуватися тієї ж інструкції щодо визначень класів і намагатися тримати мої файли під кількома "сторінками" довгими. Що-небудь понад 300, 400 рядків починає відчувати себе непростим (здебільшого тому, що клас почав брати на себе занадто багато прямих обов'язків).


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

Я не думаю, що різні класи ніколи не повинні торкатися приватних частин один одного. Хоча доступ до "товариша" зручний для отримання чого-небудь роботи та запуску, (для мене) це крокова планка до кращого дизайну. Як правило, я також уникаю аксесуарів, оскільки залежно від внутрішніх характеристик іншого класу - це ще один Code Smell .
Даш-Том-Банг

1

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


1

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


1

Оформлення замовлення на купівлю часто є складним процесом, який передбачає більше, ніж просто замовлення на купівлю. У цьому випадку, правильне ведення ділової логіки в окремому класі та спрощення замовлення на купівлю. Взагалі, правда, ти маєш рацію - ти не хочеш класів "структури даних". Наприклад, ваш метод бізнес-класу "POManager", issue()ймовірно, повинен викликати issue()метод PO . Потім PO може встановити свій статус на "Видано" та відзначити дату випуску, тоді як POManager може потім надіслати повідомлення на суму кредиторської заборгованості, щоб повідомити про очікування рахунку та інше повідомлення про інвентаризацію, щоб вони знали, що щось надходить і очікувана дата.

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


Я думаю, що POManager буде "контролером", а PurchaseOrder буде "моделлю" у шаблоні MVC, правда?
Мігель Велосо

0

Я знайшов кількох авторів, які дають такі рецепти, як "клас повинен вміщуватися лише на одній сторінці"

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

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

Я зазвичай починаю свої проекти з цього. Що робить цей клас у реальному світі? Як мій клас повинен відображати цей об'єкт, як зазначено в його вимогах? Додаємо сюди трохи стану, поля там, метод роботи над цими полями, додаємо ще кілька і вуаля! У нас працює робочий клас. Тепер заповніть підписи належними реалізаціями, і нам добре піти, або так, як ви думаєте. Зараз у нас хороший великий клас з усіма необхідними нам функціоналами. Але проблема в тому, що вона не бере до уваги те, як працює реальний світ. І я маю на увазі, що це не враховує "ЗМІНИ".

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

Також у вашому прикладі додавання IssuePO, ApprovePO та Скасувати PO у клас PurchaseOrder видається нелогічним. Замовлення на купівлю не видають, не затверджують або скасовують самі. Якщо PO повинен мати методи, то методи повинні працювати на його стані, а не на класі в цілому. Вони насправді є лише класами даних / записів, над якими працюють інші класи.


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