Qual è il modo migliore per suddividere metodi di grandi dimensioni in cui ogni attività secondaria dipende da attività precedenti?

6

Secondo questa domanda e quasi tutte le guide di stile che io abbia mai letto, i metodi grandi sono cattivi. Tuttavia, supponiamo di avere un metodo che faccia quanto segue:

  1. Ricevi una stringa JSON da un servizio e la memorizza come dizionario
  2. Per vari parametri nel dizionario, crea connessioni ad altri servizi, come l'uso delle chiavi AWS per aprire le connessioni ai servizi AWS
  3. Scarica, autentica o manipola i dati in base ai valori nel Dizionario e ai nuovi dati ricevuti dal passaggio 2.
  4. Crea un file nella directory locale e scrive vari valori su di esso, in base ai passaggi 2 e 3.
  5. Spinge il file dal passaggio 4 in un'altra posizione

Chiaramente, questo non dovrebbe essere tutto un metodo di grandi dimensioni. Tuttavia, non sono sicuro di come suddividere questo metodo in metodi più piccoli senza superare una tonnellata di parametri. Ad esempio, il primo metodo è semplice perché ho solo bisogno di dire al programma una cosa, e viene restituita solo una cosa. Tuttavia, a partire dal passaggio 3, dovrò passare l'intero dizionario al nuovo metodo o rendere il dizionario un'istanza o una variabile globale.

Quindi, in breve, ecco la mia domanda. Quando si rompe un grande metodo come questo, qual è l'alternativa migliore: dovrei creare variabili che devono essere usate da più istanze / variabili globali dei metodi (a seconda di quale sia appropriato per questa particolare attività), o dovrei passare a creare nuovi metodi che prendono molti parametri?

Per me, sembra che la prima opzione sia molto peggio, perché sembra interrompere l'incapsulamento: ogni metodo creato in questo modo ora ha una strana dipendenza da una variabile globale o di istanza che è al di fuori del suo ambito. Tuttavia, il secondo metodo sembra davvero disordinato.

    
posta Teofrostus 07.01.2016 - 01:02
fonte

4 risposte

5

Non c'è niente di sbagliato nel passare un parametro molto grande e complesso, purché la struttura e il contenuto di quel parametro siano ben definiti e facili da capire. Ciò che si desidera evitare sono parametri facili da mescolare, con parametri il cui scopo non è chiaro o che dipendono troppo dallo stato dell'istanza piuttosto che dai parametri. Anche avere molti parametri va bene se trovi un modo intuitivo per raggrupparli.

In effetti, se sto leggendo correttamente la tua domanda, sembra che tu sappia già come suddividere questo compito in un modo relativamente semplice che richiede solo funzioni pure. Dal momento che Javascript è la mia lingua principale, immagino che il codice assomiglierà a questo:

var jsonStr = fetchJSONStringFromService();
var dict = JSON.parse(jsonStr);

var serviceConnections = createServiceConnections(dict);

var processedData = doVariousDataProcessingThings(dict);

var dataToWrite = serializeStuff(serviceConnections, processedData);
var fs = require('fs');
fs.writeFile('file/path', dataToWrite);

pushFileToOtherServer('file/path');

È possibile che tu abbia omesso alcuni dettagli che renderebbero il codice reale molto più complicato di questo, ma suppongo che questo sia nel giusto ambito. Come puoi vedere, è già molto facile da seguire; l'unica cosa che manca è la documentazione di quale struttura si suppone che abbiano i vari oggetti passati.

Per rispondere direttamente alla tua domanda, sembra una situazione in cui è meglio puntare a funzioni apolidi, trasparenti e referenzialmente trasparenti che manipolano solo POJSO, connessioni di rete e file. Anche se alcuni metodi richiedono più parametri di quanti ne ho indovinato sopra. In questo modo, ogni passaggio sarà semplice per il test dell'unità (una volta che avrai effettuato tutti i mock richiesti), e molto facile da cambiare, spostarti o sostituirlo in futuro senza interrompere gli altri passaggi. Se alcune delle funzioni nel tuo codice reale sembrano prendere "troppi" parametri, c'è probabilmente un modo per suddividerle in funzioni più piccole o combinare i parametri in oggetti significativi finché non si ottengono funzioni facili da capire.

Dato che la tua domanda implica che questo oggetto dict sia abbastanza grande e complesso, un possibile miglioramento in cima a quanto sopra sarebbe aggiungere un passaggio in cui dividi dict in dizionari più piccoli o altre strutture di dati. In particolare, presumo ci sia un sottoinsieme di informazioni in dict che deve essere usato da createServiceConnections() , quindi sarebbe ideale passare solo quel sottoinsieme piuttosto che il tutto.

Per completezza, non sto dicendo che gli oggetti con variabili di istanza non sono mai utili. Infatti, nel codice di esempio sopra, è probabile che serviceConnections e fs siano oggetti molto stateful, che va bene perché rappresentano cose intrinsecamente stateful, a differenza di un blob di dati.

    
risposta data 07.01.2016 - 01:48
fonte
4

Sei preoccupato di come strutturare una singola funzione, quindi qualsiasi soluzione ti venga in mente, non dovrebbe influenzare la struttura della classe.

Ciò significa che devi assolutamente non introdurre le variabili di istanza; passare tutti i parametri necessari ai metodi è la strada da percorrere.

Per quanto riguarda le variabili globali, non dovresti nemmeno averle considerate, né averle sollevate in questa domanda. Non c'è applicazione di variabili globali che non sfocino nel male, e non c'è alcun male che non venga aggravato dall'introduzione di variabili globali. E infatti, anche le variabili di istanza non sono altro che variabili globali nell'ambito di una classe, (la classe viene pensata come il globo in questo caso), quindi è meglio non avere mai alcuna variabile di istanza che avrebbe potuto essere evitato.

Non vedo perché consideri i parametri di passaggio come un compito oneroso. Parli di dover "passare l'intero Dizionario al nuovo metodo"; beh, non è che lo trasferirai nei camion; dichiari solo il metodo per accettare il dizionario come parametro, e poi quando invochi quel metodo, assicurati di passare il dizionario come parametro. Non è un grosso problema, davvero.

Tutto ciò che è stato detto, lasciatemi aggiungere anche che la massima che proclama che le funzioni dovrebbero essere il più piccole possibile dovrebbe essere presa con un granello di sale. L'obiettivo finale è ridurre la complessità del codice e tutti questi principi elevati, linee guida e parole di saggezza non hanno che uno scopo finale: contribuire a ridurre la complessità. Quindi, tieni a mente che è del tutto possibile che un singolo metodo lungo composto da pochi pezzi in sequenza che condividono alcuni maggio stati locali rappresenti una quantità minore di complessità di un piccolo metodo che richiama in sequenza una moltitudine di altri metodi che non hanno altro scopo se non quello di spezzare il codice in blocchi più piccoli, semplicemente perché il lavoro svolto è lo stesso, il codice che lo fa è lo stesso, e ora abbiamo l'onere aggiuntivo di impacchettare e passare i parametri intorno, risultati di packaging, ecc. E, naturalmente, se questo meccanismo richiede l'introduzione di entità aggiuntive, come variabili globali, variabili di istanza o nuove classi per contenere risultati temporanei, allora è per definizione una soluzione di complessità maggiore rispetto all'approccio a metodo lungo singolo .

    
risposta data 07.01.2016 - 15:22
fonte
0

La mia opinione è leggermente diversa. Il primo passo nel flusso di lavoro è la chiave per andare avanti con i passaggi rimanenti. Credo che se il tuo primo passo si risolve in un'eccezione o in un set di risultati vuoti, allora non potresti seguire i passaggi rimanenti. E prova ad avere un meccanismo di ricerca tra i passaggi.

È necessario creare classi separate per eseguire varie azioni richieste nell'intero flusso di lavoro. Ciò manterrebbe il codice pulito & più facile da risolvere anche. Ciò promuoverebbe un accoppiamento libero tra le fasi del flusso di lavoro e renderebbe più semplice apportare modifiche indipendenti ai requisiti futuri.

  • Ricevi una stringa JSON da un servizio e la memorizzerà come dizionario.

    ---- Crea una classe che avvia la connessione al servizio esterno con il set di parametri richiesto. Diciamo che chiamiamo la classe come PreRequisiteExternalInfoClass . Scarica il risultato della chiamata in modo appropriato. Se la chiamata ha esito positivo, eseguire solo il passaggio successivo. È possibile aggiungere un meccanismo reTry al processo di connessione per il servizio esterno, se si verifica un errore. E registra i dati nel db per analisi e risoluzione dei problemi. Se la tua applicazione è basata su più thread, allora sostituisci il semplice oggetto Dictionary con ConcurrentDictionary obj (.NET framework).

  • Per vari parametri nel Dizionario, crea connessioni ad altri servizi, come l'uso delle chiavi AWS per aprire le connessioni ai servizi AWS.

    ---- Crea una nuova classe che possa occuparsi delle connessioni ai servizi AWS. Probabilmente pensa di creare un oggetto singleton per l'oggetto di connessione. Diciamo che chiamiamo la classe come AWS_ServiceClass .

  • Scarica, autentica o manipola i dati in base ai valori nel dizionario e ai nuovi dati ricevuti dal passaggio 2.

    ---- Aggiungi nuovi metodi in AWS_ServiceClass per eseguire download, autenticazione, manipolazione di set di dati, ecc ...

  • Crea un file nella directory locale e scrive vari valori su di esso, in base ai passaggi 2 e 3.

    ---- Crea una nuova classe statica dì ManageFiles per avere metodi statici come fileCreation, fileMove, fileCopy ecc. e passa i valori dei parametri appropriati a questi metodi.

  • Spinge il file dal passaggio 4 in un'altra posizione

    ---- Se il trasferimento di un numero elevato di file in un'altra posizione è probabile, è consigliabile avvalersi dell'utilità Robocopy a livello di sistema operativo.

risposta data 07.01.2016 - 10:48
fonte
0

La tua classe fa già troppo ma non la chiamerò qui.

Riguardo alla domanda specifica:

should I make variables which need to be used by multiple methods instance/global variables (whichever is appropriate for this particular task), or should I pass create new methods which take many parameters?

Avere troppi parametri passati tra i metodi è un indicatore di bassa coesione della classe. Nel caso di metodi statici appartenenti a una classe utility , forse OK ma non altrimenti.

Se il metodo appartiene realmente a quella classe, dopo averlo diviso in metodi separati, i metodi risultanti dovrebbero accedere allo stato della classe invece di passare molti parametri tra loro. Mantenere il numero di parametri a uno o due al massimo.

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.

Clean Code, A Handbook of Agile Software Craftsmanship -- Robert C. Martin

    
risposta data 07.01.2016 - 15:32
fonte

Leggi altre domande sui tag