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?
fonte
Respostas:
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 elargeReportProcessingFunction
não deve depender diretamente dela.Observe que isso, no entanto, é uma má escolha de nomeação:
Você vê
callAllSection1Functions
um nome que na verdade não fornece uma abstração, porque na verdade não diz o que faz, mas como o faz. EmprocessSection1
vez disso, deve ser chamado ou o que for realmente significativo no contexto.fonte
callAllSection1Functions
vaza detalhes sobre a implementação. Da perspectiva externa, não importa seprocessSection1
adia 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 paraprocessSection1
, 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.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
Section
classe 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?
fonte
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.
fonte
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.
fonte
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.
fonte
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:
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?
fonte
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.
fonte
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.
fonte