Kapitel 1. Was ist guter Code?
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
Dieses Buch soll dir helfen, besseren Code zu schreiben. Aber zuerst: Was macht Code "gut"? Es gibt verschiedene Möglichkeiten, darüber nachzudenken: Der beste Code könnte der sein, der am schnellsten läuft. Oder er ist am leichtesten zu lesen. Eine andere mögliche Definition ist, dass guter Code leicht zu pflegen ist. Das heißt, wenn sich das Projekt ändert, sollte es einfach sein, auf den Code zurückzugreifen und ihn an die neuen Anforderungen anzupassen. Die Anforderungen an deinen Code werden sich häufig ändern, weil sich das Geschäftsproblem, das du lösen willst, geändert hat, weil es neue Forschungsrichtungen gibt oder weil die Codebasis an anderer Stelle aktualisiert wurde.
Außerdem sollte dein Code nicht zu komplex sein und bei unerwarteten Eingaben nicht zusammenbrechen. Es sollte einfach sein, eine einfache neue Funktion zu deinem Code hinzuzufügen; wenn das schwer ist, deutet das darauf hin, dass dein Code nicht gut geschrieben ist. In diesem Kapitel stelle ich Aspekte guten Codes vor und zeige Beispiele für jeden Aspekt. Ich teile sie in fünf Kategorien ein: Einfachheit, Modularität, Lesbarkeit, Leistung und Robustheit.
Warum guter Code wichtig ist
Guter Code ist besonders wichtig, wenn dein Data Science Code in ein größeres System integriert wird. Das kann sein, wenn du ein maschinelles Lernmodell in Betrieb nimmst, Pakete für die weitere Verbreitung schreibst oder Tools für andere Data Scientists entwickelst. Das ist vor allem bei größeren Codebasen nützlich, die immer wieder ausgeführt werden sollen. Je größer und komplexer dein Projekt wird, desto mehr Wert hat guter Code.
Manchmal ist der Code, den du schreibst, eine einmalige Sache, ein Prototyp, der heute für eine Demo morgen zusammengehackt werden muss. Und wenn du den Code wirklich nur ein einziges Mal ausführst, solltest du dir nicht die Zeit nehmen, ihn schön zu machen: Schreibe einfach einen Code, der die Aufgabe erfüllt, für die er gebraucht wird. Aber meiner Erfahrung nach wird selbst der Code, den du für eine einmalige Demo schreibst, fast immer wieder ausgeführt oder für einen anderen Zweck wiederverwendet. Ich empfehle dir, dir die Zeit zu nehmen, deinen Code noch einmal zu überarbeiten, wenn die Dringlichkeit vorbei ist, und ihn für die zukünftige Verwendung aufzuräumen.
Guter Code ist auch einfacher zu warten. Es gibt ein Phänomen, das als "Bit-Rot" bekannt ist: die Notwendigkeit, Code zu aktualisieren, der seit einiger Zeit nicht mehr verwendet wurde. Das passiert, weil sich auch Dinge ändern, von denen dein Code abhängt (z. B. Bibliotheken von Drittanbietern oder sogar das Betriebssystem, das du verwendest). Wenn du auf einen Code zurückkommst, den du schon länger nicht mehr benutzt hast, musst du ihn wahrscheinlich erst einmal modernisieren. Das ist viel einfacher, wenn dein Code gut strukturiert und gut dokumentiert ist.
Hinweis
Technische Schuld (oft abgekürzt als Tech Debt) ist ein allgemeiner Begriff für aufgeschobene Arbeit, die entsteht, wenn Code schnell statt korrekt geschrieben wird. Technische Schulden können sich in Form von fehlender Dokumentation, schlecht strukturiertem Code, schlecht benannten Variablen oder anderen Abstrichen äußern. Dadurch wird es schwieriger, den Code zu warten oder zu überarbeiten, und es ist wahrscheinlich, dass du in Zukunft mehr Zeit damit verbringst, Fehler zu beheben, als du damit verbracht hättest, den Code von Anfang an richtig zu schreiben. Abgesehen davon sind technische Schulden oft notwendig, weil das Unternehmen Fristen und Budgets einhalten muss. Du hast nicht immer Zeit, deinen Code aufzupolieren.
Anpassung an sich ändernde Anforderungen
Das Schreiben von Code ist nicht wie der Bau einer Brücke, bei dem der Entwurf gründlich ausgearbeitet wird, die Pläne feststehen und dann gebaut wird. Die einzige Konstante beim Schreiben von Code, sei es für ein Data-Science-Projekt oder etwas anderes, ist, dass du damit rechnen musst, dass sich die Dinge während der Arbeit an einem Projekt ändern. Diese Änderungen können das Ergebnis deiner Entdeckungen während deines Forschungsprozesses, veränderter Geschäftsanforderungen oder von Innovationen sein, die du in das Projekt einbauen möchtest. Guter Code kann leicht angepasst werden, um mit diesen Änderungen zurechtzukommen.
Diese Anpassungsfähigkeit wird umso wichtiger, je größer deine Codebasis ist. Bei einem einzelnen kleinen Skript ist es einfach, Änderungen vorzunehmen. Aber wenn das Projekt wächst und in mehrere Skripte oder Notizbücher aufgeteilt wird, die voneinander abhängen, kann es komplexer und schwieriger werden, Änderungen vorzunehmen. Wenn du von Anfang an gute Praktiken anwendest, wird es einfacher, den Code in einem größeren Projekt zu ändern.
Data Science ist noch ein relativ neues Feld, aber Data-Science-Teams kommen immer häufiger in Situationen, in denen sie seit mehreren Jahren an derselben Codebasis arbeiten und der Code von vielen Personen bearbeitet wurde, von denen einige das Unternehmen verlassen haben können. In dieser Situation, in der ein Projekt von einer Person an eine andere weitergegeben wird, wird die Codequalität noch wichtiger. Es ist viel einfacher, die Arbeit einer anderen Person zu übernehmen, wenn sie gut dokumentiert und leicht zu lesen ist.
Die Softwareentwicklung als Disziplin beschäftigt sich seit Jahrzehnten mit wechselnden Anforderungen und zunehmender Komplexität. Sie hat eine Reihe nützlicher Strategien entwickelt, die du dir als Datenwissenschaftler/in ausleihen und zu deinem Vorteil nutzen kannst. Wenn du anfängst, dich damit zu beschäftigen, stößt du vielleicht auf Verweise auf "sauberer Code", aus dem gleichnamigen Buch von Robert C. Martin, oder auf Akronyme wie SOLID.
Wie bereits erwähnt, habe ich mich in diesem Kapitel dafür entschieden, diese Prinzipien in fünf Merkmale guten Codes zu unterteilen: Einfachheit, Modularität, Lesbarkeit, Leistung und Robustheit. Im weiteren Verlauf des Kapitels werde ich jedes dieser Merkmale ausführlich beschreiben.
Einfachheit
Einfach ist besser als komplex.
Tim Peters, Das Zen der Python
Wenn du an einem kleinen Projekt arbeitest, z. B. einem Datenvisualisierungs-Notebook oder einem kurzen Skript zur Datenverarbeitung, kannst du alle Details auf einmal im Kopf behalten. Aber wenn dein Projekt wächst und komplexer wird, ist das nicht mehr möglich. Du kannst die Trainingsschritte deines Machine Learning-Modells im Kopf behalten, aber nicht die Pipeline der Eingabedaten oder den Prozess der Modellbereitstellung.
Komplexität erschwert es, den Code zu ändern, wenn sich deine Anforderungen ändern, und kann wie folgt definiert werden:
Komplexität ist alles, was mit der Struktur eines Systems zu tun hat und es schwer macht, ein System zu verstehen und zu verändern.
John K. Ousterhout, Eine Philosophie des Softwaredesigns
Das ist keine genaue Definition, aber mit etwas Erfahrung bekommst du ein Gefühl dafür, wann ein System komplexer wird. Eine Möglichkeit, darüber nachzudenken, ist, dass eine Änderung etwas Unverbundenes auf unerwartete Weise verändert. Du könntest z.B. ein maschinelles Lernmodell auf Kundenrezensionen trainieren, um den Artikel, den der Kunde gekauft hat, mithilfe von NLP-Techniken (Natural Language Processing) herauszufinden. Du hast einen separaten Vorverarbeitungsschritt, der die Rezension auf 512 Zeichen kürzt. Aber wenn du das Modell einsetzt, vergisst du, den Vorverarbeitungsschritt in den Inferenzcode aufzunehmen. Plötzlich wirft dein Modell Fehler aus, weil die Eingabedaten größer als 512 Zeichen sind. Dieses System wird langsam schwer zu durchschauen und komplex.
Wenn ich von Komplexität im Code spreche, ist diese in der Regel zufällig und unterscheidet sich von der wesentlichen Komplexität eines Projekts. Dein Projekt zum maschinellen Lernen kann komplex sein, weil du viele verschiedene Arten von Modellen und viele verschiedene Kombinationen von Merkmalen ausprobieren willst, um herauszufinden, welches Modell am besten funktioniert. Deine Analyse kann komplex sein, weil die Daten, die du verwendest, viele verschiedene voneinander abhängige Parameter haben. Beides lässt sich nicht vereinfachen; die Komplexität ist nur ein Teil des Projekts. Unbeabsichtigte Komplexität ist dagegen, wenn du nicht sicher bist, welche Funktion in deinem Code du ändern musst, um eine bestimmte Aktion zu erreichen.
Es gibt jedoch Tools, mit denen du die Komplexität deines Codes verringern kannst. Alles nach und nach ein bisschen einfacher zu machen, hat große Vorteile, wenn das Projekt groß wird. Im nächsten Abschnitt beschreibe ich, wie du deinen Code einfach halten kannst, indem du Wiederholungen vermeidest. Dann gehe ich darauf ein, wie du deinen Code kurz und bündig halten kannst. Du kannst auch verhindern, dass dein Code zu komplex wird, indem du ihn in wiederverwendbare Teile zerlegst, wie ich in "Modularität" beschreibe .
Wiederhole dich nicht (DRY)
Einer der wichtigsten Grundsätze beim Schreiben von gutem Code ist, dass Informationen nicht wiederholt werden sollten. Alles Wissen sollte nur einmal im Code dargestellt werden. Wenn Informationen an mehreren Stellen wiederholt werden und diese Informationen aufgrund veränderter Anforderungen aktualisiert werden müssen, bedeutet eine Änderung viele Aktualisierungen. Du müsstest dich dann an alle Stellen erinnern, an denen diese Informationen aktualisiert werden müssen. Das ist schwer zu bewerkstelligen und erhöht die Komplexität des Codes. Außerdem erhöht die Duplizierung die Gefahr von Fehlern, und längerer Code erfordert mehr Zeit zum Lesen und Verstehen.
Die geistige Anstrengung steigt auch, wenn du zwei Codeabschnitte siehst, die sehr ähnlich sind, aber keine exakten Duplikate. Es ist schwer zu sagen, ob die beiden Teile des Codes dasselbe tun oder etwas anderes.
Hier ein einfaches Beispiel: Du möchtest drei CSV-Dateien öffnen, sie in einen pandas Datenrahmen einlesen, einige Verarbeitungen vornehmen und jeden Datenrahmen zurückgeben. Die Daten in diesem Beispiel stammen von den Nachhaltigen Entwicklungszielen der Vereinten Nationen (SDGs), und ich werde im gesamten Buch Daten von dieser Website verwenden. Weitere Details zu diesen Daten findest du unter "Daten in diesem Buch". Der Code und die CSV-Dateien sind im GitHub-Repository für dieses Buch verfügbar.
Für einen ersten Durchgang könntest du etwa so vorgehen:
import
pandas
as
pd
df
=
pd
.
read_csv
(
"sdg_literacy_rate.csv"
)
df
=
df
.
drop
([
"Series Name"
,
"Series Code"
,
"Country Code"
],
axis
=
1
)
df
=
df
.
set_index
(
"Country Name"
)
.
transpose
()
df2
=
pd
.
read_csv
(
"sdg_electricity_data.csv"
)
df2
=
df2
.
drop
([
"Series Name"
,
"Series Code"
,
"Country Code"
],
axis
=
1
)
df2
=
df2
.
set_index
(
"Country Name"
)
.
transpose
()
df3
=
pd
.
read_csv
(
"sdg_urban_population.csv"
)
df3
=
df3
.
drop
([
"Series Name"
,
"Series Code"
,
"Country Code"
],
axis
=
1
)
df3
=
df3
.
set_index
(
"Country Name"
)
.
transpose
()
Aber das ist unnötig langwierig und repetitiv. Ein besserer Weg, um das gleiche Ergebnis zu erzielen, wäre, den wiederholten Code in eine for
Schleife einzubauen. Wenn du diesen Code immer wieder verwenden willst, kannst du ihn in eine Funktion einbauen, z. B. so:
def
process_sdg_data
(
csv_file
,
columns_to_drop
):
df
=
pd
.
read_csv
(
csv_file
)
df
=
df
.
drop
(
columns_to_drop
,
axis
=
1
)
df
=
df
.
set_index
(
"Country Name"
)
.
transpose
()
return
df
In anderen, subtileren Fällen kann es zu Code-Duplizierung kommen. Hier sind ein paar Beispiele:
-
Du könntest feststellen, dass du sehr ähnlichen Code in mehreren Projekten verwendest, ohne es zu merken, z. B. bei der Datenverarbeitung. Wenn du den Verarbeitungscode so aufteilst, dass er leicht variierende Daten verarbeiten kann, anstatt starr nur eine bestimmte Art von Daten zu akzeptieren, kannst du diese Doppelarbeit vermeiden.
-
Mehrere Personen, die an ähnlichen Projekten arbeiten, könnten ähnlichen Code schreiben, vor allem, wenn sie sich nicht darüber austauschen, woran sie gerade arbeiten. Wenn du den Code für andere leicht zugänglich machst und eine gute Dokumentation bereitstellst, kannst du diese Art von Doppelarbeit vermeiden.
-
Kommentare und Dokumentation können auch eine Form der Duplizierung sein. Im Code und in der Dokumentation, die ihn beschreibt, wird das gleiche Wissen dargestellt. Schreibe keine Kommentare, die genau beschreiben, was der Code tut, sondern verwende sie, um Wissen hinzuzufügen. In Kapitel 9 werde ich das genauer beschreiben.
Das DRY-Prinzip ist extrem wichtig, wenn du guten Code schreiben willst. Es klingt trivial, aber Wiederholungen zu vermeiden bedeutet, dass dein Code modular und lesbar sein muss. Auf diese Konzepte gehe ich später in diesem Kapitel ein.
Ausführlichen Code vermeiden
Manchmal kannst du deinen Code einfacher machen, indem du weniger Codezeilen hast. Das bedeutet weniger Möglichkeiten für Fehler und weniger Code, den jemand anderes lesen und verstehen muss. Allerdings gibt es oft einen Kompromiss zwischen kürzerem Code und schlechterer Lesbarkeit. Wie du sicherstellst, dass dein Code lesbar ist, erfährst du im Abschnitt "Lesbarkeit".
Ich empfehle dir, darauf zu achten, dass dein Code kurz, aber dennoch lesbar ist. Um das zu erreichen, solltest du Dinge vermeiden, die deinen Code unnötig langatmig machen, wie z. B. das Schreiben eigener Funktionen, anstatt eingebaute Funktionen zu verwenden, oder die Verwendung unnötiger temporärer Variablen. Außerdem solltest du Wiederholungen vermeiden, wie im vorherigen Abschnitt beschrieben.
Hier ist ein Beispiel für eine unnötige temporäre Variable:
i
=
float
(
i
)
image_vector
.
append
(
i
/
255.0
)
Dies kann wie folgt vereinfacht werden:
image_vector
.
append
(
float
(
i
)
/
255
)
Natürlich hat es auch Nachteile, wenn du deinen Code in weniger Zeilen zusammenpackst. Wenn viel in einer Zeile passiert, kann es für andere sehr schwer sein, zu verstehen, was vor sich geht. Das bedeutet, dass es für andere schwieriger ist, an deinem Code zu arbeiten, und das kann zu mehr Fehlern führen. Im Zweifelsfall empfehle ich, dass du deinen Code lesbar hältst, auch wenn das bedeutet, dass du ein paar zusätzliche Zeilen brauchst.
Modularität
Modularen Code zu schreiben ist die Kunst, ein großes System in kleinere Komponenten zu zerlegen. Modularer Code hat mehrere wichtige Vorteile: Er macht den Code leichter lesbar, es ist einfacher herauszufinden, woher ein Problem kommt, und es ist einfacher, den Code in deinem nächsten Projekt wiederzuverwenden. Außerdem ist es einfacher, Code zu testen, der in kleinere Komponenten aufgeteilt ist. Darauf gehe ich in Kapitel 7 ein.
Aber wie gehst du eine große Aufgabe an? Du könntest einfach ein großes Skript schreiben, um alles zu erledigen, und das mag zu Beginn eines kleinen Projekts in Ordnung sein. Aber größere Projekte müssen in kleinere Teile zerlegt werden. Dazu musst du so weit wie möglich in die Zukunft des Projekts denken und versuchen, vorauszusehen, was das Gesamtsystem tun wird und wo es sinnvollerweise aufgeteilt werden kann. Auf diesen Planungsprozess gehe ich in Kapitel 8 noch genauer ein.
Das Schreiben von modularem Code ist ein fortlaufender Prozess, den du nicht von Anfang an richtig hinbekommst, selbst wenn du die besten Absichten hast. Du solltest damit rechnen, dass du deinen Code mit der Entwicklung deines Projekts ändern musst. In "Refactoring" gehe ich auf Techniken ein, mit denen du deinen Code verbessern kannst .
Du kannst ein großes Data-Science-Projekt in eine Reihe von Schritten unterteilen, indem du es dir wie ein Flussdiagramm vorstellst, wie in Abbildung 1-1 gezeigt. Zuerst extrahierst du Daten, dann untersuchst du sie, dann bereinigst du sie und dann visualisierst du sie.
Zunächst könnte dies eine Reihe von Jupyter-Notizbüchern sein. Am Ende jedes Jupyter-Notizbuchs könntest du die Daten in einer Datei speichern und sie dann in das nächste Notizbuch laden. Wenn dein Projekt reift, wirst du vielleicht feststellen, dass du eine ähnliche Analyse wiederholt durchführen willst. Dann kannst du entscheiden, wie das Grundgerüst des Systems aussehen soll: Vielleicht gibt es eine Funktion, die die Daten extrahiert und sie dann an eine Funktion weitergibt, die die Daten bereinigt. Das folgende Beispiel verwendet die Anweisung pass
, um eine leere Funktion zu erstellen. Dadurch wird sichergestellt, dass beim Aufruf dieser Funktion kein Fehler auftritt, bevor sie geschrieben wird.
Das könnte z. B. das Gerüst eines Systems sein, das Daten lädt, sie auf eine maximale Länge beschneidet und sie mit einigen Plot-Parametern ausgibt:
def
load_data
(
csv_file
):
pass
def
clean_data
(
input_data
,
max_length
):
pass
def
plot_data
(
clean_data
,
x_axis_limit
,
line_width
):
pass
Indem du dieses Framework erstellt hast, hast du das System in einzelne Komponenten zerlegt und weißt, was jede dieser Komponenten als Eingabe akzeptieren sollte. Das Gleiche kannst du auch auf der Ebene einer Python-Datei tun. Wenn du ein Programmierparadigma wie die objektorientierte Programmierung oder die funktionale Programmierung verwendest, kannst du herausfinden, wie du deinen Code in Funktionen und Klassen aufteilen kannst (mehr dazu in Kapitel 4). Wie auch immer du dein System aufteilst, jede der Komponenten sollte so unabhängig und in sich geschlossen wie möglich sein, damit die Änderung einer Komponente keine andere verändert. Auf modularen Code gehe ich in Kapitel 8 näher ein.
Lesbarkeit
...Code wird viel öfter gelesen, als er geschrieben wird...
PEP8
Wenn du Code schreibst, ist es wichtig, dass andere Leute ihn auch nutzen können. Vielleicht wechselst du zu einem anderen Projekt oder sogar zu einem anderen Job. Wenn du ein Projekt für eine Weile verlässt und in einem Monat, einem halben Jahr oder sogar in sechs Jahren wieder darauf zurückkommst, kannst du dann immer noch verstehen, was du zu der Zeit getan hast, als du den Code geschrieben hast? Du hast den Code aus einem bestimmten Grund geschrieben, für eine Aufgabe, die wichtig war, und wenn du deinen Code lesbar machst,hat ereine lange Lebensdauer.
Zu den Methoden, die deinen Code lesbarer machen, gehören die Einhaltung von Standards und Konventionen für deine Programmiersprache, die Wahl guter Namen, das Entfernen von ungenutztem Code und das Schreiben von Dokumentationen für deinen Code. Es ist verlockend, diese Dinge zu vernachlässigen und sich mehr auf die Funktionalität des Codes zu konzentrieren, aber wenn du beim Schreiben deines Codes darauf achtest, dass er lesbar ist, wirst du einen Code schreiben, der weniger komplex und leichter zu warten ist. Ich stelle diese Methoden in diesem Abschnitt vor und werde sie in den Kapiteln 6 und 9 noch ausführlicher behandeln.
Normen und Konventionen
Kodierungsstandards und Formatierung scheinen die am wenigsten aufregenden Themen zu sein, die ich in diesem Buch behandeln werde, aber sie sind erstaunlich wichtig. Es gibt viele Möglichkeiten, denselben Code auszudrücken, sogar bis hin zu kleinen Details wie dem Abstand um das +
Zeichen beim Addieren von zwei ganzen Zahlen. Codierungsstandards wurden entwickelt, um die Konsistenz zwischen allen, die Python-Code schreiben, zu fördern. Das Ziel ist, dass sich der Code auch dann noch vertraut anfühlt, wenn ihn jemand anderes geschrieben hat. Das hilft, den Aufwand für das Lesen und Bearbeiten von Code, den du nicht selbst geschrieben hast, zu verringern. Auf dieses Thema gehe ich in Kapitel 6 näher ein.
Python ist im Vergleich zu vielen anderen Programmiersprachen von Natur aus sehr gut lesbar; wenn du dich an einen Kodierungsstandard hältst, wird es noch einfacher. Der wichtigste Kodierungsstandard für Python ist PEP8 (Python Enhancement Proposal 8), der im Jahr 2001 eingeführt wurde. Das Beispiel unten zeigt einen Auszug aus PEP8, und du kannst sehen, dass es selbst für die kleinsten Details in deinem Code Konventionen gibt. Style Guides wie der Python Style Guide von Google ergänzen PEP8 mit zusätzlichen Anleitungen und Informationen.
Hier ist ein Beispiel für die Details, die PEP8 vorgibt, das zeigt, wie Leerzeichen innerhalb von Klammern richtig und falsch formatiert werden:
# Correct:
spam
(
ham
[
1
],
{
eggs
:
2
})
# Wrong:
spam
(
ham
[
1
],
{
eggs
:
2
}
)
Zum Glück gibt es viele automatisierte Möglichkeiten, um zu überprüfen, ob dein Code mit den Kodierungsstandards konform ist. Das erspart dir die langweilige Arbeit, jedes +
Zeichen mit einem einzigen Leerzeichen zu versehen und zu überprüfen. Linters wie Flake8 und Pylint markieren Stellen, an denen dein Code nicht mit PEP8 konform ist. Automatische Formatierer wie Black aktualisieren deinen Code automatisch, damit er den Kodierungsstandards entspricht. Wie du sie benutzt, erfährst du in Kapitel 6.
Namen
Wenn du Code für Data Science schreibst, musst du an vielen Stellen Namen wählen: Namen für Funktionen, Variablen, Projekte und sogar ganze Tools. Die Wahl der Namen beeinflusst, wie einfach es ist, an deinem Code zu arbeiten. Wenn du Namen wählst, die nicht beschreibend oder präzise sind, musst du ihre wahre Bedeutung im Kopf behalten, was den kognitiven Aufwand für deinen Code erhöht. Du könntest zum Beispiel die Pandas-Bibliothek als p
importieren und deine Variablen x
und f
nennen, wie hier gezeigt:
import
pandas
as
p
x
=
p
.
read_csv
(
f
,
index_col
=
0
)
Dieser Code läuft korrekt und ohne Fehler. Aber hier ist ein Beispielcode, der leichter zu lesen ist, weil die Variablennamen informativer sind und denStandardkonventionen folgen:
import
pandas
as
pd
df
=
pd
.
read_csv
(
input_file
,
index_col
=
0
)
In Kapitel 9 erfährst du mehr darüber, wie man gute Namen schreibt.
Aufräumen
Eine weitere Möglichkeit, deinen Code lesbarer zu machen, besteht darin, ihn nach der Erstellung einer Funktion aufzuräumen. Wenn du sie getestet hast und sicher bist, dass sie funktioniert, solltest du auskommentierten Code und unnötige Aufrufe der Funktion print()
entfernen, die du vielleicht als einfache Form der Fehlersuche benutzt hast. Es ist sehr verwirrend, auskommentierte Abschnitte im Code eines anderen zu sehen.
Wenn du unaufgeräumte Codeabschnitte siehst, sendet das die Botschaft, dass schlechte Codequalität in einem Projekt akzeptabel ist. Das bedeutet, dass es für andere Mitwirkende weniger Anreize gibt, guten Code zu schreiben. Der unordentliche Code kann auch in anderen Teilen des Projekts kopiert und angepasst werden. Dies ist als Broken-Window-Theorie bekannt. Hohe Standards in einem Projekt zu setzen, ermutigt alle, die daran arbeiten, guten Code zu schreiben.
Wenn du die Qualität deines Codes verbessern willst, kannst du dich entscheiden, ihn zu refaktorisieren. Refactoring bedeutet, den Code zu ändern, ohne sein Gesamtverhalten zu verändern. Vielleicht hast du dir überlegt, wie dein Code effizienter werden kann oder wie du ihn besser strukturieren kannst, damit dein Teamkollege Teile des Codes in einem anderen Projekt verwenden kann. Tests sind in diesem Prozess unerlässlich, denn sie überprüfen, ob dein neuer Code immer noch das gleiche Gesamtverhalten hat. Ich werde das Refactoring in "Refactoring" behandeln .
Dokumentation
Dokumentation hilft auch anderen Leuten, deinen Code zu lesen. Code kann auf verschiedenen Ebenen dokumentiert werden, angefangen bei einfachen Inline-Kommentaren, überdocstrings
die eine ganze Funktion erklären, bis hin zur Seite README
, die in einem GitHub-Repository angezeigt wird, und sogar Tutorials, die den Nutzern zeigen, wie man ein Paket verwendet. All diese Aspekte der Dokumentation helfen dabei, anderen Menschen zu erklären, wie sie deinen Code verwenden können. Vielleicht erklären sie dir in Zukunft sogar deinen Code (ein sehr wichtiges Publikum!). Wenn du willst, dass andere Leute deinen Code verwenden, solltest du es ihnen leicht machen, indem du eine gute Dokumentation schreibst.
Eine gute Dokumentation zu schreiben ist eine Sache, aber du musst sie auch pflegen und auf dem neuesten Stand halten. Eine Dokumentation, die auf eine veraltete Version des Codes verweist, ist schlimmer als gar keine Dokumentation. Sie stiftet Verwirrung, die nur mit viel Zeitaufwand zu beseitigen ist. Auf alle Formen der Dokumentation gehe ich in Kapitel 9 noch genauer ein.
Leistung
Guter Code muss performant sein. Dies wird sowohl an der Laufzeit des Codes als auch an der Speichernutzung gemessen. Wenn du Entscheidungen darüber triffst, wie du deinen Code schreiben sollst, ist es nützlich zu wissen, welche Datenstrukturen und Algorithmen effizienter sind. Es ist wirklich gut zu wissen, wann du Dinge tust, die deinen Code erheblich verlangsamen, besonders wenn es eine leicht verfügbare Alternative gibt. Du solltest dir auch bewusst sein, welche Teile deines Codes viel Zeit in Anspruch nehmen.
Leistung ist besonders wichtig, wenn du Produktionscode schreibst, der jedes Mal aufgerufen wird, wenn ein Nutzer eine bestimmte Aktion ausführt. Wenn dein Nutzerkreis wächst oder dein Projekt erfolgreich ist, könnte dein Code jeden Tag Millionen Mal aufgerufen werden. In diesem Fall können selbst kleine Verbesserungen an deinem Code deinen Nutzern viele Stunden Arbeit ersparen. Du willst nicht, dass dein Code der langsame Punkt in einer großen Anwendung ist. In Kapitel 2 erkläre ich, wie du die Leistung deines Codes messen kannst, und in Kapitel 3 zeige ich dir, wie du die besten Datenstrukturen auswählst, um die Leistung deines Codes zu optimieren.
Robustheit
Guter Code sollte auch robust sein. Damit meine ich, dass er reproduzierbar sein sollte: Du solltest in der Lage sein, deinen Code von Anfang bis Ende auszuführen, ohne dass er fehlschlägt. Dein Code sollte auch in der Lage sein, auf unerwartete Änderungen der Systemeingaben angemessen zu reagieren. Anstatt einen unerwarteten Fehler auszulösen, der ein größeres System fehlschlagen lassen könnte, sollte dein Code so konzipiert sein, dass er auf Änderungen reagiert. Du kannst deinen Code robuster machen, indem du Fehler richtig behandelst, protokollierst, was passiert ist, und gute Tests schreibst.
Fehler und Protokollierung
Robuster Code sollte sich nicht unerwartet verhalten, wenn er eine falsche Eingabe erhält. Du solltest dich entscheiden, ob dein Code bei einer unerwarteten Eingabe abstürzen soll oder ob du den Fehler behandeln und etwas dagegen tun willst. Wenn in deiner CSV-Datei zum Beispiel die Hälfte der erwarteten Datenzeilen fehlt, soll dein Code dann einen Fehler zurückgeben oder weiterhin nur die Hälfte der Daten auswerten? Du solltest explizit entscheiden, ob du eine Warnung ausgeben willst, dass etwas nicht so ist, wie es sein sollte, ob du den Fehler behandeln oder stillschweigend fehlschlagen willst. Auf Fehler gehe ich in Kapitel 5 genauer ein.
Auch wenn der Fehler behandelt wird, kann es wichtig sein, ihn zu protokollieren, damit er nicht stillschweigend fehlschlägt, wenn du das nicht willst. Dies ist nur ein Anwendungsfall für das Logging; andere Anwendungsfälle für das Logging werden in Kapitel 5 behandelt.
Testen
Testen ist der Schlüssel zum Schreiben von robustem Code. In der Softwareentwicklung gibt es zwei Haupttypen: Benutzertests, bei denen eine Person eine Software benutzt, um zu bestätigen, dass sie richtig funktioniert, und automatisierte Tests. Eine gängige Methode für automatisierte Tests ist das Senden einer Beispieleingabe in einen Code und die Bestätigung, dass die Ausgabe den Erwartungen entspricht. In diesem Buch werde ich nur automatisierte Tests behandeln.
Tests sind notwendig, denn selbst wenn dein Code auf deinem Rechner perfekt läuft, bedeutet das nicht, dass er auch auf dem Rechner eines anderen oder sogar auf deinem eigenen Rechner in Zukunft funktionieren wird. Daten ändern sich, Bibliotheken werden aktualisiert, und auf verschiedenen Rechnern laufen unterschiedliche Python-Versionen. Wenn jemand anderes deinen Code auf seinem Rechner verwenden möchte, kann er deine Tests durchführen, um zu bestätigen, dass er funktioniert.
Es gibt mehrere verschiedene Arten von Tests. Unit-Tests testen eine einzelne Funktion, End-to-End-Tests testen ein ganzes Projekt, und Integrationstests testen einen Teil des Codes, der viele Funktionen enthält, aber immer noch kleiner ist als ein ganzes Projekt. In Kapitel 7 beschreibe ich Teststrategien und Bibliotheken im Detail. Aber eine gute Strategie für den Anfang, wenn du eine große Codebasis ohne Tests hast, ist es, einen Test zu schreiben, wenn etwas nicht funktioniert, um sicherzustellen, dass dasselbe nicht noch einmal passiert.
Die wichtigsten Erkenntnisse
Wenn du guten Code schreibst, hilft dir das in vielerlei Hinsicht: Andere Leute können deinen Code leichter verwenden, du kannst besser nachvollziehen, was du getan hast, wenn du sechs Monate nach der letzten Berührung auf deine Arbeit zurückkommst, und dein Code lässt sich besser skalieren und in ein größeres System integrieren. Ein guter Code macht dir auch das Leben leichter, wenn du deinem Code Funktionen hinzufügen musst, die nicht im ursprünglichen Projektplan vorgesehen waren.
Wenn du mehr über die Prinzipien zum Schreiben von gutem Code lesen möchtest, empfehle ich dir diese Bücher:
-
The Pragmatic Programmer, 20th Anniversary Edition, von David Thomas und Andrew Hunt (Addison-Wesley Professional)
-
Eine Philosophie der Softwarearchitektur von John Ousterhout (Yaknyam Press)
Zusammengefasst findest du hier einige Möglichkeiten, wie du guten Code schreiben kannst:
- Einfachheit
-
Dein Code sollte Wiederholungen, unnötige Komplexität und überflüssige Codezeilen vermeiden.
- Modularität
-
Dein Code sollte in logische Funktionen mit klar definierten Ein- und Ausgängen aufgeteilt sein.
- Lesbarkeit
-
Dein Code sollte dem PEP8-Standard für die Formatierung folgen, gut gewählte Namen enthalten und gut dokumentiert sein.
- Leistung
-
Dein Code sollte nicht unnötig lange laufen oder mehr Ressourcen verbrauchen, als zur Verfügung stehen.
- Robustheit
-
Dein Code sollte reproduzierbar sein, sinnvolle Fehlermeldungen ausgeben und unerwartete Eingaben verarbeiten, ohne fehlzuschlagen.
Im nächsten Kapitel gehe ich näher auf einen Aspekt von gutem Code ein: die Leistung.
Get Software Engineering für Datenwissenschaftler now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.