Specificare i nomi dei parametri facoltativi anche se non richiesti?


25

Considera il seguente metodo:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{

}

E la seguente chiamata:

var ids = ReturnEmployeeIds(true);

Per uno sviluppatore nuovo nel sistema, sarebbe piuttosto difficile indovinare cosa ha truefatto. La prima cosa da fare è passare con il mouse sopra il nome del metodo o andare alla definizione (nessuno dei due è un compito minimo). Ma, per amor di leggibilità, ha senso scrivere:

var ids = ReturnEmployeeIds(includeManagement: true);

Esiste da qualche parte che, formalmente, si discute se specificare esplicitamente o meno i parametri opzionali quando il compilatore non ne ha bisogno?

Il seguente articolo discute alcune convenzioni di codifica: https://msdn.microsoft.com/en-gb/library/ff926074.aspx

Qualcosa di simile all'articolo sopra sarebbe fantastico.


2
Queste sono due domande piuttosto non correlate: (1) Dovresti scrivere argomenti opzionali per chiarezza? (2) Si dovrebbero usare gli booleanargomenti del metodo e come?
Kilian Foth,

5
Tutto ciò si riduce a preferenze / stile / opinioni personali. Personalmente, mi piacciono i nomi dei parametri quando il valore passato è letterale (una variabile potrebbe già trasmettere abbastanza informazioni attraverso il suo nome). Ma questa è la mia opinione personale.
MetaFight,

1
potresti includere quel link come fonte di esempio nella tua domanda?
MetaFight

4
Se aggiunge qualcosa, l'ho eseguito tramite StyleCop e ReSharper - nessuno dei due aveva una visione chiara su di esso. ReSharper dice semplicemente che il parametro nominato potrebbe essere rimosso e poi continua dicendo che un parametro nominato potrebbe essere aggiunto. Il consiglio è gratuito per un motivo suppongo ...: - /
Robbie Dee

Risposte:


28

Direi che nel mondo C #, un enum sarebbe una delle migliori opzioni qui.

Con ciò saresti costretto a precisare quello che stai facendo e qualsiasi utente vedrebbe cosa sta succedendo. È anche sicuro per future estensioni;

public enum WhatToInclude
{
    IncludeAll,
    ExcludeManagement
}

var ids = ReturnEmployeeIds(WhatToInclude.ExcludeManagement);

Quindi, secondo me qui:

enum> optional enum> opzionale bool

Modifica: a causa della discussione con LeopardSkinPillBoxHat di seguito riguardo a un [Flags]enum che in questo caso specifico potrebbe essere una buona soluzione (dal momento che stiamo specificamente parlando di includere / escludere cose), propongo invece di utilizzare ISet<WhatToInclude>come parametro.

È un concetto più "moderno" con diversi vantaggi, principalmente derivante dal fatto che si adatta alla famiglia di collezioni LINQ, ma che [Flags]ha anche un massimo di 32 gruppi. Il principale svantaggio dell'utilizzo ISet<WhatToInclude>è il modo in cui i set male sono supportati nella sintassi di C #:

var ids = ReturnEmployeeIds(
    new HashSet<WhatToInclude>(new []{ 
        WhatToInclude.Cleaners, 
        WhatToInclude.Managers});

Alcuni potrebbero essere mitigati da funzioni di supporto, sia per la generazione del set che per la creazione di "set di collegamenti" come

var ids = ReturnEmployeeIds(WhatToIncludeHelper.IncludeAll)

Tendo ad essere d'accordo; e in genere ho iniziato a usare enum nel mio nuovo codice se ci sono vagamente possibilità che le condizioni vengano espanse in futuro. Sostituire un bool con un enum quando qualcosa diventa un tri-stato finisce per creare differenze davvero rumorose e rendere il codice della colpa molto più noioso; quando si finisce per dover aggiornare nuovamente il codice un anno dopo la strada per una terza opzione.
Dan Neely,

3
Mi piace l' enumapproccio, ma non sono un grande fan del mixaggio Includee Excludenello stesso modo enumin cui è più difficile capire cosa sta succedendo. Se vuoi che questo sia veramente estensibile, puoi renderlo un [Flags] enum, renderli tutti IncludeXxxe fornire un valore IncludeAllimpostato su Int32.MaxValue. Quindi è possibile fornire 2 ReturnEmployeeIdssovraccarichi, uno che restituisce tutti i dipendenti e uno che accetta un WhatToIncludeparametro di filtro che può essere una combinazione di flag enum.
LeopardSkinPillBoxHat

@LeopardSkinPillBoxHat Ok, sono d'accordo sull'esclusione / inclusione. Quindi questo è un esempio un po 'inventato, ma avrei potuto chiamarlo IncludeAllButManagement. Per quanto riguarda l'altro punto, non sono d'accordo - penso che [Flags]sia una reliquia e debba essere utilizzata solo per motivi legati alla performance o al passato. Penso che a ISet<WhatToInclude>sia un concetto di gran lunga migliore (purtroppo a C # manca uno zucchero sintattico per renderlo piacevole da usare).
NiklasJ,

@NiklasJ - Mi piace l' Flagsapproccio perché consente al chiamante di passare WhatToInclude.Managers | WhatToInclude.Cleanerse in modo bit a bit o esprime esattamente come il chiamante lo elaborerà (cioè gestori o addetti alle pulizie). Zucchero sintattico intuitivo :-)
LeopardSkinPillBoxHat

@LeopardSkinPillBoxHat Esatto, ma questo è l'unico vantaggio di quel metodo. Gli svantaggi includono ad esempio un numero limitato di scelte e non sono compatibili con altri concetti simili a Linq.
NiklasJ,

20

ha senso scrivere:

 var ids=ReturnEmployeeIds(includeManagement: true);

È discutibile se si tratta di "buon stile", ma IMHO non è affatto un cattivo stile e utile, purché la riga di codice non diventi "troppo lunga". Senza parametri nominati, è anche uno stile comune introdurre una variabile esplicativa:

 bool includeManagement=true;
 var ids=ReturnEmployeeIds(includeManagement);

Tale stile è probabilmente più comune tra i programmatori C # rispetto alla variante di parametro denominata, poiché i parametri denominati non facevano parte del linguaggio prima della versione 4.0. L'effetto sulla leggibilità è quasi lo stesso, ha solo bisogno di una riga di codice aggiuntiva.

Quindi la mia raccomandazione: se pensi che usare un enum o due funzioni con nomi diversi sia "eccessivo", o se non vuoi o non puoi cambiare la firma per un motivo diverso, vai avanti e usa il parametro nominato.


Potrei notare che stai usando memoria aggiuntiva nel secondo esempio
Alvaro

10
@Alvaro È altamente improbabile che sia vero in un caso così banale (in cui la variabile ha un valore costante). Anche se lo fosse, non vale la pena menzionarlo. La maggior parte di noi non esegue attualmente 4KB di RAM.
Chris Hayes,

3
Poiché si tratta di C #, è possibile contrassegnare includeManagementcome locale consted evitare di utilizzare memoria aggiuntiva.
Jacob Krall,

8
Ragazzi, non aggiungerò nulla alla mia risposta che potrebbe distrarre da ciò che penso sia importante qui.
Doc Brown,

17

Se incontri codice come:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{

}

puoi praticamente garantire che cosa c'è dentro {}sarà qualcosa del tipo:

public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
    if (includeManagement)
        return something
    else
        return something else
}

In altre parole, il booleano viene utilizzato per scegliere tra due linee d'azione: il metodo ha due responsabilità. Questo è praticamente garantito un odore di codice . Dobbiamo sempre cercare di garantire che i metodi facciano solo una cosa; hanno solo una responsabilità. Pertanto, direi che entrambe le tue soluzioni sono l'approccio sbagliato. L'uso di parametri opzionali indica che il metodo sta facendo più di una cosa. Anche i parametri booleani ne sono un segno. Avere entrambi dovrebbe sicuramente far suonare le campane di allarme. Ciò che dovresti fare, quindi, è il refactoring delle due diverse serie di funzionalità in due metodi separati. Dai ai nomi dei metodi che chiariscono cosa fanno, ad esempio:

public List<Guid> GetNonManagementEmployeeIds()
{

}

public List<Guid> GetAllEmployeeIds()
{

}

Ciò migliora la leggibilità del codice e garantisce che tu stia meglio seguendo i principi SOLID.

MODIFICA Come è stato sottolineato nei commenti, il caso qui che probabilmente questi due metodi avranno funzionalità condivisa e che la funzionalità condivisa sarà probabilmente racchiusa in una funzione privata che avrà bisogno di un parametro booleano per controllare alcuni aspetti di ciò che fa .

In questo caso, deve essere fornito il nome del parametro, anche se il compilatore non ne ha bisogno? Suggerirei che non ci sia una risposta semplice e che il giudizio sia necessario caso per caso:

  • L'argomento per specificare il nome è che altri sviluppatori (incluso te stesso tra sei mesi) dovrebbero essere il pubblico principale del tuo codice. Il compilatore è sicuramente un pubblico secondario. Quindi scrivi sempre il codice per rendere più facile la lettura da parte di altre persone; piuttosto che fornire semplicemente ciò di cui il compilatore ha bisogno. Un esempio di come utilizziamo questa regola ogni giorno è che non riempiamo il nostro codice con variabili _1, _2 ecc., Usiamo nomi significativi (che non significano nulla per il compilatore).
  • L'argomento contrario è che i metodi privati ​​sono dettagli di implementazione. Ci si potrebbe ragionevolmente aspettare che chiunque guardi un metodo come quello sotto, tracci attraverso il codice per capire lo scopo del parametro booleano in quanto si trovano all'interno del codice:
public List<Guid> GetNonManagementEmployeeIds()
{
    return ReturnEmployeeIds(true);
}

Il file sorgente e i metodi sono piccoli e facilmente comprensibili? Se sì, allora il parametro indicato probabilmente equivale a rumore. In caso contrario, potrebbe essere molto utile. Quindi applicare la regola in base alle circostanze.


12
Ho sentito quello che stai dicendo, ma in qualche modo ti sei perso il punto di quello che ti sto chiedendo. Supponendo uno scenario in cui è più appropriato usare parametri opzionali (questi scenari esistono), è giusto nominare i parametri anche se non è necessario?
JᴀʏMᴇᴇ

4
Tutto vero, ma la domanda riguardava una chiamata a un tale metodo. Anche un parametro flag non garantisce affatto un ramo all'interno del metodo. Potrebbe semplicemente essere passato a un altro livello.
Robbie Dee,

Questo non è sbagliato, ma semplifica eccessivamente. Nel codice reale, troverai public List<Guid> ReturnEmployeeIds(bool includeManagement) { calculateSomethingHereForBothBranches; if (includeManagement) return something else return something else }, così dividere che semplicemente in due metodi probabilmente significherà che quei due metodi avranno bisogno del risultato del primo calcolo come parametro.
Doc Brown,

4
Penso che tutto ciò che stiamo dicendo è che il volto pubblico della tua classe dovrebbe probabilmente esporre due metodi (ciascuno con una responsabilità) ma, internamente, ciascuno di questi metodi può ragionevolmente inoltrare la richiesta a un singolo metodo privato con un parametro bool ramificato .
MetaFight

1
Posso immaginare 3 casi per il comportamento che differisce tra le due funzioni. 1. Esiste una pre-elaborazione diversa. Le funzioni pubbliche preelaborano gli argomenti nella funzione privata. 2. Esiste una post-elaborazione diversa. Chiedi alle funzioni pubbliche di post-processare il risultato dalla funzione privata. 3. Esiste un comportamento intermedio che differisce. Questo può essere passato come lambda alla funzione privata, supponendo che la tua lingua lo supporti. Spesso, questo porta a una nuova astrazione riutilizzabile che in realtà non ti era mai accaduta prima.
jpmc26,

1

Per uno sviluppatore nuovo nel sistema, sarebbe piuttosto difficile indovinare cosa ha fatto la verità. La prima cosa da fare è passare il mouse sopra il nome del metodo o andare alla definizione (nessuno dei due è un grande compito in assoluto)

Sì vero. Il codice di auto-documentazione è una cosa bellissima. Come hanno sottolineato altre risposte, ci sono modi per rimuovere l'ambiguità della chiamata ReturnEmployeeIdsutilizzando un enum, nomi di metodi diversi, ecc. Tuttavia a volte non si può davvero evitare il caso ma non si vuole uscire e usare loro ovunque (a meno che non ti piaccia la verbosità di Visual Basic).

Ad esempio, può aiutare a chiarire una chiamata ma non necessariamente un'altra.

Un argomento con nome può essere utile qui:

var ids = ReturnEmployeeIds(includeManagement: true);

Non aggiungere molta chiarezza aggiuntiva (ho intenzione di indovinare che questo sta analizzando un enum):

Enum.Parse(typeof(StringComparison), "Ordinal", ignoreCase: true);

Può effettivamente ridurre la chiarezza (se una persona non capisce qual è la capacità in un elenco):

var employeeIds = new List<int>(capacity: 24);

O dove non importa perché stai usando buoni nomi di variabili:

bool includeManagement = true;
var ids = ReturnEmployeeIds(includeManagement);

Esiste da qualche parte che, formalmente, si discute se specificare esplicitamente o meno i parametri opzionali quando il compilatore non ne ha bisogno?

Non AFAIK. Tuttavia, affrontiamo entrambe le parti: l'unica volta che è necessario utilizzare esplicitamente i parametri con nome è perché il compilatore richiede di farlo (in genere è per rimuovere l'ambiguità delle istruzioni). A parte questo, puoi usarli quando vuoi. Questo articolo di MS ha alcuni suggerimenti su quando potresti volerli usare ma non è particolarmente definitivo https://msdn.microsoft.com/en-us/library/dd264739.aspx .

Personalmente, trovo che li uso più spesso quando creo attributi personalizzati o stubbing un nuovo metodo in cui i miei parametri possono cambiare o essere riordinati (gli attributi con nome b / c possono essere elencati in qualsiasi ordine). Inoltre li uso solo quando sto fornendo un valore statico in linea, altrimenti se passo una variabile cerco di usare nomi di variabili validi.

TL; DR

Alla fine si riduce alle preferenze personali. Ovviamente ci sono alcuni casi d'uso in cui devi usare parametri nominati ma dopo devi fare una chiamata di giudizio. IMO: utilizzalo ovunque ritieni che aiuti a documentare il tuo codice, a ridurre le ambiguità o a proteggere le firme dei metodi.


0

Se il parametro è evidente dal nome (completo) del metodo e la sua esistenza è naturale a quello che si suppone che il metodo per farlo, è necessario non utilizzare la sintassi parametro denominato. Ad esempio, ReturnEmployeeName(57)è abbastanza dannatamente ovvio che 57è l'ID dell'impiegato, quindi è ridondante annotarlo con la sintassi del parametro denominato.

Con ReturnEmployeeIds(true), come hai detto, a meno che non guardi la dichiarazione / documentazione della funzione è impossibile capire cosa truesignifichi, quindi dovresti usare la sintassi del parametro indicato.

Ora, considera questo terzo caso:

void PrintEmployeeNames(bool includeManagement) {
    foreach (id; ReturnEmployeeIds(includeManagement)) {
        Console.WriteLine(ReturnEmployeeName(id));
    }
}

La sintassi del parametro denominato non viene utilizzata qui, ma poiché stiamo passando una variabile ReturnEmployeeIdse quella variabile ha un nome, e quel nome sembra significare la stessa cosa del parametro a cui lo stiamo passando (questo è spesso il caso - ma non sempre!) - non abbiamo bisogno della sintassi del parametro nominato per capire il significato del codice.

Quindi, la regola è semplice: se puoi semplicemente capire dalla chiamata del metodo cosa dovrebbe significare il parametro, non utilizzare il parametro indicato. Se non ci riesci, questa è una carenza nella leggibilità del codice, e probabilmente vorrai risolverli (non ad ogni costo, ovviamente, ma di solito vuoi che il codice sia leggibile). La correzione non deve essere denominata parametri - puoi anche inserire prima il valore in una variabile o utilizzare i metodi suggeriti nelle altre risposte - purché il risultato finale renda facile capire cosa fa il parametro.


7
Non sono d'accordo sul fatto che ReturnEmployeeName(57)sia immediatamente evidente che 57è l'ID. GetEmployeeById(57)d'altra parte ...
Hugo Zink,

@HugoZink All'inizio volevo usare qualcosa del genere per l'esempio, ma l'ho modificato intenzionalmente ReturnEmployeeNameper dimostrare casi in cui anche senza menzionare esplicitamente il parametro nel nome del metodo, è abbastanza ragionevole aspettarsi che le persone capiscano di cosa si tratta. Ad ogni modo, è degno di nota il fatto che ReturnEmployeeName, diversamente da ciò , non si può ragionevolmente rinominare ReturnEmployeeIdsqualcosa che indica il significato dietro i parametri.
Idan Arye,

1
In ogni caso, è abbastanza improbabile che scriveremo ReturnEmployeeName (57) in un vero programma. È molto più probabile che scriveremo ReturnEmployeeName (employeeId). Perché dovremmo codificare un ID dipendente? A meno che non sia l'ID del programmatore e ci sia una sorta di frode in corso, come, aggiungere un po 'di stipendio a questo dipendente. :-)
Jay

0

Sì, penso che sia di buon stile.

Questo ha molto senso per le costanti booleane e intere.

Se si passa in una variabile, il nome della variabile dovrebbe chiarire il significato. In caso contrario, dovresti dare alla variabile un nome migliore.

Se si passa in una stringa, il significato è spesso chiaro. Come se vedessi una chiamata a GetFooData ("Oregon"), penso che un lettore possa quasi indovinare che il parametro è un nome di stato.

Non tutte le stringhe sono ovvie, ovviamente. Se vedo GetFooData ("M"), a meno che non conosca la funzione non ho idea di cosa significhi "M". Se è una specie di codice, come M = "impiegati a livello di gestione", probabilmente dovremmo avere un enum piuttosto che un letterale, e il nome dell'enum dovrebbe essere chiaro.

E suppongo che una stringa potrebbe essere fuorviante. Forse "Oregon" nel mio esempio sopra si riferisce non allo stato, ma a un prodotto o un cliente o altro.

Ma GetFooData (true) o GetFooData (42) ... ciò potrebbe significare qualsiasi cosa. I parametri nominati sono un modo meraviglioso per renderlo auto-documentante. Li ho usati esattamente per quello scopo in diverse occasioni.


0

Non ci sono ragioni super chiare per cui utilizzare questo tipo di sintassi in primo luogo.

In generale, cerca di evitare di avere argomenti booleani che a distanza possano sembrare arbitrari. includeManagement(molto probabilmente) influenzerà notevolmente il risultato. Ma l'argomento sembra avere un "piccolo peso".

È stato discusso l'uso di un enum, non solo sembrerà che l'argomento "abbia più peso", ma fornirà anche scalabilità al metodo. Questa potrebbe tuttavia non essere la soluzione migliore in tutti i casi, poiché il tuo ReturnEmployeeIdsmetodo dovrà ridimensionarsi insieme al WhatToInclude-enum (vedi la risposta di NiklasJ ). Questo potrebbe farti venire il mal di testa in seguito.

Considera questo: se ridimensioni l' WhatToIncludeenum, ma non il ReturnEmployeeIdsmetodo. Potrebbe quindi lanciare un ArgumentOutOfRangeException(miglior caso) o restituire qualcosa di completamente indesiderato ( nullo vuoto List<Guid>). Il che in alcuni casi potrebbe confondere il programmatore, specialmente se sei ReturnEmployeeIdsin una libreria di classi, dove il codice sorgente non è prontamente disponibile.

Si supporrà che WhatToInclude.Traineesfunzionerà se WhatToInclude.Allfunziona, visto che i tirocinanti sono un "sottoinsieme" di tutti.

Questo (ovviamente) dipende da come ReturnEmployeeIds viene implementato.


Nei casi in cui un argomento booleano potrebbe essere passato, provo invece a suddividerlo in due metodi (o più, se necessario), nel tuo caso; si potrebbe estrarre ReturnAllEmployeeIds, ReturnManagementIdse ReturnRegularEmployeeIds. Questo copre tutte le basi ed è assolutamente autoesplicativo per chiunque lo attui. Anche questo non avrà il "ridimensionamento", di cui sopra.

Dal momento che ci sono solo due risultati per qualcosa che ha un argomento booleano. L'implementazione di due metodi richiede pochissimi sforzi extra.

Meno codice, raramente è meglio.


Detto questo, ci sono alcuni casi in cui dichiarare esplicitamente l'argomento migliora la leggibilità. Considera ad esempio. GetLatestNews(max: 10). GetLatestNews(10)è ancora abbastanza autoesplicativo, la dichiarazione esplicita di maxaiuterà a chiarire qualsiasi confusione.

E se devi assolutamente avere un argomento booleano, quale uso non può essere dedotto solo leggendo trueo false. Quindi direi che:

var ids = ReturnEmployeeIds(includeManagement: true);

.. è assolutamente migliore e più leggibile di:

var ids = ReturnEmployeeIds(true);

Dal momento che nel secondo esempio, truepotrebbe significare assolutamente qualsiasi cosa . Ma proverei a evitare questo argomento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.