====== LU07b - Code Smells erkennen ======
Ein **Code Smell** ist kein Fehler. Das Programm läuft, die Tests sind grün - und trotzdem stimmt etwas nicht: Der Code ist schwer zu lesen, schwer zu ändern oder schwer zu testen. Der Smell ist der Hinweis, an welcher Stelle sich ein Refactoring lohnt.
**Warum «Geruch»?** Weil er ein Verdacht ist, kein Beweis. Eine Funktion mit 40 Zeilen kann völlig in Ordnung sein. Ein Smell sagt nur: Schauen Sie hier genauer hin.
===== Die zehn Smells, die Sie erkennen müssen =====
==== 1. Lange Funktion ====
Die Funktion tut mehr als eine Sache. Erkennbar an Kommentaren, die Abschnitte markieren («# jetzt die Summe», «# jetzt die Ausgabe») - das sind die Schnittlinien.
def auswertung(datei):
# einlesen
...
# filtern
...
# rechnen
...
# formatieren und ausgeben
...
**Technik:** Extract Function
==== 2. Duplizierter Code ====
Dieselbe Logik steht an mehreren Stellen. Beim nächsten Fix wird eine davon vergessen.
netto_a = round(preis_a * 1.081, 2)
netto_b = round(preis_b * 1.081, 2)
**Technik:** Extract Function, bei Varianten Parameterize Function
==== 3. Magic Number ====
Eine Zahl oder ein String steht mitten im Code, und niemand weiss, was sie bedeutet.
if kunde["punkte"] > 500:
rabatt = betrag * 0.15
**Technik:** Replace Magic Number with Named Constant
==== 4. Lange Parameterliste ====
Fünf oder mehr Parameter, die inhaltlich zusammengehören. Beim Aufruf vertauscht man irgendwann zwei davon - und Python merkt es nicht.
def zeilenpreis(artikel, menge, einzelpreis, rabatt, waehrung, mwst):
...
**Technik:** Introduce Parameter Object (frozen Dataclass, siehe [[modul:m323:learningunits:lu02:byreferenceinclasses|LU02e]])
==== 5. Tief verschachtelte Bedingungen ====
Der eigentliche Fall steht ganz innen, eingerückt hinter drei ''if''. Die Sonderfälle verdecken die Regel.
if kunde is not None:
if kunde["aktiv"]:
if betrag >= 100:
return betrag * 0.1
**Technik:** Guard Clauses (Replace Nested Conditional with Guard Clauses)
==== 6. Kommentar als Krücke ====
Der Kommentar erklärt, //was// eine Zeile tut, weil die Zeile es selbst nicht sagt.
# prüft ob der Kunde volljährig und aktiv ist
if k["a"] >= 18 and k["s"] == 1:
...
**Technik:** Extract Variable oder Extract Function mit sprechendem Namen. Ein guter Kommentar erklärt das //Warum//, nicht das //Was//.
==== 7. Globaler Zustand ====
Die Funktion liest oder schreibt eine Variable ausserhalb ihrer selbst. Damit hängt ihr Ergebnis von der Programmgeschichte ab - genau das, was [[modul:m323:learningunits:lu02:purefunctions|LU02b]] als //unpure// beschreibt.
total = 0
def add_to_total(x):
global total
total += x
**Technik:** Zustand als Parameter hereingeben und als Rückgabewert herausgeben, alternativ ein Closure ([[modul:m323:learningunits:lu05:closures|LU05c]])
==== 8. Query und Modifier vermischt ====
Eine Funktion liefert einen Wert //und// verändert nebenbei etwas. Man kann sie nicht aufrufen, nur um nachzuschauen.
def entnehmen(bestand, artikel):
bestand[artikel] -= 1 # verändert
return bestand[artikel] # und liefert
**Technik:** Separate Query from Modifier
==== 9. Boolean-Flag-Parameter ====
Am Aufruf ist nicht erkennbar, was passiert: ''exportieren(daten, True)'' - was ist ''True''?
**Technik:** Zwei Funktionen mit sprechenden Namen, oder Keyword-Argument erzwingen (''exportieren(daten, mit_header=True)'')
==== 10. Schleife mit Akkumulator ====
Eine Schleife, die nur filtert, umformt oder aufsummiert - das ist der Smell mit dem stärksten Bezug zu diesem Modul.
resultat = []
for e in daten:
if e["aktiv"]:
resultat.append(e["name"].upper())
**Technik:** Replace Loop with Pipeline ([[.:funktionalesrefactoring|LU07d]])
===== Übersicht: Smell und passende Technik =====
^ Smell ^ Woran erkennbar ^ Technik ^
| Lange Funktion | Abschnittskommentare, mehr als ein Zweck | Extract Function |
| Duplizierter Code | dieselbe Zeile mehrfach | Extract / Parameterize Function |
| Magic Number | nackte Zahl oder String im Code | Named Constant |
| Lange Parameterliste | 5+ Parameter, die zusammengehören | Introduce Parameter Object |
| Verschachtelte Bedingungen | drei Einrückungsebenen und mehr | Guard Clauses |
| Kommentar als Krücke | Kommentar erklärt das Was | Extract Variable / Function |
| Globaler Zustand | ''global'', Modulvariable wird geschrieben | Parameter und Rückgabewert, Closure |
| Query und Modifier vermischt | Funktion liefert und verändert | Separate Query from Modifier |
| Boolean-Flag | ''True'' / ''False'' als Argument | zwei Funktionen oder Keyword-Argument |
| Schleife mit Akkumulator | ''resultat = []'' plus ''append'' | Replace Loop with Pipeline |
**Smell ist nicht gleich Schuld.** Ein Smell rechtfertigt einen Blick, nicht automatisch eine Änderung. Wer jede Dreizeiler-Schleife in eine verschachtelte Comprehension presst, hat den Code nicht besser gemacht, sondern nur anders unleserlich. Die Frage lautet immer: Ist die Version danach für den nächsten Leser einfacher?
----
{{tag>M323-LU07 M323-D1B}}
[[https://creativecommons.org/licenses/by-nc-sa/4.0/ch/|{{https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png}}]] (c) Kevin Maurizi