Was macht Clean Code aus? Wie kann man seinen Code verbessern? Welche Regeln helfen einem Programmierer, um zu sauberen Code zu gelangen? Welche Tipps und Tricks gibt es, mit denen man sich noch verbessern kann? Gibt es Patterns bzw. Muster, die zum Erfolg führen? Oder ist Clean Code nur Zeitverschwendung in Projekten unter Zeitdruck?
Wer das legendäre Buch 'Clean Code' noch nicht gelesen hat, oder eine Auffrischung gebrauchen kann, ist zu diesem Live-Stream gerne willkommen. Um es praxisnah zu halten, werden viele Code-Schnipsel gezeigt, die wir zusammen analysieren und verbessern.
Clean Code - A Handbook of Agile Software Craftsmanship: Englische Ausgabe
https://amzn.to/3pXpCOS
Clean Code - Refactoring, Patterns, Testen und Techniken für sauberen Code: Deutsche Ausgabe
https://amzn.to/3cNO55B
Diese Videobeschreibung enthält Amazon Affiliate Links, mit denen ihr mich beim Kauf unterstützen könnt, ich erhalte eine kleine Provision während ihr nichts extra zahlt für euren Amazon-Einkauf!
1. Clean Code
by example (Teil 1)
Auf den Punkt gebracht
Gregor Biswanger & Robert Walter
02/2021
2. Robert C. Martin (* 1952) ist ein US-
amerikanischer Softwareentwickler, IT-
Berater und Autor. Robert C. Martin, auch
bekannt als „Uncle Bob“, arbeitet seit den
1970er Jahren in diversen
Softwareentwicklungsprojekten, seit 1990 als
international anerkannter IT-Berater. 2001
initiierte er die Entwicklung des Agilen Manifests
(Manifest für Agile Softwareentwicklung, The
Agile Manifesto), das Fundament agiler
Softwareentwicklung. Er ist auch führendes
Mitglied der Bewegung Software Craftsmanship,
die sich der Clean Code Softwareentwicklung
verschrieben hat.
Der Buchautor
6. Der gute Clean Code Ratschlag
„Programmiere immer so, als ob dein
Nachfolger, der deinen Code anpassen muss, ein
Psychopath ist, der weiß wo du wohnst!“
Uncle Bob
7. Eine Geschichte aus dem Leben…
Super Application v1.0 *
Release: 1980
* programmiert von Super Codern
Glücklicher User mit Geldüberschuss
8. Super Coder im Einsatz!
v1.0 v2.0 v3.0
v3.1
v4.0
v4.01
v4.02
Zeit
Release Bug #17
Bug #35
Bug #52 Bug #101
Bug #119
Bug #210
Bug #347
Auslieferung zur Weltmesse Bug #404
Bug #1296
Bugfix
Super Coder programmiert fleißig
Release für Release…
9. Was ist da los?
0%
20%
40%
60%
80%
100%
120%
1980 1981 1982 1983 1984
Produktivität
Zeit
Super Coder frustrierter User
10. Das Schicksal mit den Kunden!
B*
B*
B* (Produkt von anderer Firma)
Zeit
Glücklicher User mit Geldüberschuss Frustrierter User mit Sorgen Glücklicher User
(leider ab jetzt bei der Konkurrenz)
11. …und der Super Coder?
Super Coder Armer Bettler*
* sein Unternehmen war pleite
Zeit
12. Das Insider-Gespräch…
?!?
„Wir haben gecodet und programmiert wie die Helden – wir haben Features, Features,
und noch mal Features gebaut. Die Zeit war immer zu knapp. Wir haben einfach alles
immer schnell rein gehauen. Irgendwann haben uns die Bugs eingeholt. Wir haben nur
noch Bugs gefixt. Manche Bugfixes haben weitere Bugs hervorgerufen. Wir hatten keine
Zeit für Bugfixes. Der Code war richtig dreckig geworden. Es hat keinen Spaß mehr
gemacht. Wir haben nicht mehr durchgeblickt. Die Kunden waren sauer und sind
abgesprungen. Kollegen haben gekündigt. Wir sind den Bach runter gegangen!“
Ex-Super Coder
Uncle Bob
…und frägt nach:
„was war da
damals los?“
woraufhin Ex-Super
Coder einen
mehrzeiligen
Kommentar
abgibt:
13. Begriff für dieses Vorkommnis
=
Technische
Schulden
Bug #101
Bug #404
Bug #52
Feature #27 Feature #39 Feature #42
Feature #31
14. Der Lerneffekt
„Zeit darauf zu verwenden, den Code sauber zu
halten, ist nicht nur kosteneffektiv, sondern auch
eine Sache des professionellen Überlebens!“
Uncle Bob
Manager
Programmierer Programmierer
Wer ist also Schuld?
a) Die Programmierer
b) Die Manager
„Die Programmierer sind selbst Schuld,
wenn sie auf Clean Code zugunsten
schneller Ergebnisse verzichten.“
15. Welche
Eigenschaften
hat CleanCode?
+ lesbar wie ein Roman
+ stellt Design-Absichten dar
+ verständlich für andere
Entwickler
+ automatisiert getestet
+ minimale
Abhängigkeiten
+ minimiert
Duplizierungen
+ minimale Längen
+ einfach
+ wartbar
+ elegant
+ bedacht
16. Constantin öffnet die Klassendatei…
…er scrollt nach unten bis zur Methode, die geändert
werden muss
…er überlegt und geht die Möglichkeiten durch
…oh, er scrollt nach oben, an den Anfang des Klasse, um
eine Initialisierung der Instanzvariable zu prüfen
…jetzt scrollt er wieder zurück und beginnt zu tippen
…ups – er löscht, was er getippt hat
…er tippt wieder
…er löscht es wieder
…er tippt die Hälfte von etwas Anderem, aber er löscht es!
...Er scrollt nach unten zu einer anderen Methode, die eine
Methode aufruft, die er anpasst, um zu sehen, wie sie
aufgerufen wird
…er scrollt nach oben und tippt den selben Code, den er
gerade gelöscht hat
…er stoppt
…er löscht den Code schon wieder!
...er öffnet ein anderes Fenster, um sich eine abgeleitete
Klasse anzusehen: Ist diese Methode überschrieben?...
Code wird
viel öfter
gelesen als
geschrieben!
§ !!! §
§ !!! §
Ein Blick über die Schulter eines Programmierers…
18. int otd; //overtime
int overtimeInDays;
int overtimeForYear;
int overtimeSinceBegin;
int daysHavingOvertime;
// noch besser mit .NET
TimeSpan overtimeSinceBegin;
Gute Benennung
Anstatt: Besser:
19. public List<double[]> DoGet() {
List<double[]> arr =
new List<double[]>();
foreach (double[] it in coll)
if (it[0] == 2)
arr.Add(it[0]);
return arr;
}
public List<Cell> FilterBlackCells() {
List<Cell> blackCells =
new List<Cell>();
forech (Cell cell in chessboard)
if (cell.IsBlack())
blackCells.Add(cell);
return blackCells;
}
Gute Benennung
Anstatt: Besser:
Für was steht der
Rückgabewert?
Was macht die
Methode?
Was repräsentiert
die Collection?
Warum gerade 2? Was bedeutet das?
→ Antipattern: Magic Number
Was ist zum Schluss
in der Liste?
20. crtmjsms creationDateTime
Gute Benennung
Anstatt: Besser:
Hey, warum verwendest du hier die Variable
„CeErTeEmUndSoWeiter“?
Hey, warum verwendest du hier die
Variable „creationDateTime“?
Sprechbare Bezeichnungen verwenden, damit
eine Konversation möglich ist.
Sprechbare Bezeichnungen
22. XlsxSheet
var data = new { Name = „Hans“ };
Sheet
var person =
new Person { Name = „Hans“ };
Gute Benennung
Anstatt: Besser:
Ohne Präfix
einfacher zu finden
Begriffe so wählen, damit sie intuitiv von vorne
schneller gefunden werden können.
Benutzer-suchbare Bezeichnungen
Weiter unten im Code ist nicht
mehr klar, was data eigentlich ist.
23. string strFullName;
string accountString;
// nach Refactoring passiert dann:
BankAccount accountString;
string fullName;
string account;
BankAccount account;
Gute Benennung
Anstatt: Besser:
Ungarische Notation (d.h. das hinten anstellen
des Typbezeichners an den Variablennamen)
ist wegen der guten Entwicklungsumgebung
nicht mehr hilfreich, sondern fehleranfällig.
Ungarische Notation
24. private string m_add; private string address;
Gute Benennung
Anstatt: Besser:
Präfixe für Membervariablen weglassen
Präfixe für Membervariablen
Sichtbarer Störfleck
Membervariable muss nicht
markiert werden, da Klassen
sowieso kurz genug sein sollten!
32. Wichtigste Regel für Funktionen
„Die erste Regel zu Funktionen ist, dass sie klein
sein sollen. Die zweite Regel für Funktionen ist,
sie sollen kleiner sein als das.“
Uncle Bob
„Und: Funktionen sollen nur eine einzige Sache
tun.“
34. „To…“-Paragraphen-Formulierung nutzen
To ShowReportWithHeadersAndFooters
we check to see if a site is a inclusion site and if so we
include the headers and footers. Otherwise we show it
as PDF.
Single
Responsibility
Principle
(SRP)
§ !!! §
§ !!! §
Wie findet man heraus, ob eine Funktion nur eine Sache tut?
Funktionen
35. Die Stepdown-Regel
Funktionen Stepdown-Regel
To include the headers and footers we include headers, then we include the
site report controls, then we include the footers.
To include the subreport header, we search the parent hierarchy for the
„subreport“ property and add an include statement for it.
To include the headers we include the subreport header if this is a supreport,
then we include the main header.
To search the parent…
ABSTRAKTIONSEBENEN
36. Show(true)
ShowMainReport()
// und
ShowSubReport()
//oder noch besser in .NET
Show(ReportType.Main)
// und
Show(ReportType.SubReport)
Anstatt: Besser:
boolean-Parameter
Was aktiviert der Parameter?
Methodendefinition:
Show(bool isSubReport)
Funktionen
funktioniert wegen Methodendefinition:
Show(ReportType reportType)
enum ReportType {
Main = 0,
SubReport = 1
}
boolean-Parameter durch zwei
Methodenaufrufe oder einen selbst
geschriebenen enum-Typen ersetzen.
37. consent = verify(user)
check(hasAgreedAll,
hasAgreedSpecialCase)
hasAgreed = verifyGDPRConsents(user)
checkAllOverridesOtherOption(
hasAgreedAll, hasAgreedSpecialCase)
// noch eleganter in .NET:
// Operation Overloading
hasOverride =
hasAgreedAll > hasAgreedSpecialCase
Anstatt: Besser:
Verben und Schlüsselwörter
Was genau wird geprüft?
Funktionen
Wie werden die Parameter verglichen?
Funktionen gegebenenfalls ausführlicher
benennen, damit Verhalten klar wird.
38. Parameteranzahl Empfehlung
1 am besten
2 gut
3
sollte vermieden
werden
Parameter-Anzahl
Funktionen
run(„a“, 1, true, false, „admin“,
new DefaultOptions(), true, null,
null, null, 1);
Anstatt: Besser:
Was wird alles übergeben? Hat
man hier noch den Überblick?
Was ist, wenn sich Parameter
ändern, oder noch welche
hinzukommen?
40. if (SaveField(„name“, „Hans“)) …
if (HasField(„name“)) {
SaveField(„name“, „Hans“)
…
}
Anstatt: Besser:
Command Query Separation
if-Abfrage
Lese-Zugriff
= Query
Funktionen
Query und Command Zugriffe müssen in zwei
separate Funktionen aufgetrennt werden.
Save-Methode
Schreib-Zugriff
= Command
Was gibt das SaveField für die Bedingung
zurück? Wann trifft es zu?
42. public void Remove(SubReport subReport) {
try {
DeleteControlsAndLinks(subReport);
} catch(Exception ex) {
LogError(ex);
}
}
private void
DeleteControlsAndLinks(page) { … }
private void
LogError(Exception ex) { … }
Funktionen
Gut:
Bei try/catch
→ 1 Zeile Methodenaufruf
try-catch-Blöcke extrahieren
„Clean Code ist lesbar wie ein Roman“
Zuerst kommt die Überschrift, dann die Details
43. Code zu kopieren!
Don‘t
repeat
yourself
(DRY)
§ !!! §
§ !!! §
Was ist die größte Todsünde beim Programmieren?
Funktionen
Code A
Code A-Kopie
mit Änderung
Code A-Kopie
mit
Änderung-
Kopie mit
Anpassung
aufrufender
Code
aufrufender
Code
aufrufender
Code
Änderung 1
Änderung 2
Änderung 2?
Änderung 2?
Änderung 3
Es entsteht ein
Vielfaches an Code
an verteilten Stellen
So entstehen
leichtsinnige Bugs
Änderung 1 ist bei
jedem Aufrufer
vorhanden
Änderung 2 nicht mehr, und
muss noch mal kopiert werden…
44. Produktive Vorgehensweise
Widerspricht es sich nicht, schnell und produktiv Funktionen zu schreiben,
die auch sauber sein müssen?
Nein, es gibt einen guten Kompromiss:
1) Code einfach mal quick & dirty „herunterschreiben“
2) Code refaktorisieren und verfeinern
Funktionen Refaktorisierung
47. Kommentare und schlechter Code
„Kommentiere schlechten Code nicht einfach –
schreibe ihn neu!“
Brian W. Kernighan
& P. J. Plaugher
48. Veraltete Kommentare
„Kommentare lügen! Ein Programmierer kann
sie realistischerweise nie richtig instand halten.“
Uncle Bob
Auskommentierter alter Code muss immer
sofort gelöscht werden! Dafür gibt es nämlich
ein Quellcodeverwaltungssystem.
49. // check to see if the employee
// has earned super bonus
if ((employee.flags
& SUPER_FLAG)
&& (employee.Earning > 0))
…
if(employee.HasEarnedSuperBonus())
…
Anstatt: Besser:
Kommentare
50. +
Gute Arten von
Kommentaren
+ Gesetzliche Kommentare (Copyright Header)
+ Erklärung der Absicht
// Dies ist unser Versuch, einen Dead Lock zu
// erzwingen, mit einer brutal hohen Menge an
// Threads
+ Warnung vor Konsequenzen
// StateRepository ist nicht ThreadSafe
+ TODO-Kommentare
+ XML-Kommentare in public APIs oder
Javadocs
+ Begründung für leere catch-Blöcke
51. -
Schlechte Arten
von Kommentaren
− Code-erklärende Kommentare
// erhöhe i um 1
− Pflichtkommentare
///<summary>
/// Gets and sets the name
///</summary>
string Name { get; set;}
− Journal-Kommentare / Änderungshistorie
− Überflüssige Kommentare
/* Default Constructor */
− Positionsmarker
///// Methods /////////////////////////
− Klammerschluss-Kommentare
− Globale Informationen innerhalb von Funktionen
// Default User ist PowerAdmin aus Travolta
− Zu viele Informationen (z.B. komplexe
Spezifikationen)
− XML-Dokumentation/Javadocs in nicht-public-Code
53. Gemeinsamer Codestyle
Check_passwd() {
//…
}
Super Coder A Super Coder B Super Coder C
checkpassword ( )
{
//…
}
DoPasswordCheck
()
{
//…
}
CheckPassword()
{
//…
}
Formatierung
Am Anfang des
Projekts sollte man
sich im Team auf
einen gemeinsamen
Stil einigen. Team-
Regeln stehen dann
über individuellen
Regeln.
54. Empfohlene Anzahl an Codezeilen
Durchschnittlich [] Minimal [min.] Maximal [max.] Projekt
65 6 400 FitNesse
200 500
Große Systeme:
JUnit
testNG
JDepend
Ant
Tomcat
Formatierung
55. Zeitungs-Methapher
DIE NEUE ZEITUNG | GLOBALES
Obama will die Krankenversicherung einführen
Xxxxxxxxxx
Xxxxxxxxx
Xxxxxxxxxx
Xxxxxxx xxxx
xxx xxxxx xxxx xxxx
xxxxx xxxxxx xxxxx
xxxxxxxx xxxxxxxxx
xxxxxxxxxxxxxx xxxxx
xxxxxxxxxx xx xxx x x
xxxxxxxxxxx x xxxxxxx
xxxxxxx xxxxxxx x xxxx
xxxx xxxxx xxxxxx xxx
xxx xxxx xxxxxxx
xxxxxxxxxxxxxxxxxx
xxxxxxx xxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxx xxxxxxx
xxxxxxxxxx xxxxxx
xxxxxx xxxx
xxxxxxxxxx.
☺ Scherz am Rande - Kommentar eines Zeitungslesers:
„I only understand English and I can read only the first
three words!“
class ReportOutputGenerator {
public Report Render() {
// xxxxxxx
// xxxxxxx
// xxxxxxx
}
private void … {
// xxxxxxx
// xxxxxxx
}
private void …
}
Titel / Worum geht es? Wo bin ich?
Zusammenfassung
Details
Formatierung
56. class ReportOutputGenerator {
private Controls controls;
public Report Render() {
// xxxxxx
int i;
while(i < 0) { … }
// xxxxxx
}
private void … {
// xxxxxxx
// xxxxxxx
}
private void …
}
Eine Zeile Zeilenabstand zwischen Methoden zeigen
den Wechsel auch optisch und sind wichtig
Zusammenhängende Methoden sollten
vertikal nah beieinander stehen
Formatierung
Variablendeklarationen sollten immer so nah wie
möglich zu Ihrer Verwendung stehen.
Instanzvariablen sollten immer am Kopf der Klasse
gesammelt werden.
Zeichenlänge: Empfehlung max. 120 Zeichen
(es muss noch auf einen normalen Bildschirm
passen)
58. Objekt Datenstruktur
Objekte und Datenstrukturen
class Report {
public List<SubReport> SubReports
{ get; set; }
public string Title { get; set; }
…
}
class PageEditor {
void Remove(SubReport report) { … }
private void
DeleteControlsAndLinks(report)
{ … }
private void
LogError(Exception ex) { … }
}
Logik / Verhalten
Datenhaltung
vs.
59. Law of
Demeter
§ !!! §
§ !!! §
Objekte und Datenstrukturen
string outputPath =
dir.getBase()
.getReportsDir()
.getFullPath();
string outputPath =
dir.Base
.ReportsDir
.FullPath;
FileInfo
outputFileInfo = dir
.BuildReportsPath(
reportCategory);
Anstatt: Besser:
Uncle Bob hasst übrigens getter/setter
in Java, da sie sinnlos sind!
Verkettung von vielen Aufrufen, wie bei
einem Zug mit vielen Locks
Ein Modul darf nichts über das Innerste
eines Objekts wissen, das es manipuliert!
Das widerspricht dem Prinzip der Kapselung.
61. void Remove(SubReport subReport) {
try {
DeleteControlsAndLinks(subReport);
} catch(Exception ex) {
LogError(ex);
}
}
private void
DeleteControlsAndLinks(subReport) { … }
private void
LogError(Exception ex) { … }
Fehlerbehandlung
Gut:
Bei try/catch
→ 1 Zeile Methodenaufruf
try-catch-Blöcke extrahieren
„Clean Code ist lesbar wie ein Roman“
Zuerst kommt die Überschrift, dann die Details
62. public int Activate(Person p) {
// …
if(p.Email == null) {
return ErrorCodes.Code47_NoMail;
}
// …
}
/// <exception
/// cref="Prog.MissingMailException">
/// Thrown when no mail address
/// is given, so that no activation
/// mail is possible. Activation
/// will be cancelled.
/// </exception>
public void Activate(Person p) {
// …
if(p.Email == null) {
throw new MissingMailException();
}
// …
}
Anstatt: Besser:
Exceptions statt return codes
Gut: Hier keine Magic Number
ABER: Magic Number kommt
trotzdem bei Anrufer an, und muss
sogar bekannt sein und erwartet
werden!
Fehlerbehandlung
Selbst geschriebene Exceptions statt return
Codes verwenden. So muss der Fehler
automatisch behandelt werden, oder wird
direkt für den Benutzer ausgegeben.
Damit Aufrufer über mögliche
Exceptions Bescheid weiß, sollte
man sie im XML Doc angeben.
63. try {
total += budgetRepo.LoadYearly();
} catch(
YearlyNotFoundException ex) {
total += LoadOutOfOrderBudget();
}
total += budgetRepo.LoadAll();
class OutOfOrderBudget
: YearlyBudget { … }
Anstatt: Besser:
Keine Exceptions für Kontrollfluss
Fehlerbehandlung
Exceptions nicht für normalen Kontrollfluss
verwenden. Besser ist es, z.B. das „Special
Case Pattern“ zu verwenden.
=„Special Case Pattern“
total kann dann mit Standardfall
und Sonderfall normal addiert
werden.
Häufig auftretender Sonderfall; ist ein
„Kontrollfluss“, da Aktion ausgelöst wird;
keine problematische unerwartete
„Ausnahme“. Exceptions zu werfen und zu
fangen ist nämlich „teuer“ (=frisst
Performance).
64. public IEnumerable<Customer> Load(
CustomerType type) {
if(type == null) throw new
IllegalArgumentException(„type“);
//…
return null;
}
public IEnumerable<Customer> Load(
CustomerType type) {
// Kein explizites null-Handling
//…
return Enumerables.Empty<Customer>();
}
Anstatt: Besser:
null-Handling
Team-Regel festlegen: null sollte am
besten nie als Parameter übergeben
werden!
Fehlerbehandlung
Team-Regel: Offensiv programmieren - nie null
als Parameter übergeben, und nie null als
Rückgabeparameter zurückgeben!
= „Null-Object-Pattern“
Aufrufer ist dadurch vor null-
Reference-Exception geschützt
Potentielle null-Reference-
Exception beim Aufrufer!
Code wird leider aufgebläht
= „Offensive Programmierung“
d.h. dafür sorgen, dass Fehler
beim Aufruf früh aufgedeckt
werden! Beim Debuggen gibt
es inzwischen gute Hinweise
zum null-Ursprung.
65. Wie es weiter geht…
Und jetzt folgt – die WERBUNG:
…seht ihr in Teil 2
beim nächsten Mal!
Eine klare Buchempfehlung
für alle Programmierer!