Specifica i nomi dei parametri opzionali anche se non richiesto?

25

Considera il seguente metodo:

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

}

E la seguente chiamata:

var ids = ReturnEmployeeIds(true);

Per uno sviluppatore nuovo al sistema, sarebbe piuttosto difficile indovinare cosa ha fatto true . La prima cosa che devi fare è passare il mouse sopra il nome del metodo o andare alla definizione (nessuno dei quali è un grosso problema in minima parte). Ma, per motivi di leggibilità, ha senso scrivere:

var ids = ReturnEmployeeIds(includeManagement: true);

C'è da qualche parte che, formalmente, discute se specificare esplicitamente o no parametri opzionali quando il compilatore non ha bisogno di te?

Il seguente articolo tratta alcune convenzioni di codifica: link

Qualcosa di simile all'articolo sopra sarebbe fantastico.

    
posta JᴀʏMᴇᴇ 19.01.2016 - 11:44
fonte

7 risposte

28

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

Con ciò sarai obbligato a spiegare cosa stai facendo e ogni utente vedrebbe che cosa sta succedendo. È anche sicuro per le estensioni future;

public enum WhatToInclude
{
    IncludeAll,
    ExcludeManagement
}

var ids = ReturnEmployeeIds(WhatToInclude.ExcludeManagement);

Quindi, a mio parere qui:

enum > opzionale enum > bool opzionale

Modifica: A causa della discussione con LeopardSkinPillBoxHat di seguito riguardante un enum [Flags] che in questo caso specifico potrebbe essere una buona scelta (poiché stiamo parlando specificamente di includere / escludere le cose), propongo invece di usare ISet<WhatToInclude> come parametro.

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

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

Alcuni potrebbero essere mitigati dalle funzioni di supporto, sia per generare l'insieme sia per creare "set di scorciatoie" come

var ids = ReturnEmployeeIds(WhatToIncludeHelper.IncludeAll)
    
risposta data 19.01.2016 - 13:00
fonte
19

does it make sense to write:

 var ids=ReturnEmployeeIds(includeManagement: true);

È discutibile se questo è un "buon stile", ma questo è quanto meno lo stile non è male, e utile, purché la linea di codice non diventi "troppo lunga". Senza parametri con nome, è anche uno stile comune introdurre una variabile esplicativa:

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

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

Quindi la mia raccomandazione: se pensate di usare un enum o due funzioni con nomi diversi è "overkill", o non volete o non potete cambiare la firma per un motivo diverso, andare avanti e usare il parametro named.

    
risposta data 19.01.2016 - 13:14
fonte
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 è usato per scegliere tra due linee di azione: il metodo ha due responsabilità. Questo è praticamente garantito un odore di codice . Dovremmo sempre sforzarci di garantire che i metodi facciano solo una cosa; hanno solo una responsabilità. Pertanto, direi che entrambe le soluzioni sono l'approccio sbagliato. L'utilizzo di parametri facoltativi indica che il metodo sta facendo più di una cosa. Anche i parametri booleani ne sono un segno. Avere entrambi dovrebbe sicuramente impostare suonerie di allarme. Quello che dovresti fare, quindi, è il refactoring delle due diverse serie di funzionalità in due metodi separati. Dai ai metodi i nomi che chiariscono cosa fanno, ad esempio:

public List<Guid> GetNonManagementEmployeeIds()
{

}

public List<Guid> GetAllEmployeeIds()
{

}

Questo migliora la leggibilità del codice e ti assicura che stai seguendo i principi SOLID.

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

In questo caso, dovrebbe essere fornito il nome del parametro, anche se il compilatore non ne ha bisogno? Suggerirei che non esiste una risposta semplice a questo e che il giudizio è 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 renderlo più facile agli altri da leggere; piuttosto che fornire solo ciò di cui ha bisogno il compilatore. Un esempio di come usiamo questa regola ogni giorno, è che non riempiamo il nostro codice con le variabili _1, _2 ecc., Usiamo nomi significativi (che non significano nulla per il compilatore).
  • L'argomento contatore è che i metodi privati sono dettagli di implementazione. Si potrebbe ragionevolmente aspettarsi che chiunque stia guardando un metodo come di seguito, rintraccerebbe il codice per capire lo scopo del parametro booleano così come lo è all'interno del codice interno:
public List<Guid> GetNonManagementEmployeeIds()
{
    return ReturnEmployeeIds(true);
}

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

    
risposta data 19.01.2016 - 12:03
fonte
1

For a developer new to the system, it'd be pretty difficult guessing what true did. First thing you'd do is hover over the method name or go to the definition (neither of which are big tasks in the slightest)

Sì, vero. Il codice di auto-documentazione è una cosa meravigliosa. Come hanno sottolineato altre risposte, ci sono modi per rimuovere l'ambiguità della chiamata a ReturnEmployeeIds usando enum, nomi di metodi diversi, ecc. Tuttavia a volte non si può davvero evitare il caso ma non si vuole andare fuori e usali ovunque (a meno che non ti piaccia la verbosità di base visiva).

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 più chiarezza (suppongo che questo stia analizzando un enum):

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

Può effettivamente ridurre la chiarezza (se una persona non capisce qual è la capacità di una lista):

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

O dove non è importante perché stai usando nomi di variabili validi:

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

Is there anywhere that, formally, discusses whether or not to explicitly specify optional parameters when the compiler doesn't need you to?

Non AFAIK. Diamo comunque una risposta ad entrambe le parti: l'unica volta che è necessario utilizzare in modo esplicito i parametri con nome è perché il compilatore richiede di farlo (in genere è necessario rimuovere l'ambiguità delle istruzioni). Quindi oltre a questo puoi usarli quando vuoi. Questo articolo di MS contiene alcuni suggerimenti su quando potresti volerli utilizzare ma non è particolarmente definitivo link .

Personalmente, trovo che li uso più spesso quando sto creando attributi personalizzati o estrapolando 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 fornisco un valore statico inline, altrimenti se passo una variabile cerco di usare nomi di variabili validi.

TL;DR

In definitiva si tratta di preferenze personali. Ovviamente ci sono alcuni casi d'uso quando devi usare i parametri con nome, ma dopo devi fare una chiamata di giudizio. IMO: utilizzalo ovunque che ritieni possa aiutare a documentare il codice, ridurre le ambiguità o consentire di proteggere le firme dei metodi.

    
risposta data 20.01.2016 - 15:58
fonte
0

Se il parametro è ovvio dal nome (completo) del metodo e la sua esistenza è naturale rispetto a ciò che il metodo dovrebbe fare, dovresti non utilizzare la sintassi dei parametri con nome. Ad esempio, in ReturnEmployeeName(57) è abbastanza dannatamente ovvio che 57 sia l'ID del dipendente, quindi è ridondante annotarlo con la sintassi dei parametri nominati.

Con ReturnEmployeeIds(true) , come hai detto, a meno che non si guardi la dichiarazione / documentazione della funzione è impossibile capire cosa significhi true - quindi dovrebbe usare la sintassi dei parametri con nome.

Ora, considera questo terzo caso:

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

La sintassi dei parametri nominati non è usata qui, ma dal momento che stiamo passando una variabile a ReturnEmployeeIds , e quella variabile ha un nome, e quel nome capita di significare la stessa cosa del parametro a cui stiamo passando (questo è spesso il caso - ma non sempre!) - non abbiamo bisogno della sintassi dei parametri nominati per capire il significato del codice.

Quindi, la regola è semplice: se puoi facilmente capire dalla sola chiamata di metodo che cosa si suppone che il parametro significhi, non usare il parametro named. Se non è possibile, si tratta di una carenza nella leggibilità del codice e probabilmente si desidera correggerli (non a qualsiasi prezzo, ovviamente, ma di solito si desidera che il codice sia leggibile). La correzione non deve essere definita come parametro - puoi anche inserire prima il valore in una variabile, o usare i metodi suggeriti nelle altre risposte - finché il risultato finale rende facile capire cosa fa il parametro.

    
risposta data 19.01.2016 - 14:11
fonte
0

Sì, penso che sia di buon gusto.

Questo ha più senso per le costanti booleane e per interi.

Se stai trasmettendo una variabile, il nome della variabile dovrebbe chiarire il significato. In caso contrario, dovresti assegnare alla variabile un nome migliore.

Se stai trasmettendo una stringa, il significato è spesso chiaro. Come se vedessi una chiamata a GetFooData ("Oregon"), penso che un lettore possa praticamente intuire che il parametro è un nome di stato.

Naturalmente non tutte le stringhe sono ovvie. 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 management", probabilmente dovremmo avere un enum piuttosto che un valore letterale, e il nome dell'enum dovrebbe essere chiaro.

E suppongo che una stringa possa essere fuorviante. Forse "Oregon" nel mio esempio sopra si riferisce, non allo stato, ma a un prodotto o un client o qualsiasi altra cosa.

Ma GetFooData (true) o GetFooData (42) ... 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.

    
risposta data 20.01.2016 - 05:13
fonte
0

Non ci sono ragioni per super clear come il motivo per cui utilizzare questo tipo di sintassi, in primo luogo.

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

L'uso di un enum è stato discusso, non solo assomiglierà all'argomento "porta più peso", ma fornirà anche la scalabilità al metodo. Questa potrebbe tuttavia non essere la soluzione migliore in tutti i casi, poiché il tuo ReturnEmployeeIds -method dovrà scalare insieme al WhatToInclude -enum (vedi Risposta di NiklasJ ). Questo potrebbe darti un mal di testa più tardi.

Considera questo: se ridimensiona il WhatToInclude -enum, ma non il ReturnEmployeeIds -method. Potrebbe quindi generare un ArgumentOutOfRangeException (best case) o restituire qualcosa di completamente indesiderato ( null o un List<Guid> vuoto). Che in alcuni casi potrebbe confondere il programmatore, specialmente se il ReturnEmployeeIds è in una libreria di classi, in cui il codice sorgente non è facilmente disponibile.

Si supporrà che WhatToInclude.Trainees funzioni se WhatToInclude.All , visto che i tirocinanti sono un "sottoinsieme" di tutti.

Questo (ovviamente) dipende da come ReturnEmployeeIds è implementato.

Nei casi in cui un argomento booleano può essere passato, cerco di scomporlo in due modi (o più, se necessario), nel tuo caso; uno potrebbe estrarre ReturnAllEmployeeIds , ReturnManagementIds e ReturnRegularEmployeeIds . Questo copre tutte le basi ed è assolutamente auto-esplicativo per chi lo implementa. Anche questo non avrà il "ridimensionamento" -issue, menzionato sopra.

Dato che ci sono sempre due risultati per qualcosa che ha un argomento booleano. L'implementazione di due metodi richiede pochissimo sforzo in più.

Meno codice, raramente è meglio.

Con questo detto, sono alcuni casi in cui la dichiarazione esplicita dell'argomento migliora la leggibilità. Pensa ad esempio. %codice%. GetLatestNews(max: 10) è ancora abbastanza auto-esplicativo, la dichiarazione esplicita di GetLatestNews(10) aiuterà a chiarire qualsiasi confusione.

E se devi assolutamente avere un argomento booleano, quale utilizzo non può essere dedotto leggendo solo max o true . Quindi direi che:

var ids = ReturnEmployeeIds(includeManagement: true);

.. è assolutamente migliore e più leggibile di:

var ids = ReturnEmployeeIds(true);

Poiché nel secondo esempio, false potrebbe significare assolutamente qualsiasi cosa . Ma proverei ad evitare questo argomento.

    
risposta data 20.01.2016 - 17:39
fonte

Leggi altre domande sui tag