Prinzipienreiterei?

321 views
Skip to first unread message

Leba

unread,
Apr 8, 2014, 4:30:29 AM4/8/14
to clean-code...@googlegroups.com
Hallo,

ich hatte gestern eine (beinahe ermüdend) lange Diskussion, in der ich argumentiert habe, dass die Implementierung von Example 2 der Implementierung von Example 1 (siehe Bilder) vorzuziehen ist. Mein Argument war, dass man in Example 1 sowohl Logik als auch Abhängigkeiten in einer Methode hat, und dass dies prinzipiell nicht gut ist. Warum? Wenn ich weitere Funktionalität hinzufügen möchte, laufe ich Gefahr, bestehende Funktionalität zu verändern und möglicherweise kaputt zu machen. Example 2 ist somit leichter änderbar, flexibler, robuster etc. Das Argument meines Gegenübers war, dass es Example 1 viel einfacher zu lesen sei. Es sei doch "nur" ein If Statement und letztendlich seien Example 1 und Example 2 ja genau das gleiche nur mit einer etwas anderer Syntax. Außerdem sei der Test für Example 2 etwas umständlicher wegen der Callbacks (letzteres stimmt, finde ich aber nicht so tragisch). Ich konnte nicht anders, als mich zu wiederholen und außerdem noch auf die Broken Window Theorie zu verweisen. Am Ende konnte ich einfach nur noch argumentieren mit: "Aus Prinzip!".
Sehe ich das falsch? Ist Prinzipienreiterei an dieser Stelle nicht angemessen? Ist die Anwendung des ISOP auf Methoden-Ebene manchmal zu kleinlich?

Example 1:

Example 2:




Collin Rogowski

unread,
Apr 8, 2014, 5:14:22 AM4/8/14
to clean-code...@googlegroups.com
Hi,

Ich hätte in Beispiel 1 eher das OCP (auf Methodenebene) verletzt gesehen, da ja hier für eine Erweiterung etwas geändert werden muss...
Damit hätte ich dann auch eine Lösung für Dein Dilemma anzubieten, denn für OCP habe ich die Daumenregel die Entkopplung erst dann zu implementieren, wenn ich das erste mal den Code ändern musste und  nicht schon bei der initialen Erstellung. Das basiert auf der Idee, das "Veränderung" im OCP sich nur auf reale Veränderungen beziehen soll und nicht auf theoretisch mögliche. Und das fand' ich immer einen schönen pragmatische Ansatz...

D.h. ich würde initial Beispiel 1 implementieren. Kommt eine neue Anforderung, für die ich die Methode "Foo" ändern muss, würde ich den Code zu dem in Beispiel 2 refactorn und dann die neue Funktionalität implementieren.

Viele Grüße,

cr

--
Sie erhalten diese Nachricht, weil Sie in Google Groups E-Mails von der Gruppe "Clean Code Developer" abonniert haben.
Wenn Sie sich von dieser Gruppe abmelden und keine E-Mails mehr von dieser Gruppe erhalten möchten, senden Sie eine E-Mail an clean-code-devel...@googlegroups.com.
Wenn Sie in dieser Gruppe einen Beitrag posten möchten, senden Sie eine E-Mail an clean-code...@googlegroups.com.
Gruppe besuchen: http://groups.google.com/group/clean-code-developer
Weitere Optionen finden Sie unter https://groups.google.com/d/optout.

Tim Gesekus

unread,
Apr 8, 2014, 7:00:33 AM4/8/14
to clean-code...@googlegroups.com

Hallo,

Ich würde noch die Prinzipien Kiss und Yagni anführen.  Das erste Example ist wunderbar lesbar und testbar. Das zweite führt eine Indirektion ein, damit man vielleicht irgendwann mal erweiterbar ist.

Ich würde example 1 wählen und es umstellen, wenn ich weiß wohin ich es erweitern will.

Hth
Tim

Leba

unread,
Apr 8, 2014, 7:21:22 AM4/8/14
to clean-code...@googlegroups.com
Danke für eure Antworten. Das macht aus meiner Sicht Sinn.

Allerdings habe ich die Beispiele etwas verkürzt. In der konkreten Situation hatte der Code etwa das folgende Muster:

// Example 2
public void Foo...
{
DoA(...);
Evaluate(() => DoB());
DoC(...);
DoD(...);
...
}

Stefan Lieser

unread,
Apr 8, 2014, 7:33:01 AM4/8/14
to clean-code...@googlegroups.com
Hallo Leif,

ich bin definitiv dafür, das IOSP Prinzip von Anfang an konsequent einzuhalten. Meiner Erfahrung nach findet die spätere Einführung, das „säubern“, im Projektalltag dann doch nicht statt. Jemand sieht den Code und quetscht seine Ergänzung halt noch rein (du erinnerst dich sicherlich an gemeinsame Beobachtungen ;-))

Spätestens jetzt, wo du das Beispiel „aufbohrst“ wird der Vorteil deutlich. Ich kann die Fallunterscheidung des „Evaluate“ isoliert testen. Ist das if in Foo reingepackt, werden DoA, DoB, DoC und DoD ausgeführt. Wer weiß schon, was die im einzelnen für Vorbedingungen brauchen, damit sie im Test durchlaufen?

Wenn Evaluate eine Action erwartet, reicht mir für den Test von Foo ein Integrationstest, da es nur einen Pfad durch die Methode gibt. Steht dort das if, müssen es mindestens 2 Tests sein.

Also mindestens aus Sicht der Testbarkeit hat das IOSP hier für mich einen Wert (den der Korrektheit). Und für die Evolvierbarkeit ist es gut, von vornherein Integration und Operation zu trennen, weil der Code dann leichter erweitert werden kann (es gibt weniger Abhängigkeiten).

Schöne Grüße an CvK ;-)


Grüße
Stefan
--

Enrico Maczkowski

unread,
Apr 8, 2014, 7:37:10 AM4/8/14
to clean-code...@googlegroups.com
An dieser Stelle hätte ein if nach SLA eh nichts zu suchen. Aber dafür gleich einen Callback erzeugen halte ich auch für übertrieben. 

Ich würde es in etwa so machen:

public void Foo...
{
DoA(...);
ValidateB();
DoC(...);
DoD(...);
...
}

private void ValidateB() {
       if (Evaluate()) {
             DoB(...);
       }
}

Ein Callback braucht m.E. einen guten Grund. In wie fern ist es z.B. der Wartbarkeit zu diensten, wenn die Lesbarkeit leidet? Oder, sehr allgemein gesagt, warum einen Ferrari bauen, wenn man nur einen Golf braucht?  


--

Ralf Westphal

unread,
Apr 9, 2014, 5:52:18 AM4/9/14
to clean-code...@googlegroups.com
Das Argument "Lesbarkeit" hat ja immer etwas mit Erfahrung zu tun. Wer an lambda expressions oder Continuations nicht gewöhnt ist, findet die erstmal schlechter lesbar. Das ist mit jedem Pattern so.

Die Frage ist also, warum sollte sich jemand umgewöhnen?

Argument 1: Das SRP ist nur in Variante 2 konsequent eingehalten. In Variante 1 hat Foo() zwei (2) Verantwortlichkeiten: 1. eine Entscheidung vornehmen und 2. weitere Handlungen zu einem Ganzen zu integrieren.

Argument 2: Es ist natürlich eine Illusion, Funktionseinheiten "für immer" gegen Veränderungen abschließen zu können. Für gewisse erwartbare Veränderungen kann man "Erweiterungshaken" anbringen (Strategy Pattern), aber irgendwann kommt es dann halt doch anders.
Wo jedoch konsequent das IOSP angewandt wird, da sind Veränderungen gar nicht mehr so gefahrvoll. Da versucht man nämlich erstmal, ein neues Feature dadurch herzustellen, dass man in eine Integration einen weiteren Aufruf einfädelt. Das ist eine Veränderung - aber eine triviale.
Der aufwändige Teil, der nun aber ungefährlich ist, besteht dann darin, eine ganz neue Funktionseinheit zu entwickelt, nämlich die eingefädelte.
IOSP + PoMO dienen also unmittelbar dem OCP.

Argument 3: Indem man die Entscheidung in eine Methode verlagert, wird einer domänenspezifischen Sprache eine Vokabel hinzugefügt. Die kann in anderen zusammenhängen wiederverwendet werden. Mit der zweiten Evaluate() Methode ist das ganze Thema Entscheidung erstens gekapselt und zweitens unabhängig vom Kontext.

Version 1:

if (Evaluate()) DoSomething();

Version 2:

Evaluate2(DoSomething);

Version 3:

ValidateB(); // mit Version 1

Bei Version 1 ist das "Thema" (Validation) in 3 "Aufrufe" verpackt: if + Evaluate() + DoSomething(). Außerdem ist klar sichtbar, wie entschieden wird: eben mit 1 if.
Wenn man das Thema woanders wieder hat, dann muss man alle kopieren.

Bei Version 2 sind es nur 2 "Aufrufe". Und Evaluate2() verpackt den das ganze Wie. Heute mag das nur 1 if sein, morgen aber vielleicht geschachtelte if oder irgendwas anderes.

Bei Version 3 gibt es zwar nur 1 "Aufruf" - aber der schafft eine Einheit aus Prüfung und Handlung. Das ist nicht schlecht; es entsteht eine Vokabel auf noch höherer Abstraktionsebene, die auch wiederverwendet werden kann. Das eigentliche Problem ist damit ja aber nur vertagt. Wie sieht es in ValidateB() aus?

"Aufwand" ist eine Continuation nur für den Ungeübten. Die Lesbarkeit ist - wie bei Kontrollanweisungen - nur bei tiefer Schachtelung schlecht.

Den vielleicht etwas höheren Testaufwand mit Continuation halte ich im Lichte der Vorteile für vernachlässigbar. Auch der folgt einem Muster, an das man sich schnell gewöhnt.

Es geht also nicht um Prinzipienreiterei.

Aber nicht jeder kann für sich die obigen Vorteile annehmen. Zumindest nicht sofort. Dann muss man auch mal aufhören zu überzeugen - und es stattdessen einfach tun. Mit Fundamentalisten muss man nicht diskutieren :-) Stattdessen an den eigenen Spaß denken und einfach so arbeiten, wie es einem leichter fällt.

Leba

unread,
Apr 11, 2014, 8:17:07 AM4/11/14
to clean-code...@googlegroups.com
Danke für die Antworten!
Reply all
Reply to author
Forward
0 new messages