Funzioni che chiamano solo altre funzioni. È una buona pratica?

11

Al momento sto lavorando a una serie di rapporti che hanno molte sezioni diverse (tutte richiedono una formattazione diversa) e sto cercando di capire il modo migliore per strutturare il mio codice. Rapporti simili che abbiamo fatto in passato finiscono per avere funzioni molto grandi (oltre 200 righe) che fanno tutta la manipolazione e la formattazione dei dati per il report, in modo tale che il flusso di lavoro assomigli a questo:

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

Mi piacerebbe essere in grado di suddividere queste grandi funzioni in blocchi più piccoli, ma temo che finirò per avere dozzine di funzioni non riusabili e una simile funzione "fai tutto qui", il cui unico lavoro è chiamare tutte queste funzioni più piccole, in questo modo:

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

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

Oppure, se facciamo un ulteriore passo avanti:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

Questa è davvero una soluzione migliore? Da un punto di vista organizzativo suppongo sia (cioè tutto è molto più organizzato di quanto potrebbe essere altrimenti), ma per quanto riguarda la leggibilità del codice non sono sicuro (potenzialmente grandi catene di funzioni che chiamano solo altre funzioni).

Pensieri?

    
posta Eric C. 23.03.2012 - 20:57
fonte

8 risposte

16

Questo è comunemente indicato come decomposizione funzionale ed è generalmente una buona cosa da fare, se fatto correttamente.

Inoltre, l'implementazione di una funzione dovrebbe essere all'interno di un singolo livello di astrazione. Se si prende largeReportProcessingFunction , il suo ruolo è definire quali differenti fasi di elaborazione devono essere eseguite in quale ordine. L'implementazione di ciascuno di questi passaggi è su uno strato di astrazione sottostante e largeReportProcessingFunction non dovrebbe dipendere direttamente da esso.

Si noti che questa è una scelta sbagliata di denominazione:

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

Vedete callAllSection1Functions è un nome che in realtà non fornisce un'astrazione, perché in realtà non dice cosa fa, ma piuttosto come lo fa esso. Dovrebbe essere chiamato processSection1 o qualunque cosa sia significativa nel contesto.

    
risposta data 23.03.2012 - 21:47
fonte
3

Dipende. Se tutte le funzioni che stai creando sono veramente una singola unità di lavoro, allora va bene, se sono solo le righe 1-15, 16-30, 31-45 della loro funzione di chiamata che è male. Le funzioni lunghe non sono malvagie, se fanno una cosa, se si dispone di 20 filtri per il report con una funzione di convalida che controlla tutti e venti sarà lungo. Va bene, suddividerlo in base al filtro o ai gruppi di filtri è solo aggiungere complessità extra senza vantaggi reali.

Avere un sacco di funzioni private rende anche più difficili i test di unità automatizzate, quindi alcuni cercheranno di evitarlo per questo motivo, ma questo è un ragionamento piuttosto scadente a mio parere in quanto tende a creare un progetto più complesso per adattarsi ai test .

    
risposta data 23.03.2012 - 21:12
fonte
3

Dato che stai usando C #, prova a pensare di più negli oggetti. Sembra che tu stia ancora codificando proceduralmente avendo variabili di classe "globali" da cui dipendono le funzioni successive.

Spezzare la logica in funzioni separate è sicuramente meglio che avere tutta la logica in una singola funzione. Scommetto che potresti andare oltre, separando anche i dati su cui funzionano le funzioni suddividendoli in diversi oggetti.

Considera di avere una classe ReportBuilder in cui puoi passare i dati del rapporto. Se riesci a rompere ReportBuilder in classi separate, esegui anche questo.

    
risposta data 23.03.2012 - 21:21
fonte
3

Hai detto che stai usando C #, quindi mi chiedo se potresti costruire una gerarchia di classi strutturate sfruttando il fatto che stai usando un linguaggio orientato agli oggetti? Ad esempio, se ha senso suddividere il rapporto in sezioni, avere una classe Section ed estenderla per i requisiti specifici del cliente.

Potrebbe essere sensato pensarci come se si stesse creando una GUI non interattiva. Pensa agli oggetti che potresti creare come se fossero componenti della GUI diversi. Chiediti che aspetto avrebbe un rapporto di base? Che ne dici di uno complesso? Dove dovrebbero essere i punti di estensione?

    
risposta data 23.03.2012 - 21:21
fonte
1

Sì.

Se si dispone di segmenti di codice che devono essere utilizzati in più posizioni, la propria funzione. Altrimenti, se devi apportare una modifica alla funzionalità, dovrai apportare la modifica in più punti. Questo non solo aumenta il lavoro ma aumenta il rischio che i bug si presentino mentre il codice viene modificato in un posto ma non in un altro o dove un ma viene introdotto in un posto ma non nell'altro.

Aiuta anche a rendere il metodo più leggibile se vengono utilizzati nomi di metodi descrittivi.

    
risposta data 23.03.2012 - 21:05
fonte
1

Il fatto che si usi la numerazione per distinguere le funzioni suggerisce che ci sono problemi di base con la duplicazione o una classe che fa troppo. Rompere una grande funzione in molte funzioni più piccole può essere un utile passo per rifattorizzare la duplicazione, ma non dovrebbe essere l'ultima. Ad esempio, crei le due funzioni:

processSection1HeaderData();
processSection2HeaderData();

Non ci sono delle somiglianze nel codice usato per elaborare le intestazioni di sezione? Puoi estrarre una funzione comune per entrambi impostando le differenze?

    
risposta data 23.03.2012 - 22:01
fonte
1

L'interruzione di una funzione in sottofunzioni logiche è utile, anche se le sottofunzioni non vengono riutilizzate: la suddivide in blocchi brevi e facilmente comprensibili. Ma deve essere fatto da unità logiche, non solo n # di linee. Il modo in cui dovresti suddividerlo dipenderà dal tuo codice, i temi comuni sarebbero loop, condizioni, operazioni sullo stesso oggetto; praticamente tutto ciò che puoi descrivere come un compito discreto.

Quindi, sì, è un buon stile - questo può sembrare strano, ma dato il tuo riutilizzo limitato descrivibile, ti consiglierei di pensare attentamente al riutilizzo TENTATIVO. Assicurati che l'utilizzo sia veramente identico, e non solo per coincidenza, lo stesso. Il tentativo di gestire tutti i casi nello stesso blocco è spesso il modo in cui si finisce con la grande funzione sgraziata nel primo posto.

    
risposta data 24.03.2012 - 06:50
fonte
0

Penso che questa sia principalmente una preferenza personale. Se le tue funzioni fossero riutilizzate (e sei sicuro di non renderle più generiche e riutilizzabili?), Direi sicuramente di dividerle. Ho anche sentito che è un buon principio che nessuna funzione o metodo debba contenere più di 2-3 schermate di codice. Quindi se la tua grande funzione va avanti e avanti, potrebbe rendere il tuo codice più leggibile per dividerlo.

Non parli di quale tecnologia stai usando per sviluppare questi rapporti. Sembra che tu stia usando C #. È corretto? In tal caso, potresti anche considerare la direttiva #region come alternativa se non vuoi dividere la tua funzione in quelle più piccole.

    
risposta data 23.03.2012 - 21:05
fonte

Leggi altre domande sui tag