Ci sono delle linee guida su quanti parametri una funzione dovrebbe accettare?

96

Ho notato che alcune funzioni con cui lavoro hanno 6 o più parametri, mentre nella maggior parte delle librerie che utilizzo è raro trovare una funzione che richiede più di 3.

Spesso molti di questi parametri aggiuntivi sono opzioni binarie per alterare il comportamento della funzione. Penso che alcune di queste funzioni con umpteen dovrebbero probabilmente essere refactored. C'è una linea guida per quale numero è troppi?

    
posta Darth Egregious 18.04.2012 - 19:31
fonte

10 risposte

93

Non ho mai visto una linea guida, ma nella mia esperienza una funzione che richiede più di tre o quattro parametri indica uno dei due problemi:

  1. La funzione sta facendo troppo. Dovrebbe essere suddiviso in diverse funzioni più piccole, ciascuna con un set di parametri più piccolo.
  2. C'è un altro oggetto nascosto lì dentro. Potrebbe essere necessario creare un altro oggetto o struttura dati che includa questi parametri. Consulta questo articolo nel Oggetto Parametro per ulteriori informazioni.

È difficile dire cosa stai guardando senza ulteriori informazioni. Probabilmente il refactoring che devi fare è dividere la funzione in funzioni più piccole chiamate dal genitore a seconda di quelle che vengono attualmente passate alla funzione.

Ci sono alcuni buoni guadagni in questo modo:

  • Rende il tuo codice più facile da leggere. Personalmente trovo molto più semplice leggere una "lista di regole" composta da una struttura if che chiama molti metodi con nomi descrittivi piuttosto che una struttura che fa tutto in un unico metodo.
  • È più unità testabile. Hai diviso il tuo problema in diversi piccoli compiti che sono individualmente molto semplici. La raccolta di test unitari sarebbe quindi costituita da una suite di test comportamentali che controlla i percorsi attraverso il metodo master e una raccolta di test più piccoli per ogni singola procedura.
risposta data 18.04.2012 - 20:09
fonte
37

Secondo "Clean Code: Un manuale di abilità software agile", zero è l'ideale, uno o due sono accettabili, tre in casi speciali e quattro o più, mai!

Le parole dell'autore:

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.

In questo libro c'è un capitolo che parla solo di funzioni in cui i parametri sono discussi, quindi penso che questo libro possa essere una buona guida di quanti parametri hai bisogno.

Secondo la mia opinione personale, un parametro è migliore di nessuno perché penso sia più chiaro cosa sta succedendo.

Ad esempio, a mio parere la seconda scelta è migliore perché è più chiaro ciò che il metodo sta elaborando:

LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();

vs.

LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);

A proposito di molti parametri, questo può essere un segno che alcune variabili possono essere raggruppate in un singolo oggetto o, in questo caso, molti booleani possono rappresentare che la funzione / metodo sta facendo più di una cosa, e in in questo caso, è meglio refactoring ciascuno di questi comportamenti in una funzione diversa.

    
risposta data 18.04.2012 - 22:01
fonte
22

Se le classi di dominio nell'applicazione sono progettate correttamente, il numero di parametri che passeremo in una funzione verrà automaticamente ridotto - perché le classi sanno come fare il loro lavoro e hanno abbastanza dati per fare il loro lavoro.

Ad esempio, supponi di avere una classe manager che richiede una classe di terza elementare per completare i compiti.

Se modelli correttamente,

3rdGradeClass.finishHomework(int lessonId) {
    result = students.assignHomework(lessonId, dueDate);
    teacher.verifyHomeWork(result);
}

Questo è semplice.

Se non hai il modello corretto, il metodo sarà come questo

Manager.finishHomework(grade, students, lessonId, teacher, ...) {
    // This is not good.
}

Il modello corretto riduce sempre i parametri di funzione tra i richiami del metodo poiché le funzioni corrette sono delegate alle proprie classi (Responsabilità singola) e dispongono di dati sufficienti per svolgere il proprio lavoro.

Ogni volta che vedo aumentare il numero di parametri, controllo il mio modello per vedere se ho progettato correttamente il mio modello di applicazione.

Ci sono alcune eccezioni però: quando ho bisogno di creare un oggetto di trasferimento o oggetti di configurazione, userò un modello di builder per produrre piccoli oggetti costruiti prima di costruire un grande oggetto di configurazione.

    
risposta data 18.04.2012 - 20:59
fonte
15

Un aspetto che le altre risposte non riguardano è il rendimento.

Se si sta programmando in un linguaggio di livello sufficientemente basso (C, C ++, assembly), un gran numero di parametri può essere piuttosto dannoso per le prestazioni su alcune architetture, specialmente se la funzione viene chiamata una grande quantità di volte.

Quando una chiamata di funzione viene eseguita in ARM, ad esempio, i primi quattro argomenti vengono posizionati nei registri r0 in r3 e gli argomenti rimanenti devono essere inseriti nello stack. Mantenere il numero di argomenti sotto i cinque può fare la differenza per le funzioni critiche.

Per le funzioni chiamate molto spesso, anche il fatto che il programma debba impostare gli argomenti prima di ogni chiamata può influire sulle prestazioni (% da% di% a% dir0 può essere sovrascritto dalla funzione chiamata e dovrà essere sostituito prima della prossima chiamata) quindi, a tale riguardo, gli argomenti zero sono i migliori.

Aggiornamento:

KjMag mostra l'argomento interessante di inlining. Inlining in qualche modo lo attenuerà poiché consentirà al compilatore di eseguire le stesse ottimizzazioni che si sarebbero in grado di fare se si scrivesse in puro assembly. In altre parole, il compilatore può vedere quali parametri e variabili vengono utilizzati dalla funzione chiamata e può ottimizzare l'utilizzo del registro in modo tale che la lettura / scrittura dello stack sia ridotta al minimo.

Tuttavia ci sono alcuni problemi con l'inlining.

  1. Inlining fa sì che il binario compilato cresca poiché lo stesso codice è duplicato in forma binaria se è chiamato da più posti. Ciò è dannoso quando si tratta dell'utilizzo della cache I.
  2. I compilatori di solito consentono solo l'allineamento fino a un certo livello (3 passi IIRC?). Immagina di chiamare una funzione in linea da una funzione in linea da una funzione in linea. La crescita binaria esploderebbe se r3 fosse considerato obbligatorio in tutti i casi.
  3. Ci sono un sacco di compilatori che ignorano completamente inline o in realtà ti danno errori quando lo incontrano.
risposta data 19.04.2012 - 16:38
fonte
6

Quando l'elenco dei parametri cresce a più di cinque, prendi in considerazione la definizione di una struttura o di un oggetto "contesto".

Questa è fondamentalmente solo una struttura che contiene tutti i parametri opzionali con alcuni valori predefiniti impostati.

Nel mondo procedurale C farebbe una struttura semplice. In Java, C ++ sarà sufficiente un oggetto semplice. Non scherzare con getter o setter perché l'unico scopo dell'oggetto è di mantenere valori "pubblici" impostabili.

    
risposta data 19.04.2012 - 03:39
fonte
5

No, non ci sono linee guida standard

Ma ci sono alcune tecniche che possono rendere una funzione con molti parametri più sopportabili.

Potresti usare un parametro list-if-args (args *) o un parametro dictionary-of-args (kwargs ** )

Per esempio, in python:

// Example definition
def example_function(normalParam, args*, kwargs**):
  for i in args:
    print 'args' + i + ': ' + args[i] 
  for key in kwargs:
    print 'keyword: %s: %s' % (key, kwargs[key])
  somevar = kwargs.get('somevar','found')
  missingvar = kwargs.get('somevar','missing')
  print somevar
  print missingvar

// Example usage

    example_function('normal parameter', 'args1', args2, 
                      somevar='value', missingvar='novalue')

Uscite:

args1
args2
somevar:value
someothervar:novalue
value
missing

Oppure potresti utilizzare la sintassi della definizione letterale dell'oggetto

Ad esempio, ecco una chiamata jQuery JavaScript per avviare una richiesta GET AJAX:

$.ajax({
  type: 'GET',
  url: 'http://someurl.com/feed',
  data: data,
  success: success(),
  error: error(),
  complete: complete(),
  dataType: 'jsonp'
});

Se dai un'occhiata alla classe ajax di jQuery ci sono un lotto (circa 30) più proprietà che possono essere impostate; soprattutto perché le comunicazioni ajax sono molto complesse. Fortunatamente, la sintassi letterale dell'oggetto rende la vita facile.

C # intellisense fornisce una documentazione attiva dei parametri, quindi non è raro vedere arrangiamenti molto complessi di metodi sovraccaricati.

Le lingue digitate dinamicamente come python / javascript non hanno tale capacità, quindi è molto più comune vedere gli argomenti delle parole chiave e le definizioni letterali degli oggetti.

Preferisco le definizioni letterali dell'oggetto ( anche in C # ) per la gestione di metodi complessi perché è possibile vedere esplicitamente quali proprietà vengono impostate quando un oggetto viene istanziato. Dovrai fare un po 'più di lavoro per gestire gli argomenti predefiniti, ma a lungo andare il tuo codice sarà molto più leggibile. Con le definizioni letterali dell'oggetto puoi interrompere la tua dipendenza dalla documentazione per capire cosa sta facendo il tuo codice a prima vista.

IMHO, i metodi sovraccaricati sono molto sopravvalutati.

Nota: se ricordo che readonly, il controllo degli accessi dovrebbe funzionare per i costruttori di oggetti letterali in C #. Funzionano essenzialmente allo stesso modo delle proprietà di impostazione nel costruttore.

Se non hai mai scritto alcun codice non banale in un linguaggio javaScript tipizzato dinamicamente (python) e / o funzionale / prototipo, consiglio vivamente di provarlo. Può essere un'esperienza illuminante.

Per prima cosa può essere spaventoso interrompere la dipendenza dai parametri per l'approccio completo alla funzione / metodo, ma imparerai a fare molto di più con il tuo codice senza aggiungere complessità inutile.

Aggiornamento:

Probabilmente avrei dovuto fornire esempi per dimostrare l'uso in un linguaggio tipizzato staticamente, ma al momento non sto pensando a un contesto tipizzato staticamente. Fondamentalmente, ho fatto troppo lavoro in un contesto tipizzato dinamicamente per tornare improvvisamente.

Quello che io do conosco è la sintassi della definizione letterale dell'oggetto è completamente possibile nei linguaggi tipizzati staticamente (almeno in C # e Java) perché li ho usati prima. Nei linguaggi tipizzati staticamente sono chiamati 'inizializzatori dell'oggetto'. Ecco alcuni link per mostrare il loro uso in Java e C # .

    
risposta data 18.04.2012 - 20:25
fonte
3

Personalmente, più di 2 è il punto in cui il mio codice avverte l'allarme. Quando consideri le funzioni come operazioni (ovvero una traduzione da input a output), è raro che in un'operazione vengano utilizzati più di 2 parametri. Le procedure (ovvero una serie di passaggi per raggiungere un obiettivo) richiedono più input e talvolta sono l'approccio migliore, ma nella maggior parte delle lingue in questi giorni non dovrebbero essere la norma.

Ma ancora una volta, questa è la linea guida piuttosto che una regola. Ho spesso funzioni che richiedono più di due parametri a causa di circostanze insolite o facilità d'uso.

    
risposta data 18.04.2012 - 20:09
fonte
2

Molto simile a Evan Plaice sta dicendo, sono un grande fan del semplice passaggio di array associativi (o della struttura di dati comparabili della tua lingua) in funzioni quando possibile.

Così, invece di (per esempio) questo:

<?php

createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');

?>

Vai per:

<?php

// create a hash of post data
$post_data = array(
  'title'    => 'the title',
  'summary'  => 'the summary',
  'author'   => 'the author',
  'pubdate'  => 'the publication date',
  'keywords' => 'the keywords',
  'category' => 'the category',
  'etc'      => 'etc',
);

// and pass it to the appropriate function
createBlogPost($post_data);

?>

Wordpress fa molte cose in questo modo, e penso che funzioni bene. (Anche se il mio codice di esempio sopra è immaginario, e non è di per sé un esempio di Wordpress.)

Questa tecnica ti consente di trasferire facilmente un sacco di dati nelle tue funzioni, ma ti libera dal dover ricordare l'ordine in cui devono essere passati.

Apprezzerai anche questa tecnica quando arriva il momento di refactoring - invece di dover modificare potenzialmente l'ordine degli argomenti di una funzione (ad esempio quando ti rendi conto che devi passare ancora un altro argomento), non devi cambiare l'elenco dei parametri delle tue funzioni.

Non solo ti risparmia di dover riscrivere la tua definizione di funzione, ma ti risparmia di dover cambiare l'ordine degli argomenti ogni volta che la funzione viene invocata. Questa è una grande vittoria.

    
risposta data 24.04.2012 - 21:26
fonte
0

Un precedente risposta ha menzionato un autore affidabile che ha affermato che meno parametri le tue funzioni hanno, meglio è. La risposta non ha spiegato perché, ma i libri lo spiegano, e qui ci sono due dei motivi più convincenti del perché è necessario adottare questa filosofia e con la quale sono personalmente d'accordo con:

  • I parametri appartengono a un livello di astrazione diverso da quello della funzione. Ciò significa che il lettore del tuo codice dovrà pensare alla natura e allo scopo dei parametri delle tue funzioni: questo modo di pensare è "di livello inferiore" rispetto a quello del nome e allo scopo delle loro funzioni corrispondenti.

  • Il secondo motivo per avere il minor numero di parametri possibile per una funzione è il test: ad esempio, se hai una funzione con 10 parametri, pensa a quante combinazioni di parametri devi coprire tutti i casi di test per , ad esempio, un test unitario. Meno parametri = meno test.

risposta data 27.09.2017 - 19:23
fonte
-2

Idealmente zero. Uno o due sono ok, tre in alcuni casi.
Quattro o più è di solito una cattiva pratica.

Oltre ai singoli principi di responibilità che altri hanno notato, puoi anche pensarlo dal test e dal debug delle prospettive.

Se c'è un parametro, conoscerne i valori, testarli e trovare errori con loro è "relativamente facile in quanto esiste un solo fattore. Aumentando i fattori, la complessità totale aumenta rapidamente. Per un esempio astratto:

Considera un programma "cosa indossare in questo tempo". Considera cosa potrebbe fare con un input: la temperatura. Come puoi immaginare, i risultati di cosa indossare sono piuttosto semplici basati su quell'unico fattore. Considerate ora cosa potrebbe / dovrebbe / dovrebbe fare il programma se è effettivamente passato temperatura, umidità, punto di rugiada, precipitazioni, ecc. Ora immaginate quanto sarebbe difficile eseguire il debug se fornisse la risposta "sbagliata" a qualcosa.

    
risposta data 18.04.2012 - 22:42
fonte

Leggi altre domande sui tag