Насколько велика для класса?

29

Я давно работаю разработчиком (мне 49 лет), но я довольно новичок в объектно-ориентированной разработке. Я читал об ОО со времен Эйфеля Бертранда Мейера, но мало занимался программированием.

Дело в том, что каждая книга по ОО-дизайну начинается с примера лодки, автомобиля или какого-либо обычного объекта, который мы часто используем, и они начинают добавлять атрибуты и методы, а также объясняют, как они моделируют состояние объекта и что можно сделать с помощью Это.

Таким образом, они обычно выглядят так: «чем лучше модель, тем лучше она представляет объект в приложении и тем лучше все получается».

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

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

Я бы сказал, что подпадает под классификацию «Зависть к особенностям» или «Неприемлемая близость» поста Джеффа Этвуда « Запахи кода» на тему «Кодирующий ужас». Я бы назвал это здравым смыслом. Если я могу оформить, утвердить или отменить свой заказ в режиме реальной покупки, то PurchaseOrderкласс должен иметь issuePO, approvePOи cancelPOметоду.

Разве это не идет с принципами «максимизировать сплоченность» и «минимизировать сцепление», которые я понимаю как краеугольные камни ОО?

Кроме того, разве это не помогает в обслуживании класса?

Мигель Велосо
источник
17
Нет причин кодировать имя типа в название метода. Т.е. , если у вас есть PurchaseOrder, вы могли бы просто назвать свои методы issue, approveи cancel. POСуффикс добавляет только отрицательное значение.
дэш-том-бэнг
2
Пока вы держитесь подальше от методов черной дыры , вы должны быть в порядке. :)
Марк С
1
С моим текущим разрешением экрана я бы сказал, 145 символов.
DavRob60
Спасибо всем за ваши комментарии, они действительно помогли мне с этим вопросом. Если бы этот сайт позволил, я бы также выбрал ответ TMN и Lazarus, но он разрешил только один, поэтому он пошел к Крейгу. С наилучшими пожеланиями.
Мигель Велосо

Ответы:

27

Класс должен использовать принцип единой ответственности. Большинство очень больших классов, которые я видел, делают для многих вещей, поэтому они слишком большие. Посмотрите на каждый метод и код, решите, должен ли он быть в этом классе или отдельно, дублированный код является подсказкой. Возможно, у вас есть метод issuePO, но он содержит, например, 10 строк кода доступа к данным? Этот код, вероятно, не должно быть там.

Craig
источник
1
Крейг, я не знал о принципе единой ответственности и просто читал об этом. Первое, что мне пришло в голову, было, какова сфера ответственности? для ПО, я бы сказал, управляет его состоянием, и это является причиной для методов issuePO, ApprovePO и cancelPO, но также включает взаимодействие с классом PurchaseOrderItem, который обрабатывает состояние элемента ПО. Поэтому мне не ясно, соответствует ли этот подход ПСП. что бы вы сказали?
Мигель Велозо
1
+1 за очень хороший и лаконичный ответ. Реально, не может быть определенной меры для определения размера класса. Применение SRP гарантирует, что класс будет настолько большим, насколько это необходимо .
С.Робинс
1
@MiguelVeloso - Принятие заказа на ПО, вероятно, связано с другой логикой и правилами, связанными с выдачей заказа на покупку. Если это так, имеет смысл разделить обязанности на POApprover и POIssuer. Идея состоит в том, что если правила изменяются для одного, зачем вам связываться с классом, который содержит другой.
Мэтью Флинн
12

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

Mike42
источник
1
С другой стороны, большой размер указывает на то, что эти методы, вероятно, не относятся к рассматриваемому классу.
Билли ONEAL
5
@ Билли: Я бы сказал, что большие классы - это запах кода, но они не являются автоматически плохими.
Дэвид Торнли
@ Дэвид: Вот почему там есть слово «вероятно» - это важно :)
Billy ONeal
Я должен был бы не согласиться ... Я работаю над проектом, который имеет довольно непротиворечивое соглашение об именах, и все хорошо продумано ... но у меня все еще есть класс, который содержит 50 тысяч строк кода. Это просто слишком большой :)
Jay
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-дверным автомобилям. Хотя кабриолеты и 2-дверные машины по определению являются «автомобилями», их реализация в классе carусложняет класс. Класс становится все труднее использовать и труднее поддерживать.

Помимо этих двух рекомендаций, я бы предложил четкое определение области действия класса. Как только вы определите область действия, реализуйте класс строго на основании определения. Большую часть времени люди получают «слишком большой» класс из-за плохого определения области действия или из-за произвольных функций, добавленных в класс

YYC
источник
7

Скажите, что вам нужно в 300 строк

Примечание. Это практическое правило предполагает, что вы придерживаетесь подхода «один класс на файл», который сам по себе является хорошей идеей в отношении удобства сопровождения.

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

В основном это сводится к ремонтопригодности. Если ваш объект настолько сложен, что вы не можете выразить его поведение в 300 строк, это будет очень трудно понять кому-то еще.

Я обнаружил, что изгибаю это правило в Model -> ViewModel -> View world, где я создаю «объект поведения» для страницы пользовательского интерфейса, потому что для описания полной серии поведения часто требуется более 300 строк. страница. Однако я все еще новичок в этой модели программирования и нахожу хорошие способы рефакторинга / повторного использования поведения между страницами / моделями представления, что постепенно уменьшает размер моих ViewModels.

Джей Биверс
источник
Мое личное руководство также составляет около 300 строк. +1
Марси
Я думаю, что ОП ищет эвристику, и это хорошо.
Неонтапир
Спасибо, важно использовать точные цифры в качестве ориентира.
Уилл Шеппард
6

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

Разве это не идет с принципами «максимизировать сплоченность» и «минимизировать сцепление», которые я понимаю как краеугольные камни ОО?

На самом деле, это краеугольный камень программирования, из которого ОО является лишь одной парадигмой.

Я позволю Антуану де Сент-Экзюпери ответить на ваш вопрос (потому что я ленивый;)):

Совершенство достигается не тогда, когда нечего добавить, а когда нечего отнять.

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

Матье М.
источник
3

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

С carи personмодели настолько распространены, что было бы , как положить код для создания электрического соединения, или даже вращение стартера, в personклассе. Я надеюсь, что это будет явно неправильно для любого опытного программиста.

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

штрих-кот-бэнг
источник
+1 - хорошие эмпирические правила, но не быть авторитарным в этом. Я бы сказал, однако, что просто потому, что класс разбит, не означает, что разные классы должны касаться частных членов друг друга.
Билли ONEAL
Я не думаю, что разные классы должны когда-либо касаться личных частей друг друга. Хотя доступ «друга» удобен для того, чтобы что-то запустить и запустить, (для меня) это ступенька к лучшему дизайну. В общем, я также избегаю доступа, так как в зависимости от внутренних функций другого класса это еще один запах кода .
dash-tom-bang
1

Когда в определении класса есть пара сотен методов, он слишком велик.

С Джонсон
источник
1

Я полностью согласен с использованием принципа единой ответственности для классов, но если он соблюдается и приводит к большим классам, то пусть будет так. Реальное внимание должно быть уделено тому, чтобы отдельные методы и свойства не были слишком большими, цикломатическая сложность должна быть там руководством, а последующее может привести ко многим частным методам, которые увеличат общий размер класса, но сделают его читаемым и тестируемым до детального уровня. Если код остается читаемым, то размер не имеет значения.

Лазарь
источник
1

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

Любой, кто выдвигает «правила» для размера метода / класса, очевидно, не провел достаточно времени в реальном мире. Как уже упоминали другие, это часто показатель рефакторинга, но иногда (особенно в работе типа «обработка данных») вам действительно нужно написать класс с одним методом, который охватывает более 500 строк. Не зацикливайтесь на этом.

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

Я нашел несколько авторов, которые дают рецепты, такие как «класс должен уместиться всего на одной странице»

С точки зрения только физического размера класса, вид большого класса является признаком запаха кода, но не обязательно означает, что всегда есть запахи. (Обычно они есть) Как сказали бы пираты в Пиратах Карибского моря, это скорее то, что вы бы назвали «руководящими принципами», чем фактическими правилами. Я старался строго следовать «не более ста LOC в классе» один раз, и в результате получилось МНОГО ненужного чрезмерного проектирования.

Таким образом, они обычно выглядят так: «чем лучше модель, тем лучше она представляет объект в приложении и тем лучше все получается».

Я обычно начинаю свои проекты с этого. Что этот класс делает в реальном мире? Как мой класс должен отражать этот объект, как указано в его требованиях? Мы добавим немного состояния здесь, поле там, метод для работы с этими полями, добавим еще несколько и вуаля! У нас есть рабочий класс. Теперь заполните подписи правильными реализациями, и мы готовы, или вы так думаете. Теперь у нас есть хороший большой класс со всей необходимой нам функциональностью. Но проблема в том, что он не принимает во внимание то, как работает реальный мир. И под этим я подразумеваю, что это не учитывает "ИЗМЕНЕНИЕ".

Причина, по которой классы предпочтительно делятся на более мелкие части, заключается в том, что позже трудно изменить большие классы, не затрагивая существующую логику, не вводя новую или две ошибки непреднамеренно или просто усложняя повторное использование. Это где рефакторинг, шаблоны проектирования, SOLID и т. Д. Вступают в игру, и конечным результатом обычно является маленький класс, работающий с другими более мелкими, более детальными подклассами.

Кроме того, в вашем примере добавление IssuePO, ApprovePO и Cancel PO в класс PurchaseOrder кажется нелогичным. Заказы на покупку не выдают, не утверждают и не отменяют сами. Если у PO должны быть методы, то методы должны работать в своем состоянии, а не в классе в целом. Это действительно классы данных / записей, над которыми работают другие классы.

Jonn
источник