Функции, которые вызывают только другие функции. Это хорошая практика?


13

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

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

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

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

Или, если мы пойдем еще дальше:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

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

Мысли?


В конце дня ... это более читабельно? Да, так что это лучшее решение.
Сильванаар

Ответы:


18

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

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

Обратите внимание, что это неправильный выбор именования:

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

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


6
Я в принципе согласен с этим ответом, за исключением того, что не вижу, как «processSection1» является значимым именем. Это практически эквивалентно "callAllSection1Functions".
Эрик Кинг,

2
@EricKing: callAllSection1Functionsутечка деталей о реализации. С внешней точки зрения, не имеет значения, processSection1откладывает ли свою работу «все функции section1», выполняет ли она это напрямую или использует «пыльную пыль» для вызова единорога, который будет выполнять реальную работу. Все , что на самом деле дело в том, что после вызова processSection1, section1 можно считать обработаны . Я согласен, что обработка - это общее имя, но если у вас высокая сплоченность (к которой вы всегда должны стремиться), тогда ваш контекст обычно достаточно узок, чтобы устранить неоднозначность.
back2dos

2
Я имел в виду только то, что методы с именем "processXXX" - это почти всегда ужасные, бесполезные имена. Все методы «обрабатывают» вещи, поэтому называть его «processXXX» примерно так же полезно, как называть его «doSomethingWithXXX» или «манипулироватьXXX» и тому подобное. Почти всегда есть лучшее имя для методов с именем 'processXXX'. С практической точки зрения, это так же полезно (бесполезно), как 'callAllSection1Functions'.
Эрик Кинг,

4

Вы сказали, что используете C #, поэтому мне интересно, могли бы вы построить структурированную иерархию классов, используя тот факт, что вы используете объектно-ориентированный язык? Например, если имеет смысл разбить отчет на разделы, создайте Sectionкласс и расширьте его для конкретных требований клиента.

Возможно, имеет смысл думать об этом так, как если бы вы создавали неинтерактивный графический интерфейс. Подумайте об объектах, которые вы можете создать, как если бы они были различными компонентами графического интерфейса. Спросите себя, как будет выглядеть базовый отчет? Как насчет сложного? Где должны быть точки расширения?


3

По-разному. Если все функции, которые вы создаете, на самом деле являются одной единицей работы, то это нормально, если это просто строки 1-15, 16-30, 31-45 их вызывающей функции, что плохо. Длинные функции не являются злыми, если они делают одно, если у вас есть 20 фильтров для вашего отчета, имеющих функцию проверки, которая проверяет, что все двадцать будут длинными. Это прекрасно, разбивка по фильтрам или группам фильтров просто добавляет дополнительную сложность без реальной выгоды.

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


3
По моему опыту, длинные функции являются злом.
Бернард

Если бы ваш пример был на языке OO, я бы создал базовый класс фильтров, и каждый из 20 фильтров был бы в разных производных классах. Оператор Switch будет заменен вызовом создания, чтобы получить правильный фильтр. Каждая функция делает одно, а все маленькие.
ДХМ

3

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

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

Подумайте о наличии класса ReportBuilder, в котором вы можете передать данные отчета. Если вы можете разбить ReportBuilder на отдельные классы, сделайте это также.


2
Процедуры выполняют совершенно хорошую функцию.
DeadMG

1

Да.

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

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


1

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

processSection1HeaderData();
processSection2HeaderData();

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


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

1

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

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


0

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

Вы не упоминаете, какую технологию вы используете для разработки этих отчетов. Похоже, вы могли бы использовать C #. Это верно? Если это так, вы можете также рассмотреть директиву #region в качестве альтернативы, если вы не хотите разбивать свою функцию на более мелкие.


Да, я работаю в C #. Возможно, я смогу повторно использовать некоторые функции, но для многих из этих отчетов разные разделы имеют совершенно разные данные и макеты (требование заказчика).
Эрик С.

1
+1 за исключением предложений регионов. Я бы не использовал регионы.
Бернард

@ Бернард - какая-то конкретная причина? Я предполагаю, что спрашивающий рассмотрел бы использование #regions, только если он уже исключил разбиение функции.
Джошуа Кармоди

2
Регионы, как правило, скрывают код и могут злоупотреблять (т.е. вложенные регионы). Мне нравится видеть весь код сразу в файле кода. Если мне нужно визуально отделить код в файле, я использую строку с косой чертой.
Бернард

2
Регионы обычно являются признаком того, что код нуждается в рефакторинге
кодировщик
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.