A volte le condizioni possono diventare piuttosto complesse, quindi per motivi di leggibilità solitamente le divido e attribuisco a ciascun componente un nome significativo. Ciò sconfigge tuttavia la valutazione del cortocircuito, che può rappresentare un problema. Mi è venuto in mente un approccio wrapper, ma a mio avviso è troppo prolisso.Come dividere le condizioni complesse e mantenere la valutazione del cortocircuito?
Qualcuno può venire con una soluzione pulita per questo?
Vedi codice qui sotto per gli esempi di ciò che intendo:
public class BooleanEvaluator {
// problem: complex boolean expression, hard to read
public static void main1(String[] args) {
if (args != null && args.length == 2 && !args[0].equals(args[1])) {
System.out.println("Args are ok");
}
}
// solution: simplified by splitting up and using meaningful names
// problem: no short circuit evaluation
public static void main2(String[] args) {
boolean argsNotNull = args != null;
boolean argsLengthOk = args.length == 2;
boolean argsAreNotEqual = !args[0].equals(args[1]);
if (argsNotNull && argsLengthOk && argsAreNotEqual) {
System.out.println("Args are ok");
}
}
// solution: wrappers to delay the evaluation
// problem: verbose
public static void main3(final String[] args) {
abstract class BooleanExpression {
abstract boolean eval();
}
BooleanExpression argsNotNull = new BooleanExpression() {
boolean eval() {
return args != null;
}
};
BooleanExpression argsLengthIsOk = new BooleanExpression() {
boolean eval() {
return args.length == 2;
}
};
BooleanExpression argsAreNotEqual = new BooleanExpression() {
boolean eval() {
return !args[0].equals(args[1]);
}
};
if (argsNotNull.eval() && argsLengthIsOk.eval() && argsAreNotEqual.eval()) {
System.out.println("Args are ok");
}
}
}
Risposta alle risposte:
Grazie per tutte le vostre idee! I seguenti alternative sono state presentate finora:
- linee di interruzione e aggiungere commenti
- lasciare così com'è
- metodi Estratto
- rendimenti primi
- nidificati/Split in su se c'è
Interrompe le linee e aggiungi commenti:
L'aggiunta di interruzioni di riga all'interno di una condizione viene annullata dal programma di formattazione del codice in Eclipse (ctrl + shift + f). I commenti in linea aiutano con questo, ma lascia poco spazio su ogni riga e può risultare in un involucro brutto. In casi semplici questo potrebbe essere abbastanza tuttavia.
lasciare così com'è:
La condizione esempio che ho dato è piuttosto semplicistico, in modo da non avere bisogno di affrontare le questioni di leggibilità in questo caso. Stavo pensando di situazioni in cui la condizione è molto più complesso, ad esempio:
private boolean targetFound(String target, List<String> items,
int position, int min, int max) {
return ((position >= min && position < max && ((position % 2 == 0 && items
.get(position).equals(target)) || (position % 2 == 1)
&& position > min && items.get(position - 1).equals(target)))
|| (position < min && items.get(0).equals(target)) || (position >= max && items
.get(items.size() - 1).equals(target)));
}
non mi consiglia di lasciare questo così com'è.
metodiEstratto:
ho considerato l'estrazione di metodi, come è stato suggerito in varie risposte. Lo svantaggio di questo è che questi metodi hanno in genere molto bassa granularità e non possono essere molto significativo di per sé, in modo che possa ingombrare la tua classe, per esempio:
private static boolean lengthOK(String[] args) {
return args.length == 2;
}
Questo non sarebbe davvero merita di essere un metodo separato a livello di classe. Inoltre devi passare tutti gli argomenti rilevanti per ciascun metodo. Se crei una classe separata puramente per valutare una condizione molto complessa, allora questa potrebbe essere una soluzione OK IMO.
Quello che ho cercato di ottenere con l'approccio BooleanExpression è che la logica rimane locale. Si noti che anche la dichiarazione di BooleanExpression è locale (non credo di aver mai trovato un caso d'uso per una dichiarazione di classe locale prima!).
ritorni presto:
La rapida soluzione rendimenti sembra adeguata, anche se io non favoriscono l'idioma. Una notazione alternativa:
public static boolean areArgsOk(String[] args) {
check_args: {
if (args == null) {
break check_args;
}
if (args.length != 2) {
break check_args;
}
if (args[0].equals(args[1])) {
break check_args;
}
return true;
}
return false;
}
mi rendo conto che la maggior parte delle persone odiano le etichette e le pause, e questo stile potrebbe essere troppo raro per essere considerato leggibile.
nidificati/contempla se è:
Permette l'introduzione di nomi significativi in combinazione con la valutazione ottimizzata. Un inconveniente è l'albero complesso di istruzioni condizionali che possono derivarne
Showdown
Quindi, per vedere quale approccio mi utlimately prediligo, ho applicato alcune delle soluzioni proposte per l'esempio targetFound complesso di cui sopra. Qui ci sono i miei risultati:
nidificato/Spalato, se lo è, con nomi significativi molto prolisso, nomi significativi in realtà non aiutano la leggibilità qui
private boolean targetFound1(String target, List<String> items,
int position, int min, int max) {
boolean result;
boolean inWindow = position >= min && position < max;
if (inWindow) {
boolean foundInEvenPosition = position % 2 == 0
&& items.get(position).equals(target);
if (foundInEvenPosition) {
result = true;
} else {
boolean foundInOddPosition = (position % 2 == 1)
&& position > min
&& items.get(position - 1).equals(target);
result = foundInOddPosition;
}
} else {
boolean beforeWindow = position < min;
if (beforeWindow) {
boolean matchesFirstItem = items.get(0).equals(target);
result = matchesFirstItem;
} else {
boolean afterWindow = position >= max;
if (afterWindow) {
boolean matchesLastItem = items.get(items.size() - 1)
.equals(target);
result = matchesLastItem;
} else {
result = false;
}
}
}
return result;
}
nidificato/split se di, con i commenti meno prolissa, ma ancora difficile da leggere e facile da creare bug
private boolean targetFound2(String target, List<String> items,
int position, int min, int max) {
boolean result;
if ((position >= min && position < max)) { // in window
if ((position % 2 == 0 && items.get(position).equals(target))) {
// even position
result = true;
} else { // odd position
result = ((position % 2 == 1) && position > min && items.get(
position - 1).equals(target));
}
} else if ((position < min)) { // before window
result = items.get(0).equals(target);
} else if ((position >= max)) { // after window
result = items.get(items.size() - 1).equals(target);
} else {
result = false;
}
return result;
}
torna presto ancora più compatto, ma l'albero condizionale rimane altrettanto complesso
private boolean targetFound3(String target, List<String> items,
int position, int min, int max) {
if ((position >= min && position < max)) { // in window
if ((position % 2 == 0 && items.get(position).equals(target))) {
return true; // even position
} else {
return (position % 2 == 1) && position > min && items.get(
position - 1).equals(target); // odd position
}
} else if ((position < min)) { // before window
return items.get(0).equals(target);
} else if ((position >= max)) { // after window
return items.get(items.size() - 1).equals(target);
} else {
return false;
}
}
metodi estratti risultati in metodi privi di senso nella tua classe il passaggio di parametri è fastidioso
private boolean targetFound4(String target, List<String> items,
int position, int min, int max) {
return (foundInWindow(target, items, position, min, max)
|| foundBefore(target, items, position, min) || foundAfter(
target, items, position, max));
}
private boolean foundAfter(String target, List<String> items, int position,
int max) {
return (position >= max && items.get(items.size() - 1).equals(target));
}
private boolean foundBefore(String target, List<String> items,
int position, int min) {
return (position < min && items.get(0).equals(target));
}
private boolean foundInWindow(String target, List<String> items,
int position, int min, int max) {
return (position >= min && position < max && ((position % 2 == 0 && items
.get(position).equals(target)) || (position % 2 == 1)
&& position > min && items.get(position - 1).equals(target)));
}
espressione booleana wrapper rivisitato nota che i parametri del metodo devono essere dichiarati definitivi per questo caso complesso il livello di dettaglio è difendibile IMO Forse chiusure renderà questo più facile, se mai d'accordo su quello (-
private boolean targetFound5(final String target, final List<String> items,
final int position, final int min, final int max) {
abstract class BooleanExpression {
abstract boolean eval();
}
BooleanExpression foundInWindow = new BooleanExpression() {
boolean eval() {
return position >= min && position < max
&& (foundAtEvenPosition() || foundAtOddPosition());
}
private boolean foundAtEvenPosition() {
return position % 2 == 0 && items.get(position).equals(target);
}
private boolean foundAtOddPosition() {
return position % 2 == 1 && position > min
&& items.get(position - 1).equals(target);
}
};
BooleanExpression foundBefore = new BooleanExpression() {
boolean eval() {
return position < min && items.get(0).equals(target);
}
};
BooleanExpression foundAfter = new BooleanExpression() {
boolean eval() {
return position >= max
&& items.get(items.size() - 1).equals(target);
}
};
return foundInWindow.eval() || foundBefore.eval() || foundAfter.eval();
}
Immagino che dipende molto dalla situazione (come sempre). Per condizioni molto complesse l'approccio del wrapper potrebbe essere difendibile, sebbene non sia comune.
Grazie per tutto il vostro contributo!
MODIFICA: ripensamento. Potrebbe essere ancora migliore per creare una classe specifica per la logica complessa come questa:
import java.util.ArrayList;
import java.util.List;
public class IsTargetFoundExpression {
private final String target;
private final List<String> items;
private final int position;
private final int min;
private final int max;
public IsTargetFoundExpression(String target, List<String> items, int position, int min, int max) {
this.target = target;
this.items = new ArrayList(items);
this.position = position;
this.min = min;
this.max = max;
}
public boolean evaluate() {
return foundInWindow() || foundBefore() || foundAfter();
}
private boolean foundInWindow() {
return position >= min && position < max && (foundAtEvenPosition() || foundAtOddPosition());
}
private boolean foundAtEvenPosition() {
return position % 2 == 0 && items.get(position).equals(target);
}
private boolean foundAtOddPosition() {
return position % 2 == 1 && position > min && items.get(position - 1).equals(target);
}
private boolean foundBefore() {
return position < min && items.get(0).equals(target);
}
private boolean foundAfter() {
return position >= max && items.get(items.size() - 1).equals(target);
}
}
La logica è sufficiente a giustificare una classe separata complessa (e test di unità, yay!).Manterrà il codice utilizzando questa logica più leggibile e promuoverà il riutilizzo nel caso in cui questa logica sia necessaria altrove. Penso che questa sia una bella lezione perché ha una sola responsabilità e solo campi finali.
Mi piace la tua prima soluzione, tuttavia con un'appropriata denominazione delle variabili, potrebbe non essere necessario. La tua seconda soluzione è troppo verbosa IMO. – BacMan