Parametri di output con nome e valori di ritorno

0

Quale codice è migliore:

// C++
void handle_message(...some input parameters..., bool& wasHandled)
void set_some_value(int newValue, int* oldValue = nullptr) 

// C#
void handle_message(...some input parameters..., out bool wasHandled)
void set_some_value(int newValue, out int oldValue)

o

bool handle_message(...some input parameters...) ///< Returns -1 if message was handled
                                                //(sorry, this documentation was broken a year ago and we're too busy to fix it)
int set_some_value(T newValue) // (well, it's obvious what this function returns, so I didn't write any documentation for it)

Il primo non ha documentazione, ma non ne ha bisogno. È un codice di auto-documentazione. Il valore di uscita indica chiaramente cosa significa, ed è davvero difficile apportare una modifica come questa:

- void handle_message(Message msg, bool& wasHandled) {
-    wasHandled = false;
-    if (...) { wasHandled = true; ...
+ void handle_message(Message msg, int& wasHandled) {
+    wasHandled = -1;
+    if (...) { wasHandled = ...;

Quando il codice utilizza valori di ritorno, è facile cambiarli e lasciare i commenti interrotti:

  /// Return true if message was handled
- bool handle_message(Message msg) {
+ int handle_message(Message msg) {
...
-     return true;
+     return -1;

La maggior parte dei compilatori non controlla (e non può) la documentazione che è scritta nei commenti. I programmatori tendono anche a ignorare i commenti quando modificano il codice.

Quindi la domanda è:
se una routine ha un singolo valore di uscita,
dovrebbe essere una procedura con parametro di output auto-documentante ben chiamato,
o dovrebbe essere una funzione che restituisce un valore senza nome con un commento che lo descrive?

    
posta Abyx 27.06.2013 - 14:54
fonte

5 risposte

7

Modifica (poiché la domanda è cambiata)

Se il tuo metodo ha solo un output, ovviamente restituisci quel valore.

bool DoStuff(...)

è molto più leggibile quando lo usi che

void DoStuff(..., out bool success)

Guarda l'utilizzo del campione:

if(DoStuff(....))

vs

DoStuff(..., out success)
if (success)

Inoltre, consente di inserire e concatenare (se la lingua lo supporta):

ProcessRequest(GetRequest(reqID))
newval = ProcessString(oldval).Replace("/t","")

Convertire questi parametri in "out" porterebbe a un codice molto più brutto, in più è semplicemente sbagliato.

Per 1 ritorno, usa sempre i valori di ritorno.

    
risposta data 27.06.2013 - 15:15
fonte
9

Crea funzioni restituiscono valori. Questo non è alcun tipo di cosa funzionale o anche relativamente moderna. Se non sei chiaro su cosa sta restituendo la funzione, allora la tua funzione ha bisogno di un nome migliore. Se il tuo codice sta facendo le cose sbagliate, correggilo e / o testalo.

Nel vuoto, l'unica volta che la modifica degli input è accettabile è quando quell'input è this nei linguaggi orientati agli oggetti o quando il valore di ritorno è preso da un'altra parte della chiamata alla funzione (cose come TryParse ).

modifica: (perché la domanda è cambiata)

So, again, the question is: if subroutine has single output value, should it be a procedure with well-named self-documenting output parameter, or should it be a function which returns an unnamed value and have a comment describing it?

Nessuno dei due. Dovrebbe essere una funzione ben denominata in modo che il suo output sia chiaro nel sito di chiamata . Il commento alla dichiarazione aiuta solo nella dichiarazione. Fare commenti su tutti i siti di chiamata è inutile e non gestibile.

Se non riesci a creare un buon nome di funzione per descriverne l'output, ripensa il tuo design.

    
risposta data 27.06.2013 - 15:16
fonte
4

Un motivo per scrivere funzioni appropriate con valori di ritorno (al contrario di procedure-in-camuffamento con le variabili di "output") è la componibilità.

Se hai delle funzioni (scusi il mio pseudocodice) void f(in: int x, out: int y) e int g(in: int x) , come le componi?

Non puoi applicare g a f applicato a ... dire, 42:

int y = g(f(42));

Invece, devi scrivere:

int x = 0;
f(42, x);
int y = g(x);

Che è decisamente più goffo e più soggetto a errori. E la componibilità conta molto nelle lingue con funzioni di prima classe.

Ecco un altro esempio, si spera più convincente: mi piacerebbe poter scrivere

int maxSum = max(sumOfArray(array1), sumOfArray(array2)));

invece di saltare attraverso i cerchi per ottenere lo stesso risultato:

int sum1 = 0;
sumOfArray(array1, sum1);

int sum2 = 0;
sumOfArray(array2, sum2);

int maxSum = 0;
max(sum1, sum2, maxSum);

Si spera che questo sia uno scenario meno elaborato che illustra il motivo per cui la composizione delle funzioni è utile e perché richiede che la funzione restituisca il risultato anziché modificare una variabile "out".

    
risposta data 27.06.2013 - 17:44
fonte
1

Puoi rendere la tua funzione restituire qualcosa che è ovviamente un valore di ritorno. È anche utile inviare un messaggio di errore all'interfaccia utente quando qualcosa non funziona.

ReturnValue myFunction(object newValue)

class ReturnValue
{
    public bool Success {get;set;}
    public object OldValue {get;set;}
    public string Message {get;set;}
}
    
risposta data 27.06.2013 - 17:59
fonte
0

La tua domanda è

If subroutine has single output value, should it be a procedure with well-named self-documenting output parameter, or should it be a function which returns an unnamed value and have a comment describing it?

e la risposta è (o, forse, entrambi). Se hai un solo valore di output, dovresti sicuramente usare le funzioni, non void procedure. Ma il nome della funzione dovrebbe rendere banale la visualizzazione del valore restituito, quindi non è un valore "senza nome" e non ha bisogno di un commento.

Il tuo esempio è chiamato handle_value(Message msg) . Questo è un brutto nome di funzione, a meno che non ti interessi dei risultati (anche se vedi l'ultimo esempio qui sotto). Confrontalo con l'impostazione predefinita di C # per la gestione degli eventi: void btnCreateTransfer_Click - la funzione chiamante non si cura di cosa succede all'interno di questa funzione, quindi non restituiamo nulla, e il nome dice semplicemente che fa un "clic" su "btnCreateTransfer" .

A seconda di ciò che desideri rimuovere dalla tua logica handle_value , puoi rinominarlo in vari modi.

  • Se vuoi solo verificare che sia in grado di essere gestito (vale a dire che il messaggio era valido), puoi definirlo come bool is_valid_message(Message msg) . Il risultato è chiaramente vero se è valido e falso se non lo è.

    • C #: string.IsNullOrEmpty()
  • Se vuoi solo sapere se è stato gestito correttamente, puoi chiamarlo bool try_handling_message(Message msg) , o bool handled_message(Message msg) , o avere una convenzione di codifica che qualsiasi bool risultante da una funzione non chiara è espressamente riuscita o fallita.

    • C #: int.TryParse() - questo ha un parametro out per il valore, ma la funzione stessa ti dice se ha avuto successo.
  • Se hai bisogno di maggiori dettagli dalla tua risposta, puoi utilizzare int get_message_status(Message msg) . Ovviamente, avresti bisogno di un modo per sapere cosa significasse il risultato di int . Potrebbe avere un significato da solo o potrebbe essere definito in una costante.

    • C #: Stream.Read() restituisce il numero di byte letti.
  • Infine, puoi restituire un tipo personalizzato - un enum o un class effettivo. enum funziona proprio come int , tranne per il fatto che trasmette un significato a sé stante, quindi ti offre la massima libertà di denominazione. class ti consente di incapsulare e restituire tutti i dati di cui hai bisogno.

    • C #: DialogResult myform.ShowDialog() - ShowDialog() non è molto esplicito su ciò che restituisce, ma poiché è un valore DialogResult , sai esattamente cosa sta restituendo.
    • MessageResult process_message(Message msg) - Elabora il messaggio e ottieni il risultato di farlo. Questo può anche essere il tuo handle_message originale, ma in questo caso sarebbe qualcosa come HandledMessageResult handle_message(Message msg) .

Il buon codice è auto-documentante in quanto le funzioni, le classi e le variabili spiegano tutti cosa sono e cosa fanno. Se è possibile assegnare un nome a un parametro out in modo sufficientemente chiaro da sapere cosa viene restituito, è possibile utilizzarlo nel nome della funzione. E se non riesci a nominare bene il tuo out e non riesci a nominare correttamente la funzione, puoi ancora nominare il tipo del rendimento utilmente. (vedi ShowDialog() )

    
risposta data 27.06.2013 - 21:09
fonte

Leggi altre domande sui tag