2013-08-30 8 views
8

Quando il flusso di codice è simile a questo:Quali sono alcuni modi migliori per evitare il do-mentre (falso); hack in Java?

if(check()) 
{ 
    ... 
    ... 
    if(check()) 
    { 
    ... 
    ... 
    if(check()) 
    { 
     ... 
     ... 
    } 
    } 
} 

ho generalmente visto questo lavoro in giro per evitare questo flusso di codice disordinato:

do { 
    if(!check()) break; 
    ... 
    ... 
    if(!check()) break; 
    ... 
    ... 
    if(!check()) break; 
    ... 
    ... 
} while(false); 

Quali sono alcuni modi migliori che evitano questa soluzione/hack così che diventa un codice di livello superiore (a livello di settore)?

Ci sono forse dei costrutti che provengono da Apache Commons o Google Guava?

Nota: questa è una copia di the same question for C++. Le migliori risposte ci sono veramente funzioni di puntatori e il comando GOTO. Entrambi non esistono in Java. Sono interessato con interesse alla stessa cosa per Java.

Inserirlo in una nuova funzione e utilizzare restituire non è, a mio avviso, una buona soluzione, poiché return chiude il metodo. Quindi se la mia classe ha 20 metodi con questi costrutti, dovrei aggiungere 20 metodi aggiuntivi per ottenere questo risultato. Ecco perché GOTO è stata la migliore risposta per C++.

+6

Alcune delle risposte continuano a detenere - spostandolo nella sua funzione, e l'utilizzo di ritorno, per esempio. – Sinkingpoint

+0

@ MarounMaroun Perché è possibile avere questo 20 volte in una funzione per passare 20 passaggi seriali. Se lo faccio in questo modo, l'ultimo se scorre già 20 schede a destra, il che rende il codice illeggibile. –

+3

Non sono una persona religiosa, ma credo fermamente che "GOTO" non sia la risposta giusta per qualsiasi cosa relativa a Java. – WarrenFaith

risposta

6

Cos'è questa "nonsenso (falsa)"? Usa un'etichetta!

myLabel: { 
    if(!check()) break myLabel; 
    ... 
    ... 
    if(!check()) break myLabel; 
    ... 
    ... 
    if(!check()) break myLabel; 
    ... 
} 

E 'nucleo Java: http://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html

+1

Accetto questo, perché è significativamente vicino alla fonte originale. Ovviamente anche le altre risposte sono piuttosto buone. Questo risolve esattamente il problema che hanno gli utenti C++ (e Java) e ovviamente c'è una soluzione molto semplice in Java. Grazie per questo contributo. –

+1

Prego. – mvmn

7

Per ridurre il cyclomatic complexity, è possibile separare le logiche in submethods:

public void go() { 
    first(); 
} 

public void first() { 
    if(check()) { 
     // #1 
     second(); 
    } 
} 

public void second() { 
    if(check()) { 
     // #2 
     third(); 
    } 
} 

public void third() { 
    if(check()) { 
     // #3 
    } 
} 
+0

Grazie per la risposta. Sì, hai ragione riguardo alla complessità. Tuttavia, in questa domanda l'attenzione è più focalizzata sulla leggibilità e sulla bellezza del codice. La velocità non è l'obiettivo principale. Presumo comunque che i compilatori JIT moderni gestiranno quella multa. –

2

Poiché è possibile avere questo 20 volte in una funzione per passare 20 passaggi seriali. Se lo faccio in questo modo, l'ultimo se scorre già 20 schede a destra, il che rende il codice illeggibile.

Questo tuo commento mostra il problema: ti immagini la struttura del codice errata.

Per esempio il codice è come questo

if (bool) { 
    // do something 
    if (anotherBoolean) { 
     //do even more 
     if (thirdBoolean) { 
      // here we go! I spare deeper structure... 
     } 
    } 
} 

Questo può essere riscritta facilmente:

public void method() { 
    if (bool) { 
     doSomeStuff(); 
    } 
} 

public void doSomeStuff() { 
    if (anotherBoolean) { 
     doThirdStuff(); 
    } 
} 

public void doThirdStuff() { 
    if (thirdBoolean) { 
     doEvenMore(); 
    } 
} 

public void doEvenMore() { 
    // deeper structure 
} 

Questo tipo di struttura del codice sarebbe un buon esempio di codice pulito, come i metodi stanno facendo il loro "singolo scopo", non si esegue un hack loop ma si potrebbe anche riutilizzare i metodi da qualche parte.

+0

Grazie per questo contributo. Nel mio caso ho 4 metodi nella mia classe e corrono oltre 6 booleani. Quindi dovrei aggiungere 24 metodi alla classe. Ora prova a trovare 24 nomi diversi per i metodi. Va bene, sono d'accordo, dipende dal mio codice attuale. Invece di trovare altri nomi, potrei aggiungere parametri ecc. Quindi la tua soluzione potrebbe funzionare per casi specifici. Pensavo che qualcuno potesse avere in mente qualcosa di più cool come switch, eccezioni o importazioni statiche. Nient'altro che creare nuovi metodi. –

+1

Se i tuoi 6 booleani rappresentano un tipo di stati, puoi provare enum e usarli in un'istruzione switch case. Lo uso per la gestione degli input, ad esempio. State.PRESSED, RELEASE, MOVING, NONE etc e metodi di chiamata in base a quello stato. – WarrenFaith

-1

Qui potrebbe essere un'altra soluzione:

public interface Stuff { 
    void run(); 
} 

private List<Stuff> stuffs; 

// logical parts 
public void init() { 
    Stuff first = new Stuff() { 
     public void run() { 
      // stuff #1 
     } 
    }; 
    Stuff second = new Stuff() { 
     public void run() { 
      // stuff #2 
     } 
    }; 
    Stuff third = new Stuff() { 
     public void run() { 
      // stuff #3 
     } 
    }; 
    // LinkedList to keep order 
    stuffs = new LinkedList<Stuff>(Arrays.asList(first, second, third)); 
} 

// main method, just launch the stuffs while check() returns true 
// no change needed when new stuff 
public void go() { 
    for (Stuff stuff : stuffs) { 
     if (!check()) break; 
     stuff.run(); 
    } 
} 

Se i vostri animali possono avere diverse condizioni per verificare, è possibile migliorare con:

public interface Stuff { 
    void run(); 
    boolean condition(); 
} 

private List<Stuff> stuffs; 

public void init() { 
    Stuff first = new Stuff() { 
     public void run() { 
      // stuff #1 
     } 
     public boolean condition() { 
      return true; // condition to call this run() 
     } 
    }; 
    Stuff second = new Stuff() { 
     public void run() { 
      // stuff #2 
     } 
     public boolean condition() { 
      return false; // condition to call this run() 
     } 
    }; 
    Stuff third = new Stuff() { 
     public void run() { 
      // stuff #3 
     } 
     public boolean condition() { 
      return true; // condition to call this run() 
     } 
    }; 
    stuffs = new LinkedList<Stuff>(Arrays.asList(first, second, third)); 
} 

public void go() { 
    for (Stuff stuff : stuffs) { 
     if (!stuff.condition()) break; 
     stuff.run(); 
    } 
} 
+0

+1 questo è veramente bello. –

+3

-1 Mi spiace. È così lontano da leggibile che è triste. Confronta questo codice con il codice originale refactored di OP. Il codice OP è molto più chiaro. Questa risposta è più una curiosità e un anti-modello. – Bohemian

+1

Non vedo un anti-pattern in questo. Mi sembra populista. Il primo codice sorgente di questo esempio è per me il modello OOP ideale per questo problema. Ad ogni modo, il secondo codice sorgente, che è stato aggiunto lì per estendere la risposta, sembra confondere tutti e sembra ora generalmente una cattiva risposta, che complica il semplice problema iniziale. –

0

E 'una buona domanda. La semantica di qualsiasi soluzione deve coinvolgere un qualche tipo di blocco eccitabile, letterale o implicito dall'implementazione che può essere abbandonata.

Alcune delle altre risposte sono così complicate che mentre ottengono il ciclo di disimpegno, perdono ogni chiarezza e leggibilità, il che rende il loro codice "cattivo".

Dopo qualche pensiero, questo è come vorrei codificarlo:

doSomething(); // Replace current code with a method call 

private void doSomething() { 
    if(!check()) return; 
    ... 
    ... 
    if(!check()) return; 
    ... 
    ... 
    if(!check()) return; 
    ... 
    ... 
} 

punti principali:

  • un metodo è un blocco, che è nettamente esce dal return
  • Le tre parti chiaramente funzionano come un'unica unità di programmazione, quindi insieme sono una stringa candidata per il refactoring in un metodo separato
  • Nessun "accorgimento", come il perfunct ORY do-while (false) costrutto, che esiste solo per fornire un blocco exitable e che possono confondere altri
  • Utilizzando test negativi successiva chiusura, due cose si ottengono:
    • la "rapida uscita" codifica paradigma
    • meno indentazione/nesting, che riduce il cyclic complexity
  • Senza supplementari blocchi/classi/oggetti vengono creati, mantenendo la leggibilità e prestazioni (anche se un frame viene inserito nello stack per la chiamata di metodo - impatto trascurabile
+0

Eh? Downvote? Cura di condividere perché? L'approccio mi sembra OK (buono anche). – Bohemian

Problemi correlati