В настоящее время я работаю над набором отчетов, которые имеют много разных разделов (все требуют различного форматирования), и я пытаюсь найти лучший способ структурировать мой код. Подобные отчеты, которые мы делали в прошлом, в итоге имеют очень большие (более 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();
...
}
Это действительно лучшее решение? С организационной точки зрения я полагаю, что это так (т.е. все гораздо более организовано, чем могло бы быть), но я не уверен в читабельности кода (потенциально большие цепочки функций, которые вызывают только другие функции).
Мысли?
источник
Ответы:
Обычно это называется функциональной декомпозицией и, как правило, полезно сделать, если все сделано правильно.
Кроме того, реализация функции должна быть в пределах одного уровня абстракции. Если вы берете
largeReportProcessingFunction
, то его роль состоит в том, чтобы определить, какие различные этапы обработки должны быть выполнены в каком порядке. Реализация каждого из этих шагов находится на уровне абстракции ниже иlargeReportProcessingFunction
не должна зависеть от него напрямую.Обратите внимание, что это неправильный выбор именования:
Вы видите
callAllSection1Functions
, это имя, которое на самом деле не обеспечивает абстракцию, потому что оно на самом деле не говорит, что оно делает, а скорее как оно это делает.processSection1
Вместо этого он должен быть вызван или что-то еще значимое в контексте.источник
callAllSection1Functions
утечка деталей о реализации. С внешней точки зрения, не имеет значения,processSection1
откладывает ли свою работу «все функции section1», выполняет ли она это напрямую или использует «пыльную пыль» для вызова единорога, который будет выполнять реальную работу. Все , что на самом деле дело в том, что после вызоваprocessSection1
, section1 можно считать обработаны . Я согласен, что обработка - это общее имя, но если у вас высокая сплоченность (к которой вы всегда должны стремиться), тогда ваш контекст обычно достаточно узок, чтобы устранить неоднозначность.Вы сказали, что используете C #, поэтому мне интересно, могли бы вы построить структурированную иерархию классов, используя тот факт, что вы используете объектно-ориентированный язык? Например, если имеет смысл разбить отчет на разделы, создайте
Section
класс и расширьте его для конкретных требований клиента.Возможно, имеет смысл думать об этом так, как если бы вы создавали неинтерактивный графический интерфейс. Подумайте об объектах, которые вы можете создать, как если бы они были различными компонентами графического интерфейса. Спросите себя, как будет выглядеть базовый отчет? Как насчет сложного? Где должны быть точки расширения?
источник
По-разному. Если все функции, которые вы создаете, на самом деле являются одной единицей работы, то это нормально, если это просто строки 1-15, 16-30, 31-45 их вызывающей функции, что плохо. Длинные функции не являются злыми, если они делают одно, если у вас есть 20 фильтров для вашего отчета, имеющих функцию проверки, которая проверяет, что все двадцать будут длинными. Это прекрасно, разбивка по фильтрам или группам фильтров просто добавляет дополнительную сложность без реальной выгоды.
Наличие большого количества закрытых функций также усложняет автоматическое модульное тестирование, поэтому некоторые по этой причине попытаются избежать его, но, на мой взгляд, это довольно плохая аргументация, поскольку она имеет тенденцию создавать более крупный и сложный дизайн для проведения тестирования.
источник
Поскольку вы используете C #, попробуйте больше думать об объектах. Похоже, что вы все еще кодируете процедурно, имея «глобальные» переменные класса, от которых зависят последующие функции.
Разбить вашу логику на отдельные функции определенно лучше, чем иметь всю логику в одной функции. Могу поспорить, что вы могли бы пойти дальше, также разделив данные, над которыми работают функции, разбив их на несколько объектов.
Подумайте о наличии класса ReportBuilder, в котором вы можете передать данные отчета. Если вы можете разбить ReportBuilder на отдельные классы, сделайте это также.
источник
Да.
Если у вас есть сегменты кода, которые нужно использовать в нескольких местах, используйте собственную функцию. В противном случае, если вам нужно внести изменения в функциональность, вам нужно будет внести изменения в нескольких местах. Это не только увеличивает объем работы, но и увеличивает риск появления ошибок, когда код изменяется в одном месте, но не в другом, или там, где вводится, но не в другом месте.
Это также помогает сделать метод более читабельным, если используются описательные имена методов.
источник
Тот факт, что вы используете нумерацию для различения функций, говорит о том, что существуют базовые проблемы с дублированием или тем, что класс делает слишком много. Разбиение большой функции на множество мелких может быть полезным шагом для рефакторинга дублирования, но это не должно быть последним. Например, вы создаете две функции:
Нет ли сходства в коде, используемом для обработки заголовков разделов? Можете ли вы извлечь общую функцию для них обоих путем параметризации различий?
источник
Разбивка функции на логические подфункции полезна, даже если эти подфункции не используются повторно - она разбивает ее на короткие, легко понимаемые блоки. Но это должно быть сделано логическими единицами, а не просто числом строк. То, как вы должны разбить его, будет зависеть от вашего кода, общими темами будут циклы, условия, операции над одним и тем же объектом; в основном все, что вы можете описать как дискретную задачу.
Так что, да, это хороший стиль - это может звучать странно, но, учитывая ограниченное повторное использование вашего описания, я бы порекомендовал вам тщательно продумать попытку повторного использования. Убедитесь, что использование действительно идентично, а не просто совпадение. Попытка обработать все случаи в одном и том же блоке часто приводит к тому, что в итоге вы получаете большую неуклюжую функцию.
источник
Я думаю, что это в основном личные предпочтения. Если бы ваши функции собирались использовать повторно (и вы уверены, что не могли бы сделать их более общими и многократно используемыми?), Я бы определенно сказал, разделите их. Я также слышал, что это хороший принцип, что ни у одной функции или метода не должно быть более 2-3 экранов кода. Так что, если ваша большая функция просто продолжается и может продолжаться, это может сделать ваш код более читабельным, чтобы разделить его.
Вы не упоминаете, какую технологию вы используете для разработки этих отчетов. Похоже, вы могли бы использовать C #. Это верно? Если это так, вы можете также рассмотреть директиву #region в качестве альтернативы, если вы не хотите разбивать свою функцию на более мелкие.
источник