Lo sviluppatore insiste se le dichiarazioni non devono avere condizioni negate e devono sempre avere un blocco altro


169

Ho una conoscenza, uno sviluppatore più esperto di me. Stavamo parlando di pratiche di programmazione e sono rimasto sorpreso dal suo approccio alle dichiarazioni "if". Insiste su alcune pratiche relative alle dichiarazioni if ​​che trovo piuttosto strane.

In primo luogo , un'istruzione if dovrebbe essere seguita da un'istruzione else, indipendentemente dalla presenza o meno di qualcosa. Il che porta a un codice simile al seguente:

if(condition) 
{
    doStuff();
    return whatever;
}
else
{
}

In secondo luogo , è meglio verificare i valori veri anziché falsi. Ciò significa che è meglio testare una variabile 'doorClosed' anziché una variabile '! DoorOpened'

La sua tesi è che chiarisce cosa sta facendo il codice.

Il che mi confonde un po ', poiché una combinazione di queste due regole può portarlo a scrivere questo tipo di codice se vuole fare qualcosa quando la condizione non è soddisfatta.

if(condition)
{
}
else
{
    doStuff();
    return whatever;
}

La mia sensazione al riguardo è che è davvero molto brutto e / o che il miglioramento della qualità, se presente, è trascurabile. Ma da giovane, sono incline a dubitare del mio istinto.

Quindi le mie domande sono: è una pratica buona / cattiva / "non importa"? È pratica comune?



57
È possibile che il tuo collega provenga da uno sfondo in cui potrebbero essere in gioco la vita e l'arto? Credo di aver visto alcune menzioni di questo genere di cose nei sistemi in cui si fa un sacco di test e analisi automatiche sul codice e dove sbagliare potrebbe letteralmente costare la vita a qualcuno.
jpmc26

11
Cosa dice la guida di stile del codice dell'azienda?
Thorbjørn Ravn Andersen,

4
Risposta parziale alla tua domanda "Secondly". Se uno dei blocchi di azioni è lungo e l'altro è corto, prova una condizione che inserisce per primo il blocco corto, in modo che sia meno probabile che si perda durante la lettura del codice. Tale condizione potrebbe essere una negazione o una ridenominazione per renderla una prova vera.
Ethan Bolker,

9
Resharper avrebbe qualcosa da dire al tuo amico, sulla falsariga di "Il tuo codice è ridondante e inutile, vuoi che lo elimini / refactualizzalo?"
BgrWorker,

Risposte:


184

elseBlocco esplicito

La prima regola inquina il codice e non lo rende né più leggibile, né meno soggetto a errori. L'obiettivo del tuo collega - suppongo - è quello di essere esplicito, dimostrando che lo sviluppatore era pienamente consapevole del fatto che la condizione può essere valutata false. Sebbene sia una buona cosa essere espliciti, tale esplicitazione non dovrebbe comportare un costo di tre righe di codice aggiuntive .

Non ho nemmeno menzionato il fatto che ifun'affermazione non è necessariamente seguita da uno elseo niente: potrebbe essere seguita anche da una o più elifs.

La presenza della returndichiarazione peggiora le cose. Anche se avessi effettivamente del codice da eseguire all'interno del elseblocco, sarebbe più leggibile farlo in questo modo:

if (something)
{
    doStuff();
    return whatever;
}

doOtherThings();
return somethingElse;

Questo fa in modo che il codice prenda due righe in meno e indenta il elseblocco. Le clausole di guardia riguardano tutto ciò.

Nota, tuttavia, che la tecnica del tuo collega potrebbe risolvere parzialmente un modello molto brutto di blocchi condizionali impilati senza spazi:

if (something)
{
}
if (other)
{
}
else
{
}

Nel codice precedente, la mancanza di un'interruzione di linea sana dopo il primo ifblocco semplifica l'interpretazione errata del codice. Tuttavia, mentre la regola del tuo collega renderebbe più difficile la lettura errata del codice, una soluzione più semplice sarebbe quella di aggiungere semplicemente una nuova riga.

Prova per true, non perfalse

La seconda regola potrebbe avere un senso, ma non nella sua forma attuale.

Non è falso che il test per una porta chiusa sia più intuitivo del test per una porta non aperta . Le negazioni, e in particolare quelle annidate, sono generalmente difficili da comprendere:

if (!this.IsMaster || (!this.Ready && !this.CanWrite))

Per risolvere questo, invece di aggiungere blocchi vuoti, creare proprietà aggiuntive, quando variabili rilevanti, o locali .

La condizione sopra potrebbe essere resa leggibile piuttosto facilmente:

if (this.IsSlave || (this.Synchronizing && this.ReadOnly))

169
I blocchi vuoti mi sembrano sempre degli errori. Attirerà immediatamente la mia attenzione e chiederò "Perché è qui?"
Nelson,

50
@Neil La negazione di una condizione non richiede necessariamente alcun tempo CPU aggiuntivo. C compilato per x86 potrebbe semplicemente scambiare le istruzioni di salto da JZ(Salta se zero) per if (foo)a JNZ(Salta se non zero) per if (!foo).
8bittree

71
" crea proprietà aggiuntive con nomi chiari ": A meno che tu non abbia un'analisi più approfondita del dominio, questo potrebbe cambiare l'ambito globale (la classe originale) per risolvere un problema locale (l'applicazione di una specifica regola aziendale). Una cosa che può essere fatta qui è creare booleani locali con lo stato desiderato, come "var isSlave =! This.IsMaster" e applicare il tuo if con i booleani locali invece di creare molte altre proprietà. Quindi, prendi il consiglio con un granello di sale e prenditi del tempo per analizzare il dominio prima di creare nuove proprietà.
Machado,

82
Non si tratta di "tre righe di codice", si tratta di scrivere per il pubblico appropriato (qualcuno con almeno una conoscenza di base della programmazione). An ifè già esplicito sul fatto che la condizione potrebbe essere falsa o che non si verificherebbe. Un blocco vuoto rende le cose meno chiare, non di più, perché nessun lettore è pronto ad aspettarselo, e ora devono fermarsi e pensare a come avrebbe potuto arrivarci.
Hobbs,

15
@Mark è meno chiaro, anche con il commento, che dire if (!commonCase) { handle the uncommon case },però.
Paul,

62

Per quanto riguarda la prima regola, questo è un esempio di digitazione inutile. Non solo ci vuole più tempo per scrivere, ma causerà enormi confusioni a chiunque legga il codice. Se il codice non è necessario, non scriverlo. Ciò si estenderebbe anche al non avere un popolato elsenel tuo caso poiché il codice ritorna dal ifblocco:

if(condition) 
{
    doStuff();
    return whatever;
}

doSomethingElse(); // no else needed
return somethingelse;

Per quanto riguarda il secondo punto, è bene evitare nomi booleani che contengono un negativo:

bool doorNotOpen = false; // a double negative is hard to reason
bool doorClosed = false; // this is much clearer

Tuttavia, estenderlo a non testare nuovamente un negativo, come fai notare, porta a una digitazione più inutile. Quanto segue è molto più chiaro che avere un ifblocco vuoto :

if(!condition)
{
    doStuff();
    return whatever;
}

120
Quindi ... potresti dire che il doppio negativo è un no-no? ;)
Patsuan,

6
@Patsuan, molto intelligente. Mi piace. :)
David Arno,

4
Sento che non molte persone sarebbero d'accordo su come gestire if-else con i rendimenti, e alcuni potrebbero persino dire che non dovrebbe essere gestito allo stesso modo in ogni scenario. Non ho una forte preferenza me stesso, ma posso sicuramente vedere l'argomento dietro molti altri modi di farlo.
Dukeling,

6
Chiaramente, if (!doorNotOpen)è terribile. Quindi i nomi dovrebbero essere il più positivi possibile. if (! doorClosed)va benissimo.
David Schwartz,

1
Per quanto riguarda il tuo esempio di porta, i due nomi di esempio non sono necessariamente equivalenti. Nel mondo fisico doorNotOpennon è lo stesso di doorCloseduno stato di transizione tra l'essere aperto e chiuso. Lo vedo continuamente nelle mie applicazioni industriali in cui gli stati booleani sono determinati da sensori fisici. Se hai un singolo sensore sul lato aperto della porta, puoi solo dire che la porta è aperta o non aperta. Allo stesso modo se il sensore è sul lato chiuso, puoi solo dire chiuso o non chiuso. Anche il sensore stesso determina come viene affermato lo stato logico.
Peter M,

45

1. Un argomento a favore di elsedichiarazioni vuote .

Spesso uso (e discuto) qualcosa di simile a quel primo costrutto, un altro vuoto. Segnala ai lettori del codice (strumenti di analisi sia umani che automatizzati) che il programmatore ha riflettuto sulla situazione. elseDichiarazioni mancanti che avrebbero dovuto essere presenti hanno ucciso persone, fatto schiantare veicoli e costato milioni di dollari. MISRA-C, ad esempio, impone almeno un commento in cui si afferma che il finale mancante è intenzionale in una if (condition_1) {do_this;} else if (condition_2) {do_that;} ... else if (condition_n) {do_something_else;}sequenza. Altri elevati standard di affidabilità vanno ancora oltre: con poche eccezioni, sono vietate altre dichiarazioni mancanti.

Un'eccezione è un semplice commento, qualcosa del genere /* Else not required */. Questo indica lo stesso intento delle tre righe vuote. Un'altra eccezione in cui quel vuoto altro non è necessario è dove è palesemente ovvio sia per i lettori del codice che per gli strumenti di analisi automatizzata che quel vuoto vuoto è superfluo. Ad esempio, allo if (condition) { do_stuff; return; }stesso modo, non è necessario un altro vuoto nel caso di throw somethingo goto some_label1 al posto di return.

2. Un argomento per preferire if (condition)oltre if (!condition).

Questo è un elemento di fattori umani. La logica booleana complessa fa scattare molte persone. Anche un programmatore esperto dovrà pensarci if (!(complex || (boolean && condition))) do_that; else do_this;. Come minimo, riscrivi questo come if (complex || (boolean && condition)) do_this; else do_that;.

3. Ciò non significa che si dovrebbero preferire thendichiarazioni vuote .

La seconda sezione dice "preferisci" piuttosto che "tu". È una linea guida piuttosto che una regola. La ragione per cui questa linea guida preferisce ifcondizioni positive è che il codice dovrebbe essere chiaro ed evidente. Una clausola vuota (ad esempio, if (condition) ; else do_something;) viola questo. È una programmazione offuscata, che fa sì che anche i programmatori più esperti possano eseguire il backup e rileggere la ifcondizione nella sua forma negata. Quindi scrivilo nella forma negata in primo luogo e ometti la dichiarazione else (o se hai un altro oggetto vuoto o commenta in tal senso se ti viene richiesto di farlo).



1 ho scritto che allora le clausole che terminano con return, throwo gotonon richiedono un vuoto altrimenti. È ovvio che la clausola else non è necessaria. Ma che dire goto? A parte questo, le regole di programmazione critiche per la sicurezza a volte non consentono il rientro anticipato e quasi sempre vietano il lancio di eccezioni. Tuttavia consentono gotoin una forma limitata (ad esempio, goto cleanup1;). Questo uso limitato di gotoè la pratica preferita in alcuni luoghi. Il kernel Linux, per esempio, è pieno zeppo di tali gotoaffermazioni.


6
!inoltre è facilmente trascurato. È troppo conciso.
Thorbjørn Ravn Andersen,

4
No, un altro vuoto non chiarisce che il programmatore ci abbia pensato. Porta a maggiore confusione "Sono state pianificate alcune affermazioni che hanno dimenticato o perché esiste una clausola vuota?" Un commento è molto più chiaro.
Simon,

9
@Simon: una forma tipica è else { /* Intentionally empty */ }, o qualcosa del genere. L'altro vuoto soddisfa l'analizzatore statico che cerca inconsapevolmente violazioni delle regole. Il commento informa i lettori che il vuoto è intenzionale. Inoltre, i programmatori esperti in genere omettono il commento come non necessario, ma non il vuoto. La programmazione ad alta affidabilità è un dominio a sé stante. I costrutti sembrano piuttosto strani agli estranei. Questi costrutti sono usati a causa delle lezioni della scuola di colpi duri, colpi che sono molto duri quando la vita e la morte o $$$$$$$$$ sono a portata di mano.
David Hammen,

2
Non capisco come le clausole vuote allora siano un male quando le clausole vuote else non lo sono (supponendo che scegliamo quello stile). Posso vedereif (target == source) then /* we need to do nothing */ else updateTarget(source)
Bergi l'

2
@ ThorbjørnRavnAndersen no, notè una parola chiave in C ++, così come altri operatori logici e bit per bit. Vedi rappresentazioni operatore alternative .
Ruslan,

31

Uso un ramo else vuoto (e talvolta un ramo if vuoto) in casi molto rari: quando è ovvio che sia la parte if che else dovrebbe essere gestita in qualche modo, ma per qualche motivo non banale il caso può essere gestito da facendo nulla. E quindi chiunque legga il codice con l'azione necessaria altrimenti sospetterebbe immediatamente che manca qualcosa e perderebbe tempo.

if (condition) {
    // No action needed because ...
} else {
    do_else_action()
}

if (condition) {
    do_if_action()
} else {
    // No action needed because ...
}

Ma no:

if (condition) {
    do_if_action()
} else {
    // I was told that an if always should have an else ...
}

5
Questo. Trovo che l'affermazione if test succeeds -> no action needed -> else -> action neededsia semanticamente molto diversa dall'affermazione if it applies that the opposite of a test outcome is true then an action is needed. Mi viene in mente la citazione di Martin Fowler: "chiunque può scrivere codici che i computer possono capire; i bravi programmatori scrivono codici che gli umani possono capire".
Tasos Papastylianou,

2
@TasosPapastylianou Questo è barare. L'opposto di "se il test ha esito positivo -> nessuna azione necessaria" è "se il test non ha esito positivo -> azione necessaria". L'hai riempito con circa 10 parole.
Jerry B,

@JerryB dipende dal "test". A volte la negazione è (linguisticamente) semplice, ma a volte non lo è, e porterebbe a confusione. In questi casi, è più chiaro parlare di "negazione / opposto del risultato come vero" vs "risultato non è vero"; tuttavia il punto è che sarebbe ancora più chiaro introdurre un passaggio "vuoto" o invertire il significato dei nomi delle variabili. Questo è tipico nelle situazioni "de morgan": ad es. if ((missing || corrupt) && !backup) -> skip -> else do stuffÈ molto più chiaro (+ diverso intento implicito) diif ((!missing && !corrupt) || backup) -> do stuff
Tasos Papastylianou,

1
@TasosPapastylianou Potresti scrivere if(!((missing || corrupt) && !backup)) -> do stuffo meglio if((aviable && valid) || backup) -> do stuff. (Ma in un esempio reale è necessario verificare se il backup è valido prima di utilizzarlo)
12431234123412341234123

2
@TasosPapastylianou dove ci sono doppi negativi in ​​quello che ho detto? Certo, se si usasse non corrotto come nome di una variabile, si avrebbe un punto chiaro. Tuttavia, quando si arriva a operatori booleani misti come questo, potrebbe essere meglio avere variabili temporanee da usare in if: file_ok = !missing && !corrupt; if(file_ok || backup) do_stuff();che personalmente ritengo lo renda ancora più chiaro.
Baldrickk,

23

A parità di tutto il resto, preferisco la brevità.

Ciò che non scrivi, nessuno deve leggere e capire.

Anche se essere espliciti può essere utile, è solo il caso se rende evidente senza indebita verbosità che ciò che hai scritto è davvero ciò che volevi scrivere.


Quindi, evita i rami vuoti, non sono solo inutili ma anche rari e quindi creano confusione.

Inoltre, evita di scrivere un ramo else se esci direttamente dal ramo if.

Un'utile applicazione di explicitness sarebbe quella di inserire un commento ogni volta che si passa da un caso all'altro // FALLTHRUe di utilizzare un commento o un blocco vuoto in cui è necessaria un'istruzione vuota for(a;b;c) /**/;.


7
// FALLTHRUUgh, non in SCREAMING CAPS o con abbreviazioni txtspk. Altrimenti, sono d'accordo e seguo questa pratica da solo.
Cody Gray,

7
@CodyGray - Questo è un posto dove SHOUTING è una buona idea. La maggior parte delle istruzioni switch termina ogni caso con break. Non averlo fatto breakha portato ad alcuni spettacolari guasti del software. Un commento che grida ai lettori del codice che il fall-through è intenzionale è una buona cosa. Che gli strumenti di analisi statica possano cercare questo modello specifico e non lamentarsi di una situazione di caduta, lo rende ancora migliore.
David Hammen,

1
@Wossname: ho appena controllato il mio vime non riconosce alcuna variazione di FALLTHRU. E penso, per una buona ragione: TODOe i FIXMEcommenti sono commenti che devono essere plausibili perché vuoi essere in grado di chiedere git grepquali difetti conosci hai nel tuo codice e dove si trovano. Una caduta non è un difetto, e non c'è motivo di sostenerlo, che mai.
cmaster

4
@DavidHammen No, gridare non è una buona idea: se hai un // fallthrough al posto di una pausa prevista, questo dovrebbe essere sufficiente per qualsiasi sviluppatore per capire immediatamente. Inoltre, il // fallthrough non parla di un difetto, ma semplicemente segnala che le circostanze sono state considerate e che la rottura; che sembra mancare, è davvero destinato a non essere lì. Questo è uno scopo puramente informativo e nulla di cui urlare.
cmaster

1
@cmaster - Che gridare non è una buona idea è la tua opinione. Le opinioni degli altri sono diverse. E solo perché la tua configurazione di vim non riconosce FALLTHRUnon significa che altre persone non abbiano configurato vim per farlo.
David Hammen,

6

Non esiste una regola rigida e rapida sulle condizioni positive o negative per un'istruzione IF, non per quanto ne sappia. Personalmente preferisco la codifica per un caso positivo piuttosto che negativo, ove applicabile. Certamente non lo farò però, se mi porta a creare un blocco IF vuoto, seguito da un ELSE pieno di logica. Se si verificasse una situazione del genere, occorrerebbero circa 3 secondi per rifattorarla nuovamente in prova per un caso positivo.

Quello che in realtà non mi piace dei tuoi esempi è lo spazio verticale completamente inutile occupato dal vuoto ELSE. Non c'è semplicemente alcun motivo per farlo. Non aggiunge nulla alla logica, non aiuta a documentare cosa sta facendo il codice e non aumenta affatto la leggibilità. In effetti, direi che lo spazio verticale aggiunto potrebbe ridurre la leggibilità.


2
Questo inutile spazio verticale è una conseguenza dei mandati per usare sempre le parentesi graffe, per impedire la mancanza di altro e per usare lo stile Allman per il rientro.
David Hammen,

Bene, penso che usare sempre le parentesi graffe sia una buona cosa, perché rende esplicite le intenzioni degli sviluppatori - non c'è spazio per le congetture quando si legge sul loro codice. Potrebbe sembrare sciocco, ma fidati di me, specialmente quando fai da mentore agli sviluppatori junior, accadono errori relativi a parentesi graffe mancanti. Personalmente non sono mai stato un fan del rientro in stile Allman, proprio per il motivo che dici: spazio verticale non necessario. Ma questa è solo la mia opinione e il mio stile personale. È proprio quello che ho imparato inizialmente, quindi è sempre stato più comodo leggere.
Langecrew

Stile personale: indipendentemente dallo stile di parentesi graffe che utilizzo (Allman, 1TB, ...), utilizzo sempre parentesi graffe.
David Hammen,

I condizionali complessi o quelli che ritieni possano essere difficili da capire possono quasi sempre essere risolti rientrando nei loro metodi / funzioni. Qualcosa del genere if(hasWriteAccess(user,file))potrebbe essere complesso sotto, ma a colpo d'occhio sai esattamente quale dovrebbe essere il risultato. Anche se si tratta solo di un paio di condizioni, ad esempio if(user.hasPermission() && !file.isLocked())una chiamata di metodo con un nome appropriato ottiene il punto, quindi i negativi diventano meno un problema.
Chris Schneider,

Concordato. Poi di nuovo, sono un grande fan del refactoring quando possibile :)
Langecrew

5

elseBlocco esplicito

Non sono d'accordo con questo come un'affermazione generale che copre tutte le ifaffermazioni, ma ci sono momenti in cui l'aggiunta di un elseblocco per abitudine è una buona cosa.

Una ifdichiarazione, a mio avviso, in realtà si estende su due funzioni distinte.

Se dovremmo fare qualcosa, fallo qui.

Roba del genere ovviamente non ha bisogno di una elseparte.

    if (customer.hasCataracts()) {
        appointmentSuggestions.add(new CataractAppointment(customer));
    }
    if (customer.isDiabetic()) {
        customer.assignNurse(DiabeticNurses.pickBestFor(customer));
    }

e in alcuni casi insistendo sull'aggiunta di un elsepotrebbe fuorviare.

    if (k > n) {
        return BigInteger.ZERO;
    }
    if (k <= 0 || k == n) {
        return BigInteger.ONE;
    }

non è lo stesso di

    if (k > n) {
        return BigInteger.ZERO;
    } else {
        if (k <= 0 || k == n) {
            return BigInteger.ONE;
        }
    }

anche se funzionalmente è lo stesso. Scrivere il primo ifcon un vuoto elsepuò portare al secondo risultato che è inutilmente brutto.

Se stiamo verificando uno stato specifico, spesso è una buona idea aggiungere un vuoto elsesolo per ricordarti di coprire quella eventualità

        // Count wins/losses.
        if (doors[firstChoice] == Prize.Car) {
            // We would have won without switching!
            winWhenNotSwitched += 1;
        } else {
            // We win if we switched to the car!
            if (doors[secondChoice] == Prize.Car) {
                // We picked right!
                winWhenSwitched += 1;
            } else {
                // Bad choice.
                lost += 1;
            }
        }

Ricorda che queste regole si applicano solo quando scrivi un nuovo codice . IMHO Le elseclausole vuote devono essere rimosse prima del check-in.


Prova per true, non perfalse

Anche in questo caso si tratta di buoni consigli a livello generale, ma in molti casi ciò rende il codice inutilmente complesso e meno leggibile.

Anche se il codice piace

    if(!customer.canBuyAlcohol()) {
        // ...
    }

è stonante per il lettore, ma ce la fa

    if(customer.canBuyAlcohol()) {
        // Do nothing.
    } else {
        // ...
    }

è almeno altrettanto male, se non peggio.

Ho codificato in BCPL molti anni fa e in quella lingua c'è una IFclausola e una UNLESSclausola in modo che tu possa codificare molto più facilmente come:

    unless(customer.canBuyAlcohol()) {
        // ...
    }

che è significativamente migliore, ma ancora non perfetto.


Il mio processo personale

In genere, quando scrivo un nuovo codice, spesso aggiungo un elseblocco vuoto a ifun'istruzione solo per ricordarmi che non ho ancora coperto tale eventualità. Questo mi aiuta a evitare la DFStrappola e assicura che quando rivedo il codice noto che c'è ancora molto da fare. Tuttavia, di solito aggiungo un TODOcommento per tenere traccia.

  if (returnVal == JFileChooser.APPROVE_OPTION) {
    handleFileChosen();
  } else {
    // TODO: Handle case where they pressed Cancel.
  }

Trovo che generalmente uso elseraramente nel mio codice in quanto spesso può indicare un odore di codice.


La tua gestione di blocchi "vuoti" si legge come il codice standard di stubbing durante l'implementazione di thngs ...
Deduplicator

Il unlessblocco è un buon suggerimento e difficilmente limitato a BCPL.
tchrist l'

@TobySpeight Oops. In qualche modo è sfuggito alla mia attenzione che ciò che ho scritto ...era in realtà return. (In effetti, con k=-1, n=-2viene preso il primo ritorno, ma questo è in gran parte discutibile.)
David Richerby,

Il Perl moderno ha ife unless. Inoltre, consente anche questi dopo un'affermazione, quindi puoi dire print "Hello world!" if $born;o print "Hello world!" unless $dead;ed entrambi faranno esattamente quello che pensi che facciano.
un CVn del

1

Per il primo punto, ho usato un linguaggio che ha costretto le istruzioni IF a essere utilizzate in questo modo (in Opal, la lingua dietro uno scraper dello schermo del mainframe per mettere un front-end della GUI sui sistemi mainframe), e con una sola riga per l'IF e l'ELSE. Non è stata un'esperienza piacevole!

Mi aspetto che qualsiasi compilatore ottimizzi tali clausole ELSE aggiuntive. Ma per il codice live non si aggiunge nulla (in fase di sviluppo può essere un utile marker per ulteriore codice).

Una volta che uso qualcosa di simile a queste clausole extra è quando uso l'elaborazione di tipo CASE / WHEN. Aggiungo sempre una clausola predefinita anche se è vuota. Questa è un'abitudine a lungo termine da parte delle lingue che sbaglierà se una tale clausola non viene utilizzata e forza un pensiero sul fatto che le cose debbano davvero passare.

Molto tempo fa la pratica del mainframe (ad es. PL / 1 e COBOL) è stato accettato che i controlli negativi fossero meno efficienti. Questo potrebbe spiegare il secondo punto, anche se oggigiorno ci sono risparmi di efficienza enormemente più importanti che vengono ignorati come microottimizzazioni.

La logica negativa tende ad essere meno leggibile, anche se non tanto su un'istruzione IF così semplice.


2
Vedo, quindi era pratica comune ad un certo punto.
Patsuan,

@Patsuan - sì, fino a un certo punto. Anche se questo era quando l'assemblatore di mainframe era una seria alternativa al codice critico per le prestazioni.
Calcio d'

1
"Molto tempo fa ... era stato accettato che i controlli negativi fossero meno efficienti." Ebbene, essi sono meno che il compilatore ottimizza if !A then B; else Cal if A then C; else B. Il calcolo della negazione della condizione è un'istruzione CPU aggiuntiva. (Anche se come dici tu, è improbabile che questo sia significativamente meno efficiente.)
David Richerby,

@DavidRicherby E se stiamo parlando in generale, alcune lingue possono rendere tali ottimizzazioni davvero difficili. Prendi C ++ con overload !e ==operatori, per esempio. Non che nessuno dovrebbe effettivamente farlo , intendiamoci ...
un CVn del

1

Secondo la maggior parte delle risposte, i elseblocchi vuoti sono praticamente sempre uno spreco dannoso di inchiostro elettronico. Non aggiungere questi a meno che tu non abbia un'ottima ragione per farlo, nel qual caso il blocco vuoto non dovrebbe essere affatto vuoto, dovrebbe contenere un commento che spiega perché è lì.

La questione dell'evitare gli aspetti negativi merita tuttavia maggiore attenzione: in genere, ogni volta che è necessario utilizzare un valore booleano, sono necessari alcuni blocchi di codice per operare quando è impostato e altri blocchi per funzionare quando non è impostato. Pertanto, se imponi una regola di non negazione, imponi di avere if() {} else {...}istruzioni (con un ifblocco vuoto !) O crei un secondo booleano per ogni valore booleano che contiene il suo valore negato. Entrambe le opzioni sono sbagliate, poiché confondono i tuoi lettori.

Una politica utile è questa: non usare mai una forma negata nel nome di un booleano ed esprimere la negazione come singola !. Un'affermazione come if(!doorLocked)è perfettamente chiara, un'affermazione come il if(!doorUnlocked)cervello dei nodi. Il tipo di espressione successivo è la cosa che devi evitare a tutti i costi, non la presenza di un singolo !in una if()condizione.


0

Direi che è decisamente una cattiva pratica. L'aggiunta di altre istruzioni aggiungerà un sacco di righe inutili al codice che non fanno nulla e rendono ancora più difficile la lettura.


1
Ho sentito che non hai mai lavorato per un datore di lavoro che valuta le tue prestazioni in base agli SLOC prodotti per periodo ...
un CVn del

0

C'è un altro modello che non è stato ancora menzionato sulla gestione del secondo caso che ha un if-block vuoto. Un modo per affrontarlo sarebbe quello di tornare nel blocco if. Come questo:

void foo()
{
    if (condition) return;

    doStuff();
    ...
}

Questo è un modello comune per il controllo delle condizioni di errore. Il caso affermativo è ancora usato, e l'interno di esso non è più vuoto, lasciando i futuri programmatori a chiedersi se una no-op fosse intenzionale o meno. Può anche migliorare la leggibilità poiché potrebbe essere necessario creare una nuova funzione (suddividendo in tal modo le funzioni di grandi dimensioni in singole funzioni più piccole).


0

C'è un punto quando si considera l'argomento "avere sempre un'altra clausola" che non ho visto in nessun'altra risposta: può avere senso in uno stile di programmazione funzionale. Una specie di.

In uno stile di programmazione funzionale, ti occupi delle espressioni piuttosto che delle dichiarazioni. Quindi ogni blocco di codice ha un valore di ritorno, inclusa if-then-elseun'espressione. Ciò, tuttavia, escluderebbe un elseblocco vuoto . Lasciami fare un esempio:

var even = if (n % 2 == 0) {
  return "even";
} else {
  return "odd";
}

Ora, in linguaggi con sintassi ispirata allo stile C o C (come Java, C # e JavaScript, solo per citarne alcuni) questo sembra strano. Tuttavia sembra molto più familiare se scritto come tale:

var even = (n % 2 == 0) ? "even" : "odd";

Lasciare il elseramo vuoto qui causerebbe la definizione di un valore - nella maggior parte dei casi, non quello che vogliamo essere un caso valido durante la programmazione della funzionalità. Lo stesso con lasciarlo completamente fuori. Tuttavia, quando stai programmando iterativamente, ho ben poche ragioni per avere sempre un elseblocco.


0

... un'istruzione if dovrebbe essere seguita da un'istruzione else, indipendentemente dal fatto che ci sia qualcosa da inserire o meno.

Non sono d'accordo if (a) { } else { b(); }dovrebbe essere riscritto come if (!a) { b(); }e if (a) { b(); } else { }dovrebbe essere riscritto come if (a) { b(); }.

Tuttavia, vale la pena sottolineare che raramente ho un ramo vuoto. Questo perché normalmente registro che sono andato nel ramo altrimenti vuoto. In questo modo posso sviluppare esclusivamente messaggi di log. Uso raramente un debugger. Negli ambienti di produzione non si ottiene un debugger; è bello essere in grado di risolvere i problemi di produzione con gli stessi strumenti che usi per sviluppare.

In secondo luogo, è meglio verificare i valori veri anziché falsi. Ciò significa che è meglio testare una variabile 'doorClosed' anziché una variabile '! DoorOpened'

Ho sentimenti contrastanti su questo. Uno svantaggio di avere doorCloseded doorOpenedè potenzialmente il doppio del numero di parole / termini di cui devi essere consapevole. Un altro svantaggio è nel tempo il significato doorClosede doorOpenedpuò cambiare (altri sviluppatori arrivano dopo di te) e potresti finire con due proprietà che non sono più negazioni precise l'una dell'altra. Invece di evitare negazioni, apprezzo l'adattamento della lingua del codice (nomi delle classi, nomi delle variabili, ecc.) Al vocabolario degli utenti aziendali e ai requisiti che mi vengono dati. Non vorrei inventare un termine completamente nuovo per evitare a!se quel termine ha significato solo per uno sviluppatore che gli utenti aziendali non capiranno. Voglio che gli sviluppatori parlino la stessa lingua degli utenti e delle persone che scrivono i requisiti. Ora, se i nuovi termini semplificano il modello, questo è importante, ma dovrebbe essere gestito prima che i requisiti siano finalizzati, non dopo. Lo sviluppo dovrebbe gestire questo problema prima che inizino a programmare.

Sono incline a dubitare del mio istinto.

È bello continuare a interrogarsi.

È una pratica buona / cattiva / "non importa"?

In una certa misura molto di questo non importa. Ottieni la revisione del tuo codice e adattalo al tuo team. Di solito è la cosa giusta da fare. Se tutti i membri del tuo team vogliono codificare in un determinato modo, probabilmente è meglio farlo in quel modo (cambiare 1 persona - te stesso - richiede meno punti trama che cambiare un gruppo di persone).

È pratica comune?

Non ricordo di aver mai visto un ramo vuoto. Vedo che le persone aggiungono proprietà per evitare negazioni, però.


0

Affronterò solo la seconda parte della domanda e questo viene dal mio punto di vista di lavorare nei sistemi industriali e manipolare i dispositivi nel mondo reale.

Tuttavia, questo è in qualche modo un commento esteso piuttosto che una risposta.

Quando è indicato

In secondo luogo, è meglio verificare i valori veri anziché falsi. Ciò significa che è meglio testare una variabile 'doorClosed' anziché una variabile '! DoorOpened'

La sua tesi è che chiarisce cosa sta facendo il codice.

Dal mio punto di vista si suppone che lo stato della porta sia uno stato binario quando in realtà è almeno uno stato ternario:

  1. La porta è aperta.
  2. La porta non è né completamente aperta né completamente chiusa.
  3. La porta è chiusa.

Pertanto, a seconda di dove si trova un singolo sensore digitale binario, è possibile ipotizzare solo uno dei due concetti:

  1. Se il sensore si trova sul lato aperto della porta: la porta è aperta o non aperta
  2. Se il sensore si trova sul lato chiuso della porta: la porta è chiusa o non chiusa.

E non è possibile scambiare questi concetti attraverso l'uso di una negazione binaria. Pertanto, doorClosede !doorOpenedprobabilmente non sono sinonimi e qualsiasi tentativo di fingere di essere sinonimi è un pensiero erroneo che presuppone una maggiore conoscenza dello stato del sistema di quanto effettivamente esista.

Tornando alla tua domanda, supporterei una verbosità che corrisponda all'origine delle informazioni che la variabile rappresenta. Pertanto, se le informazioni derivano dal lato chiuso della porta, procedere con doorClosedecc. Ciò può implicare l'uso di uno doorClosedo di !doorClosedquanto necessario in varie affermazioni come richiesto, con conseguente potenziale confusione con l'uso della negazione. Ma ciò che non fa è implicitamente propagare ipotesi sullo stato del sistema.


A fini di discussione per il lavoro svolto, la quantità di informazioni disponibili sullo stato del sistema dipende dalla funzionalità richiesta dal sistema stesso.

A volte ho solo bisogno di sapere se la porta è chiusa o non chiusa. Nel qual caso sarà sufficiente un solo sensore binario. Ma altre volte ho bisogno di sapere che la porta è aperta, chiusa o in transizione. In tali casi ci sarebbero o due sensori binari (ad ogni estremo del movimento della porta - con controllo degli errori contro l'apertura e la chiusura simultanee della porta) o un sensore analogico che misura quanto "aperta" era la porta.

Un esempio del primo sarebbe la porta di un forno a microonde. Si abilita il funzionamento del forno in base alla porta chiusa o non chiusa. Non ti interessa quanto sia aperta la porta.

Un esempio del secondo sarebbe un semplice attuatore motorizzato. Si inibisce l'avanzamento del motore quando l'attuatore è completamente fuori. E inibisci la guida del motore al contrario quando l'attuatore è completamente inserito.

Fondamentalmente il numero e il tipo di sensori si riduce all'esecuzione di un'analisi dei costi dei sensori rispetto a un'analisi dei requisiti di ciò che è necessario per ottenere la funzionalità richiesta.


"Dal mio punto di vista si suppone qui che lo stato della porta sia uno stato binario quando in realtà è almeno uno stato ternario:" e "Si abilita il funzionamento del forno in base alla porta chiusa o non chiusa Non ti interessa quanto sia aperta la porta. " si contraddicono a vicenda. Inoltre, non penso che la domanda riguardi affatto porte e sensori.
Sanchises,

@Sanchise Le dichiarazioni non sono contraddittorie in quanto le hai tolte dal contesto. Le prime affermazioni sono nel contesto che due misure binarie opposte di uno stato ternario non possono essere scambiate tramite negazione binaria. La seconda affermazione è nel contesto che solo una conoscenza parziale di uno stato ternario può essere sufficiente per il corretto funzionamento di un'applicazione. Per quanto riguarda le porte, la domanda dei PO fa riferimento a porte aperte e chiuse. Sto solo sottolineando un'ovvia supposizione che può influenzare il modo in cui i dati vengono trattati.
Peter M,

-1

Le regole di stile concrete sono sempre sbagliate. Le linee guida sono buone, ma alla fine ci sono spesso casi in cui puoi rendere le cose più chiare facendo qualcosa che non segue abbastanza le linee guida.

Detto questo, c'è molto odio per l'altro vuoto "spreca righe" "digitazione extra", che sono semplicemente cattive. Potresti argomentare per spostare le parentesi sulla stessa linea se hai davvero bisogno dello spazio verticale, ma se questo è un problema non dovresti comunque mettere il tuo {su una linea separata.

Come menzionato altrove, avere il blocco else è molto utile per dimostrare che non si desidera esplicitamente che ciò non accada nell'altro caso. Dopo aver fatto molta programmazione funzionale (dove è richiesto altro), ho imparato che dovresti sempre considerare l'altro quando scrivi il if, anche se come menziona @OldCurmudgeon ci sono davvero due diversi casi d'uso. Si dovrebbe avere un altro, non si dovrebbe. Purtroppo non è qualcosa che puoi sempre dire a colpo d'occhio, figuriamoci con una linter, quindi il dogmatico "metti sempre un altro blocco".

Per quanto riguarda il "no negativi", di nuovo, le regole assolute sono cattive. Avere un if vuoto può essere strano, soprattutto se è il tipo di if che non ha bisogno di un altro, quindi scrivere tutto questo piuttosto che un! o un == false è errato. Detto questo, ci sono molti casi in cui il negativo ha un senso. Un esempio comune potrebbe essere la memorizzazione nella cache di un valore:

static var cached = null

func getCached() {
    if !cached {
        cached = (some calculation, etc)
    }

    return cached
}

se la logica effettiva (mentale / inglese) implica un negativo, così dovrebbe essere l'istruzione if.

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.