Smells like switch

Auch wenn das etwas OT ist: kann das jemand verdeutlichen, wie das gemeint ist?

Ist ein Klassiker des Refactoring

Replace Conditional with Polymorphism

siehe auch

Replace Conditional with Polymorphism

conditional statements - Alternative to Switch Case in Java - Stack Overflow

Java and other things: Swich Statement code smell and Polymorphism

die Beharrlichkeit dazu ist der erstaunlichste Unsinn, an den ich mich in den letzten Jahren hier im Forum/ Foren erinnern kann,
seit Scala und ‘alles muss funktional sein’ sowieso Verbot von null-Werten

für Beispiele wie in
conditional statements - Alternative to Switch Case in Java - Stack Overflow
FilePersistance/ NetWorkPersistance/ DataBasePersistace
ist es natürlich klar, das baut man nicht als Enum mit switch für 5 wichtige Methoden, sondern einzelne Klassen

aber so wichtig wie es ist, dort darauf zu achten, muss man auch die Grenzen erkennen können,
etwa im Beispiel der Column-Enum eines TableModels

[QUOTE=Timothy_Truckle][quote=SlaterB;105606]interessant wird es bei der getValueAt()-Methode:public Object getValue(int row, COLUMNS col){ Person p = find(row); switch(col){ case ID: return p.getId(); case NAME: return p.getName(); case AGE: return p.getAge(); // usw. } }wie soll man dann ohne switch auskommen?[/quote]```enum COLUMNS{
NAME(){
@Override public Object getValueOf(Person p){ return p.getName();}
}
AGE(){
@Override public Object getValueOf(Person p){ return p.getAge();}
}
//usw
;
abstract public Object getValueOf(Person p);
}

// im TableModel:
public Object getValue(int row, COLUMNS col){
Person p = find(row);
return col.getValueOf§;
}
}``` Alternativ konnte man die abstrakte Methode auch in einem Inteface definieren, welches dass enum implementiert.

bye
TT[/QUOTE]

kategorisch jedes switch abzulehnen ist genauso verbohrt wie die angemängelte Sicht, nur auf switches zu setzen,
erstaunlich dass man mit augenscheinlicher Weitsicht auf denselben wenn nicht noch schlimmeren Fehler verfällt

in der Konsequenz dürfte es eigentlich auch kein if/ else mehr geben, beliebige Methode x:


wird zu 

map.put(Boolean.TRUE, kompliziertes Action-Objekt mit doA());
map.put(Boolean.FALSE, komplizierte Action-Objekt mit do nothing);

boolean check = irgendein Test;
map.get(check).execute();

// edit: oder extra Enum definiert für dieses eine if, damit man weiß was zu ergänzen ist, wenn der False-Fall dazukommt..

es wäre genau abzugrenzen an welcher Stelle denn nun der Übergang ist zwischen erlaubten einfachen Weg und Refactoring-Zwang,

oder die Erkenntnis dass ein switch ein ganz normale Kontrollstruktur ist, wie jede andere (auch Schleifen) aber in manchen Fällen besser anders zu lösen ist,

häufig Thema Enum mit mehreren switches auf dieselben Werte ist ein guter Fall, aber oft genug auch noch ausreichend/ nicht zwingend,
im Thema des Zitats des ersten Postings hier ging es aber noch nichtmal um Enums…

[quote=SlaterB]in der Konsequenz dürfte es eigentlich auch kein if/ else mehr geben, beliebige Methode x:[/quote]In einer OO-Sprache (die Java ja ist) ist das (außerhalb von Factory-Klassen) tatsächlich der Idealzustand.

Manchmal lassen sich ifs tatsächlich nicht sinnvoll auflösen, weil sie wo möglich zu einem Algorythmus gehören (was aber kein KO-Kriterium ist).
switches in der Regel schon.

Und für beide gilt: wenn ich das selbe switch oder die selbe if-Bedingung mehr als 1 mal in meinem Code brauche sollte ernsthaft über Vererbung und Polymorphie nachdenken.

bye
TT

nach mehreren Threads die erste Abkehrung vom kategorischen ‘jedes switch = falsch’

noch lange nicht das Ziel, aber schon ein guter Zwischenschritt

Gut man könnte sagen, dass kleine switch-Blöcke nicht so schlimm sind, so wie ein kleiner Pickel auch. Im Hobby-Bereich oder bei Kleinprojekten ist das oft ganz egal.

case 1, case 2, case 3,…, case 54, /* case 54 */, case 55…

Was mich eigentlich ärgert, dass die oft wachsen und wachsen - bis eine Eiterbeule im Quelltext da ist. Dann erst ein Refactoring. Wäre eben einfacher, wenn man gleich am Anfang darauf verzichtet hätte.

Und von einer wirklich schönen, kurzen Lösung wie dem Pattern Matching in Scala oder Haskell ist man in beiden Fällen weit entfernt

na, case 1-54 liegt wohl eher an Berechnung bzw. ein if (x > 0 && x < 54) statt switch,
anderes Thema, da helfen 54 Subklassen/ Map-Einträge bestimmt nicht

Wenn man ein Konzept, hier Polymorphie, „rueberbringen“ will, macht es schon Sinn, etwas extermer zu argumentieren.

Ansonsten waere ein Beispiel fuer ein „gutes“ switch mal angebracht, die Gegenseite hat ja aschon konkrete Beispiele und Argumente gebracht warum ein swutch vermieden werden sollte und besser durch Polymorphy zu erstzen ist :wink:
Wetten dass dieses „gute“ switch Bespiel sehr kurz und nicht OO sein wird? :wink:

Naja, das Argument voelig zu uebertreiben und nun auf ein anderen Sprachelement zu beziehen hilft nicht wirklich der Dikussion uber switch, oder?
Riecht sehr stark nach reductio ad absurdum

Wundere mich immer wieder wie man an solche alten Argumente immer wieder rangeht, die Diskussion switch vs. Polymorphie ist weder neu noch hat bis jetzt jemand etwas vernuenftiges gegen Polymorphie sagen koennen.

ein switch ist ja ein if/ else if, daher passt du Frage sehr gut, ob ein if/else if mit 5 Fällen genauso verboten wäre wie JEDES switch,
und dann 4, 3, 2, 1 Fälle, ein normales einzelnes if, wo ist die Grenze?

boolean-Unterscheidung (damit geht switch interessanterweise nicht), int-Unterscheidung, noch höhere Werte wie String/ Enum?
mit if/ else weniger Einschränkungen

was ist wenn Wert-Unterscheidung zu boolean wird, für einen Wert x, für alle anderen y

wenn es wenigstens nur bei Enum wäre, aber im Thread
http://forum.byte-welt.net/java-forum-erste-hilfe-vom-java-welt-kompetenz-zentrum/java-grundlagen-f-r-anf-nger-und-umsteiger-java-se-/13427-switch-anwendung.html
taucht Enum an keiner Stelle auf, das Beispiel dort ist schon ziemlich kurz, stattdessen pauschal

[QUOTE=Timothy_Truckle]Nur mal als Denkanstoß:

Ein switch, dass nicht in einer Factory zum Erzeugen konkreter Klassen eingesetzt wird ist immer ein Zeichen für nicht eingesetzt Polymorphie.
[/QUOTE]

(dass es ein Zeichen dafür ist, ist natürlich die reine Wahrheit, aber nicht immer ist das ein Problem)


das Beispiel mit dem TableModel ist schon gegeben,

wieviele derartige Beispiele gibt es im Internet mit Zugriff nicht auf Array-Daten sondern ein konkretes Objekt und dann switch auf int-Column in get und vielleicht auch set? :
Creating simple JTable using AbstractTableModel : Table Model*«Swing JFC«*Java
ein einfaches sauberes switch, das ist ein normales Vorgehen, da muss man nicht die große Bug-Keule a la gerade
http://forum.byte-welt.net/sonstiges/spielwiese/13434-richtiges-testen-oder-doch-lieber-bug-listen.html
herausholen

Enums für die Columns einzuführen ist bei TableModel etwas ungewöhnlich, die 5 Methoden hat man für vielleicht 20 Tabellen auch so jeweils unter Kontrolle, braucht nicht 20 Enums,
aber wenn der Aufwand gemacht wird, vielleicht austauschbare Spaltennamen in verschiedenen Sprachen gewünscht usw.,
dann ist es ja ok, switch auf die Enum-Werte sicherer, ordentlich was gewonnen,

an dieser Stelle kann man dann auf Vorteile weiterer Änderungen/ Nutzung dieser Enum hinweisen,
aber es ist doch verrückt dass auf einmal das (verbesserte!) switch als schlecht gilt,

und die Alternative dann mehrzeiliger ‘boilerplate code’ für simple get-Methoden,
kann man natürlich gut finden, in dieser Form aber auch womöglich die schlechtere Lösung

getter/setter an sich versuchen manche Sprachen zu vermeiden, aber hier Vielfaches mehr je Attribut einzeln
@Override public Object getValueOf(Person p){ return p.getName();}


hinzu kommt hier noch dass in der Enum Aufrufe von der Person-Klasse stehen,
muss im TableModel auch nicht unbedingt sein,

Alternative ist eine Methode get(Enum) in der Person-Klasse,
wobei die dann die Enum kennen muss, hinsichtlich Column-Enum gewiss nicht schön,

ich habe eine Enum IdType mit InterneId, ExterneId und paar anderen, nützlich bei mehreren Persistenz-, Transfer- und Datenbankabfrageergebnis-Klassen
in einem separaten Projekt, Enum würde die konkreten Klassen gar nicht kennen,
eine umständliche Map je Klasse muss es nicht sein, auch kein Interface mit zig Methoden implementiert die sonst nicht unbedingt benötigt, es reicht eine Methode:

        switch (idType) {
            case InterneId:
                return id;
                // evtl. paar mehr je nach verknüpften Objekten
            default:
                return null;
        }
    }

der Einsatz an sich in wichtigen Fällen ist ja auch unbestritten, das Problem ist der generelle Einsatz überall (die ganze Welt ein Nagel),
inbesondere in Anfänger-Themen bedenklich, unnötig neue Konfusion reingebracht

[quote=SlaterB]aber nicht immer ist das ein Problem[/quote]Wie ich schon schrieb ist es dann ein Problem, wenn ich das selbe switch zum zweiten mal einsetzte.

[quote=SlaterB;106667]inbesondere in Anfänger-Themen bedenklich, unnötig neue Konfusion reingebracht[/quote]Wann wäre denn der geeignete Zeitpunk, OO-Konzepte zu vermitteln?
Leute die prozedural in Java programmieren haben wir genug!

bye
TT

Mir ist das schon mehrfach (unangenehm) aufgefallen: Irgendwo wird irgendetwas von irgendwem als “best practice” propagiert, und Leute folgen dem dann. Skalvisch und unreflektiert. Und insbesondere scheint Fowler einige solcher Jünger zu haben, die seine Hinweise SO oft (und oft kontextfrei) wiederkäuen, dass noch der “Das steht auf SO vielen Seiten, das MUSS richtig sein”-Effekt dazukommt.

(Gelegentlich frage ich mich dann, wer von denen nicht erkennt, dass es z.B. sowohl Extract Method als auch Inline Method gibt, und man als Softwareentwickler die ganze Kohle nicht zuletzt dafür bekommt, in der jeweiligen Situation das richtige zu machen…)

In diesem Fall:

Es kann nicht schaden, sich bei jedem “if” oder “switch”, das irgendwie ein Verhalten beeinflusst, die Frage zu stellen, ob man an dieser Stelle nicht mit Polymorphie arbeiten sollte. Bei den plakativen Beispielen, mit denen dieses Konzept verdeutlicht werden soll, ist es meistens schon fast ZU offensichtlich. Wenn man gründlicher drüber nachdenkt, gibt es auch subtilere Fälle, wo das sinnvoll ist. Aber (und das ist der entscheidende Punkt) : Es gibt auch Fälle, wo es NICHT sinnvoll ist.

Man kann mit Abstraktionen und Verallgemeinerungen praktisch immer beliebig weit gehen. Und ich hadere oft mit mir, und habe das Gefühl, an vielen Stellen (im Vergleich zu anderen) damit ZU weit zu gehen. Um mal indirekt eine andere “Größe” zu zitieren (deren Namen ich jetzt nicht nenne, und bei der ich mich selbst schon dabei ertappt habe, ihr vielleicht etwas zu “sklavisch” zu folgen) : Wenn man code hat wie

if (car.speed() > 100) {
    alert("Watch out for cops");
}

und den dann refactort, und dort am Ende sowas steht wie

VehicleVelocityCondition<Car, Float> 
   c =  VehicleVelocityConditionFactory.generateGreaterThanCondition(
       SpeedLimitRepository.getMaximumSpeed(Locale.getDefault(());
VehicleMessageAction<Car, Float, String> a = 
    VehicleMessageActionFactory.create(vehicle, c);
a.execute(OutputChannelFactory.createDefaultGuiOutputChannel());

hat man zwar ein “if” durch Polymorphie ersetzt, aber trotzdem etwas falsch gemacht…

[quote=Timothy_Truckle;106679]Wann wäre denn der geeignete Zeitpunk, OO-Konzepte zu vermitteln?
Leute die prozedural in Java programmieren haben wir genug![/quote]
alles zu seiner Zeit,
man muss erst prozedural programmieren, um das höhere zu verstehen,
und Klassen/ Objekte/ Interface/ gar Enum kommt ja auch ziemlich am Anfang,
wirklich genug Stoff ohne ausgefallene Pattern

man braucht immer Vergleich mit dem alten, direktes Sehen des Vorteils,
nicht an komische höhere Gesetze halten mit der Angst was falsch zu machen aber es nicht beurteilen können

Leute ‚gleich auf die richtige Seite zu ziehen‘ damit es da mehr gibt oder um späteres Umlernen einzusparen ist ein niederer Beweggrund :wink:
und das ist was bisschen anderes als der Hinweis jedes if von Anfang an mit öffnenden und schließenden Klammern zu schreiben, sowas kann man sofort vorschlagen

switch ist völlig überflüssig, ich verwende nur noch pick:

int i = new Random().nextInt(5);
pick(i).when(0, () -> System.out.println("nix"))
       .when(1, () -> System.out.println("eins"))
       .when(2, () -> System.out.println("zwei"))
       .orElse(() -> System.out.println("viele"));

Dazu braucht man nur diese Klasse (und Java 8)

import java.util.Objects;

public class Pick<A> {

    private final A value;
    private boolean found = false;

    public Pick(A value) {
        this.value = value;
    }

    public Pick<A> when(A testValue, Runnable action) {
        if (! found && Objects.equals(value, testValue)) {
            action.run();
            found = true;
        }
        return this;
    }

    public void orElse(Runnable action) {
        if (! found) {
            action.run();
        }
    }

    public static <A> Pick<A> pick(A value) {
        return new Pick<>(value);
    }

}

So, damit dürfte diese Frage ein für allemal geklärt sein.

da lohnt sich aber eine optimierte Variante für diesen bestimmt häufigen Fall,
mit Zwischenwertspeicherung und nur einmal am Ende statt so redundant oft Methode

pickValue(i).when(0, "nix")
       .when(1, "eins")
       .when(2, "zwei")
       .orElse("viele")
       .workValue( System.out.println(foundValue) );```
;)

oder auch, gar etwas imperativer in Java 1.tief machbar:
```int i = new Random().nextInt(5);
String value = pickValue(i).when(0, "nix")
       .when(1, "eins")
       .when(2, "zwei")
       .orElse("viele"); // hier gut Rückgabewert möglich
System.out.println(value);```

Eben, fand das gut geloest mit der Polymorphie.

[quote=SlaterB;106667]ich habe eine Enum IdType mit InterneId, ExterneId und paar anderen, nützlich bei mehreren Persistenz-, Transfer- und Datenbankabfrageergebnis-Klassen
in einem separaten Projekt, Enum würde die konkreten Klassen gar nicht kennen,[/quote]
Wenn man diese Flexibilitaet braucht, dann ok, aber wenn nicht…
Typen mit ihrem Verhalten zu definieren ist ja sozusagen einer der Grundlagen fuer OO, daran ist nix verkehrt oder haesslich.
Enums eignen sich sehr gut um Typeen und ihr Verhalten zu definieren.

[quote=SlaterB;106699]alles zu seiner Zeit,
man muss erst prozedural programmieren, um das höhere zu verstehen,
und Klassen/ Objekte/ Interface/ gar Enum kommt ja auch ziemlich am Anfang,
wirklich genug Stoff ohne ausgefallene Pattern[/quote]
Sehe ich nicht so, Java ist sehr schlecht geeignet um prodzedurale SW Entwicklung zu lernen, fast die ganze API und Sprache ist irgendwie auf OO ausgelegt, C hat auch seine Vorteile.
Finde das nicht verkehrt Java Anfaengern gleich OO Prinzipien beizubringen, wenn natuerlich keine sonstigen Grundlagen da sind ist das Thema sowieso sehr muessig.

@Marco13
Dachte eigentlich das mein Hinweis auf reductio ad absurdum offensichtlich genug war, ein einzelnes if durch Quark und Polymorphie zu ersetzen ist nicht das Thema gewesen, sorry, aber da gehe ich nicht weiter drauf ein :wink:

@Landei 

Das sieht aus wie ein switch mit anderer (funktionaler) Syntax :wink:

Welche Vorteile hat es denn das switch (oder eben auch if else Kaskaden) durch Ploymorphie zu ersetzen?
Die Komplexitaet sinkt, naemlich wie viele Ausfuerhungspfade gibt es in einer Methode, hilft ungemein bei Unit Tests wenige Pfade zu haben.

Ansonsten sind manchmal switch Statement unausweichlich bzw. das kleinere Uebel, aber immer haesslich, besser ganz tief vergraben wenn es sein muss.

Die Klasse Pick ist nett, aber ein if bleibt ein if, auch wenn es sich einen OO-Mantel umhängt…

bye
TT

Falls es irgendjemand nicht mitbekommen haben sollte: Mein Pick ist natürlich nicht ganz ernst gemeint.

[QUOTE=SlaterB]da lohnt sich aber eine optimierte Variante für diesen bestimmt häufigen Fall,
mit Zwischenwertspeicherung und nur einmal am Ende statt so redundant oft Methode

pickValue(i).when(0, "nix")
       .when(1, "eins")
       .when(2, "zwei")
       .orElse("viele")
       .workValue( System.out.println(foundValue) );```
;)
[/quote]

Der letzte Fall müsste aber ein Lambda benutzen: `.workValue(foundValue -> System.out.println(foundValue));`



> oder auch, gar etwas imperativer in Java 1.tief machbar:
> ```int i = new Random().nextInt(5);
> String value = pickValue(i).when(0, "nix")
>        .when(1, "eins")
>        .when(2, "zwei")
>        .orElse("viele"); // hier gut Rückgabewert möglich
> System.out.println(value);```


Aber was ist, wenn die Berechnung der Werte aufwendig ist oder Seiteneffekte hervorruft? 

Eine bessere Implementierung, die einen Wert liefert, wäre:

import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

public class Pick<A,B> {

private final A value;
private Optional<B> result = Optional.empty();

public Pick(A value) {
    this.value = value;
}

public Pick<A,B> when(A testValue, Supplier<B> supplier) {
    if (! result.isPresent() && Objects.equals(value, testValue)) {
        result = Optional.of(supplier.get());
    }
    return this;
}

public B orElse(Function<A,B> fn) {
    return result.orElse(fn.apply(value));
}


public static <A,B> Pick<A,B> pick(A value) {
    return new Pick<>(value);
}

}


Anwendung:

int i = new Random().nextInt(5);
String s = Pick.<Integer, String>pick(i).when(0, () -> “nix”)
.when(1, () -> “eins”)
.when(2, () -> “zwei”)
.orElse(n -> "viele, und zwar " + n);
System.out.println(s);


Allerdings müsste man dann noch etwas tricksen, um die Notwendigkeit für Typparameter beim Aufruf zu umgehen. Die Schwäche von Javas Typinferenz ist wirklich nervig...

Eines der Probleme haben wir noch gar nicht angesprochen:

Wenn man 8 Büstenhalter hat, zwischen denen ein switch unterscheidet - dann sind da 8 compile-time-Konstanten im Code.

Sobald diese Unterscheidung noch an irgendeiner anderen Stelle auftaucht - müssen da auch diese 8 cases stehen - was (zumindest strukturell) eine Art Code-Verdopplung ist. Sobald dann der 9. Büstenhalter auftaucht, müssen zwei Stellen im Programm geändert werden.

Ist mit Polymorphie meistens leichter zu handhaben.

[QUOTE=maki] @Marco13
Dachte eigentlich das mein Hinweis auf reductio ad absurdum offensichtlich genug war, ein einzelnes if durch Quark und Polymorphie zu ersetzen ist nicht das Thema gewesen, sorry, aber da gehe ich nicht weiter drauf ein ;)[/QUOTE]

Auf die Frage, wie viele cases ein Switch haben muss, damit es als “schummeln” gälte, es in eine Folge von “ifs” umzuwandeln, um sich der gesamten Diskussion zu entziehen, muss man wohl wirklich nicht eingehen. Es ging allgemein um Entscheidungen, egal ob sie nun mit einem Switch oder einem If gemacht werden. Die Argumente sind die gleichen.

… wenn man sich ein Pseudoargument an hand eines sehr naiv konstruierten Bespieles zurechtbastelt das rein gar nix mir der Diskussion oder der Realitaet zu tun hat, darf man sich nicht wundern wenn man nicht ernst genommen wird.

Dein Beispiel ist ein schlechter Witz, die Diskussion dreht sich nicht um ein einzelnes if und dein Argument ist ungueltig weil komplett an der Fragestellung und der Realitaet vorbei :slight_smile: