Altri blocchi aumentano la complessità del codice? [chiuso]


18

Ecco un esempio molto semplificato . Questa non è necessariamente una domanda specifica della lingua e ti chiedo di ignorare i molti altri modi in cui la funzione può essere scritta e le modifiche che possono essere apportate ad essa. . Il colore è di un tipo unico

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

Un sacco di persone che ho incontrato, ReSharper, e questo ragazzo (il cui commento mi ha ricordato che stavo cercando di chiederlo da un po ') mi consiglia di rifattorizzare il codice per rimuovere il elseblocco lasciando questo:

(Non ricordo cosa hanno detto la maggioranza, altrimenti non avrei potuto chiedere questo)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

Domanda: c'è un aumento della complessità introdotto non includendo il elseblocco?

Ho l'impressione che l' elseintenzione sia più diretta, affermando che il codice in entrambi i blocchi è direttamente correlato.

Inoltre, trovo che posso prevenire errori sottili nella logica, specialmente dopo modifiche al codice in un secondo momento.

Prendi questa variazione del mio esempio semplificato (ignorando il fatto che l' oroperatore è un esempio volutamente semplificato):

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

Qualcuno può ora aggiungere un nuovo ifblocco basato su una condizione dopo il primo esempio senza riconoscere immediatamente correttamente che la prima condizione sta ponendo un vincolo sulla propria condizione.

Se elsefosse presente un blocco, chiunque aggiungesse la nuova condizione sarebbe costretto ad andare a spostare il contenuto del elseblocco (e se in qualche modo passasse sopra di esso l'euristica mostrerà che il codice non è raggiungibile, cosa che non si verifica nel caso in cui uno ifvincoli un altro) .

Naturalmente ci sono altri modi in cui l'esempio specifico dovrebbe essere definito comunque, tutti che impediscono quella situazione, ma è solo un esempio.

La lunghezza dell'esempio che ho dato può distorcere l'aspetto visivo di questo, quindi supponiamo che lo spazio occupato dalle parentesi sia relativamente insignificante rispetto al resto del metodo.

Ho dimenticato di menzionare un caso in cui sono d'accordo con l'omissione di un altro blocco, ed è quando si utilizza un ifblocco per applicare un vincolo che deve essere logicamente soddisfatto per tutto il codice seguente, come un controllo null (o qualsiasi altra protezione) .


In realtà, quando esiste un'istruzione "if" con un "return", può essere vista come "blocco di protezione" in> 50% di tutti i casi. Quindi penso che il tuo malinteso sia qui che un "blocco di guardia" è il caso eccezionale, e il tuo esempio il caso normale.
Doc Brown,

2
@AssortedTrailmix: concordo sul fatto che un caso come quello sopra potrebbe essere più leggibile quando si scrive la elseclausola (per i miei gusti, sarà ancora più leggibile tralasciando le parentesi inutili. Ma penso che l'esempio non sia ben scelto, perché nella caso sopra vorrei scrivere return sky.Color == Color.Blue(senza if / else). Un esempio con un tipo di ritorno diverso da bool probabilmente renderebbe questo più chiaro.
Doc Brown,

1
come indicato nei commenti sopra, l'esempio suggerito è troppo semplificato, invitando risposte speculative. Per quanto riguarda la parte della domanda che inizia con "Qualcuno può ora aggiungere un nuovo blocco se ..." , è stato chiesto e risposto molte volte prima, vedi ad esempio Approcci alla verifica di più condizioni? e più domande ad esso collegate.
moscerino

1
Non riesco a vedere cos'altro hanno a che fare i blocchi con il principio DRY. DRY ha più a che fare con l'astrazione e i riferimenti al codice comunemente usato sotto forma di funzioni, metodi e oggetti rispetto a ciò che stai chiedendo. La tua domanda è relativa alla leggibilità del codice e, eventualmente, alle convenzioni o ai modi di dire.
Shashank Gupta,

2
Votazione per riaprire. Le buone risposte a questa domanda non sono particolarmente basate sull'opinione. Se l'esempio fornito in una domanda è inadeguato, migliorarlo modificandolo sarebbe la cosa ragionevole da fare, non votare per chiudere per un motivo che non è effettivamente vero.
Michael Shaw,

Risposte:


27

A mio avviso, è preferibile il blocco esplicito else. Quando vedo questo:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

So che ho a che fare con opzioni reciprocamente esclusive. Non ho bisogno di leggere cosa c'è dentro i blocchi if per poterlo dire.

Quando vedo questo:

if (sky.Color != Blue) {
   ...
} 
return false;

A prima vista sembra che ritorni falso indipendentemente e abbia un effetto collaterale facoltativo il cielo non è blu.

A mio avviso, la struttura del secondo codice non riflette ciò che il codice effettivamente fa. Questo è male. Invece, scegli la prima opzione che riflette la struttura logica. Non voglio controllare la logica return / break / continue in ogni blocco per conoscere il flusso logico.

Perché qualcuno preferisca l'altro è un mistero per me, spero che qualcuno prenderà la posizione opposta mi illuminerà con la sua risposta.

Ho dimenticato di menzionare un caso in cui sono d'accordo con l'omissione di un altro blocco, ed è quando si utilizza un blocco if per applicare un vincolo che deve essere logicamente soddisfatto per tutto il codice seguente, come un controllo null (o qualsiasi altra protezione ).

Direi che le condizioni di guardia vanno bene nel caso in cui tu abbia lasciato la strada felice. Si è verificato un errore o un errore che impedisce di continuare l'esecuzione ordinaria. Quando ciò accade, dovresti generare un'eccezione, restituire un codice di errore o qualsiasi cosa sia appropriata per la tua lingua.

Poco dopo la versione originale di questa risposta, nel mio progetto è stato scoperto un codice come questo:

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

Non inserendo il ritorno negli altri, è stato trascurato il fatto che mancava una dichiarazione di reso. Se fosse stato impiegato altro, il codice non sarebbe nemmeno stato compilato. (Certo, la preoccupazione di gran lunga maggiore che ho è che i test scritti su questo codice erano piuttosto cattivi per non rilevare questo problema.)


Questo su riassume i miei pensieri su di esso, sono contento di non essere l'unico a pensarla così. Lavoro con programmatori, strumenti e IDE preferendo la rimozione del elseblocco (può essere fastidioso quando sono costretto a farlo solo per rimanere in linea con le convenzioni per una base di codice). Anch'io sono interessato a vedere il punto di contrasto qui.
Selali Adobor,

1
Io variare su questo. A volte il esplicito altro non ti sta guadagnando molto dal momento che è improbabile che scateni i sottili errori logici menzionati dall'OP - è solo rumore. Ma per lo più concordo, e occasionalmente farò qualcosa come } // elsedove l'altro andrebbe normalmente come un compromesso tra i due.
Telastyn,

3
@Telastyn, ma cosa guadagni saltando l'altro? Sono d'accordo sul fatto che il beneficio è molto leggero in molte situazioni, ma anche per il leggero beneficio penso che valga la pena farlo. Certamente, mi sembra strano inserire qualcosa in un commento quando potrebbe essere un codice.
Winston Ewert,

2
Penso che tu abbia la stessa incrocio dell'OP - "if" con "return" può essere visto principalmente come un blocco di guardia, quindi stai discutendo qui il caso eccezionale, mentre il caso più frequente di blocchi di guardia è IMHO più leggibile senza il "altro".
Doc Brown,

... quindi quello che hai scritto non è sbagliato, ma IMHO non vale i molti voti che hai ricevuto.
Doc Brown,

23

Il motivo principale per la rimozione del elseblocco che ho trovato è il rientro in eccesso. Il corto circuito dei elseblocchi consente una struttura di codice molto più piatta.

Molte ifdichiarazioni sono essenzialmente delle guardie. Stanno impedendo la dereferenziazione di puntatori null e altri "non farlo!" errori. E portano a una rapida chiusura della funzione corrente. Per esempio:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

Ora può sembrare che una elseclausola sarebbe molto ragionevole qui:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

E in effetti, se è tutto ciò che stai facendo, non è molto più rientrato dell'esempio sopra. Ma raramente questo è tutto ciò che stai facendo con strutture di dati reali a più livelli (una condizione che si verifica continuamente durante l'elaborazione di formati comuni come JSON, HTML e XML). Quindi, se si desidera modificare il testo di tutti i figli di un determinato nodo HTML, dire:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

Le guardie non aumentano il rientro del codice. Con una elseclausola, tutto il lavoro effettivo inizia a spostarsi verso destra:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

Questo sta iniziando ad avere un significativo movimento verso destra, e questo è un esempio piuttosto banale, con solo due condizioni di guardia. Ogni guardia aggiuntiva aggiunge un altro livello di rientro. Codice reale Ho scritto XML di elaborazione o che consumano feed JSON dettagliati possono facilmente impilare 3, 4 o più condizioni di guardia. Se usi sempre il else, finisci per rientrare di 4, 5 o 6 livelli. Forse di più. E ciò contribuisce sicuramente a un senso di complessità del codice, e più tempo speso a capire cosa si allinea con cosa. Lo stile rapido, piatto, con uscita anticipata elimina parte di quella "struttura in eccesso" e sembra più semplice.

Addendum I commenti su questa risposta mi hanno fatto capire che potrebbe non essere stato chiaro che la gestione NULL / vuota non è l'unica ragione per i condizionali di guardia. Non vicino! Mentre questo semplice esempio si concentra su NULL / gestione vuota e non contiene altri motivi, la ricerca di contenuti "pertinenti" in strutture profonde come XML e AST, ad esempio, spesso ha una lunga serie di test specifici per eliminare nodi irrilevanti e poco interessanti casi. Una serie di test "se questo nodo non è rilevante, restituisce" provocherà lo stesso tipo di deriva a destra di NULL / vuote protezioni. E in pratica, i successivi test di pertinenza sono frequentemente accoppiati con NULL / vuoti ripari per le strutture di dati corrispondentemente più profonde ricercate - un doppio smorfia per la deriva a destra.


2
Questa è una risposta dettagliata e valida, ma aggiungerò questa alla domanda (all'inizio mi è sfuggita la mente); Esiste una "eccezione" in cui trovo sempre elsesuperfluo un blocco, quando utilizzo un ifblocco per applicare un vincolo che deve essere logicamente soddisfatto per tutto il codice seguente, come un controllo null (o qualsiasi altra protezione).
Selali Adobor,

@AssortedTrailmix Credo che se si escludono i casi in cui è possibile effettuare abitualmente un'uscita anticipata nella thenclausola (come le successive dichiarazioni di guardia), allora non c'è alcun valore particolare per evitare la particolare elseclausola. Anzi, ridurrebbe la chiarezza e potrebbe essere pericoloso (non riuscendo semplicemente a indicare la struttura alternativa che è / dovrebbe essere lì).
Jonathan Eunice,

Penso che sia ragionevole usare le condizioni di guardia per espellere una volta che hai lasciato il percorso felice. Tuttavia, trovo anche nel caso dell'elaborazione di XML / JSON che se si convalida prima l'input su uno schema, si ottengono migliori messaggi di errore e codice più pulito.
Winston Ewert,

Questa è una spiegazione perfetta dei casi in cui altro è evitato.
Chris Schiffhauer,

2
Avrei avvolto l'API per non produrre NULL sciocchi invece.
Winston Ewert,

4

Quando vedo questo:

if(something){
    return lamp;
}
return table;

Vedo un linguaggio comune . Un modello comune , che nella mia testa si traduce in:

if(something){
    return lamp;
}
else return table;

Poiché questo è un linguaggio molto comune nella programmazione, il programmatore che è abituato a vedere questo tipo di codice ha una "traduzione" implicita nella sua testa ogni volta che lo vede, che lo "converte" nel significato proprio.

Non ho bisogno dell'esplicito else, la mia testa "lo mette lì" per me perché ha riconosciuto il modello nel suo insieme . Capisco il significato dell'intero schema comune, quindi non ho bisogno di piccoli dettagli per chiarirlo.

Ad esempio, leggi il seguente testo:

inserisci qui la descrizione dell'immagine

Hai letto questo?

Adoro Parigi in primavera

Se è così, ti sbagli. Leggi il testo ancora. Ciò che è effettivamente scritto è

Amo Parigi nella primavera

Ci sono due "the" parole nel testo. Allora perché ti sei perso uno dei due?

È perché il tuo cervello ha visto uno schema comune e lo ha immediatamente tradotto nel suo significato noto. Non ha bisogno di leggere l'intero dettaglio in dettaglio per capire il significato, perché ha già riconosciuto lo schema nel vederlo. Questo è il motivo per cui ha ignorato il "the " extra .

Stessa cosa con il linguaggio di cui sopra. I programmatori che hanno letto molto codice sono abituati a vedere questo schema , di if(something) {return x;} return y;. Non hanno bisogno di un'esplosione elseper capirne il significato, perché il loro cervello "lo mette lì per loro". Lo riconoscono come uno schema con un significato noto: "ciò che è dopo il tutore di chiusura verrà eseguito solo come implicito elsedel precedente if".

Quindi secondo me elsenon è necessario. Ma usa solo ciò che sembra più leggibile.


2

Aggiungono disordine visivo nel codice, quindi sì, potrebbero aggiungere complessità.

Tuttavia, è anche vero il contrario, un eccessivo refactoring per ridurre la lunghezza del codice può anche aggiungere complessità (non in questo semplice caso ovviamente).

Concordo con l'affermazione che il elseblocco afferma l'intenzione. Ma la mia conclusione è diversa, perché stai aggiungendo complessità al tuo codice per raggiungere questo obiettivo.

Non sono d'accordo con la tua affermazione che ti consente di prevenire errori sottili nella logica.

Vediamo un esempio, ora dovrebbe tornare vero solo se il cielo è blu e c'è un'umidità elevata, l'umidità non è alta o se il cielo è rosso. Ma poi, si sbaglia un tutore e lo si mette nel torto else:

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

Abbiamo visto tutti questi tipi di errori sciocchi nel codice di produzione e può essere molto difficile da rilevare.

Suggerirei quanto segue:

Lo trovo più facile da leggere, perché posso vedere a colpo d'occhio che l'impostazione predefinita è false.

Inoltre, l'idea di forzare lo spostamento del contenuto di un blocco per inserire una nuova clausola è soggetta a errori. Questo in qualche modo si riferisce al principio aperto / chiuso (il codice dovrebbe essere aperto per l'estensione ma chiuso per modifica). Stai forzando la modifica del codice esistente (ad es. Il programmatore potrebbe introdurre un errore nel bilanciamento delle parentesi graffe).

Infine, stai inducendo le persone a rispondere in un modo predeterminato che ritieni sia quello giusto. Per esempio, presenti un esempio molto semplice ma in seguito dichiari che le persone non dovrebbero prenderlo in considerazione. Tuttavia, la semplicità dell'esempio influisce sull'intera domanda:

  • Se il caso è semplice come quello presentato, la mia risposta sarebbe quella di omettere qualsiasi if elseclausola.

    ritorno (sky.Color == Color.Blue);

  • Se il caso fosse un po 'più complicato, con varie clausole, risponderei:

    bool CanLeaveWithoutUmbrella () {

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

  • Se il caso è complicato e non tutte le clausole tornano vere (forse restituiscono un doppio) e vorrei introdurre un punto di interruzione o un comando di accesso, prenderei in considerazione qualcosa del tipo:

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • Se non stiamo parlando di un metodo che restituisce qualcosa, ma invece di un blocco if-else che imposta una variabile o chiama un metodo, allora sì, includerei una elseclausola finale .

Pertanto, anche la semplicità dell'esempio è un fattore da considerare.


Sento che stai commettendo lo stesso errore che altri hanno commesso e trasformando un po ' troppo l'esempio , esaminando così un nuovo problema, ma devo prendere un secondo ed esaminare il processo per arrivare al tuo esempio, quindi per ora sembra valido per me E una nota a margine, questo non è correlato al principio aperto / chiuso . Lo scopo è troppo limitato per applicare questo linguaggio. Ma è interessante notare che la sua applicazione a un livello superiore rimuoverà l'intero aspetto del problema (eliminando la necessità di qualsiasi modifica della funzione).
Selali Adobor,

1
Ma se non possiamo modificare questo esempio semplificato aggiungendo clausole, né modificare il modo in cui è scritto, né apportato alcuna modifica ad esso, allora dovresti considerare che questa domanda si riferisce solo a quelle precise righe di codice e non è più una domanda generale. In tal caso, hai perfettamente ragione, non aggiunge alcuna complessità (né la rimuove) la clausola else perché non c'era complessità per cominciare. Tuttavia, non sei onesto, perché sei tu a suggerire l'idea che potrebbero essere aggiunte nuove clausole.
FranMowinckel,

E come nota a margine, non sto affermando che ciò si riferisce al noto principio aperto / chiuso. Penso che l'idea di indurre le persone a modificare il codice come suggerisci sia soggetta a errori. Sto solo prendendo questa idea da quel principio.
FranMowinckel,

Aspetta le modifiche, stai per qualcosa di buono, sto scrivendo un commento in questo momento
Selali Adobor

1

Sebbene il caso if-else descritto abbia una maggiore complessità, nella maggior parte (ma non in tutte) le situazioni pratiche non importa molto.

Anni fa, quando stavo lavorando su un software utilizzato per l'industria aeronautica (doveva passare attraverso un processo di certificazione), la tua era una domanda che mi veniva in mente. Si è scoperto che c'era un costo finanziario per quell'affermazione 'else' in quanto aumentava la complessità del codice.

Ecco perché.

La presenza dell '"altro" ha creato un terzo caso implicito che doveva essere valutato - quello del "se" né dell'altro "preso in considerazione. Potresti pensare (come ero io al momento) WTF?

La spiegazione è andata così ...

Da un punto di vista logico, ci sono solo due opzioni: "if" e "else". Tuttavia, ciò che stavamo certificando era il file oggetto generato dal compilatore. Pertanto, quando c'era un 'altro', doveva essere fatta un'analisi per confermare che il codice di ramificazione generato era corretto e che non appariva un misterioso terzo percorso.

Contrastalo con il caso in cui esiste solo un 'if' e nessun 'altro'. In tal caso, ci sono solo due opzioni: il percorso 'if' e il percorso 'non-if'. Due casi da valutare sono meno complessi di tre.

Spero che sia di aiuto.


in un file oggetto, entrambi i casi non sarebbero resi come jmps identici?
Winston Ewert,

@WinstonEwert - Ci si aspetterebbe che siano uguali. Tuttavia, ciò che viene reso e ciò che uno si aspetta di essere reso sono due cose diverse, indipendentemente da quanto si sovrappongono.
Sparky

(Immagino che SE abbia mangiato il mio commento ...) Questa è una risposta molto interessante. Mentre direi che il caso che espone è in qualche modo di nicchia, mostra che alcuni strumenti troverebbero una differenza tra i due (anche la metrica di codice più comune basata sul flusso che vedo, la complessità ciclomatica, non misurerebbe alcuna differenza.
Selali Adobor

1

L'obiettivo più importante del codice è quello di essere comprensibile come impegnato (al contrario di facilmente refactored, che è utile ma meno importante). Un esempio di Python leggermente più complesso può essere usato per illustrare questo:

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

Questo è abbastanza chiaro: è l'equivalente di caseun'istruzione o di una ricerca nel dizionario, la versione di livello superiore di return foo == bar:

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

Questo è più chiaro, perché anche se il numero di token è maggiore (32 vs 24 per il codice originale con due coppie chiave / valore), la semantica della funzione è ora esplicita: è solo una ricerca.

(Ciò ha conseguenze per il refactoring - se si desidera avere un effetto collaterale se key == NIK, si hanno tre scelte:

  1. Ripristina lo stile if/ elif/ elsee inserisci una sola riga nel key == NIKcaso.
  2. Salvare il risultato della ricerca su un valore e aggiungere ifun'istruzione valueper il caso speciale prima di tornare.
  3. Inserisci una ifdichiarazione nel chiamante.

Ora vediamo perché la funzione di ricerca è una potente semplificazione: rende la terza opzione ovviamente la soluzione più semplice, poiché oltre a comportare una differenza minore rispetto alla prima opzione è l'unica che si assicura che valuefaccia solo una cosa. )

Ritornando al codice OP, penso che questo possa servire da guida per la complessità delle elseistruzioni: il codice con un'istruzione esplicita elseè oggettivamente più complesso, ma più importante è se rende la semantica del codice chiara e semplice. E questo deve essere risolto caso per caso, poiché ogni parte di codice ha un significato diverso.


Mi piace la tua riscrittura della tabella di ricerca della semplice custodia. E so che è un linguaggio preferito di Python. In pratica, tuttavia, trovo spesso che i condizionali multidirezionali (aka caseo switchdichiarazioni) siano meno omogenei. Diverse opzioni sono solo rendimenti di valore semplici, ma poi uno o due casi richiedono un'elaborazione aggiuntiva. E quando si scopre la strategia di ricerca non soddisfa, è necessario riscrivere in modo del tutto diverso if elif... elsesintassi.
Jonathan Eunice,

Ecco perché penso che la ricerca di solito dovrebbe essere una riga o una funzione, in modo che la logica attorno al risultato della ricerca sia separata dalla logica di cercarla in primo luogo.
l0b0

Questa domanda porta una visione di livello superiore alla domanda che sicuramente dovrebbe essere menzionata e tenuta a mente, ma ritengo che sia stato posto un vincolo sufficiente sul caso che tu possa sentirti libero di espandersi sulla tua posizione
Selali Adobor,

1

Penso che dipenda dal tipo di ifaffermazione che stai usando. Alcune ifdichiarazioni possono essere considerate espressioni, mentre altre possono essere viste solo come dichiarazioni di flusso di controllo.

Il tuo esempio mi sembra un'espressione. Nei linguaggi simili a C, l'operatore ternario ?:è molto simile a ifun'istruzione, ma è un'espressione e l'altra parte non può essere omessa. Ha senso che un altro ramo non possa essere omesso in un'espressione, poiché l'espressione deve sempre avere un valore.

Usando l'operatore ternario, il tuo esempio sarebbe simile al seguente:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

Dal momento che è un'espressione booleana, tuttavia, dovrebbe davvero essere semplificata fino in fondo

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

Includere una clausola esplicita else rende più chiare le tue intenzioni. Le guardie possono avere senso in alcune situazioni, ma nella scelta tra due possibili valori, nessuno dei quali eccezionale o insolito, non aiutano.


+1, l'OP è l'IMHO che discute di un caso patologico che dovrebbe essere meglio scritto come mostrato sopra. Il caso molto più frequente di "if" con "return" è per la mia esperienza il caso "guard".
Doc Brown,

Non so perché questo accada, ma le persone continuano a ignorare il primo paragrafo della domanda. In effetti, state tutti usando la riga esatta di codice che dico esplicitamente di non usare. I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor,

E rileggendo la tua ultima domanda, sembra che tu abbia frainteso l'intento della domanda.
Selali Adobor,

Hai usato un esempio che praticamente chiedeva di essere semplificato. Ho letto e prestato attenzione al tuo primo paragrafo, motivo per cui il mio primo blocco di codice è lì, invece di saltare direttamente al modo migliore per fare quel particolare esempio. Se pensi che io (o chiunque altro) abbia frainteso l'intento della tua domanda, forse dovresti modificarla per chiarire le tue intenzioni.
Michael Shaw,

Lo modificherei, ma non saprei come renderlo più chiaro poiché uso esattamente la riga di codice che fai e dici "non farlo". Ci sono infiniti modi in cui potresti riscrivere il codice e rimuovere la domanda, non posso coprirli tutti, eppure così tante persone hanno capito la mia affermazione e l'hanno rispettata ...
Selali Adobor,

1

Un'altra cosa a cui pensare in questo argomento sono le abitudini che ogni approccio promuove.

  • if / else modifica un campione di codifica in termini di rami. Come dici tu, a volte questa esplicitazione è buona. Esistono davvero due possibili percorsi di esecuzione e vogliamo evidenziarlo.

  • In altri casi, c'è solo un percorso di esecuzione e quindi tutte quelle cose eccezionali di cui i programmatori devono preoccuparsi. Come hai detto, le clausole di guardia sono ottime per questo.

  • Esiste, ovviamente, il 3 o più casi (n), in cui di solito incoraggiamo ad abbandonare la catena if / else e usare un'istruzione case / switch o polimorfismo.

Il principio guida che tengo a mente qui è che un metodo o una funzione dovrebbe avere un solo scopo. Qualsiasi codice con un'istruzione if / else o switch è solo per la ramificazione. Quindi dovrebbe essere assurdamente breve (4 righe) e delegare solo al metodo corretto o produrre il risultato ovvio (come il tuo esempio). Quando il tuo codice è così corto, è difficile perdere quei ritorni multipli :) Proprio come "goto considerato dannoso", qualsiasi affermazione di diramazione può essere abusata, quindi vale la pena mettere qualche pensiero critico in altre dichiarazioni. Come hai già detto, il solo fatto di avere un altro blocco offre un posto per il raggruppamento del codice. Tu e il tuo team dovrete decidere cosa ne pensate.

Ciò di cui lo strumento si sta davvero lamentando è il fatto che hai un ritorno / pausa / lancio all'interno del blocco if. Per quanto riguarda, hai scritto una clausola di guardia ma l'hai rovinata. Puoi scegliere di rendere felice lo strumento o "intrattenere" il suo suggerimento senza accettarlo.


Non ho mai pensato al fatto che lo strumento potrebbe prendere in considerazione l'idea di essere una clausola di protezione non appena si adatta al modello per uno, è probabilmente il caso, e spiega il ragionamento di un "gruppo di sostenitori" per la sua assenza
Selali Adobor,

@AssortedTrailmix Giusto, stai pensando elsesemanticamente, gli strumenti di analisi del codice di solito cercano istruzioni estranee perché evidenziano spesso un malinteso sul flusso di controllo. Il elsedopo ifche ritorna è bit sprecati. if(1 == 1)spesso attiva lo stesso tipo di avviso.
Steve Jackson,

1

Penso che la risposta sia che dipende. Il tuo esempio di codice (come indichi indirettamente) sta semplicemente restituendo la valutazione di una condizione, ed è rappresentato al meglio come ovviamente sai, come una singola espressione.

Per determinare se l'aggiunta di un'altra condizione chiarisce o oscura il codice, è necessario determinare cosa rappresenta l'IF / ELSE. È un'espressione (come nel tuo esempio)? In tal caso, tale espressione dovrebbe essere estratta e utilizzata. È una condizione di guardia? In tal caso, l'ELSE è superfluo e fuorviante, in quanto rende equivalenti i due rami. È un esempio di polimorfismo procedurale? Estrai. Sta ponendo i presupposti? Quindi può essere essenziale.

Prima di decidere di includere o eliminare l'ELSE, decidere cosa rappresenta, che ti dirà se dovrebbe essere presente o meno.


0

La risposta è un po 'soggettiva. Un elseblocco può aiutare la leggibilità stabilendo che un blocco di codice viene eseguito esclusivamente su un altro blocco di codice, senza la necessità di leggere entrambi i blocchi. Questo può essere utile se, per esempio, entrambi i blocchi sono relativamente lunghi. Ci sono casi, anche se avere ifs senza elses può essere più leggibile. Confronta il seguente codice con un numero di if/elseblocchi:

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

con:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

Il secondo blocco di codice modifica le ifistruzioni nidificate in clausole di guardia. Ciò riduce il rientro e rende più chiare alcune delle condizioni di ritorno ("if a != b, then return 0!")


Nei commenti è stato sottolineato che potrebbero esserci dei buchi nel mio esempio, quindi lo chiarirò un po 'di più.

Avremmo potuto scrivere qui il primo blocco di codice in questo modo per renderlo più leggibile:

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

Questo codice è equivalente ad entrambi i blocchi sopra, ma presenta alcune sfide. La elseclausola qui collega queste istruzioni if ​​insieme. Nella versione della clausola di protezione sopra, le clausole di guardia sono indipendenti l'una dall'altra. Aggiungerne uno nuovo significa semplicemente scrivere un nuovo iftra l'ultimo ife il resto della logica. Qui, ciò può anche essere fatto, con solo una leggera quantità di carico cognitivo in più. La differenza, tuttavia, è quando deve verificarsi una logica aggiuntiva prima di quella nuova clausola di protezione. Quando le dichiarazioni erano indipendenti, potevamo fare:

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

Con la if/elsecatena connessa , questo non è così facile da fare. È un dato di fatto, il percorso più semplice da prendere sarebbe quello di spezzare la catena per assomigliare più alla versione della clausola di guardia.

Ma davvero, questo ha senso. L'uso if/elsedebolmente implica una relazione tra le parti. Potremmo supporre che a != bè in qualche modo legato alla c > dnella if/elseversione incatenato. Tale supposizione sarebbe fuorviante, come possiamo vedere dalla versione della clausola di guardia che in realtà non sono collegati. Ciò significa che possiamo fare di più con clausole indipendenti, come riordinarle (magari per ottimizzare le prestazioni delle query o per migliorare il flusso logico). Possiamo anche ignorarli cognitivamente una volta superati. In una if/elsecatena, sono meno sicuro che la relazione di equivalenza tra ae bnon comparirà più tardi.

La relazione implicita elseè evidente anche nel codice di esempio profondamente annidato, ma in un modo diverso. Le elseistruzioni sono separate dal condizionale a cui sono associate e sono correlate in ordine inverso ai condizionali (il primo elseè attaccato all'ultimo if, l'ultimo elseè attaccato al primo if). Questo crea una dissonanza cognitiva in cui dobbiamo tornare indietro nel codice, cercando di allineare il rientro nel nostro cervello. È un po 'come provare a mettere insieme una parentesi. Peggio ancora se il rientro è sbagliato. Le parentesi graffe opzionali non aiutano neanche.

Ho dimenticato di menzionare un caso in cui sono d'accordo con l'omissione di un altro blocco, ed è quando si utilizza un blocco if per applicare un vincolo che deve essere logicamente soddisfatto per tutto il codice seguente, come un controllo null (o qualsiasi altra protezione ).

Questa è una bella concessione, ma in realtà non invalida alcuna risposta. Potrei dire che nel tuo esempio di codice:

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

un'interpretazione valida per scrivere codice come questo potrebbe essere che "dobbiamo partire con un ombrello solo se il cielo non è blu". Qui c'è un vincolo che il cielo deve essere blu affinché il codice seguente sia valido per l'esecuzione. Ho incontrato la definizione della tua concessione.

E se dicessi che se il colore del cielo è nero, è una notte limpida, quindi non è necessario un ombrello. (Ci sono modi più semplici per scrivere questo, ma ci sono modi più semplici per scrivere l'intero esempio.)

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

Come nell'esempio più grande sopra, posso semplicemente aggiungere la nuova clausola senza pensarci troppo. In un if/else, non posso essere così sicuro di non rovinare qualcosa, specialmente se la logica è più complicata.

Sottolineerò anche che il tuo semplice esempio non è neanche così semplice. Un test per un valore non binario non è uguale all'inverso di quel test con risultati invertiti.


1
La mia modifica alla domanda risolve questo caso. Quando un vincolo che deve essere logicamente soddisfatto per tutto il codice seguente, come un controllo null (o qualsiasi altra protezione), accetto che l'omissione di un elseblocco sia essenziale per la leggibilità.
Selali Adobor,

1
La tua prima versione è difficile da capire, ma non penso che sia necessario usando if / else. Guarda come lo scriverei: gist.github.com/winstonewert/c9e0955bc22ce44930fb .
Winston Ewert,

@WinstonEwert Vedi le modifiche per la risposta ai tuoi commenti.
cbojar,

La modifica rende alcuni punti validi. E certamente penso che le clausole di guardia abbiano un posto. Ma nel caso generale, sono a favore di altre clausole.
Winston Ewert,

0

Hai semplificato troppo il tuo esempio. Entrambe le alternative possono essere più leggibili, a seconda della situazione più ampia: in particolare, dipende dal fatto che uno dei due rami della condizione sia anormale . Lascia che ti mostri: nel caso in cui entrambi i rami siano ugualmente probabili, ...

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

... è più leggibile di ...

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

... perché il primo mostra una struttura parallela e il secondo no. Ma, quando la maggior parte della funzione è dedicata a un'attività e devi solo verificare prima alcune condizioni preliminari, ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

... è più leggibile di ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

... perché deliberatamente non impostare una struttura parallela fornisce un indizio a un lettore futuro che ottenere input che sono "davvero di tipo due" qui è anormale . (Nella vita reale, ProcessInputDefinitelyOfTypeOnenon sarebbe una funzione separata, quindi la differenza sarebbe ancora più netta.)


Potrebbe essere meglio scegliere un esempio più concreto per il secondo caso. La mia reazione immediata ad esso è "non può essere così anormale se andassi avanti e lo elaborassi comunque".
Winston Ewert,

@WinstonEwert anormale è forse il termine sbagliato. Ma sono d'accordo con Zack, che se hai un default (case1) e un'eccezione, dovrebbe essere scritto come » if-> Fai l'eccezionale e lascia« una strategia di ritorno all'inizio . Pensa al codice, in cui il valore predefinito include più controlli, sarebbe più veloce gestire prima il caso eccezionale.
Thomas Junk,

Questo sembra cadere nel caso di cui stavo parlando when using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards). Logicamente, qualsiasi lavoro ProcessInputOfTypeOnedipende dal fatto che !InputIsReallyTypeTwo(input), quindi dobbiamo catturarlo al di fuori della nostra normale elaborazione. Normalmente ProcessInputOfTypeTwo(input);sarebbe davvero throw ICan'tProccessThatExceptionciò che renderebbe la somiglianza più evidente.
Selali Adobor,

@ WinstonEwert avrei forse potuto dirlo meglio, sì. Stavo pensando a una situazione che emerge molto quando si codificano i tokenizzatori a mano: nei CSS, ad esempio, la sequenza di caratteri " u+" di solito introduce un token "intervallo unicode", ma se il carattere successivo non è una cifra esadecimale, quindi è necessario eseguire il backup ed elaborare "u" come identificatore. Quindi il ciclo principale dello scanner invia "scansionare un token con intervallo unicode" in modo un po 'impreciso, e inizia con "... se siamo in questo insolito caso speciale, esegui il backup, quindi chiama la subroutine scan-an-identifier".
Zwol,

@AssortedTrailmix Anche se l'esempio a cui stavo pensando non proveniva da una base di codice legacy in cui le eccezioni non possono essere utilizzate, questa non è una condizione di errore , è solo che il codice che chiama ProcessInputOfTypeOneutilizza un controllo impreciso ma veloce che a volte cattura invece il secondo tipo.
zwol,

0

Sì, c'è un aumento della complessità perché la clausola else è ridondante . È quindi meglio lasciare fuori per mantenere il codice snello e cattivo. È sullo stesso accordo di omettere thisqualificatori ridondanti (Java, C ++, C #) e omettere parentesi nelle returndichiarazioni, e così via.

Un buon programmatore pensa "cosa posso aggiungere?" mentre un grande programmatore pensa "cosa posso rimuovere?".


1
Quando vedo l'omissione del elseblocco, se è spiegato in una riga (che non è spesso) è spesso con questo tipo di ragionamento, quindi +1 per presentare l'argomento "predefinito" che vedo, ma non lo faccio trovarlo un ragionamento abbastanza forte. A good programmer things "what can I add?" while a great programmer things "what can I remove?".sembra essere un adagio comune che molte persone si estendono eccessivamente senza attenta considerazione, e personalmente trovo che questo sia uno di questi casi.
Selali Adobor,

Sì, leggendo la tua domanda ho subito sentito che avevi già deciso, ma volevo la conferma. Questa risposta non è ovviamente ciò che volevi, ma a volte è importante considerare opinioni con cui non sei d'accordo. Forse c'è anche qualcosa?
Vidstige,

-1

Ho notato, cambiando idea su questo caso.

Quando ho fatto questa domanda circa un anno fa, avrei risposto a qualcosa del tipo:

»Ci dovrebbe essere solo uno entrye uno exitper un metodo« E con questo in mente, avrei suggerito di refactificare il codice per:

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

Dal punto di vista della comunicazione è chiaro cosa si dovrebbe fare. Ifqualcosa è il caso, manipolare il risultato in un modo , else manipolarlo in un altro modo .

Ma questo comporta un inutile else . La elseesprime il default-caso . Quindi, in un secondo momento, ho potuto rifattarlo

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

Quindi sorge la domanda: perché usare una variabile temporanea? Il prossimo passo della rifattorizzazione porta a:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

Quindi questo è chiaro in ciò che fa. Vorrei anche fare un ulteriore passo avanti:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

O per i deboli di cuore :

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

Potresti leggerlo così: »CanLeaveWithoutUmbrella restituisce un bool . Se non succede nulla di speciale, restituisce false «Questa è la prima lettura, dall'alto verso il basso.

E poi "analizzi" la condizione »Se la condizione è soddisfatta, ritorna vera « Potresti saltare completamente il condizionale e capire cosa fa questa funzione nel caso predefinito . Occhio e mente devono solo sfogliare alcune lettere sul lato sinistro, senza leggere la parte a destra.

Ho l'impressione che l'altro dichiari più direttamente l'intenzione, affermando che il codice in entrambi i blocchi è direttamente correlato.

Si è giusto. Ma quell'informazione è ridondante . Hai un caso predefinito e una "eccezione" che è coperta dalla ifdichiarazione.

Quando ho a che fare con strutture più complesse, sceglierei un'altra strategia, omettendo del iffratello brutto switch.


La ridondanza +1 mostra sicuramente un aumento non solo della complessità, ma della confusione. So che ho sempre dovuto ricontrollare il codice scritto in questo modo proprio a causa della ridondanza.
dietbuddha,

1
Potresti rimuovere i casi dopo il caso iniziando con So this is clear in what it does...ed espanderlo? (Anche i casi precedenti sono di dubbia rilevanza). Lo dico perché è ciò che la domanda che poniamo esaminiamo. Hai dichiarato la tua posizione, omettendo il blocco else, ed è in realtà la posizione che necessita di maggiori spiegazioni (e quella che mi ha fatto porre questa domanda). Dalla domanda:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor,

@AssortedTrailmix Certo! Su cosa esattamente dovrei elaborare? Poiché la tua domanda iniziale era "Altri blocchi aumentano la complessità del codice?" E la mia risposta è: sì. In questo caso potrebbe essere omesso.
Thomas Junk,

E: No! Non capisco il downvote.
Thomas Junk,

-1

Per farla breve, in questo caso, sbarazzarsi della elseparola chiave è una buona cosa. Qui è totalmente ridondante e, a seconda della modalità di formattazione del codice, aggiungerà una o tre righe completamente inutili al codice. Considera cosa c'è nel ifblocco:

return true;

Pertanto, se ifsi dovesse inserire il blocco, l'intero resto della funzione non viene, per definizione, eseguito. Ma se non viene inserito, poiché non ci sono ulteriori ifdichiarazioni, viene eseguito l'intero resto della funzione. Sarebbe totalmente diverso se returnun'istruzione non fosse nel ifblocco, nel qual caso la presenza o l'assenza di un elseblocco avrebbe un impatto, ma qui non avrebbe un impatto.

Nella tua domanda, dopo aver invertito la tua ifcondizione e omesso else, hai dichiarato quanto segue:

Qualcuno ora può aggiungere un nuovo blocco if basato su una condizione dopo il primo esempio senza riconoscere immediatamente correttamente la prima condizione sta ponendo un vincolo sulla propria condizione.

Devono leggere il codice un po 'più da vicino allora. Usiamo linguaggi di alto livello e una chiara organizzazione del codice per mitigare questi problemi, ma l'aggiunta di un elseblocco completamente ridondante aggiunge "rumore" e "disordine" al codice e non dovremmo nemmeno invertire o invertire le nostre ifcondizioni. Se non riescono a distinguere la differenza, non sanno cosa stanno facendo. Se sanno distinguere la differenza, possono sempre invertire le ifcondizioni originali da soli.

Ho dimenticato di menzionare un caso in cui sono d'accordo con l'omissione di un altro blocco, ed è quando si utilizza un blocco if per applicare un vincolo che deve essere logicamente soddisfatto per tutto il codice seguente, come un controllo null (o qualsiasi altra protezione ).

Questo è essenzialmente ciò che sta accadendo qui però. Stai tornando dal ifblocco, quindi la ifvalutazione a condizione false è un vincolo che deve essere logicamente soddisfatto per tutto il codice seguente.

C'è un aumento della complessità introdotto non includendo il blocco else?

No, costituirà una diminuzione della complessità del codice e potrebbe persino costituire una diminuzione del codice compilato, a seconda che si verifichino o meno determinate ottimizzazioni super banali.


2
"Hanno bisogno di leggere il codice un po 'più da vicino allora." Lo stile di programmazione esiste in modo che i programmatori possano prestare meno attenzione ai dettagli nitidi e più attenzione a ciò che stanno effettivamente facendo. Se il tuo stile ti fa prestare attenzione a quei dettagli inutilmente, è uno stile cattivo. "... linee completamente inutili ..." Non sono inutili: ti permettono di vedere a colpo d'occhio qual è la struttura, senza dover perdere tempo a ragionare su cosa accadrebbe se questa parte o quella parte venissero eseguite.
Michael Shaw,

@MichaelShaw Penso che la risposta di Aviv Cohn entri in quello di cui sto parlando.
Panzercrisis,

-2

Nell'esempio specifico che hai dato, lo fa.

  • Obbliga un revisore a leggere nuovamente il codice per assicurarsi che non sia un errore.
  • Aggiunge complessità ciclomatica (non necessaria) perché aggiunge un nodo al diagramma di flusso.

Penso che non potrebbe essere più semplice questo:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

6
Mi rivolgo in modo specifico al fatto che questo esempio molto semplificato può essere riscritto per rimuovere l' ifaffermazione. In realtà scoraggio esplicitamente l'ulteriore semplificazione usando il codice esatto che hai usato. Inoltre, la complessità ciclomatica non viene aumentata dal elseblocco.
Selali Adobor,

-4

Cerco sempre di scrivere il mio codice in modo che l'intenzione sia il più chiara possibile. Nel tuo esempio manca un po 'di contesto, quindi lo modificherò un po':

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

La nostra intenzione sembra essere quella di poter partire senza un ombrello quando il cielo è blu. Tuttavia, quella semplice intenzione si perde in un mare di codice. Esistono diversi modi per semplificare la comprensione.

In primo luogo, c'è la tua domanda sul elseramo. Non mi dispiace in entrambi i casi, ma tendo a tralasciare il else. Capisco l'argomento sull'essere esplicito, ma la mia opinione è che le ifdichiarazioni dovrebbero avvolgere rami "opzionali", come return truequello. Non definirei il return falseramo "opzionale", dal momento che agisce come predefinito. Ad ogni modo, vedo questo come un problema molto minore, e molto meno importante di quelli seguenti:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

Un problema molto più importante è che le tue filiali stanno facendo troppo! I rami, come tutto, dovrebbero essere "il più semplice possibile, ma non di più". In questo caso hai usato ifun'istruzione, che si ramifica tra i blocchi di codice (raccolte di istruzioni) quando tutto ciò di cui hai veramente bisogno per diramare sono espressioni (valori). Ciò è chiaro poiché a) i blocchi di codice contengono solo singole istruzioni eb) sono la stessa istruzione ( return). Possiamo usare l'operatore ternario ?:per restringere il ramo solo alla parte diversa:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

Ora raggiungiamo l'evidente ridondanza a cui il nostro risultato è identico this.color == Color.Blue. So che la tua domanda ci chiede di ignorarlo, ma penso sia molto importante sottolineare che questo è un modo di pensare procedurale di prim'ordine: stai abbattendo i valori di input e costruendo alcuni valori di output. Va bene, ma se possibile è molto più potente pensare in termini di relazioni o trasformazioni tra input e output. In questo caso la relazione è ovviamente che sono uguali, ma spesso vedo un codice procedurale contorto come questo, che oscura alcune relazioni semplici. Andiamo avanti e facciamo questa semplificazione:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

La prossima cosa che vorrei sottolineare è che la tua API contiene un doppio negativo: è possibile CanLeaveWithoutUmbrellache lo sia false. Questo, ovviamente, semplifica l' NeedUmbrellaessere true, quindi facciamolo:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

Ora possiamo iniziare a pensare al tuo stile di codifica. Sembra che tu stia usando un linguaggio orientato agli oggetti, ma usando le ifistruzioni per diramare tra i blocchi di codice, hai finito per scrivere codice procedurale .

Nel codice orientato agli oggetti, tutti i blocchi di codice sono metodi e tutte le ramificazioni vengono eseguite tramite invio dinamico , ad es. utilizzando classi o prototipi. È discutibile se ciò renda le cose più semplici, ma dovrebbe rendere il tuo codice più coerente con il resto della lingua:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

Si noti che l'utilizzo di operatori ternari per ramificare tra espressioni è comune nella programmazione funzionale .


Il primo paragrafo della risposta sembra essere sulla buona strada per rispondere alla domanda, ma diverge immediatamente nell'argomento esatto che chiedo esplicitamente di ignorare per questo esempio molto semplice . Dalla domanda: I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.). In effetti usi esattamente la riga di codice che cito. Inoltre, mentre questa parte della domanda è fuori tema, direi che stai andando troppo lontano nella tua inferenza. Hai radicalmente cambiato il codice.
Selali Adobor,

@AssortedTrailmix Questa è puramente una questione di stile. Ha senso prendersi cura dello stile e ha senso ignorarlo (concentrandosi solo sui risultati). Non credo abbia senso preoccuparsi return false;vs else { return false; }pur non preoccuparsi circa ramo di polarità, la spedizione dinamica, ternari, ecc Se si sta scrivendo codice procedurale in un linguaggio OO, un cambiamento radicale è necessario;)
Warbo

Puoi dire qualcosa del genere in generale, ma non è applicabile quando rispondi a una domanda con parametri ben definiti (soprattutto quando senza rispettare tali parametri puoi "sottrarre" l'intera domanda). Direi anche che l'ultima frase è semplicemente sbagliata. OOP riguarda "l'astrazione procedurale", non "l'abolizione procedurale". Direi che il fatto che tu sia passato da una riga a un numero infinito di classi (una per ogni colore) mostra perché. Inoltre, mi sto ancora chiedendo quale invio dinamico debba avere a che fare con if/elseun'affermazione e quale sia la polarità del ramo.
Selali Adobor,

Non ho mai detto che la soluzione OO sia migliore (in effetti, preferisco la programmazione funzionale), ho semplicemente detto che è più coerente con il linguaggio. Per "polarità" intendevo quale cosa è "vera" e quale "falsa" (l'ho cambiata con "NeedUmbrella"). Nella progettazione OO, tutto il flusso di controllo è determinato dalla spedizione dinamica. Ad esempio, Smalltalk non consente nient'altro en.wikipedia.org/wiki/Smalltalk#Control_structures (vedi anche c2.com/cgi/wiki?HeInventedTheTerm )
Warbo

2
Lo avrei votato fino a quando non sono arrivato all'ultimo paragrafo (su OO), che invece gli è valso un downvote! Questo è esattamente il tipo di uso eccessivo senza pensarci dei costrutti OO che produce il codice dei ravioli , che è illeggibile quanto gli spaghetti.
zwol,
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.