Есть ли такая вещь, как наличие слишком большого количества частных функций / методов?

63

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

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

Конечно, теперь легче читать эти общедоступные методы, но я не могу не думать, что слишком много функций - плохая практика.

[Редактировать]

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

Простой ответ: это внутреннее чувство.

Мое убеждение не подкреплено какими-то часами опыта разработки программного обеспечения. Это просто неопределенность, которая дала мне «писательский блок», но для программиста.

В прошлом я программировал только личные проекты. Совсем недавно я перешел к групповым проектам. Теперь я хочу, чтобы другие могли читать и понимать мой код.

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

Итак, я прошу это просветить себя, чтобы выбрать правильный путь.

[Редактировать]

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

Первая версия:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Вторая версия:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

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

Примечание: приведенный выше код обобщен, но имеет ту же природу, что и моя проблема.

Теперь вот мой вопрос: какой? Я выбираю первый или второй?

Сэл
источник
1
«слишком много функций - плохая практика». Почему? Пожалуйста, обновите ваш вопрос, чтобы объяснить, почему это кажется вам плохим.
S.Lott
Я бы порекомендовал прочесть понятие «сплоченность классов» - Боб Мартин обсуждает это в «Чистом коде».
19:18
Добавляя к другим ответам, split и name, но давайте помнить, что по мере роста класса переменные-члены становятся все более похожими на глобальные переменные. Один из способов улучшить проблему (помимо лучших решений, таких как рефакторинг ;-)) - это разделить эти маленькие частные методы на отдельные функции, если это возможно (в том смысле, что им не нужен доступ к большому количеству состояний). Некоторые языки допускают функции вне класса, другие имеют статические классы. Таким образом, таким образом, вы можете понять эту функцию в изоляции.
Пабло Х
Если кто-нибудь может сказать мне, почему «этот ответ бесполезен», я был бы благодарен. Всегда можно научиться. :-)
Пабло Х
@PabloH Ответ, который вы предоставили, дает хорошее наблюдение и понимание ОП, однако на самом деле он не отвечает на заданный вопрос. Я переместил его в поле для комментариев.
maple_shaft

Ответы:

36

Теперь умножьте пять частных методов на пять открытых, и вы получите около двадцати пяти скрытых методов, которые, вероятно, будут вызываться только один раз этими открытыми.

Это то, что известно как Инкапсуляция , которая создает контрольную абстракцию на более высоком уровне. Это хорошая вещь.

Это означает , что каждый , кто читает ваш код, когда они попадают в startTheEngine()метод в вашем коде, может игнорировать все нижние детали уровня , такие как openIgnitionModule(), turnDistributorMotor(), sendSparksToSparkPlugs(), injectFuelIntoCylinders(), activateStarterSolenoid(), и все другой сложной, маленькой, функции , которые должны быть запущены, чтобы облегчить гораздо большую, более абстрактную функцию startTheEngine().

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

Это также имеет дополнительное преимущество, заключающееся в упрощении тестирования кода. , Например, я могу написать контрольный пример turnAlternatorMotor(int revolutionRate)и проверить его функциональность, полностью независимую от других систем. Если есть проблема с этой функцией и результат не соответствует ожиданиям, я знаю, в чем проблема.

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

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

jmort253
источник
3
Таким образом, вы считаете, что это хорошая «инкапсуляция» - сделать логику доступной для всего класса, который вызывается только в одном месте?
Стивен Джеурис
9
@ Steven Jeuris: пока эти функции делаются приватными, я не вижу в этом проблем; на самом деле, я считаю, что, как указано в этом ответе, легче читать. Гораздо проще понять высокоуровневую публичную функцию, которая вызывает только ряд низкоуровневых приватных функций (которые, конечно, были названы соответствующим образом). Конечно, весь этот код можно было бы поместить в саму функцию высокого уровня, но тогда понадобилось бы больше времени, чтобы понять код, поскольку вам придется просматривать намного больше его в одном и том же месте.
Габлин
2
@gablin: частные функции по-прежнему доступны из всего класса. В этом (видимо спорной) статье я расскажу , как «глобальная читаемость» теряются при наличии слишком много функций. На самом деле общий принцип, кроме того, что функции должны делать только одно, их не должно быть слишком много. Вы получаете «локальную читабельность» только путем краткости, что также может быть достигнуто с помощью простых комментариев и «параграфов кода». Хорошо, код должен быть самодокументированным, но комментарии не устарели!
Стивен Джеурис
1
@ Стивен - Что легче понять? myString.replaceAll(pattern, originalData);или я вставил весь метод replaceAll в свой код, включая массив вспомогательных символов, который представляет базовый строковый объект каждый раз, когда мне нужно использовать функциональность строки?
jmort253
7
@ Стивен Джеурис: У тебя есть точка зрения. Если у класса слишком много функций (открытых или закрытых), то, возможно, весь класс пытается сделать слишком много. В таких случаях может быть лучше разделить его на несколько более мелких классов, так же, как вы бы разбили слишком большую функцию на несколько более мелких функций.
Габлин
22

И да и нет. Фундаментальный принцип: метод должен делать одно и только одно

Правильно «разбить» ваш метод на более мелкие методы. Опять же, эти методы должны быть оптимально достаточно общими, чтобы не только обслуживать этот «большой» метод, но и использоваться в других местах. В некоторых случаях метод будет следовать простой древовидной структуре, такой как метод Initialize, который вызывает InitializePlugins, InitializeInterface и т. Д.

Если у вас действительно большой метод / класс, это, как правило, признак того, что он делает много, и вам нужно провести некоторый рефакторинг, чтобы разбить «блоб». Возможно скрыть некоторую сложность в другом классе под абстракцией, и использовать внедрение зависимости

Homde
источник
1
Приятно читать, не все слепо следуют правилу «одно»! ; p Хороший ответ!
Стивен Джеурис
16
Я часто разбиваю большой метод на меньшие, хотя они будут служить только большему методу. Причиной его разделения является то, что с ним становится легче обращаться и понимать, а не просто иметь один большой кусок кода (который может быть или не быть само- и хорошо прокомментированным).
Габлин
1
Это тоже хорошо в некоторых случаях, когда вы действительно не можете избежать большого метода. Например, если у вас есть метод Initialize, он может вызывать InitializeSystems, InitializePlugins и т. Д. И т. Д. Всегда следует проверять себя, что рефакторинг не нужен
Homde
1
Ключевым моментом в каждом методе (или функции) является то, что он должен иметь четкий концептуальный интерфейс, это «контракт». Если вы не можете это зафиксировать, это будет проблемой, и вы ошиблись в факторинге. (Явное документирование контракта может быть хорошей вещью - или использование инструмента для обеспечения его исполнения в стиле Эйфелевой - но это не так важно, как иметь его там в первую очередь.)
Donal Fellows
3
@gablin: еще одно преимущество: когда я делю вещи на более мелкие функции, не думайте, что «они служат только одной другой функции», думайте, что «они пока выполняют только одну другую функцию ». Было несколько случаев, когда перефакторинг больших методов сэкономил мне кучу времени при написании других методов в будущем, потому что меньшие функции были более универсальными и пригодными для повторного использования.
GSto
17

Я думаю, что в целом лучше ошибиться из-за слишком большого количества функций, а не слишком мало. Два исключения из этого правила, которые я видел на практике, относятся к принципам СУХОЙ и ЯГНИ . Если у вас есть много почти идентичных функций, вы должны объединить их и использовать параметры, чтобы избежать повторения. У вас также может быть слишком много функций, если вы создали их «на всякий случай», и они не используются. Я не вижу абсолютно ничего плохого в том, чтобы иметь функцию, которая используется только один раз, если она добавляет удобочитаемость и удобство обслуживания.

Карл Билефельдт
источник
10

Отличный ответ jmort253 вдохновил меня на ответ "да, но" ...

Наличие большого количества маленьких частных методов не так уж и плохо, но обратите внимание на это чувство, которое заставляет вас задать вопрос здесь :). Если вы попадаете в точку, в которой вы пишете тесты, которые фокусируются только на частных методах (либо вызывая их напрямую, либо настраивая сценарий так, чтобы он вызывался определенным образом, чтобы вы могли проверить результат), тогда ты должен сделать шаг назад.

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

Пит Ходжсон
источник
Я думаю, что это отличный ответ и направление, в котором код наверняка может пойти. Лучшие практики говорят, что нужно использовать наиболее ограничивающий модификатор доступа, который дает вам необходимую функциональность. Если методы не используются за пределами класса, частный имеет смысл. Если имеет больше смысла делать методы общедоступными или перемещать их в другой класс, чтобы их можно было использовать в другом месте, то это можно сделать любым способом. +1
jmort253
8

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

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

Кевин Клайн
источник
7

Класс с 5 открытыми и 25 закрытыми методами не кажется мне таким большим. Просто убедитесь, что у ваших классов есть четко определенные обязанности, и не беспокойтесь о количестве методов.

Тем не менее, эти частные методы должны быть сосредоточены на одном конкретном аспекте всей задачи, но не способом «doSomethingPart1», «doSomethingPart2». Вы ничего не получите, если эти частные методы - просто куски произвольно разделенной длинной истории, каждый из которых не имеет смысла вне всего контекста.

user281377
источник
+1 за «Вы ничего не получите, если эти частные методы - просто части произвольно разделенной длинной истории, каждая из которых бессмысленна вне всего контекста».
Махди
4

Выдержка из Чистого кодекса Р. Мартина (стр. 176):

Даже такие фундаментальные понятия, как устранение дублирования, выразительности кода и SRP, могут зайти слишком далеко. Чтобы сделать наши классы и методы небольшими, мы можем создать слишком много крошечных классов и методов. Таким образом, это правило [минимизировать количество классов и методов] предполагает, что мы также сохраняем низкое число функций и классов. Высокие показатели класса и метода иногда являются результатом бессмысленного догматизма.

user2418306
источник
3

Некоторые варианты:

  1. Все в порядке. Просто назовите приватные методы так же, как и ваши публичные методы. И назовите свои публичные методы хорошо.

  2. Абстрагируйте некоторые из этих вспомогательных методов в вспомогательные классы или вспомогательные модули. И убедитесь, что эти вспомогательные классы / модули названы правильно и являются самостоятельными абстракциями.

yfeldblum
источник
1

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

Вот как я об этом думаю ... Если архитектура хорошо спроектирована, то вообще очевидно, где должен быть код, который делает X. Это допустимо, если есть несколько исключений, но они должны быть действительно исключительными, в противном случае реорганизовать архитектуру, чтобы обрабатывать их как стандартные.

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

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

Майкл Шоу
источник
0

Есть ли такая вещь, как наличие слишком большого количества частных функций / методов?

Да.

В Python концепция «private» (используемая в C ++, Java и C #) на самом деле не существует.

Существует «своеобразное» соглашение об именах, но это все.

Приватный может привести к сложному для тестирования коду. Это также может нарушить принцип «Открыто-закрыто», просто делая код закрытым.

Следовательно, для людей, которые использовали Python, частные функции не имеют значения. Просто сделайте все публичным и покончите с этим.

С. Лотт
источник
1
Как это может сломать открытый закрытый принцип? Код открыт как для расширения, так и для модификации независимо от того, были ли публичные методы разделены на более мелкие приватные.
byxor