Clean Code: funziona con pochi parametri [chiuso]

47

Ho letto i primi capitoli di Clean Code di Robert C. Martin, e mi sembra che sia abbastanza buono, ma ho un dubbio, in una parte si dice che è buono ( cognitivamente) che le funzioni dovrebbero avere il minor numero possibile di parametri, suggerisce anche che 3 o più parametri sono troppo per una funzione (che trovo molto esagerata e idealistica), quindi ho iniziato a chiedermi ...

Entrambe le pratiche di utilizzo di variabili globali e il passaggio di molti argomenti sulle funzioni sarebbero cattive pratiche di programmazione, ma l'uso di variabili globali può ridurre notevolmente il numero di parametri nelle funzioni ...

Quindi volevo sapere cosa ne pensate, vale la pena utilizzare variabili globali per ridurre il numero di parametri delle funzioni o no? In quali casi sarebbe?

Quello che penso è che dipenda da diversi fattori:

  • Dimensioni del codice sorgente.
  • Numero di parametri in media delle funzioni.
  • Numero di funzioni.
  • Frequenza in cui vengono utilizzate le stesse variabili.

Secondo me se la dimensione del codice sorgente è relativamente piccola (come meno di 600 righe di codice), ci sono molte funzioni, le stesse variabili vengono passate come parametri e le funzioni hanno molti parametri, quindi l'utilizzo di variabili globali varrebbe la pena , ma mi piacerebbe sapere ...

  • Condividi la mia opinione?
  • Che ne pensi degli altri casi in cui il codice sorgente è più grande, ecc.?

P.S . Ho visto questo post , i titoli sono molto simile, ma non chiede cosa voglio sapere.

    
posta OiciTrap 13.07.2017 - 05:27
fonte

15 risposte

110

Non condivido la tua opinione. Secondo me, l'uso di variabili globali è una pratica peggiore di altri parametri, indipendentemente dalle qualità che hai descritto. Il mio ragionamento è che più parametri possono rendere un metodo più difficile da capire, ma le variabili globali possono causare molti problemi per il codice tra cui scarsa testabilità, bug di concorrenza e accoppiamento stretto. Indipendentemente dal numero di parametri di una funzione, non avrà gli stessi problemi delle variabili globali.

...the same variables are passed as parameters

È potrebbe essere un odore di design. Se si hanno gli stessi parametri passati alla maggior parte delle funzioni nel proprio sistema, potrebbe esserci una preoccupazione trasversale che dovrebbe essere gestita introducendo un nuovo componente. Non credo che passare la stessa variabile a molte funzioni sia un valido ragionamento per introdurre variabili globali.

In una modifica della tua domanda hai indicato che l'introduzione di variabili globali potrebbe migliorare la leggibilità del codice. Non sono d'accordo. L'utilizzo di variabili globali è nascosto nel codice di implementazione mentre i parametri di funzione sono dichiarati nella firma. Le funzioni dovrebbero idealmente essere pure. Dovrebbero operare solo sui loro parametri e non dovrebbero avere effetti collaterali. Se hai una funzione pura, puoi ragionare sulla funzione osservando solo una funzione. Se la tua funzione non è pura, devi considerare lo stato degli altri componenti e diventa molto più difficile ragionare.

    
risposta data 13.07.2017 - 05:47
fonte
65

Devi evitare le variabili globali come la peste .

Non metterei un limite rigido al numero di argomenti (come 3 o 4), ma tu fai vuoi mantenerli al minimo, se possibile.

Usa struct s (o oggetti in C ++) per raggruppare le variabili in una singola entità e passare quella (per riferimento) alle funzioni. Di solito una funzione ottiene una struttura o un oggetto (con alcune cose diverse in esso) passate ad essa insieme ad un paio di altri parametri che dicono alla funzione di fare qualcosa al struct .

Per un codice buono, pulito e modulare, prova ad attenersi al principio di responsabilità singola . Fatelo con le vostre strutture (o oggetti), le funzioni e i file sorgente. Se lo fai, il numero naturale dei parametri passati a una funzione sarà ovvio.

    
risposta data 13.07.2017 - 07:31
fonte
55

Stiamo parlando di carico cognitivo, non di sintassi. Quindi la domanda è ... Che è un parametro in questo contesto?

Un parametro è un valore che influenza il comportamento della funzione. Più parametri, più combinazioni possibili di valori ottieni, più ragionamento sulla funzione diventa.

In questo senso, le variabili globali che la funzione utilizza sono parametri. Sono parametri che non appaiono nella sua firma, che hanno problemi di ordine di costruzione, controllo accessi e rimanenza.

A meno che detto parametro non sia quello che viene chiamato un interesse trasversale , cioè uno stato a livello di programma che tutto utilizza ma nulla altera (es. un oggetto di logging), non si dovrebbero sostituire i parametri di funzione con variabili globali. Sarebbero comunque parametri, ma più cattivi.

    
risposta data 13.07.2017 - 10:31
fonte
34

IMHO la tua domanda si basa su un fraintendimento. In "Clean Code", Bob Martin non suggerisce di sostituire i parametri delle funzioni ripetute da globals, sarebbe un consiglio davvero terribile. Suggerisce di sostituirli con variabili membro private della classe della funzione. E propone anche classi piccole e coerenti (tipicamente più piccole delle 600 righe di codice che hai menzionato), quindi queste variabili membro non sono sicuramente globali.

Quindi quando hai un'opinione in un contesto con meno di 600 righe "usando le variabili globali varrà la pena" , allora condividerai perfettamente l'opinione di Uncle Bob. Certo, è discutibile se "3 parametri al massimo" è il numero ideale e se questa regola a volte porta a troppe variabili membro anche in classi piccole. IMHO questo è un trade-off, non esiste una regola dura e veloce in cui tracciare la linea.

    
risposta data 13.07.2017 - 07:41
fonte
33

Avere molti parametri è considerato indesiderabile, ma trasformarli in campi o variabili globali è molto peggio perché non risolve il problema reale ma introduce nuovi problemi.

Avere molti parametri non è di per sé un problema, ma è un sintomo che potresti avere un problema. Considera questo metodo:

Graphics.PaintRectangle(left, top, length, height, red, green, blue, transparency);

Avere 7 parametri è un segnale di avvertimento definito. Il problema di underling è che questi parametri non sono indipendenti ma appartengono a gruppi. left e top appartengono insieme come Position -struttura, length e height come struttura Size e red , blue e green come struttura Color . E forse Color e trasparenza appartengono a una struttura Brush ? Forse Position e Size appartengono insieme in una struttura Rectangle , nel qual caso potremmo anche considerare di trasformarlo in un metodo Paint sull'oggetto Rectangle invece? Quindi potremmo finire con:

Rectangle.Paint(brush);

Missione compiuta! Ma la cosa importante è che abbiamo effettivamente migliorato la progettazione generale e la riduzione del numero di parametri è una conseguenza di questo. Se riduciamo il numero di parametri senza affrontare i problemi sottostanti, potremmo fare qualcosa di simile:

Graphics.left = something;
Graphics.top = something;
Graphics.length = something;
...etc
Graphics.PaintRectangle();

Qui abbiamo raggiunto la stessa riduzione del numero di parametri, ma in realtà abbiamo reso il peggiore del design

.

In conclusione: per qualsiasi consiglio di programmazione e regole pratiche è molto importante capire il ragionamento sottostante.

    
risposta data 13.07.2017 - 13:38
fonte
7

is it worth using global variables to reduce the number of parameters of the functions or not?

Non

I read the first chapters of this book

Hai letto il resto del libro?

Un globale è solo un parametro nascosto. Provocano un dolore diverso. Ma è ancora dolore. Smetti di pensare ai modi per aggirare questa regola. Pensa a come seguirlo.

Che cos'è un parametro?

È roba. In una scatola ben etichettata. Perché importa quante scatole hai quando puoi inserire qualsiasi cosa in essa?

Sono i costi di spedizione e gestione.

Move(1, 2, 3, 4)

Dimmi che puoi leggerlo. Vai, prova.

Move(PointA, PointB)

Ecco perché.

Questo trucco si chiama introduce l'oggetto parametro .

E sì, è solo un trucco se tutto quello che stai facendo è il conteggio dei parametri. Quello che dovresti contare è IDEE! Astrazioni! Quanto mi stai facendo pensare in una volta? Mantienilo semplice.

Ora questo è lo stesso conteggio:

Move(xyx, y)

OW! È orribile! Cosa è andato storto qui?

Non è sufficiente limitare il numero di idee. Devono essere idee chiare. Che diamine è una xyx?

Ora questo è ancora debole. Qual è un modo più efficace per pensarci?

Functions should be small. No smaller than that.

Uncle Bob

Move(PointB)

Perché fare in modo che la funzione faccia più di quello che deve realmente fare? Il principio della responsabilità unica non è solo per le classi. Seriamente, vale la pena cambiare un'intera architettura solo per evitare che una funzione si trasformi in un incubo sovraccarico con 10 parametri a volte correlati, alcuni dei quali non possono essere usati con altri.

Ho visto il codice in cui il numero più comune di righe in una funzione era 1. Seriamente. Non sto dicendo che devi scrivere in questo modo ma sheesh, non dirmi che un globale è l'UNICO modo in cui puoi rispettare questa regola. Smettila di cercare di uscire dal refactoring che funziona correttamente. Sai che puoi. Potrebbe suddividersi in poche funzioni. Potrebbe effettivamente trasformarsi in pochi oggetti. Potresti addirittura romperne una parte in un'applicazione completamente diversa.

Il libro non ti sta dicendo di contare i tuoi parametri. Ti sta dicendo di prestare attenzione al dolore che stai causando. Tutto ciò che risolve il dolore risolve il problema. Sii consapevole quando stai semplicemente scambiando un dolore con un altro.

    
risposta data 14.07.2017 - 04:44
fonte
4

Non userei mai le variabili globali per ridurre i parametri. La ragione è che le variabili globali possono essere modificate da qualsiasi funzione / comando, rendendo quindi la funzione input inaffidabile e incline a valori che non rientrano nell'ambito di ciò che la funzione può gestire. Cosa succede se la variabile è stata modificata durante l'esecuzione della funzione e metà della funzione ha valori diversi rispetto all'altra metà?

Passando invece i parametri, si limita l'ambito della variabile solo alla propria funzione in modo tale che solo la funzione può modificare un parametro una volta chiamato.

Se è necessario passare variabili globali anziché un parametro, è preferibile riprogettare il codice.

Solo i miei due centesimi.

    
risposta data 13.07.2017 - 07:04
fonte
2

Sono con lo zio Bob su questo e sono d'accordo sul fatto che più di 3 parametri sono qualcosa da evitare (raramente uso più di 3 parametri in una funzione). Avere un sacco di parametri su una singola funzione crea un problema di manutenzione più grande ed è probabilmente un odore che la tua funzione sta facendo troppo / ha troppe responsabilità.

Se stai usando più di 3 in un metodo in un linguaggio OO, dovresti considerare che i param non sono in alcun modo correlati tra loro e quindi dovresti davvero passare un oggetto invece?

Inoltre, se crei più funzioni (più piccole) noterai anche che le funzioni tendono ad avere più spesso 3 parametri o meno. La funzione / metodo di estrazione è tuo amico: -).

Non usare le variabili globali come un modo per aggirare con più parametri! Questo è lo scambio di una cattiva pratica per una ancora peggiore!

    
risposta data 13.07.2017 - 12:01
fonte
1

Un'alternativa valida a molti parametri di funzione è introdurre un oggetto parametro . Questo è utile se hai un metodo composto che passa (quasi) tutti i suoi parametri a un mucchio di altri metodi.

In casi semplici questo è un semplice DTO che non ha nient'altro che i vecchi parametri come proprietà.

    
risposta data 13.07.2017 - 09:56
fonte
1

L'uso delle variabili globali sembra sempre un modo semplice per codificare (specialmente in un programma di piccole dimensioni), ma renderà difficile estendere il codice.

Sì, puoi ridurre il numero di parametri in una funzione utilizzando una matrice per associare i parametri in una entità.

function <functionname>(var1,var2,var3,var4.....var(n)){}

La funzione sopra riportata sarà modificata e modificata in [usando array associativo] -

data=array(var1->var1,
           var2->var2
           var3->var3..
           .....
           ); // data is an associative array

function <functionname>(data)

Sono d'accordo con la risposta di robert bristow-johnson : puoi anche usare una struttura per legare i dati in una singola entità.

    
risposta data 13.07.2017 - 08:14
fonte
1

Prendendo un esempio da PHP 4, guarda la firma della funzione per mktime() :

  • int mktime ([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] )

Non lo trovi confuso? La funzione è chiamata "tempo di realizzazione" ma richiede i parametri di giorno, mese e anno nonché tre parametri temporali. Quanto è facile ricordare in che ordine vanno? Cosa succede se vedi mktime(1, 2, 3, 4, 5, 2246); ? Riesci a capirlo senza dover fare riferimento a qualcos'altro? 2246 è interpretato come orario di 24 ore "22:46"? Cosa significano gli altri parametri? Sarebbe meglio come oggetto.

Passando a PHP 5, ora c'è un oggetto DateTime. Tra i suoi metodi vi sono due chiamati setDate() e setTime() . Le loro firme sono le seguenti:

  • public DateTime setDate ( int $year , int $month , int $day )
  • public DateTime setTime ( int $hour , int $minute [, int $second = 0 ] )

Devi ancora ricordare che l'ordine dei parametri va dal più grande al più piccolo, ma è un grande miglioramento. Si noti che non esiste un singolo metodo che consente di impostare tutti e sei i parametri contemporaneamente. Devi fare due chiamate separate per farlo.

Di cosa sta parlando lo zio Bob sta evitando di avere una struttura piatta. I parametri correlati dovrebbero essere raggruppati in un oggetto, e se hai più di tre parametri, è molto probabile che tu abbia un oggetto pseudo lì, che ti dà l'opportunità di creare un oggetto appropriato per una maggiore separazione. Sebbene PHP non abbia una classe separata Date e Time , potresti considerare che DateTime contiene davvero un oggetto Date e un oggetto Time .

Potresti avere la seguente struttura:

<?php
$my_date = new Date;
$my_date->setDay(5);
$my_date->setMonth(4);
$my_date->setYear(2246);

$my_time = new Time;
$my_time->setHour(1);
$my_time->setMinute(2);
$my_time->setSecond(3);

$my_date_time = new DateTime;
$my_date_time->setTime($my_time);
$my_date_time->setDate($my_date);
?>

È obbligatorio impostare due o tre parametri ogni volta? Se vuoi cambiare solo l'ora o solo il giorno, è ora facile farlo. Sì, l'oggetto deve essere convalidato per assicurarsi che ogni parametro funzioni con gli altri, ma prima era comunque il caso.

La cosa più importante è, è più facile da capire, e quindi mantenere? Il blocco di codice in fondo è più grande di una singola funzione mktime() , ma direi che è molto più facile da capire; anche un non programmatore non avrebbe molti problemi a capire cosa fa. L'obiettivo non è sempre codice più corto o codice più intelligente, ma codice più gestibile.

Oh, e non usare i globali!

    
risposta data 14.07.2017 - 09:19
fonte
1

Un sacco di buone risposte qui, ma la maggior parte non stanno affrontando il punto. Perché queste regole empiriche? Riguarda l'ambito, riguarda le dipendenze e riguarda la modellazione corretta.

Globali per argomenti è peggio perché sembra solo che tu l'abbia reso più semplice ma in realtà hai solo nascosto la complessità. Non lo vedi più nel prototipo ma devi comunque esserne consapevole (il che è difficile dal momento che non è lì per te da vedere) e farti un'idea della logica della funzione non ti aiuterà perché potrebbe essere un'altra logica nascosta che interferisce alle tue spalle. Il tuo ambito è andato dappertutto e hai introdotto una dipendenza da qualsiasi cosa, perché qualsiasi cosa ora può interferire con la tua variabile. Non va bene.

La cosa principale da mantenere per una funzione è che puoi capire che cosa fa guardando il prototipo e chiama. Quindi il nome dovrebbe essere chiaro e non ambiguo. Ma anche più argomenti ci saranno, più difficile sarà afferrare ciò che fa. Ti allarga la portata della tua mente, troppe cose stanno succedendo, è per questo che vuoi limitare il numero. È importante che tipo di argomenti hai a che fare, alcuni sono peggiori di altri. Un booleano extra opzionale che consente l'elaborazione senza distinzione tra maiuscole e minuscole non rende la funzione più difficile da capire, quindi non vorrai fare un grosso problema. Come nota a margine, le enumerazioni costituiscono argomenti migliori rispetto ai booleani perché il significato di enum è ovvio nella chiamata.

Il tipico problema non è che tu scriva una nuova funzione con un'enorme quantità di argomenti, inizierai solo con pochi quando non ti rendi conto di quanto sia complesso il problema che stai risolvendo. Man mano che il tuo programma si evolve, gli elenchi di argomenti tendono gradualmente ad allungarsi. Una volta che un modello è stato messo nella tua mente, vuoi tenerlo perché è un riferimento sicuro che conosci. Ma a posteriori il modello potrebbe non essere così eccezionale. Hai perso un passaggio o anche nella logica e non sei riuscito a riconoscere un'entità o due. "OK ... Potrei ricominciare e passare un giorno o due a refactoring del mio codice o ... Potrei aggiungere questo argomento in modo che possa fare in modo che faccia la cosa giusta dopotutto e averne fatto. Per ora. Per questo scenario Per togliere questo insetto dal mio piatto in modo da poter spostare la nota adesiva fino in fondo. "

Più spesso vai con la seconda soluzione, più costosa sarà l'ulteriore manutenzione e più difficile sarà il refactoring.

Non esiste una soluzione per piastra di riscaldamento per ridurre il numero di argomenti in una funzione esistente. Raggrupparli in composti non significa semplificare le cose, è solo un altro modo di cancellare la complessità sotto il tappeto. Farlo bene implica di nuovo guardare l'intero stack delle chiamate e riconoscere ciò che manca o è stato fatto male.

    
risposta data 16.07.2017 - 10:06
fonte
0

Diverse volte ho scoperto che raggruppare molti parametri che sono stati inviati insieme a cose migliorate.

  • Ti costringe a dare un buon nome a questa fusione di concetti che vengono usati insieme. Se si utilizzano questi parametri insieme, è possibile che abbiano una relazione (o che non si debba usare affatto insieme).

  • Una volta con l'oggetto, di solito trovo che un certo tipo di funzionalità può essere spostato da questo oggetto apparentemente stuido. Di solito è facile da testare e con una grande coesione e un basso aggancio, rendendo la cosa ancora migliore.

  • La prossima volta che ho bisogno di aggiungere un nuovo parametro ho un bel posto per includerlo senza modificare la firma di molti metodi.

Quindi potrebbe non funzionare al 100% delle volte, ma chiediti se questo elenco di parametri dovrebbe essere raggruppato in un oggetto. E per favore. Non usare classi non tipizzate come Tuple per evitare di creare l'oggetto. Stai utilizzando la programmazione orientata agli oggetti, non importa creare più oggetti se ne hai bisogno.

    
risposta data 14.07.2017 - 10:48
fonte
0

Con tutto il dovuto rispetto, sono abbastanza sicuro che hai perso completamente il punto di avere un piccolo numero di parametri in una funzione.

L'idea è che il cervello possa contenere così tante "informazioni attive" in una sola volta, e se hai parametri n in una funzione, hai n altro pezzi di "informazioni attive" che devono essere nel tuo cervello per comprendere in modo semplice e preciso cosa sta facendo il codice (Steve McConnell, in Code Complete (un libro molto migliore, IMO), dice qualcosa di simile a 7 variabili in un metodo corpo: raramente lo raggiungiamo, ma più e stai perdendo l'abilità di tenere tutto dritto nella tua testa).

Il punto di un basso numero di variabili è di essere in grado di mantenere bassi i requisiti cognitivi di lavorare con questo codice, in modo da poter lavorare su di esso (o leggerlo) in modo più efficace. Una causa laterale di questo è che il codice ben calcolato tende ad avere sempre meno parametri (ad esempio, il codice scarsamente fattorizzato tende a raggruppare un mucchio di cose in un pasticcio).

Passando oggetti al posto dei valori, forse guadagni un livello di astrazione per il tuo cervello perché ora ha bisogno di realizzare sì, ho un SearchContext con cui lavorare qui invece di pensare al 15 proprietà che potrebbero trovarsi all'interno del contesto di ricerca.

Cercando di utilizzare le variabili globali, hai completamente perso l'intera direzione sbagliata. Ora non solo non hai risolto i problemi di avere troppi parametri per una funzione, hai preso quel problema e lo hai gettato in un problema molto, molto meglio che ora devi portare in giro nella tua mente!

Ora invece di lavorare solo a livello di funzione, devi considerare anche lo scopo globale del tuo progetto (stupido, terribile! rabbrividisco ...). Non hai nemmeno tutte le informazioni di fronte a te nella funzione (schifo, qual è il nome della variabile globale?) (Spero che questo non sia stato cambiato da qualcos'altro da quando ho chiamato questa funzione).

Le variabili con ambito globale sono una delle cose peggiori da vedere in un progetto (grande o piccolo). Indicano una disabilità per una corretta gestione dell'ambito, che è un'abilità fondamentale per la programmazione. Essi. Siamo. Il male.

Spostando i parametri fuori dalla tua funzione e inserendoli in globali, ti sei sparato nel foor (o nella gamba o nella faccia). E Dio ti proibisce mai di decidere che hey, posso riutilizzare questo globale per qualcos'altro ... la mia mente trema al pensiero.

L'intero ideale è di mantenere le cose facili da gestire e le variabili globali NON lo faranno. Direi che sono una delle cose peggiori che puoi fare per andare nella direzione opposta.

    
risposta data 16.07.2017 - 14:35
fonte
-1

Ho trovato un approccio molto efficace (in JavaScript) per ridurre al minimo l'attrito tra le interfacce: Utilizza un'interfaccia uniforme per tutti i moduli , in particolare: singole funzioni param.

Quando hai bisogno di più parametri: usa un singolo oggetto / hash o array.

Resta con me, prometto che non sto trollando ...

Prima di dire "a cosa serve una singola funzione param?" o "C'è una differenza tra 1 Array e le funzioni multi arg?"

Bene, si. È forse visivamente sottile, ma la differenza è molteplice - Esplora i numerosi vantaggi qui

Apparentemente alcuni pensano che 3 sia il giusto numero di argomenti. Alcuni pensano che sia 2. Beh, questo continua a chiedersi "quale parametro va in arg [0]?" Invece di scegliere l'opzione che vincola l'interfaccia con una dichiarazione più rigida.

Suppongo di argomentare per una posizione più radicale: non fare affidamento su argomenti posizionali. Sento che è fragile e porta a discussioni sulla posizione dei maledetti argomenti. Salta e vai avanti all'inevitabile scontro sui nomi di funzioni e variabili.

risposta data 13.07.2017 - 13:33
fonte

Leggi altre domande sui tag