Funções que chamam apenas outras funções. Esta é uma boa prática?

13

Atualmente, estou trabalhando em um conjunto de relatórios que têm muitas seções diferentes (todas exigindo formatação diferente) e estou tentando descobrir a melhor maneira de estruturar meu código. Relatórios semelhantes que fizemos no passado acabam tendo funções muito grandes (mais de 200 linhas) que fazem toda a manipulação e formatação de dados para o relatório, de modo que o fluxo de trabalho se parece com isso:

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

Eu gostaria de poder dividir essas funções grandes em partes menores, mas tenho medo de acabar com dezenas de funções não reutilizáveis ​​e uma função semelhante "faça tudo aqui", cuja única tarefa é chame todas essas funções menores, assim:

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

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

Ou, se dermos um passo adiante:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

Esta é realmente uma solução melhor? Do ponto de vista organizacional, suponho que seja (ou seja, tudo é muito mais organizado do que poderia ser), mas, quanto à legibilidade do código, não tenho certeza (potencialmente grandes cadeias de funções que chamam apenas outras funções).

Pensamentos?

Eric C.
fonte
No final do dia ... é mais legível? Sim, é uma solução melhor.
23412 sylvanaar

Respostas:

18

Isso é comumente chamado de decomposição funcional e geralmente é uma boa coisa a fazer, se feito corretamente.

Além disso, a implementação de uma função deve estar dentro de um único nível de abstração. Se você aceitar largeReportProcessingFunction, seu papel é definir quais etapas de processamento diferentes devem ser executadas em que ordem. A implementação de cada uma dessas etapas está em uma camada de abstração abaixo e largeReportProcessingFunctionnão deve depender diretamente dela.

Observe que isso, no entanto, é uma má escolha de nomeação:

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

Você vê callAllSection1Functionsum nome que na verdade não fornece uma abstração, porque na verdade não diz o que faz, mas como o faz. Em processSection1vez disso, deve ser chamado ou o que for realmente significativo no contexto.

back2dos
fonte
6
Concordo principalmente com esta resposta, exceto que não consigo ver como "processSection1" é um nome significativo. É praticamente equivalente a "callAllSection1Functions".
Eric King
2
@ EricKing: callAllSection1Functionsvaza detalhes sobre a implementação. Da perspectiva externa, não importa se processSection1adia seu trabalho para "todas as funções da seção 1" ou se o faz diretamente ou se usa pó de pixie para convocar um unicórnio que fará o trabalho real. Tudo o que realmente importa é que, após uma chamada para processSection1, a seção1 pode ser considerada processada . Concordo que o processamento é um nome genérico, mas se você tem alta coesão (pela qual sempre deve se esforçar), seu contexto geralmente é estreito o suficiente para desambigüá-lo.
back2dos
2
Tudo o que eu quis dizer foi que métodos chamados "processXXX" são quase sempre terríveis, nomes inúteis. Todos os métodos 'processam' as coisas, portanto, nomeá-lo como 'processXXX' é tão útil quanto nomear 'doSomethingWithXXX' ou 'manipulateXXX' e similares. Quase sempre existe um nome melhor para os métodos denominados 'processXXX'. Do ponto de vista prático, é tão útil (inútil) quanto 'callAllSection1Functions'.
Eric King
4

Você disse que está usando C #, então eu me pergunto se você poderia criar uma hierarquia de classes estruturada aproveitando o fato de estar usando uma linguagem orientada a objetos. Por exemplo, se faz sentido dividir o relatório em seções, tenha uma Sectionclasse e estenda-o para os requisitos específicos do cliente.

Pode fazer sentido pensar nisso como se você estivesse criando uma GUI não interativa. Pense nos objetos que você pode criar como se fossem componentes diferentes da GUI. Pergunte a si mesmo como seria um relatório básico. Que tal um complexo? Onde devem estar os pontos de extensão?

Jeremy Heiler
fonte
3

Depende. Se todas as funções que você está criando são realmente uma única unidade de trabalho, tudo bem, se são apenas as linhas 1-15, 16-30, 31-45 da função de chamada que estão ruins. Funções longas não são ruins, se fizerem uma coisa, se você tiver 20 filtros para o seu relatório com uma função de validação que verifica se todas as vinte serão longas. Tudo bem, dividi-lo por filtro ou grupos de filtros está apenas adicionando complexidade extra para nenhum benefício real.

Ter muitas funções privadas também dificulta o teste de unidade automatizado, por isso alguns tentarão evitá-lo por esse motivo, mas, na minha opinião, esse raciocínio é muito ruim, pois tende a criar um design maior e mais complexo para acomodar o teste.

Ryathal
fonte
3
Na minha experiência, funções longas são más.
Bernard
Se o seu exemplo estivesse na linguagem OO, eu criaria uma classe de filtro base e cada um dos 20 filtros estaria em diferentes classes derivadas. A instrução Switch seria substituída por uma chamada de criação para obter o filtro correto. Cada função faz uma coisa e todas são pequenas.
DXM
3

Como você está usando C #, tente pensar mais nos objetos. Parece que você ainda está codificando proceduralmente tendo variáveis ​​de classe "globais" das quais dependem as funções subseqüentes.

Quebrar sua lógica em funções separadas é definitivamente melhor do que ter toda a lógica em uma única função. Eu apostaria que você poderia ir além, separando os dados nas quais as funções funcionam, dividindo-os em vários objetos.

Considere ter uma classe ReportBuilder na qual você pode passar os dados do relatório. Se você pode dividir o ReportBuilder em classes separadas, faça isso também.

codificador
fonte
2
Os procedimentos cumprem uma função perfeitamente boa.
23412 DeadMG
1

Sim.

Se você possui um segmento de código que precisa ser usado em vários locais, possui uma função. Caso contrário, se você precisar fazer uma alteração na funcionalidade, precisará fazer a alteração em vários locais. Isso não apenas aumenta o trabalho, mas também aumenta o risco de erros aparecerem à medida que o código é alterado em um local, mas não em outro ou em que um é introduzido em um local, mas não no outro.

Também ajuda a tornar o método mais legível se nomes de métodos descritivos forem usados.

SoylentGray
fonte
1

O fato de você usar numeração para distinguir funções sugere que há problemas subjacentes à duplicação ou que uma classe está fazendo muito. Dividir uma função grande em muitas funções menores pode ser uma etapa útil para refatorar a duplicação, mas não deve ser a última. Por exemplo, você cria as duas funções:

processSection1HeaderData();
processSection2HeaderData();

Não existem semelhanças no código usado para processar cabeçalhos de seção? Você pode extrair uma função comum para os dois parametrizando as diferenças?

Garrett Hall
fonte
Na verdade, esse não é um código de produção; portanto, os nomes das funções são apenas exemplos (na produção, os nomes das funções são mais descritivos). Geralmente, não, não há semelhanças entre seções diferentes (embora, quando houver semelhanças, eu tente reutilizar o código o máximo possível).
Eric C.
1

Dividir uma função em subfunções lógicas é útil, mesmo que as subfunções não sejam reutilizadas - ela as divide em blocos curtos e fáceis de entender. Mas isso deve ser feito por unidades lógicas, não apenas pelo número de linhas. Como você deve dividir isso dependerá do seu código; temas comuns seriam loops, condições, operações no mesmo objeto; basicamente qualquer coisa que você possa descrever como uma tarefa discreta.

Portanto, sim, é um bom estilo - isso pode parecer estranho, mas, como você descreve a reutilização limitada, eu recomendo que você pense com atenção na tentativa de reutilização. Certifique-se de que o uso seja realmente idêntico, e não apenas coincidentemente o mesmo. Tentar lidar com todos os casos no mesmo bloco é frequentemente como você acaba com a grande função desagradável em primeiro lugar.

jmoreno
fonte
0

Eu acho que isso é principalmente preferência pessoal. Se suas funções fossem reutilizadas (e você tem certeza de que não poderia torná-las mais genéricas e reutilizáveis?), Eu definitivamente diria que as separamos. Também ouvi dizer que é um bom princípio que nenhuma função ou método tenha mais de 2-3 telas de código. Portanto, se sua grande função continuar, ela poderá tornar seu código mais legível para dividi-lo.

Você não menciona a tecnologia que está usando para desenvolver esses relatórios. Parece que você pode estar usando C #. Isso está correto? Nesse caso, você também pode considerar a diretiva #region como uma alternativa, se não desejar dividir sua função em outras menores.

Joshua Carmody
fonte
Sim, estou trabalhando em c #. É possível que eu seja capaz de reutilizar algumas funções, mas para muitos desses relatórios as diferentes seções têm dados e layouts muito diferentes (exigência do cliente).
Eric C.
1
+1, exceto para as regiões sugeridas. Eu não usaria regiões.
23312 Bernard
@ Bernard - Alguma razão em particular? Suponho que o autor da pergunta só considere usar #regions se ele já descartou a divisão da função.
21712 Joshua Carmody
2
As regiões tendem a ocultar o código e podem ser abusadas (ou seja, regiões aninhadas). Eu gosto de ver todo o código de uma vez em um arquivo de código. Se eu precisar separar visualmente o código em um arquivo, uso uma linha de barras.
Bernard
2
As regiões são geralmente um sinal às necessidades refatoração de código
codificador