Zum Inhalt
wannacode

Namen im Code

Kurz gesagt: Gute Namen im Code sagen, was etwas ist und warum es existiert: Konstanten statt magischer Zahlen, boolesche Werte als Fragen und die Wörter des Geschäfts statt Abkürzungen.

Problem

Die Funktion, die niemand anfassen will

Im Code eines Online-Shops gibt es eine Funktion, durch die jede Bestellung geht:

def calc(a, t, f):
d = 4.99
if a >= 50:
d = 0
if t == 2:
d += 7
if f:
d = 0
return a + d
print(f"{calc(40, 1, False):.2f} €")
print(f"{calc(40, 2, False):.2f} €")
print(f"{calc(60, 2, False):.2f} €")
print(f"{calc(40, 2, True):.2f} €")
Ausgabe
44.99 €
51.99 €
67.00 €
40.00 €

Sie funktioniert. Jetzt sag, was sie tut. Was ist t? Warum addiert t == 2 7? Was ist f, und warum setzt es alles zurück? Warum genau 50?

Der Autor wusste es. Er ist vor einem Jahr gegangen. Das Marketing will kostenlose Lieferung ab 60 €, nicht ab 50 €. Dein Kollege sucht im Projekt nach 50 und findet 14 Stellen: An einer ist 50 die Grenze für die Lieferung, an einer anderen das größte Paketgewicht in Kilogramm, an einer dritten die Anzahl der Produkte pro Seite. Welche soll sich ändern?

Die Logik hier ist richtig. Das Problem ist, dass der Code dem Leser nicht sagt, was der Autor gedacht hat.

Die Idee

Der Name sagt, was und warum, nicht wie

Der Computer liest calc(a, t, f) ohne Probleme. Ein Mensch nicht. Und Code wird viel öfter gelesen als geschrieben: bei jeder Korrektur, bei jedem Review, jedes Mal, wenn jemand einen Fehler sucht.

Ein guter Name beantwortet die Fragen, die du sonst dem Autor stellen würdest: Was ist das, warum ist es hier, wie wird es benutzt. Das nennt man einen aussagekräftigen Namen (intention-revealing name). Hier ist dieselbe Funktion, nur mit geänderten Namen:

def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
delivery_fee = 4.99
if subtotal >= 50:
delivery_fee = 0
if delivery_type == 2:
delivery_fee += 7
if has_free_delivery_coupon:
delivery_fee = 0
return subtotal + delivery_fee
print(f"{total_with_delivery(40, 1, False):.2f} €")
print(f"{total_with_delivery(40, 2, False):.2f} €")
print(f"{total_with_delivery(60, 2, False):.2f} €")
print(f"{total_with_delivery(40, 2, True):.2f} €")
Ausgabe
44.99 €
51.99 €
67.00 €
40.00 €

Die Ausgabe ist bis auf die letzte Ziffer dieselbe. Die Funktion liest sich jetzt wie eine Regel: Die Lieferung kostet 4,99 €, ab 50 € ist sie kostenlos, die Expresslieferung kostet 7 € mehr, und der Gutschein macht sie kostenlos.

Namen, die nicht helfen:

  • ein Name, der sagt, wie, nicht was: float_value, list1, tmp;
  • ein allgemeiner Name, der zu allem passt: data, info, value, process, handle, manager;
  • eine Abkürzung, die nur der Autor kennt: calc, cnt, dlv_fee.

Die Länge richtet sich nach dem Gültigkeitsbereich. i in einer Schleife mit drei Zeilen ist in Ordnung. Der Parameter einer Funktion, die von zehn Stellen aufgerufen wird, verdient einen vollen Namen.

Genauer, für Neugierige

Und ein Kommentar über der Funktion, t: 1 = standard, 2 = express? Er hilft, bis jemand die Zahlen ändert und den Kommentar vergisst. Ein Name kann nicht getrennt vom Code veralten, weil er Teil des Codes ist. Wann ein Kommentar hilft und wann er lügt, ist das Thema der dritten Lektion dieses Moduls.

Die Idee

Magische Zahl → benannte Konstante

Die Namen der Variablen sind jetzt an ihrem Platz, aber 4.99, 50, 7 und 2 stehen noch im Code. Eine Zahl ohne Namen im Code heißt magische Zahl: Was sie bedeutet, weiß nur der Autor, und wenn sie sich ändern muss, suchst du sie überall.

Die Lösung ist eine benannte Konstante. Du schreibst den Wert einmal an einer Stelle und benutzt überall sonst den Namen:

FREE_DELIVERY_FROM = 50
STANDARD_DELIVERY_FEE = 4.99
EXPRESS_SURCHARGE = 7
STANDARD = 1
EXPRESS = 2
Python hat keine Konstanten. Die Großbuchstaben, UPPER_SNAKE_CASE, sind eine Konvention aus PEP 8: „Dieser Wert ändert sich nicht.“ Die Sprache hindert dich nicht daran, aber jeder, der den Code liest, weiß, was sie bedeuten.

Das Marketing will 60 €: Du änderst eine Zeile, und jede Stelle, die fragt „ab wann ist die Lieferung kostenlos“, sieht den neuen Wert. Und die 50er, die Gewichte sind, bleiben unangetastet, weil sie nicht FREE_DELIVERY_FROM sind.

Genauer, für Neugierige

Nicht jede Zahl ist magisch. 0 für „keine Gebühr“, 1 in i + 1 und 100 in einem Prozentwert versteht man aus ihrer Stelle. Eine Zahl ist magisch, wenn sie eine Geschäftsregel trägt (eine Grenze, eine Gebühr, ein Limit, ein Statuscode) oder sich ändern kann. Eine Konstante, die nach ihrem Wert benannt ist (FIFTY = 50), hilft nicht: Der Name sagt die Rolle, nicht die Zahl.

Nichts verhindert eine zweite Zuweisung:

FREE_DELIVERY_FROM = 50
FREE_DELIVERY_FROM = 60
print(FREE_DELIVERY_FROM)
Ausgabe
60

typing.Final (FREE_DELIVERY_FROM: Final = 50) sorgt dafür, dass ein Werkzeug wie mypy sich über die zweite Zuweisung beschwert. Das Programm selbst akzeptiert sie trotzdem.

Sieh es dir an

Dieselbe Rechnung, durch die Namen gelesen

Die Namen und die Konstanten zusammen:

FREE_DELIVERY_FROM = 50
STANDARD_DELIVERY_FEE = 4.99
EXPRESS_SURCHARGE = 7
STANDARD = 1
EXPRESS = 2
def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
delivery_fee = STANDARD_DELIVERY_FEE
if subtotal >= FREE_DELIVERY_FROM:
delivery_fee = 0
if delivery_type == EXPRESS:
delivery_fee += EXPRESS_SURCHARGE
if has_free_delivery_coupon:
delivery_fee = 0
return subtotal + delivery_fee
print(f"{total_with_delivery(40, STANDARD, False):.2f} €")
print(f"{total_with_delivery(40, EXPRESS, False):.2f} €")
print(f"{total_with_delivery(60, EXPRESS, False):.2f} €")
print(f"{total_with_delivery(40, EXPRESS, True):.2f} €")
Ausgabe
44.99 €
51.99 €
67.00 €
40.00 €

Sieh zu, wie jede Zahl und jeder Buchstabe aus der ersten Version im Lauf der Rechnung eine Bedeutung bekommt:

FREE_DELIVERY_FROM = 50
STANDARD_DELIVERY_FEE = 4.99
EXPRESS_SURCHARGE = 7
STANDARD = 1
EXPRESS = 2
def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
delivery_fee = STANDARD_DELIVERY_FEE
if subtotal >= FREE_DELIVERY_FROM:
delivery_fee = 0
if delivery_type == EXPRESS:
delivery_fee += EXPRESS_SURCHARGE
if has_free_delivery_coupon:
delivery_fee = 0
return subtotal + delivery_fee
print(f"{total_with_delivery(40, EXPRESS, False):.2f} €")
Schritt für Schritt, in Worten
  1. Der alte Aufruf war calc(40, 2, False). Jetzt sagt er, was er berechnet, und 2 ist EXPRESS.
  2. Die Parameter: a, t, f werden zu subtotal, delivery_type, has_free_delivery_coupon. Die Werte sind dieselben.
  3. „d = 4.99“ wird zu „delivery_fee = STANDARD_DELIVERY_FEE“. Dieselben 4.99, aber jetzt weißt du, was es ist: die Gebühr für die Standardlieferung.
  4. 50 ist FREE_DELIVERY_FROM: die Grenze für kostenlose Lieferung. 40 liegt darunter, also ist die Lieferung nicht kostenlos.
  5. „t == 2“ war ein Rätsel. delivery_type == EXPRESS ist ein Satz: Die Expresslieferung kostet EXPRESS_SURCHARGE mehr.
  6. „if f:“ sagte dir nichts. has_free_delivery_coupon ist eine Frage, und die Antwort ist False: kein Gutschein.
  7. Die Bestellung plus die Lieferung. Das Programm gibt 51.99 € aus.
  8. Hin und zurück: dieselben Werte, dieselbe Rechnung und dieselbe Ausgabe wie bei calc(40, 2, False). Nur die Namen haben sich geändert.

Alle drei Versionen geben dasselbe aus. Der Computer sieht keinen Unterschied. Der Unterschied ist für den Menschen, der morgen die Regel ändert. Eine Stelle sagt noch immer nichts: False im Aufruf. Darum kümmern wir uns in der nächsten Lektion.

Probier es aus

Sag die Ausgabe voraus

Eine Bestellung über genau 50 € mit Standardlieferung und eine Bestellung über 49,90 € mit Expresslieferung. Lies die Regel durch die Namen. Was gibt das Programm aus?

print(f"{total_with_delivery(50, STANDARD, False):.2f} €")
print(f"{total_with_delivery(49.90, EXPRESS, False):.2f} €")
Antwort

Probier es aus

Fülle die Lücke

Die Grenze für die kostenlose Lieferung ist jetzt eine Konstante. Was gehört in die Lücke?

def total_with_delivery(subtotal, delivery_type, has_free_delivery_coupon):
    delivery_fee = STANDARD_DELIVERY_FEE
    if subtotal >= :
        delivery_fee = 0
    if delivery_type == EXPRESS:
        delivery_fee += EXPRESS_SURCHARGE
    if has_free_delivery_coupon:
        delivery_fee = 0
    return subtotal + delivery_fee

Die Idee

Ein boolescher Wert ist eine Frage

Ein boolescher Wert antwortet mit Ja oder Nein, deshalb ist sein Name eine Frage: is_paid, has_coupon, can_ship. Dann liest sich das if wie ein Satz:

subtotal = 62.40
is_paid = True
qualifies_for_free_delivery = subtotal >= FREE_DELIVERY_FROM
if is_paid and qualifies_for_free_delivery:
print("Wir versenden ohne Liefergebühr.")
Ausgabe
Wir versenden ohne Liefergebühr.

Was nicht funktioniert:

  • flag, status, check, ok: Sie sagen nicht, was die Frage ist;
  • eine Verneinung im Namen: is_not_paid. Dann ist if not is_not_paid: eine doppelte Verneinung, und jeder stolpert darüber. Schreib is_paid, und setz die Verneinung in die Prüfung, wenn du sie brauchst.

Dasselbe gilt für die anderen Namen. Eine Klasse und eine Variable sind Substantive (Order, delivery_fee). Eine Funktion tut etwas, also ist sie ein Verb (send_invoice). Eine Funktion, die wie eine Frage heißt (is_in_stock), verspricht, dass sie nur fragt. In der nächsten Lektion siehst du, was passiert, wenn sie dieses Versprechen bricht.

Die Idee

Die Wörter des Shops im Code

Die besten Namen erfindest du nicht. Es sind die Wörter der Menschen, für die das Programm gemacht ist: Paket, Kurier, Nachnahme, Rückerstattung, Rechnung. Wenn im Code parcel, courier, refund steht, sprechen der Programmierer und der Mensch im Lager über dasselbe. Das sind Domänennamen (domain names).

  • Ein Wort für ein Konzept. Wenn der Kunde customer heißt, wird er in der nächsten Datei nicht zu client und in der Datenbank zu buyer. Wenn Lesen find… heißt, wird es anderswo nicht zu get… und fetch…. Ein anderes Wort lässt den Leser nach einer anderen Sache suchen.
  • Ein Wort bedeutet nicht zwei Dinge. Wenn total die Summe mit Lieferung ist, hat die Summe ohne Lieferung einen eigenen Namen, subtotal.
  • Technische Wörter sind für technische Dinge. queue, cache, parser sind in Ordnung, wenn genau das gemeint ist. Manager, Processor und Helper bedeuten meistens „Ich weiß nicht genau, was es tut“.

Probier es aus

Finde den Fehler

Die Buchhaltung beschwert sich: Eine Rechnung über 50 € ohne Mehrwertsteuer kommt auf 110 €. Die Mehrwertsteuer beträgt 20%, also müssten es 60 € sein.

def vat(price):
return price * 1.20
def print_invoice(net):
total = net + vat(net)
print(f"Netto: {net:.2f} €")
print(f"Gesamt mit MwSt.: {total:.2f} €")
print_invoice(50)
Ausgabe
Netto: 50.00 €
Gesamt mit MwSt.: 110.00 €

In welcher Zeile steckt der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

Wie deine Sprache Namen schreibt

Ein guter Name in einer Sprache sieht in einer anderen merkwürdig aus. Jede Sprache hat ihre eigene Konvention, und Code, der ihr folgt, liest sich schneller, weil die Form des Namens dir sagt, was er ist.

Die Konvention stammt aus PEP 8:

  • Funktionen, Methoden, Variablen und Module: snake_case (total_with_delivery, delivery_fee);
  • Klassen: PascalCase (DeliveryCalculator);
  • Konstanten auf Modulebene: UPPER_SNAKE_CASE (FREE_DELIVERY_FROM);
  • Ein führendes _ (_cache) bedeutet „intern, nicht von außen benutzen“. Die Sprache verbietet es nicht, es ist eine Konvention.
Genauer, für Neugierige

Derselbe Name in den vier Sprachen:

Java C# Python JavaScript
Funktion totalWithDelivery TotalWithDelivery total_with_delivery totalWithDelivery
Variable deliveryFee deliveryFee delivery_fee deliveryFee
Konstante FREE_DELIVERY_FROM FreeDeliveryFrom FREE_DELIVERY_FROM FREE_DELIVERY_FROM
Klasse Order Order Order Order
boolescher Wert isPaid isPaid (Property: IsPaid) is_paid isPaid

Die Idee

Rename: das billigste Refactoring

Einen Namen zu ändern, ohne das Verhalten zu ändern, heißt Rename. Es ist das kleinste und billigste Refactoring: Du änderst keinen Wert und keinen Vergleich, und das Programm gibt dasselbe aus.

Mach es nicht von Hand mit Suchen und Ersetzen. Der Editor weiß, welches d deine Variable ist und welches der Buchstabe in "delivery": In IntelliJ IDEA und PyCharm ist es Umschalt+F6, in VS Code F2, in Visual Studio Strg+R, Strg+R. Er ändert jede Stelle auf einmal.

Hier gibt es keinen Compiler, der eine vergessene Stelle fängt. Der alte Name führt erst zu einem Fehler (NameError), wenn diese Zeile läuft. Nach einem Rename führst du also das Programm aus, besser noch die Tests. Und ein Name, der als Text vorkommt (getattr(order, "fee"), ein Schlüssel in JSON), ist für den Editor unsichtbar.

Wie du in kleinen Schritten refaktorierst, sodass das Programm nach jedem funktioniert, ist das Thema der sechsten Lektion dieses Moduls. Rename ist der erste Schritt.

Übung

Der Kurier

Löse die Aufgabe in deinem Editor.

Der Kurier. Diese Funktion berechnet den Preis eines Pakets:

def f(w, z):
p = 3.50
if w > 2:
p += (w - 2) * 0.80
if z == 3:
p *= 1.5
return p
print(f"{f(1.5, 1):.2f} €")
print(f"{f(5, 1):.2f} €")
print(f"{f(5, 3):.2f} €")
Ausgabe
3.50 €
5.90 €
8.85 €

Die Regel lautet: Ein Paket bis 2 kg kostet 3,50 €, jedes Kilogramm darüber 0,80 €, und Zone 3 ist abgelegen, dort ist der Preis um 50% höher. Gib der Funktion, den Parametern und der Variable Namen, die das sagen, und mach aus den Zahlen Konstanten. Die Ausgabe muss dieselbe bleiben:

Ausgabe
3.50 €
5.90 €
8.85 €

Weitere Aufgaben

  • Der boolesche Wert. Füge einen Parameter hinzu, der sagt, ob das Paket zerbrechlich ist (eine Gebühr von 1,20 €). Wie nennst du ihn? Wie sieht der Aufruf aus?
  • Dein Code. Öffne eine alte Übung von dir. Finde drei Namen, die du heute nicht auf den ersten Blick verstehst, und ändere sie mit dem Rename des Editors. Führe das Programm aus: Ist die Ausgabe dieselbe?
  • Das Wörterbuch des Shops. Schreib zehn Wörter auf, die der Mensch im Lager benutzt (Paket, Kurier, Zone …), und wie du sie im Code deiner Sprache benennen würdest.

Test

Teste dich

1. Eine Variable enthält die Summe der Produkte nach dem Rabatt, aber ohne Lieferung. Welcher Name ist am besten?
2. Was ist an if not is_not_paid: falsch?
3. Die kostenlose Lieferung beginnt jetzt bei 60 € statt bei 50 €. Wie hilft die Konstante?
4. get_user(email) sucht den Benutzer, und wenn es keinen gibt, legt es einen neuen an und speichert ihn in der Datenbank. Was ist das Problem?
5. Warum ist Rename das billigste Refactoring?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·