Über 100 Downloads in Zwei Wochen: Meine .NET-Bibliothek „Revertible“

Ich bin begeistert, bekannt zu geben, dass meine .NET Standard 2.0 Bibliothek „Revertible„, die ich vor zwei Wochen auf NuGet veröffentlicht habe, bereits über 100 Mal heruntergeladen wurde. Diese Bibliothek erlaubt es, Properties, die mit dem RevertibleAttribute gekennzeichnet sind, einfach auf ihren ursprünglichen Zustand/Wert zurückzusetzen.

Anfangs war ich unsicher, ob es einen Bedarf für eine solche Funktion gibt. Aber die Nachfrage in der .NET-Community hat meine Erwartungen übertroffen. Ich freue mich, dass mein Projekt Anklang findet und hilfreich für Entwickler ist.

Für weitere Informationen besuchen Sie das NuGet-Paket hier und das GitHub-Projekt hier.

Wenn der Code-Schreiber keine Ahnung von Compiler hat, sich aber auf ihn und seine Optimierungsalgorithmen verlässt …

Es gibt Code-Schreiberlinge, die glauben tatsächlich, dass „der Compiler eh alles optimiert„.
Andere glauben sogar, dass der Compiler eine gewisse Intelligenz besitzt.
Dann gibt es noch die Code-Schreiberlinge, die glauben, dass es gut ist, wenn sie jede Methode mit einem Try-Catch-Block zu versehen. Nachdem Motto „Bringt nichts, schadet nichts“ und/oder „Doppelt hält besser„.


Schauen wir uns das ganze Mal genauer an.
Wieder mal ein Stück Code aus freie Wildbahn (Industrie):

// Listing 1 (ugly code)

void Main()
{
	Console.WriteLine(GetSomething(0));
}

public double GetSomething(int value)
{
    try
    {
        return 0;
    }
    catch(Exception ex)
    {
        return 0;
    }
}

Man stelle sich nun vor (das wird mir niemand glauben) es gäbe ein SW-System mit hundert und mehr solche Methoden wie „GetSomething(int)„.

Der folgende Code tut exakt das Gleiche, nur viel CPU und Speicher freundlicher, überschaubarer, verständlicher und einfacher zu lesen:

// Listing 2 (better code)

public const double DBL_CONST = 0.0;

void Main()
{
	Console.WriteLine(0);
}

Und nun schauen wir uns mal an, was der Compiler:

  • Optimiert, und es dann
  • in „Intermediate Language“ (IL) Befehle für CLR übersetzt, und anschließen für den echten CPU (Hardware)
  • ins Assembler übersetzt

Fangen wir mal mit Listing 1 (ugly code) an:

Von links nach rechts: C# optimiert, IL, ASM (intel x64)

Man stelle sich vor der Code oben wird in einer Schleife immer wieder aufgerufen …

Und nun schauen wir uns Listing 2 (better code) an:

Von links nach rechts: C# optimiert, IL, ASM (intel x64)

Wie man sieht, wird der CPU bei Listing 1 (ugly code) unnötig beschäftigt. Man könnte meinen, es handle sich um eine „Beschäftigungstherapie“ für den CPU: „Das es eahm jo ned langweilig wird!„.

Wegen solche Code-Schreiberlinge, benötigen wir immer stärkere CPUs, noch mehr Cores pro Sockel, und immer mehr RAM. Von der Energieverschwendung und dem CO₂ Bilanz ganz zu schweigen.

Clean Code: Trennen von Daten aus Algorithmen

Es ist immer eine gute Idee, Daten von Algorithmen zu trennen, um mehr Abstraktion zu schaffen. Dadurch erhält man viele Vorteile, die wir nach ein kurzes, praktisches Beispiel aus dem Alltag sehen werden.

Zuerst schauen wir uns mal, exemplarisch, eine Methode zum Initialisieren einer Datentabelle, namens „settings“ an:

public void InitSettingsValues()
{
    _DataSet.Tables["settings"].Rows[0]["column0"] = false;
    _DataSet.Tables["settings"].Rows[0]["column1"] = true;
    _DataSet.Tables["settings"].Rows[0]["column2"] = null;
    _DataSet.Tables["settings"].Rows[0]["column3"] = 47.11;
    _DataSet.Tables["settings"].Rows[0]["column4"] = "Hello World!";
    _DataSet.Tables["settings"].Rows[0]["column5"] = new DateTime(2001, 9, 11);
    _DataSet.Tables["settings"].Rows[0]["column6"] = 6;
    _DataSet.Tables["settings"].Rows[0]["column7"] = 7;
    _DataSet.Tables["settings"].Rows[0]["column8"] = 8;
    _DataSet.Tables["settings"].Rows[0]["column9"] = 9;
    _DataSet.Tables["settings"].Rows[0]["column10"] = 10;
    //... usw.
    _DataSet.Tables["settings"].Rows[0]["column99"] = 99;
    _DataSet.Tables["settings"].Rows[0]["column100"] = 100;
}

Dieser Code ist alles andere als optimal:

  1. Ist weder leicht lesbar, noch überschaubar
  2. Enthält viele Wiederholungen (DRY: Don’t Repeat Yourself)
  3. Ist nicht abstrakt, sondern explizit („settings“ und 0 als Zeilennummer)
  4. Man kann es nicht wiederverwenden (für die Tabelle „nichtSettings“ muss eigene Methode geschrieben werden)
  5. Man kann es nicht leicht pflegen, erweitern, oder mehrere Varianten davon, nebeneinander erstellen/verwenden/aufrufen (siehe den besseren Code unten):
    • Was tun wir, wenn bei nächstem Release, per Konfiguration, entweder die eine oder andere Methode aufgerufen werden soll? (z.B. für spezielle Kunden, oder Rollback der Version aufgrund gravierende Sicherheitsmangel udg.)
    • Was tun wir, wenn es N (N >= 2) Daten-Varianten gibt (der eine hat X Spalten mehr/weniger)?
  6. Man kann nicht wirklich von „Algorithmus“ sprechen
  7. Der Code für CPU und RAM wird unnötig aufgeblasen, und lässt sich schlecht optimieren (siehe IL und Assembler Listings für Intel x64 zum Vergleichen am Ende des Beitrags)


Und nun trennen wir die Daten von Algorithmus:

// Daten.
private (string ColumnName, object Value)[] _SettingsInitValues = { ("column0", false), ("column1", true), ("column2", null), ("column3", 47.11), ("column4", "Hello World!"), ("column5", new DateTime(2001, 9, 11)), ("column6", 6), ("column7", 7), ("column8", 8), ("column9", 9), ("column10", 10),
    //... usw.
    ("column99", 99), ("column100", 100)
};

// Algorithmus.
public void SetTableValues(DataSet pDataSet, string pTableName, int pRowIndex, IEnumerable<(string ColumnName, object Value)> pColumnNameValueTuples)
{
    foreach (var tuple in pColumnNameValueTuples)
    {
        pDataSet.Tables[pTableName].Rows[pRowIndex][tuple.ColumnName] = tuple.Value;
    }
}

Die Methode besteht nun aus die Signatur plus 2 Zeilen, und bietet folgende Qualitätsmerkmale:

  1. Leichter lesbar: 2 Zeilen
  2. Überschaubar: 2 Zeilen
  3. Verständlicher/Selbsterklärend: 2 Zeilen
  4. Abstrakter: kann auch für andere Tabellen des „_DataSet“ oder sogar andere DataSets verwendet werden
  5. Leichter zu pflegen
  6. Leichter zu Debuggen
  7. Leichter zu erweitern
  8. Grundsätzlich: leichter zu ändern
  9. Man kann weitere Daten-Varianten nebeneinander einsetzen

Mit diesem Code, brauchen wir uns keine Sorgen machen, denn:

  • Es ist wiederverwendbar (für andere DataSets, Tabellen, Zeilen-Index)
  • Falls es für Kunde X oder Version Y andere Daten-Varianten genommen werden müssen: kein Problem
  • Falls es gleichzeitig unterschiedliche Daten-Varianten für unterschiedliche SW-Versionen genommen werden müssen: kein Problem!
  • Das Gleiche für die Nutzung unterschiedliche Daten-Varianten per Konfiguration: kein Problem!
  • Sollte aus welchem Grund auch immer, ein Rollback der SW-Version unternommen werden muss: kein Problem!
  • Sollte Microsoft in der nächste .NET Framework, Core, NET 9 usw. irgendwas an DataSet, deren Tabelle, Zeile oder Spalten ändern: kein Problem! Wir müssen nur eine einzige Zeile ändern

Wie man hier sieht, kann man für 10 verschiedene Versionen/Kunden, die Settings-Tabelle per Konfiguration mit 10 unterschiedliche Daten-Werte initialisieren, OHNE unsere 2 Zeilen Code ändern zu müssen:

private (string ColumnName, object Value)[] _Vers_1_0 = ("column0", 0), ("column1", "dw4mrkzy") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_1 = ("column0", 2), ("column1", "LR0_uClc") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_2 = ("column0", 0), ("column1", "SR1Vj5ts") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_3 = ("column0", 2), ("column1", "hiKlLG55") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_4 = ("column0", 8), ("column1", "s2Z4MkKZ") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_5 = ("column0", 0), ("column1", "qXZM0x4h") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_6 = ("column0", 0), ("column1", "UQryY_SV") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_7 = ("column0", 0), ("column1", "CB80QEcy") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_8 = ("column0", 8), ("column1", "S59sdhFc") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_9 = ("column0", 7), ("column1", "FSgCWXii") /*... usw. */};
private (string ColumnName, object Value)[] _Vers_1_10 = ("column0", 3), ("column1", "hdNvQ8Wt") /*... usw. */};

Die Unterschiede in IL:

Zuerst der IL Code von „SetTableValues(…)“ (optimaler Code):

Die letzte Zeilennummer (in Hex) ist 0x0057 = 87 dez.

IL_0000  nop   
IL_0001  nop   
IL_0002  ldarg.3   
IL_0003  callvirt  IEnumerable <ValueTuple<String,Object>>.GetEnumerator () 
IL_0008  stloc.0   
IL_0009  br.s  IL_0042 
IL_000B  ldloc.0   
IL_000C  callvirt  IEnumerator <ValueTuple<String,Object>>.get_Current () 
IL_0011  stloc.1     // tuple 
IL_0012  nop   
IL_0013  ldarg.0   
IL_0014  ldfld  UserQuery._DataSet 
IL_0019  callvirt  DataSet.get_Tables () 
IL_001E  ldarg.1   
IL_001F  callvirt  DataTableCollection.get_Item (String) 
IL_0024  callvirt  DataTable.get_Rows () 
IL_0029  ldarg.2   
IL_002A  callvirt  DataRowCollection.get_Item (Int32) 
IL_002F  ldloc.1     // tuple 
IL_0030  ldfld  ValueTuple <String, Object>.Item1 
IL_0035  ldloc.1     // tuple 
IL_0036  ldfld  ValueTuple <String, Object>.Item2 
IL_003B  callvirt  DataRow.set_Item (String, Object) 
IL_0040  nop   
IL_0041  nop   
IL_0042  ldloc.0   
IL_0043  callvirt  IEnumerator.MoveNext () 
IL_0048  brtrue.s  IL_000B 
IL_004A  leave.s  IL_0057 
IL_004C  ldloc.0   
IL_004D  brfalse.s  IL_0056 
IL_004F  ldloc.0   
IL_0050  callvirt  IDisposable.Dispose () 
IL_0055  nop   
IL_0056  endfinally   
IL_0057  ret  

Der IL Code von „InitSettingsValues()“ (nicht optimaler Code):

Die letzte Zeilennummer (in Hex) lautet: 0x0291 = 657 dez.

IL_0000  nop   
IL_0001  ldarg.0   
IL_0002  ldfld  UserQuery._DataSet 
IL_0007  callvirt  DataSet.get_Tables () 
IL_000C  ldstr  "settings" 
IL_0011  callvirt  DataTableCollection.get_Item (String) 
IL_0016  callvirt  DataTable.get_Rows () 
IL_001B  ldc.i4.0   
IL_001C  callvirt  DataRowCollection.get_Item (Int32) 
IL_0021  ldstr  "column0" 
IL_0026  ldc.i4.0   
IL_0027  box  Boolean 
IL_002C  callvirt  DataRow.set_Item (String, Object) 
IL_0031  nop   
IL_0032  ldarg.0   
IL_0033  ldfld  UserQuery._DataSet 
IL_0038  callvirt  DataSet.get_Tables () 
IL_003D  ldstr  "settings" 
IL_0042  callvirt  DataTableCollection.get_Item (String) 
IL_0047  callvirt  DataTable.get_Rows () 
IL_004C  ldc.i4.0   
IL_004D  callvirt  DataRowCollection.get_Item (Int32) 
IL_0052  ldstr  "column1" 
IL_0057  ldc.i4.1   
IL_0058  box  Boolean 
IL_005D  callvirt  DataRow.set_Item (String, Object) 
IL_0062  nop   
IL_0063  ldarg.0   
IL_0064  ldfld  UserQuery._DataSet 
IL_0069  callvirt  DataSet.get_Tables () 
IL_006E  ldstr  "settings" 
IL_0073  callvirt  DataTableCollection.get_Item (String) 
IL_0078  callvirt  DataTable.get_Rows () 
IL_007D  ldc.i4.0   
IL_007E  callvirt  DataRowCollection.get_Item (Int32) 
IL_0083  ldstr  "column2" 
IL_0088  ldnull   
IL_0089  callvirt  DataRow.set_Item (String, Object) 
IL_008E  nop   
IL_008F  ldarg.0   
IL_0090  ldfld  UserQuery._DataSet 
IL_0095  callvirt  DataSet.get_Tables () 
IL_009A  ldstr  "settings" 
IL_009F  callvirt  DataTableCollection.get_Item (String) 
IL_00A4  callvirt  DataTable.get_Rows () 
IL_00A9  ldc.i4.0   
IL_00AA  callvirt  DataRowCollection.get_Item (Int32) 
IL_00AF  ldstr  "column3" 
IL_00B4  ldc.r8  AE 47 E1 7A 14 8E 47 40  // 47,11 
IL_00BD  box  Double 
IL_00C2  callvirt  DataRow.set_Item (String, Object) 
IL_00C7  nop   
IL_00C8  ldarg.0   
IL_00C9  ldfld  UserQuery._DataSet 
IL_00CE  callvirt  DataSet.get_Tables () 
IL_00D3  ldstr  "settings" 
IL_00D8  callvirt  DataTableCollection.get_Item (String) 
IL_00DD  callvirt  DataTable.get_Rows () 
IL_00E2  ldc.i4.0   
IL_00E3  callvirt  DataRowCollection.get_Item (Int32) 
IL_00E8  ldstr  "column4" 
IL_00ED  ldstr  "Hello World!" 
IL_00F2  callvirt  DataRow.set_Item (String, Object) 
IL_00F7  nop   
IL_00F8  ldarg.0   
IL_00F9  ldfld  UserQuery._DataSet 
IL_00FE  callvirt  DataSet.get_Tables () 
IL_0103  ldstr  "settings" 
IL_0108  callvirt  DataTableCollection.get_Item (String) 
IL_010D  callvirt  DataTable.get_Rows () 
IL_0112  ldc.i4.0   
IL_0113  callvirt  DataRowCollection.get_Item (Int32) 
IL_0118  ldstr  "column5" 
IL_011D  ldc.i4  D1 07 00 00  // 2001 
IL_0122  ldc.i4.s  09  // 9 
IL_0124  ldc.i4.s  0B  // 11 
IL_0126  newobj  DateTime..ctor 
IL_012B  box  DateTime 
IL_0130  callvirt  DataRow.set_Item (String, Object) 
IL_0135  nop   
IL_0136  ldarg.0   
IL_0137  ldfld  UserQuery._DataSet 
IL_013C  callvirt  DataSet.get_Tables () 
IL_0141  ldstr  "settings" 
IL_0146  callvirt  DataTableCollection.get_Item (String) 
IL_014B  callvirt  DataTable.get_Rows () 
IL_0150  ldc.i4.0   
IL_0151  callvirt  DataRowCollection.get_Item (Int32) 
IL_0156  ldstr  "column6" 
IL_015B  ldc.i4.6   
IL_015C  box  Int32 
IL_0161  callvirt  DataRow.set_Item (String, Object) 
IL_0166  nop   
IL_0167  ldarg.0   
IL_0168  ldfld  UserQuery._DataSet 
IL_016D  callvirt  DataSet.get_Tables () 
IL_0172  ldstr  "settings" 
IL_0177  callvirt  DataTableCollection.get_Item (String) 
IL_017C  callvirt  DataTable.get_Rows () 
IL_0181  ldc.i4.0   
IL_0182  callvirt  DataRowCollection.get_Item (Int32) 
IL_0187  ldstr  "column7" 
IL_018C  ldc.i4.7   
IL_018D  box  Int32 
IL_0192  callvirt  DataRow.set_Item (String, Object) 
IL_0197  nop   
IL_0198  ldarg.0   
IL_0199  ldfld  UserQuery._DataSet 
IL_019E  callvirt  DataSet.get_Tables () 
IL_01A3  ldstr  "settings" 
IL_01A8  callvirt  DataTableCollection.get_Item (String) 
IL_01AD  callvirt  DataTable.get_Rows () 
IL_01B2  ldc.i4.0   
IL_01B3  callvirt  DataRowCollection.get_Item (Int32) 
IL_01B8  ldstr  "column8" 
IL_01BD  ldc.i4.8   
IL_01BE  box  Int32 
IL_01C3  callvirt  DataRow.set_Item (String, Object) 
IL_01C8  nop   
IL_01C9  ldarg.0   
IL_01CA  ldfld  UserQuery._DataSet 
IL_01CF  callvirt  DataSet.get_Tables () 
IL_01D4  ldstr  "settings" 
IL_01D9  callvirt  DataTableCollection.get_Item (String) 
IL_01DE  callvirt  DataTable.get_Rows () 
IL_01E3  ldc.i4.0   
IL_01E4  callvirt  DataRowCollection.get_Item (Int32) 
IL_01E9  ldstr  "column9" 
IL_01EE  ldc.i4.s  09  // 9 
IL_01F0  box  Int32 
IL_01F5  callvirt  DataRow.set_Item (String, Object) 
IL_01FA  nop   
IL_01FB  ldarg.0   
IL_01FC  ldfld  UserQuery._DataSet 
IL_0201  callvirt  DataSet.get_Tables () 
IL_0206  ldstr  "settings" 
IL_020B  callvirt  DataTableCollection.get_Item (String) 
IL_0210  callvirt  DataTable.get_Rows () 
IL_0215  ldc.i4.0   
IL_0216  callvirt  DataRowCollection.get_Item (Int32) 
IL_021B  ldstr  "column10" 
IL_0220  ldc.i4.s  0A  // 10 
IL_0222  box  Int32 
IL_0227  callvirt  DataRow.set_Item (String, Object) 
IL_022C  nop   
IL_022D  ldarg.0   
IL_022E  ldfld  UserQuery._DataSet 
IL_0233  callvirt  DataSet.get_Tables () 
IL_0238  ldstr  "settings" 
IL_023D  callvirt  DataTableCollection.get_Item (String) 
IL_0242  callvirt  DataTable.get_Rows () 
IL_0247  ldc.i4.0   
IL_0248  callvirt  DataRowCollection.get_Item (Int32) 
IL_024D  ldstr  "column99" 
IL_0252  ldc.i4.s  63  // 99 
IL_0254  box  Int32 
IL_0259  callvirt  DataRow.set_Item (String, Object) 
IL_025E  nop   
IL_025F  ldarg.0   
IL_0260  ldfld  UserQuery._DataSet 
IL_0265  callvirt  DataSet.get_Tables () 
IL_026A  ldstr  "settings" 
IL_026F  callvirt  DataTableCollection.get_Item (String) 
IL_0274  callvirt  DataTable.get_Rows () 
IL_0279  ldc.i4.0   
IL_027A  callvirt  DataRowCollection.get_Item (Int32) 
IL_027F  ldstr  "column100" 
IL_0284  ldc.i4.s  64  // 100 
IL_0286  box  Int32 
IL_028B  callvirt  DataRow.set_Item (String, Object) 
IL_0290  nop   
IL_0291  ret  

Die Intel x64 Assembler Unterschiede:

Zuerst der Assembler Code von „SetTableValues(…)“ (optimaler Code):

Die letzte Zeilennummer (in Hex) ist 0x017d = 575 dez.

L0000 push rbp 
L0001 sub rsp, 0x80 
L0008 lea rbp, [rsp+0x80] 
L0010 xor eax, eax 
L0012 mov [rbp-0x58], rax 
L0016 xorps xmm4, xmm4 
L0019 movaps [rbp-0x50], xmm4 
L001d movaps [rbp-0x40], xmm4 
L0021 movaps [rbp-0x30], xmm4 
L0025 movaps [rbp-0x20], xmm4 
L0029 movaps [rbp-0x10], xmm4 
L002d mov [rbp-0x60], rsp 
L0031 mov [rbp+0x10], rcx 
L0035 mov [rbp+0x18], rdx 
L0039 mov [rbp+0x20], r8d 
L003d mov [rbp+0x28], r9 
L0041 mov rax, 0x7ffd31adc320 
L004b cmp dword ptr [rax], 0 
L004e je short L0055 
L0050 call 0x00007ffd906485f0 
L0055 nop  
L0056 nop  
L0057 mov rcx, [rbp+0x28] 
L005b mov r11, 0x7ffd31ad7020 
L0065 mov rax, 0x7ffd31ad7020 
L006f call qword ptr [rax] 
L0071 mov [rbp-0x20], rax 
L0075 mov rcx, [rbp-0x20] 
L0079 mov [rbp-8], rcx 
L007d nop  
L007e jmp L0106 
L0083 lea rdx, [rbp-0x38] 
L0087 mov rcx, [rbp-8] 
L008b mov r11, 0x7ffd31ad7030 
L0095 mov rax, 0x7ffd31ad7030 
L009f call qword ptr [rax] 
L00a1 movups xmm0, [rbp-0x38] 
L00a5 movups [rbp-0x18], xmm0 
L00a9 nop  
L00aa mov rcx, [rbp+0x10] 
L00ae mov rcx, [rcx+0x10] 
L00b2 cmp [rcx], ecx 
L00b4 call System.Data.DataSet.get_Tables() 
L00b9 mov [rbp-0x40], rax 
L00bd mov rcx, [rbp-0x40] 
L00c1 mov rdx, [rbp+0x18] 
L00c5 cmp [rcx], ecx 
L00c7 call System.Data.DataTableCollection.get_Item(System.String) 
L00cc mov [rbp-0x48], rax 
L00d0 mov rcx, [rbp-0x48] 
L00d4 cmp [rcx], ecx 
L00d6 call System.Data.DataTable.get_Rows() 
L00db mov [rbp-0x50], rax 
L00df mov rcx, [rbp-0x50] 
L00e3 mov edx, [rbp+0x20] 
L00e6 cmp [rcx], ecx 
L00e8 call System.Data.DataRowCollection.get_Item(Int32) 
L00ed mov [rbp-0x58], rax 
L00f1 mov rcx, [rbp-0x58] 
L00f5 mov rdx, [rbp-0x18] 
L00f9 mov r8, [rbp-0x10] 
L00fd cmp [rcx], ecx 
L00ff call System.Data.DataRow.set_Item(System.String, System.Object) 
L0104 nop  
L0105 nop  
L0106 mov rcx, [rbp-8] 
L010a mov r11, 0x7ffd31ad7028 
L0114 mov rax, 0x7ffd31ad7028 
L011e call qword ptr [rax] 
L0120 mov [rbp-0x24], eax 
L0123 cmp dword ptr [rbp-0x24], 0 
L0127 jne L0083 
L012d nop  
L012e jmp short L0130 
L0130 mov rcx, rsp 
L0133 call L0140 
L0138 nop  
L0139 nop  
L013a lea rsp, [rbp] 
L013e pop rbp 
L013f ret  
L0140 push rbp 
L0141 sub rsp, 0x30 
L0145 mov rbp, [rcx+0x20] 
L0149 mov [rsp+0x20], rbp 
L014e lea rbp, [rbp+0x80] 
L0155 cmp qword ptr [rbp-8], 0 
L015a je short L0177 
L015c mov rcx, [rbp-8] 
L0160 mov r11, 0x7ffd31ad7038 
L016a mov rax, 0x7ffd31ad7038 
L0174 call qword ptr [rax] 
L0176 nop  
L0177 nop  
L0178 add rsp, 0x30 
L017c pop rbp 
L017d ret 

Der Assembler Code von „InitSettingsValues()“ (nicht optimaler Code):

Die letzte Zeilennummer (in Hex) lautet: 0x0843 = 2115 dez.

L0000 push rbp 
L0001 sub rsp, 0x1e0 
L0008 lea rbp, [rsp+0x1e0] 
L0010 xorps xmm4, xmm4 
L0013 movaps [rbp-0x1c0], xmm4 
L001a mov rax, 0xfffffffffffffe50 
L0024 movaps [rax+rbp], xmm4 
L0028 movaps [rbp+rax+0x10], xmm4 
L002d movaps [rbp+rax+0x20], xmm4 
L0032 add rax, 0x30 
L0036 jne short L0024 
L0038 mov [rbp+0x10], rcx 
L003c mov rax, 0x7ffd3193c320 
L0046 cmp dword ptr [rax], 0 
L0049 je short L0050 
L004b call 0x00007ffd906485f0 
L0050 nop  
L0051 mov rcx, [rbp+0x10] 
L0055 mov rcx, [rcx+0x10] 
L0059 cmp [rcx], ecx 
L005b call System.Data.DataSet.get_Tables() 
L0060 mov [rbp-8], rax 
L0064 mov rdx, 0x1b5d3cd23a8 
L006e mov rdx, [rdx] 
L0071 mov rcx, [rbp-8] 
L0075 cmp [rcx], ecx 
L0077 call System.Data.DataTableCollection.get_Item(System.String) 
L007c mov [rbp-0x10], rax 
L0080 mov rcx, [rbp-0x10] 
L0084 cmp [rcx], ecx 
L0086 call System.Data.DataTable.get_Rows() 
L008b mov [rbp-0x18], rax 
L008f mov rcx, [rbp-0x18] 
L0093 xor edx, edx 
L0095 cmp [rcx], ecx 
L0097 call System.Data.DataRowCollection.get_Item(Int32) 
L009c mov [rbp-0x20], rax 
L00a0 mov rcx, 0x7ffd30967238 
L00aa call 0x00007ffd9050af00 
L00af mov [rbp-0x28], rax 
L00b3 mov r8, [rbp-0x28] 
L00b7 mov byte ptr [r8+8], 0 
L00bc mov r8, [rbp-0x28] 
L00c0 mov rdx, 0x1b5d3ceb6b8 
L00ca mov rdx, [rdx] 
L00cd mov rcx, [rbp-0x20] 
L00d1 cmp [rcx], ecx 
L00d3 call System.Data.DataRow.set_Item(System.String, System.Object) 
L00d8 nop  
L00d9 mov rcx, [rbp+0x10] 
L00dd mov rcx, [rcx+0x10] 
L00e1 cmp [rcx], ecx 
L00e3 call System.Data.DataSet.get_Tables() 
L00e8 mov [rbp-0x30], rax 
L00ec mov rdx, 0x1b5d3cd23a8 
L00f6 mov rdx, [rdx] 
L00f9 mov rcx, [rbp-0x30] 
L00fd cmp [rcx], ecx 
L00ff call System.Data.DataTableCollection.get_Item(System.String) 
L0104 mov [rbp-0x38], rax 
L0108 mov rcx, [rbp-0x38] 
L010c cmp [rcx], ecx 
L010e call System.Data.DataTable.get_Rows() 
L0113 mov [rbp-0x40], rax 
L0117 mov rcx, [rbp-0x40] 
L011b xor edx, edx 
L011d cmp [rcx], ecx 
L011f call System.Data.DataRowCollection.get_Item(Int32) 
L0124 mov [rbp-0x48], rax 
L0128 mov rcx, 0x7ffd30967238 
L0132 call 0x00007ffd9050af00 
L0137 mov [rbp-0x28], rax 
L013b mov r8, [rbp-0x28] 
L013f mov byte ptr [r8+8], 1 
L0144 mov r8, [rbp-0x28] 
L0148 mov rdx, 0x1b5d3ceb6c0 
L0152 mov rdx, [rdx] 
L0155 mov rcx, [rbp-0x48] 
L0159 cmp [rcx], ecx 
L015b call System.Data.DataRow.set_Item(System.String, System.Object) 
L0160 nop  
L0161 mov rcx, [rbp+0x10] 
L0165 mov rcx, [rcx+0x10] 
L0169 cmp [rcx], ecx 
L016b call System.Data.DataSet.get_Tables() 
L0170 mov [rbp-0x50], rax 
L0174 mov rdx, 0x1b5d3cd23a8 
L017e mov rdx, [rdx] 
L0181 mov rcx, [rbp-0x50] 
L0185 cmp [rcx], ecx 
L0187 call System.Data.DataTableCollection.get_Item(System.String) 
L018c mov [rbp-0x58], rax 
L0190 mov rcx, [rbp-0x58] 
L0194 cmp [rcx], ecx 
L0196 call System.Data.DataTable.get_Rows() 
L019b mov [rbp-0x60], rax 
L019f mov rcx, [rbp-0x60] 
L01a3 xor edx, edx 
L01a5 cmp [rcx], ecx 
L01a7 call System.Data.DataRowCollection.get_Item(Int32) 
L01ac mov [rbp-0x68], rax 
L01b0 mov rdx, 0x1b5d3ceb6c8 
L01ba mov rdx, [rdx] 
L01bd mov rcx, [rbp-0x68] 
L01c1 xor r8d, r8d 
L01c4 cmp [rcx], ecx 
L01c6 call System.Data.DataRow.set_Item(System.String, System.Object) 
L01cb nop  
L01cc mov rcx, [rbp+0x10] 
L01d0 mov rcx, [rcx+0x10] 
L01d4 cmp [rcx], ecx 
L01d6 call System.Data.DataSet.get_Tables() 
L01db mov [rbp-0x70], rax 
L01df mov rdx, 0x1b5d3cd23a8 
L01e9 mov rdx, [rdx] 
L01ec mov rcx, [rbp-0x70] 
L01f0 cmp [rcx], ecx 
L01f2 call System.Data.DataTableCollection.get_Item(System.String) 
L01f7 mov [rbp-0x78], rax 
L01fb mov rcx, [rbp-0x78] 
L01ff cmp [rcx], ecx 
L0201 call System.Data.DataTable.get_Rows() 
L0206 mov [rbp-0x80], rax 
L020a mov rcx, [rbp-0x80] 
L020e xor edx, edx 
L0210 cmp [rcx], ecx 
L0212 call System.Data.DataRowCollection.get_Item(Int32) 
L0217 mov [rbp-0x88], rax 
L021e mov rcx, 0x7ffd3096e688 
L0228 call 0x00007ffd9050af00 
L022d mov [rbp-0x28], rax 
L0231 mov r8, [rbp-0x28] 
L0235 movsd xmm0, [UserQuery.InitSettingsValues()] 
L023d movsd [r8+8], xmm0 
L0243 mov r8, [rbp-0x28] 
L0247 mov rdx, 0x1b5d3ceb6d0 
L0251 mov rdx, [rdx] 
L0254 mov rcx, [rbp-0x88] 
L025b cmp [rcx], ecx 
L025d call System.Data.DataRow.set_Item(System.String, System.Object) 
L0262 nop  
L0263 mov rcx, [rbp+0x10] 
L0267 mov rcx, [rcx+0x10] 
L026b cmp [rcx], ecx 
L026d call System.Data.DataSet.get_Tables() 
L0272 mov [rbp-0x90], rax 
L0279 mov rdx, 0x1b5d3cd23a8 
L0283 mov rdx, [rdx] 
L0286 mov rcx, [rbp-0x90] 
L028d cmp [rcx], ecx 
L028f call System.Data.DataTableCollection.get_Item(System.String) 
L0294 mov [rbp-0x98], rax 
L029b mov rcx, [rbp-0x98] 
L02a2 cmp [rcx], ecx 
L02a4 call System.Data.DataTable.get_Rows() 
L02a9 mov [rbp-0xa0], rax 
L02b0 mov rcx, [rbp-0xa0] 
L02b7 xor edx, edx 
L02b9 cmp [rcx], ecx 
L02bb call System.Data.DataRowCollection.get_Item(Int32) 
L02c0 mov [rbp-0xa8], rax 
L02c7 mov r8, 0x1b5d3ceb6e0 
L02d1 mov r8, [r8] 
L02d4 mov rdx, 0x1b5d3ceb6d8 
L02de mov rdx, [rdx] 
L02e1 mov rcx, [rbp-0xa8] 
L02e8 cmp [rcx], ecx 
L02ea call System.Data.DataRow.set_Item(System.String, System.Object) 
L02ef nop  
L02f0 mov rcx, [rbp+0x10] 
L02f4 mov rcx, [rcx+0x10] 
L02f8 cmp [rcx], ecx 
L02fa call System.Data.DataSet.get_Tables() 
L02ff mov [rbp-0xb0], rax 
L0306 mov rdx, 0x1b5d3cd23a8 
L0310 mov rdx, [rdx] 
L0313 mov rcx, [rbp-0xb0] 
L031a cmp [rcx], ecx 
L031c call System.Data.DataTableCollection.get_Item(System.String) 
L0321 mov [rbp-0xb8], rax 
L0328 mov rcx, [rbp-0xb8] 
L032f cmp [rcx], ecx 
L0331 call System.Data.DataTable.get_Rows() 
L0336 mov [rbp-0xc0], rax 
L033d mov rcx, [rbp-0xc0] 
L0344 xor edx, edx 
L0346 cmp [rcx], ecx 
L0348 call System.Data.DataRowCollection.get_Item(Int32) 
L034d mov [rbp-0xc8], rax 
L0354 xor ecx, ecx 
L0356 mov [rbp-0xd0], rcx 
L035d lea rcx, [rbp-0xd0] 
L0364 mov edx, 0x7d1 
L0369 mov r8d, 9 
L036f mov r9d, 0xb 
L0375 call System.DateTime..ctor(Int32, Int32, Int32) 
L037a mov rdx, 0x1b5d3ceb6e8 
L0384 mov rdx, [rdx] 
L0387 mov [rbp-0x1c0], rdx 
L038e lea rdx, [rbp-0xd0] 
L0395 mov rcx, 0x7ffd30bef900 
L039f call 0x00007ffd9050af40 
L03a4 mov [rbp-0x1b8], rax 
L03ab mov r8, [rbp-0x1b8] 
L03b2 mov rdx, [rbp-0x1c0] 
L03b9 mov rcx, [rbp-0xc8] 
L03c0 cmp [rcx], ecx 
L03c2 call System.Data.DataRow.set_Item(System.String, System.Object) 
L03c7 nop  
L03c8 mov rcx, [rbp+0x10] 
L03cc mov rcx, [rcx+0x10] 
L03d0 cmp [rcx], ecx 
L03d2 call System.Data.DataSet.get_Tables() 
L03d7 mov [rbp-0xd8], rax 
L03de mov rdx, 0x1b5d3cd23a8 
L03e8 mov rdx, [rdx] 
L03eb mov rcx, [rbp-0xd8] 
L03f2 cmp [rcx], ecx 
L03f4 call System.Data.DataTableCollection.get_Item(System.String) 
L03f9 mov [rbp-0xe0], rax 
L0400 mov rcx, [rbp-0xe0] 
L0407 cmp [rcx], ecx 
L0409 call System.Data.DataTable.get_Rows() 
L040e mov [rbp-0xe8], rax 
L0415 mov rcx, [rbp-0xe8] 
L041c xor edx, edx 
L041e cmp [rcx], ecx 
L0420 call System.Data.DataRowCollection.get_Item(Int32) 
L0425 mov [rbp-0xf0], rax 
L042c mov rcx, 0x7ffd3096b258 
L0436 call 0x00007ffd9050af00 
L043b mov [rbp-0x28], rax 
L043f mov r8, [rbp-0x28] 
L0443 mov dword ptr [r8+8], 6 
L044b mov r8, [rbp-0x28] 
L044f mov rdx, 0x1b5d3ceb6f0 
L0459 mov rdx, [rdx] 
L045c mov rcx, [rbp-0xf0] 
L0463 cmp [rcx], ecx 
L0465 call System.Data.DataRow.set_Item(System.String, System.Object) 
L046a nop  
L046b mov rcx, [rbp+0x10] 
L046f mov rcx, [rcx+0x10] 
L0473 cmp [rcx], ecx 
L0475 call System.Data.DataSet.get_Tables() 
L047a mov [rbp-0xf8], rax 
L0481 mov rdx, 0x1b5d3cd23a8 
L048b mov rdx, [rdx] 
L048e mov rcx, [rbp-0xf8] 
L0495 cmp [rcx], ecx 
L0497 call System.Data.DataTableCollection.get_Item(System.String) 
L049c mov [rbp-0x100], rax 
L04a3 mov rcx, [rbp-0x100] 
L04aa cmp [rcx], ecx 
L04ac call System.Data.DataTable.get_Rows() 
L04b1 mov [rbp-0x108], rax 
L04b8 mov rcx, [rbp-0x108] 
L04bf xor edx, edx 
L04c1 cmp [rcx], ecx 
L04c3 call System.Data.DataRowCollection.get_Item(Int32) 
L04c8 mov [rbp-0x110], rax 
L04cf mov rcx, 0x7ffd3096b258 
L04d9 call 0x00007ffd9050af00 
L04de mov [rbp-0x28], rax 
L04e2 mov r8, [rbp-0x28] 
L04e6 mov dword ptr [r8+8], 7 
L04ee mov r8, [rbp-0x28] 
L04f2 mov rdx, 0x1b5d3ceb6f8 
L04fc mov rdx, [rdx] 
L04ff mov rcx, [rbp-0x110] 
L0506 cmp [rcx], ecx 
L0508 call System.Data.DataRow.set_Item(System.String, System.Object) 
L050d nop  
L050e mov rcx, [rbp+0x10] 
L0512 mov rcx, [rcx+0x10] 
L0516 cmp [rcx], ecx 
L0518 call System.Data.DataSet.get_Tables() 
L051d mov [rbp-0x118], rax 
L0524 mov rdx, 0x1b5d3cd23a8 
L052e mov rdx, [rdx] 
L0531 mov rcx, [rbp-0x118] 
L0538 cmp [rcx], ecx 
L053a call System.Data.DataTableCollection.get_Item(System.String) 
L053f mov [rbp-0x120], rax 
L0546 mov rcx, [rbp-0x120] 
L054d cmp [rcx], ecx 
L054f call System.Data.DataTable.get_Rows() 
L0554 mov [rbp-0x128], rax 
L055b mov rcx, [rbp-0x128] 
L0562 xor edx, edx 
L0564 cmp [rcx], ecx 
L0566 call System.Data.DataRowCollection.get_Item(Int32) 
L056b mov [rbp-0x130], rax 
L0572 mov rcx, 0x7ffd3096b258 
L057c call 0x00007ffd9050af00 
L0581 mov [rbp-0x28], rax 
L0585 mov r8, [rbp-0x28] 
L0589 mov dword ptr [r8+8], 8 
L0591 mov r8, [rbp-0x28] 
L0595 mov rdx, 0x1b5d3ceb700 
L059f mov rdx, [rdx] 
L05a2 mov rcx, [rbp-0x130] 
L05a9 cmp [rcx], ecx 
L05ab call System.Data.DataRow.set_Item(System.String, System.Object) 
L05b0 nop  
L05b1 mov rcx, [rbp+0x10] 
L05b5 mov rcx, [rcx+0x10] 
L05b9 cmp [rcx], ecx 
L05bb call System.Data.DataSet.get_Tables() 
L05c0 mov [rbp-0x138], rax 
L05c7 mov rdx, 0x1b5d3cd23a8 
L05d1 mov rdx, [rdx] 
L05d4 mov rcx, [rbp-0x138] 
L05db cmp [rcx], ecx 
L05dd call System.Data.DataTableCollection.get_Item(System.String) 
L05e2 mov [rbp-0x140], rax 
L05e9 mov rcx, [rbp-0x140] 
L05f0 cmp [rcx], ecx 
L05f2 call System.Data.DataTable.get_Rows() 
L05f7 mov [rbp-0x148], rax 
L05fe mov rcx, [rbp-0x148] 
L0605 xor edx, edx 
L0607 cmp [rcx], ecx 
L0609 call System.Data.DataRowCollection.get_Item(Int32) 
L060e mov [rbp-0x150], rax 
L0615 mov rcx, 0x7ffd3096b258 
L061f call 0x00007ffd9050af00 
L0624 mov [rbp-0x28], rax 
L0628 mov r8, [rbp-0x28] 
L062c mov dword ptr [r8+8], 9 
L0634 mov r8, [rbp-0x28] 
L0638 mov rdx, 0x1b5d3ceb708 
L0642 mov rdx, [rdx] 
L0645 mov rcx, [rbp-0x150] 
L064c cmp [rcx], ecx 
L064e call System.Data.DataRow.set_Item(System.String, System.Object) 
L0653 nop  
L0654 mov rcx, [rbp+0x10] 
L0658 mov rcx, [rcx+0x10] 
L065c cmp [rcx], ecx 
L065e call System.Data.DataSet.get_Tables() 
L0663 mov [rbp-0x158], rax 
L066a mov rdx, 0x1b5d3cd23a8 
L0674 mov rdx, [rdx] 
L0677 mov rcx, [rbp-0x158] 
L067e cmp [rcx], ecx 
L0680 call System.Data.DataTableCollection.get_Item(System.String) 
L0685 mov [rbp-0x160], rax 
L068c mov rcx, [rbp-0x160] 
L0693 cmp [rcx], ecx 
L0695 call System.Data.DataTable.get_Rows() 
L069a mov [rbp-0x168], rax 
L06a1 mov rcx, [rbp-0x168] 
L06a8 xor edx, edx 
L06aa cmp [rcx], ecx 
L06ac call System.Data.DataRowCollection.get_Item(Int32) 
L06b1 mov [rbp-0x170], rax 
L06b8 mov rcx, 0x7ffd3096b258 
L06c2 call 0x00007ffd9050af00 
L06c7 mov [rbp-0x28], rax 
L06cb mov r8, [rbp-0x28] 
L06cf mov dword ptr [r8+8], 0xa 
L06d7 mov r8, [rbp-0x28] 
L06db mov rdx, 0x1b5d3ceb710 
L06e5 mov rdx, [rdx] 
L06e8 mov rcx, [rbp-0x170] 
L06ef cmp [rcx], ecx 
L06f1 call System.Data.DataRow.set_Item(System.String, System.Object) 
L06f6 nop  
L06f7 mov rcx, [rbp+0x10] 
L06fb mov rcx, [rcx+0x10] 
L06ff cmp [rcx], ecx 
L0701 call System.Data.DataSet.get_Tables() 
L0706 mov [rbp-0x178], rax 
L070d mov rdx, 0x1b5d3cd23a8 
L0717 mov rdx, [rdx] 
L071a mov rcx, [rbp-0x178] 
L0721 cmp [rcx], ecx 
L0723 call System.Data.DataTableCollection.get_Item(System.String) 
L0728 mov [rbp-0x180], rax 
L072f mov rcx, [rbp-0x180] 
L0736 cmp [rcx], ecx 
L0738 call System.Data.DataTable.get_Rows() 
L073d mov [rbp-0x188], rax 
L0744 mov rcx, [rbp-0x188] 
L074b xor edx, edx 
L074d cmp [rcx], ecx 
L074f call System.Data.DataRowCollection.get_Item(Int32) 
L0754 mov [rbp-0x190], rax 
L075b mov rcx, 0x7ffd3096b258 
L0765 call 0x00007ffd9050af00 
L076a mov [rbp-0x28], rax 
L076e mov r8, [rbp-0x28] 
L0772 mov dword ptr [r8+8], 0x63 
L077a mov r8, [rbp-0x28] 
L077e mov rdx, 0x1b5d3ceb718 
L0788 mov rdx, [rdx] 
L078b mov rcx, [rbp-0x190] 
L0792 cmp [rcx], ecx 
L0794 call System.Data.DataRow.set_Item(System.String, System.Object) 
L0799 nop  
L079a mov rcx, [rbp+0x10] 
L079e mov rcx, [rcx+0x10] 
L07a2 cmp [rcx], ecx 
L07a4 call System.Data.DataSet.get_Tables() 
L07a9 mov [rbp-0x198], rax 
L07b0 mov rdx, 0x1b5d3cd23a8 
L07ba mov rdx, [rdx] 
L07bd mov rcx, [rbp-0x198] 
L07c4 cmp [rcx], ecx 
L07c6 call System.Data.DataTableCollection.get_Item(System.String) 
L07cb mov [rbp-0x1a0], rax 
L07d2 mov rcx, [rbp-0x1a0] 
L07d9 cmp [rcx], ecx 
L07db call System.Data.DataTable.get_Rows() 
L07e0 mov [rbp-0x1a8], rax 
L07e7 mov rcx, [rbp-0x1a8] 
L07ee xor edx, edx 
L07f0 cmp [rcx], ecx 
L07f2 call System.Data.DataRowCollection.get_Item(Int32) 
L07f7 mov [rbp-0x1b0], rax 
L07fe mov rcx, 0x7ffd3096b258 
L0808 call 0x00007ffd9050af00 
L080d mov [rbp-0x28], rax 
L0811 mov r8, [rbp-0x28] 
L0815 mov dword ptr [r8+8], 0x64 
L081d mov r8, [rbp-0x28] 
L0821 mov rdx, 0x1b5d3ceb720 
L082b mov rdx, [rdx] 
L082e mov rcx, [rbp-0x1b0] 
L0835 cmp [rcx], ecx 
L0837 call System.Data.DataRow.set_Item(System.String, System.Object) 
L083c nop  
L083d nop  
L083e lea rsp, [rbp] 
L0842 pop rbp 
L0843 ret 

Dazu mehr ist auf Wikipedia in Separation of Concerns zu lesen.

Schiache Pfuscher-Code, im Vergleich zu sauberer Professioneller-Code #1

Für J.E.
Wünsche dir alles Gute und viel Erfolg bei deinem Studium.


Diesen Code gab es tatsächlich, und ist nicht aus der Luft gegriffen.
Schaue dir diesen Code mal gründlich in Ruhe an. Was fällt dir auf?

/// <summary>
/// Gibt einen wert zurück
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public object GetValue(string name) 
{
    try
    {
        if (_DataSet == null) return null;

        if (_DataSet.Tables.Contains("Settings"))
        {
            return Convert.ChangeType(
                _DataSet.Tables["Settings"].Rows[0][name],
                _DataSet.Tables["Settings"].Columns[name].DataType);
        }
    }
    catch(Exception ex)
    {
        return null;
    }

    return "";
}

  1. Kommentar:
    • Ist nicht auf Englisch
    • Sagt nichts Neues aus, somit sagt nichts aus
  2. Rückgabe-Typ:
    • Unklarer/Vager Rückgabe-Typ (ein Objekt kann alles Mögliche sein: Animal, bool, Gott, Welt, …). Der Aufrufer muss Unboxing betreiben (falls der Wert nicht NULL ist, wie wir sehen werden)
  3. Der Name der Methode:
    • ist weder klar noch eindeutig
    • lässt vermuten, dass keine Ausnahmefehler abgefangen/behandelt werden
    • Der Aufrufer wird selbst, nicht notwendigerweise, Try-Catch-Block herumbauen
  4. NULL-Rückgabe:
    • Was macht der Aufrufer, wenn er für Einstellungsname „enabled“, true oder false erwartet, jedoch einen NULL bekommt?
    • Was machen 100 aufrufende Stellen im Code? 1000?
    • Wie oft muss aufrufender Code, die NULL-Eventualität prüfen, um darauf richtig zu reagieren?
    • Was passiert mit 100 aufrufende Stellen im Code, wenn diese Methode ausgebessert wird?
  5. Mehrfaches Erzeugen und Verwenden von inhaltlich gleichem String:
    • Erzeugen von Strings ist teuer. Warum also dreimal das Gleiche?
    • Was passiert, wenn der DB-Entwickler die Tabelle in „Config“ umbenennt? Wie findet man diese Stellen überall im Code? Wie oft muss man dann der Name aktualisieren?
  6. Rückgabe-Typ: Angenommen „DataType“ = Single oder Double …
    • Der float/double Wert wird geboxt, damit der Aufrufer es wieder unboxt? Boxing/Unboxing ist ein teurer Spaß!
  7. Try-Catch-Block:
    • Wozu? Was soll schiefgehen? Welcher Ausnahmefehler soll da geworfen werden? Eine DB-Tabelle mit Einstellungen taucht (in ein ordentliches Schema und SW-System) nicht einfach auf, und verschwindet einfach so!
  8. Exception-Typ und Variable:
    • Wozu? Was wird mit den Informationen in „ex“ gemacht?
  9. Rückgabe-Typ: wurde schon weiter oben thematisiert
  10. Rückgabe-Typ:
    • Aha! Ach, so ist das! Warum wird auf einmal eine leere Zeichenkette zurückgeliefert? Welcher Aufrufer rechnet damit?
    • Was macht ein Aufrufer, der, zum Beispiel auf die true/false Werte wartet, mit einer leeren Zeichenkette?

So ein Code kann man auch sauber und professionell gestalten

Principle Of Least Surprise : POLS

Sauberer Code weil:

  1. Die Namen sind klar und eindeutig, lassen weder Vermutungen noch Überraschungen zu:
    • Get Value Or Null ⇒ liefert einen Wert oder NULL zurück, schmeißt aber keine Ausnahmefehler. Deswegen müssen an zig oder hunderte Stellen, keine Try-Catch-Blocks geschrieben werden.
    • Get Value ⇒ liefert einen Wert oder schmeißt einen Ausnahmefehler
    • Get Value Or Default (generisch) ⇒ liefert einen Wert oder den Standardwert:
      • bool: false
      • byte/sbyte/short/ushort/int/unit/long/ulong: 0
      • float/double/decimal: 0.0
      • string: null
      • schmeißt keine Ausnahmefehler
    • Get Value Or Default (generisch) ⇒ liefert einen Wert oder den von Aufrufer bestimmter Standardwert, jedoch keine Ausnahmefehler geworfen
    • Get Value (generisch) ⇒ liefert einen Wert oder wirft einen Ausnahmefehler
  2. Es werden Alternativen angeboten (siehe Unterpunkte von Punkt 1!)
  3. Der Rückgabe-Typ ist bei alternative generische Funktionen von Aufrufer festgesetzt/bestimmt und eindeutig
  4. Die Rückgabe-Typen sind alle konsistent: entweder einen Wert oder Ausnahmefehler, Wert oder NULL, Wert oder Standardwert ⇒ keine Überraschungen
  5. Bei Aufruf von generische Varianten sind keine Unboxings auf der Aufrufer-Seite notwendig

Gibt es da unten eine Methode, welche ein Kommentar braucht?

public class CleanCode
{
    public const string TNAME_SETTINGS = @"Settings";
    private DataSet _DataSet;

    // 1. Variante: Objekt als Return-Value. Ist POLS-Konform.
    // Tut das Gleiche wie oben, nur viel einfacher (ohne leere Strings)
    // Die Aufrufer müssen sich um Exceptions und NULL kümmern.
    // Ansonsten, verlässlicher als der Code von oben (trotzdem NICHT GUT!)
    public object GetValue(string pSettingName, Type pType)
    {
        var value = _DataSet.Tables[TNAME_SETTINGS].Rows[0][pSettingName];
        return Convert.ChangeType(value, pType);
    }

    // 2. Variante: Objekt als Return-Value. Ist POLS-Konform.
    public object GetValueOrNull(string pSettingName)
    {
        if (_DataSet == null
            || !_DataSet.Tables.Contains(TNAME_SETTINGS)
            || _DataSet.Tables[TNAME_SETTINGS].Rows.Count == 0
            || !_DataSet.Tables[TNAME_SETTINGS].Columns.Contains(pSettingName))
            return null;

        return _DataSet.Tables[TNAME_SETTINGS].Rows[0][pSettingName];
    }

    // 3. Variante: Generischen Typ. Ist POLS-Konform.
    // Sollte etwas nicht passen oder schiefgehen, erhält der Aufrufer
    // den Standardwert des Typs.
    public T GetValueOrDefault<T>(string pSettingName)
    {
        if (_DataSet == null
            || !_DataSet.Tables.Contains(TNAME_SETTINGS)
            || _DataSet.Tables[TNAME_SETTINGS].Rows.Count == 0
            || !_DataSet.Tables[TNAME_SETTINGS].Columns.Contains(pSettingName))
            return default;

        try
        {
            var value = _DataSet.Tables[TNAME_SETTINGS].Rows[0][pSettingName];
            return (T)Convert.ChangeType(value, typeof(T));
        }
        catch
        {
            return default;
        }
    }

    // 4. Variante: Generischen Typ. Ist POLS-Konform.
    // Die Aufrufer können selbst entscheiden welchen Wert Sie erhalten möchten,
    // falls diese an den Standardwert des Typs nicht sind.
    public T GetValueOrDefault<T>(string pSettingName, T pDefault = default)
    {
        if (_DataSet == null
            || !_DataSet.Tables.Contains(TNAME_SETTINGS)
            || _DataSet.Tables[TNAME_SETTINGS].Rows.Count == 0
            || !_DataSet.Tables[TNAME_SETTINGS].Columns.Contains(pSettingName))
            return pDefault;

        try
        {
            var value = _DataSet.Tables[TNAME_SETTINGS].Rows[0][pSettingName];
            return (T)Convert.ChangeType(value, typeof(T));
        }
        catch
        {
            return pDefault;
        }
    }

    // 5. Variante: Generischer Typ. Ist POLS-Konform.
    public T GetValue<T>(string pSettingName)
    {
        var value = _DataSet.Tables[TNAME_SETTINGS].Rows[0][pSettingName];
        return (T)Convert.ChangeType(value, typeof(T));
    }
}

OOP Sprache == OOP Programm?

Alles (aus) Gold glänzt, aber nicht alles was glänzt ist Gold.
Alle Nüsse sind rund, aber nicht alles, was rund ist, ist eine Nuss.


Jeder kocht Zuhause. Aber deswegen ist nicht jeder ein Koch.
Jeder kann ein Auto-Motor oder -Getriebe zerlegen. Aber deswegen ist nicht jeder ein Auto-Mechaniker.
Jeder kann mit einer Lötpistole (oder einem Lötkolben) in einem Radio oder Fernseher herum löten. Aber deswegen ist nicht jeder ein Elektroniker.
Jeder kann mit einem Taschenrechner umgehen, aber deswegen ist nicht jeder ein Mathematiker.
Jeder kann beim Schachspielen drei Züge vorausdenken, aber deswegen ist nicht jeder ein Profi-Schachspieler.

Nur weil man in eine Objekt-orientierte Programmier-Sprache wie C++, Java oder C# Code schreibt, heißt es noch lange nicht, dass das Programm (der Code) Objekt orientiert ist!

Ich habe C# Code in „professioneller“ Umgebung (Industrie) gesehen und gelesen … Es war SPS/PLC Programmier-Stil in .NET/C# !
Ja, ich weiß! Ich hätte es selbst nicht geglaubt, wenn ich den Code nicht persönlich gelesen hätte (eine mind. viermonatige Tortur! Eine pure Verschwendung von teurem Arbeits- und Lebens-Zeit! Dazu noch: Augen- und Hirn-Schmerzen sowie schlaflose Nächte! Das wünsche ich niemandem!).
Ich selbst habe früher Mal, als Autodidakt, Visual Basic programmiert, ohne je von OOP, Klassen, Interfaces, Kapselung, Vererbung, Polymorphismus usw. je gehört/gelesen oder verstanden zu haben. Es hat (damals für mich) „funktioniert“. Dessen Code-Qualität war gleich 0 (null), bzw. eher am Ende der Minus-Bereich, je-nach Code-Qualitäts-Bewertungs-System.

Jede Programmier-Sprache hat seine Geschichte, geschichtliche Entwicklung, Konzepte, Paradigmen sowie „Eigenheiten“ und „Features“.

Es ist von großer Wichtigkeit die Geschichte einer Programmier-Sprache, dessen Sprach-/Programm-Elemente, Paradigmen und Konzepte zu kennen. Erst dadurch kann man sie 1.) begreifen und 2.) wissen Wann, Wo, Wie und Warum ein Programm-Element (Pre-Compiler-Symbol, Konstanten, Enumerations, Properties, Delegates, Events, usw.) oder OOP-Muster zu verwenden sei.

Jemand der C++ beherrscht kann nicht automatisch auch Java oder C# beherrschen.
Jemand der Java beherrscht kann nicht automatisch auch C# beherrschen.

Beweis durch Widerspruch:
Annahme: Wer C++ beherrscht, kann automatisch genauso Java/C# beherrschen.
Vergleiche in alle drei Sprachen werden mit dem Schlüsselwort „if“ durchgeführt.
Gegeben: if ( person != null && person.Name == "Bob" ) und die Variable person ist null.
In C++ (bis mind. C99 Standard) wird ein Fehler ausgelöst (Null Reference), weil person.Name == ... ausgeführt wird, und die Variable person ist/referenziert ja null! Man kann nicht auf Etwas das NULL ist, auf dessen Eigenschaft/Variable/Methode (in diesem Fall „Name“) zugegriffen werden.

In Java und C# hingegen geht es gut, weil beide Sprachen den sogenannte „Kurzschluss-Ausschluss-Verfahren“ beim Vergleichen anwenden (was in C++ je nach Compiler und Standard nicht gibt/gab). Das heißt, nachdem „person != null“ Falsch (false) ist, und der nächste Vergleich Konjunktiv („&&“) gebunden ist, wird der Rest („person.Name == ...„) nicht mehr ausgewertet,
da es gilt: false && True_Or_False ==> false oder binär (0 && (0 || 1) ==> 0). („==>“ steht hier für „daraus folgt“).
Daher der Name: Kurzschluss-Ausschluss-Verfahren.
Quod erat demonstrandum! (1)

Nun unterscheiden sich auch die Semantik (Bedeutung) und somit das Verhalten zwischen Java und C# auch noch:
Gegeben: person ist nicht null UND der Inhalt von person.Name ist „Bob„.

In Java: person.Name == "Bob" erzeugt einen neuen anonymen immutable String Object(eine Konstante) von Typ String mit dem Inhalt „Bob“ und vergleicht den Referenzen von person.Name mit der Referenz von anonymen immutable String Object. Das Resultat ist: false, da jedes Object auf eine andere Adresse zeigt, und somit es sich um zwei unterschiedliche Referenzen handelt.

In C# werden hingegen die Inhalte von beiden immutable String Objekten (person.Name und anonymen String Objekt, = Konstante) durch den Operator „==“ verglichen. Das Resultat ist: true, da der Wert von „person.Nameinhaltlich dem anonymen immutable String Objekt (Konstante) „Bob“ gleicht.
Kurz: Strings in Java und C# sind immutable Objects (siehe und vergleiche ECMA-334, Abschnitt 8.2.1 „Predefined types“ mit Java-String von Oracle!)
In Java müsste der Code so umgeschrieben werden damit es funktioniert:
if (person != null && person.Name.equals("Bob"))
Quod erat demonstrandum! (2)

Dazu kommt noch…

Das Kennen von Schlüsselwörter (if, else, new, while, …) sowie die Grammatik (Syntax) einer Programmiersprache reicht nicht aus um es zu „können“. Man kann dadurch den Code höchstens lesen.
Um eine Programmiersprache zu beherrschen („können“), sind daher weitere Kenntnisse unbedingt erforderlich:

  • Paradigmen und Konzepte (Heap vs. Stack, Class vs. Struct, Copy-by-Value, Copy-by-Reference, Immutable Types, OOP Design Patterns, Garbage-Collector/-Collection, Exception-Handling, Generizität, statischer vs. dynamischer Datentyp einer Variable,…)
  • Compiler (Was, Wie und Wo wird optimiert? Was wird während Compilierung und was während Runtime (Laufzeit) übersetzt, geprüft, ausgeführt?
    Z. B. findet in Java (ab Vers. 1.5 = Java 5) die Typ-Prüfung bei generische Typen erst zur Laufzeit (Runtime), weshalb der Compiler (da Java Type-Safety garantieren möchte) während Compilierung „Warning“ ausgibt.
    Bei C# jedoch findet die Typ-Prüfung bei generische Typen während Compilierung statt, und sollte Type-Safety nicht gegeben sein, gibt der C#-Compiler „Error“ aus. Siehe und Vergleiche auch Just-In-Time sowie Ahead-Of-Time Compiler!
    Zusätzlich: C# ermöglicht direkten Zugriff und Manipulation auf Speicher und dessen Inhalt (nur wenn der Code-Block mit „unsafe“ Schlüsselwort, sowie die Assembly als „Unsafe“ markiert wird), was in C++ normal/business as usual ist und Java gar nicht anbietet!
  • Frameworks
  • Events bzw. Nachrichten-Schleife
  • Parallelität


To comment, or not to comment – that is the question

Eine kurze Geschichte übers Kommentieren und Kommentare

Ich wünsche die Kollegen beim Neuschreiben des Codes in C, C++, C#, Java,… oder WasAuchImmer++ viel Geduld, Kraft und Nervenstärke.
Wenn Kommentare genauso kryptisch sind, wie der Code
Was machen diese 15 Zeilen?
Man kann die Kommentare übersetzen und erahnen was diese Zeilen tun

Back to the Future…
Kommentare sind wichtige Informationen für die Zukunft.
Kommentare sind wichtige Informationen aus der Vergangenheit.
Kommentare sind Notizen/Erklärungs-Ergänzungen für sich selbst, Kolleginnen und die Nachwelt (neue Kolleginnen, wenn man nicht mehr in der Firma (oder am Leben) ist).

Ich lese immer wieder Sätze mit Rufzeichen am Ende, wie „Jede Code-Zeile muss kommentiert werden!“, oder „Pro Methode, mindestens drei Zeilen Kommentare!“ usw.

An der Uni hieß es „Alles muss kommentiert werden!“.

Dann hieß es (eine Show namens „Linux Nerds vs. Geeks“ glaube ich) „Wenn man schönen/guten Code schreibt, braucht man keine Kommentare“ (sagte der Linux Grafik-Programmierer-Guru!).

Irgendwann sagte ein ehemaliger Kollege „Kommentare verwirren nur“ und dass „Nur Amateure und Doofis kommentieren“ …

Mein derzeitiger Standpunkt ist:

  1. Extrem ist und bleibt extrem. Daher entscheide ich mich für die goldene Mitte.
  2. Bevor ich Kommentiere stelle ich mir folgende Fragen:
    1. Muss ich Kommentieren?
    2. Reicht der Name nicht?
    3. Ist der Name gut gewählt?
    4. Sollte ich nicht lieber den Namen ändern?
    5. Wie groß ist der Informationsgehalt des Kommentars?
    6. Bietet der Kommentar eine zusätzliche oder ergänzende Information, die sonst nicht (oder sehr schwer) zu lesen/sehen/wissen (Code) wäre?
    7. Erleichtert/Verkürzt der Kommentar die Suche/Recherche oder das Verständnis des Algorithmus?
    8. Lässt der Kommentar Raum für falsche Interpretationen zu?
    9. Hilft der Kommentar Fehler zu minimieren, oder verursacht es mehr Fehler durch Missverständnis?

Nehmen wir den (C#) Code von Unten als Beispiel und stellen wir uns gemeinsam pro Kommentar die obigen neun Fragen unter Punkt zwei:

public class Person
{
    /// <summary>
    /// Gets/Sets the ID of Person.
    /// </summary>
    public int ID { get; set; }

    /// <summary>
    /// Gets/Sets the first/given name of Person.
    /// </summary>
    public string FirstName { get; set; }

    /// <summary>
    /// Gets/Sets the last/second name of Person.
    /// </summary>
    public string LastName { get; set; }

    /// <summary>
    /// Gets/Sets the day of birth of Person.
    /// </summary>
    public DateTime BirthDay { get; set; }

    /// <summary>
    /// Returns the whole/full name beginning with FirstName then LastName in uppercase.
    /// </summary>
    /// <returns></returns>
    public string GetName()
    {
        // todo: check!
        return $"{FirstName} {LastName.ToUpper()}"; // NG! PGH 2021-07-05 V0.8.15
    }
}

Die Property-Namen sagen eindeutig, unmissverständlich und klar aus, was sie beinhalten, und deren Kommentare haben/bringen keine zusätzliche/ergänzende Informationen… Also ⇒ Weg damit!

Der Kommentar von der Methode „GetName“ möchte, und MUSS, etwas erklären, was der Name leider nicht tut. Zusätzlich verwirrt dieser Kommentar.
Innerhalb der Methode „GetName“ steht ein „todo“ Kommentar, was kein Mensch entschlüsseln kann: „check!“ Check-What? Was soll gecheckt/überprüft werden FirstName, LastName oder Return-Value (string)?
Am Ende der Return-Zeile steht wieder kryptische Buchstaben-Suppe:

  1. NG!“ steht für…??? No Guns? No Game? Next Generation? Not Good? No Grade? Network Grid?
  2. Danach steht „PGH„, meine Initialien plus Datum und anscheinend eine Version:
    1. Gibt es für sowas Versionsverwaltungs-Systemen (CVS, SVN und GIT). Dort kann man all das lesen, von Anbeginn der Zeit (Projekt-Geburt) bis zum letzten Commit: Wer (Name, E-Mail-Adresse) hat, wann (Datum + Uhrzeit mit Millisekunden) was (Verzeichnis, Datei, Zeile) in welche Version geändert (gelöscht, hinzugefügt, umbenannt, ergänzt, entfernt,…) hat.
    2. Was sollen die Kolleginnen damit anfangen? Mehrwert? Informationsgehalt?

Das Ganze könnte man auch so schreiben:

public class Person
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDay { get; set; }

    /// <summary>
    /// Returns full name, like: "John DOE", "Alice BOYLE".
    /// </summary>
    /// <returns></returns>
    public string GetFullName()
    {
        return $"{FirstName} {LastName?.ToUpper()}";
    }
}

Ich habe diese Beispiele nicht aus der Luft gegriffen.
Ähnliche, und teilweise noch schlimmere Fälle aus der Praxis (was mir unter die Augen gekommen sind) werde ich noch hinzufügen. Entweder hier oder im neuen Beitrag.

Englisch, Deutsch oder Denglisch?

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

In einer Firma, wo alle Software-Entwickler und IT’ler Deutschsprachige waren, hat der Inhaber & CEO uns befohlen alle Kommentare (nur Kommentare) auf Deutsch zu schreiben. Ein Jahr später, nachdem das Projekt im Verzug war und die Entwicklung des Software-Systems nicht schnell genug vorangegangen war, wurden drei neue Software-Entwickler aus Spanien eingestellt. Die neuen Kollegen waren alle sehr gute Software-Entwickler und beherrschten souverän ihr Fach, jedoch verstanden kein Wort Deutsch. Danach wurden noch mindestens fünf weitere Software-Entwickler, ebenfalls aus Spanien, eingestellt…
Nun hatten wir alle Hände voll zu tun. Wir drei Deutschsprachigen müssten alle Kommentare nun auf Englisch übersetzen und ersetzen, anstatt unsere Aufgaben nachzugehen.

In eine andere Firma, hatte ich das „Vergnügen“ Code auf Denglisch zu lesen. Manche Namen waren Deutsch, manche waren Englisch, und der Rest eine beliebige Mischung aus Deutsch und Englisch. Dass alle Programm-Elemente (Interfaces, Klassen, Delegates, Events, Methoden, Properties etc.) in Microsoft .NET Frameworks auf US-Englisch waren, ist natürlich trivial…

Das Lesen der Code war extrem schwierig und eine Herausforderung an/für sich. Beispiel für Variable-Namen:
messValue, measWert, measValue, messWert (diese Namen standen für: measured value, bzw. gemessener Wert).
Auch wenn man alle Namen auf Deutsch schreibt, die Hälfte von Code ist und bleibt auf Englisch, da die Schlüsselwörter und alles Andere in MS .NET Frameworks in US-Englisch ist, wie: string.IsNullOrEmpty(…), PropertyChanged, File.Exist(…), using(var x = new MemoryStream(…)), usw. usf.

Deswegen behaupte ich: Es kann kein Code auf „Deutsch“ geschrieben werden, wenn man das versucht, dann entsteht immer ein Code auf „Denglisch“. Man stelle sich nur vor, jemand würde Denglisch schreiben oder reden:
„I war very froh to sehen you nochmal“.

Ich verwende gerne US englische Namen (Color statt Colour, Synchronize statt Synchronise, usw.). Somit bleibt alles einheitlich, unmissverständlich, eindeutig, klar und der Lese-Fluss bleibt sehr flüssig.

Denglisch kostet mehr Zeit, mehr Zeit zum Lesen und mehr Zeit zum Verstehen.
Je öfter der Code von je mehr Kollegen (wieder)gelesen wird, desto mehr Zeit wird verschwendet!
Zeit ist Geld. Somit verursacht Code auf Denglisch unnötige zusätzliche Kosten.

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Naming System & Code lesen

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Um Code schneller lesen und durchsuchen zu können, habe ich im Laufe der Zeit, mein System für die Benennung einige Programmelemente verfeinert:

  • Interfaces beginnen mit „I„, wie: IAuthor, IBook, IPublisher,…

  • Abstrakte Klassen beginnen mit „A„, wie: AAuthor, ABook, APublisher,…

  • Enums beginnen (bei mir) mit „E„, wie: „EOperationMode“, „EError“, „EDeviceType“, „EPeriodKind“,…

  • Helper-Klassen enden mit „Helper„, wie: „FileHelper“, „CommandHelper“, „SecurityHelper“,…

  • Model-Klassen (MVC, MVVM) enden mit „Model„, wie: ServiceModel, MachineModel, ConfigModel, ProductModel, CategoryModel,…

  • DTO-Klassen enden mit „DTO„, wie: PersonDTO, ProductDTO,…

  • POCO-Klassen enden mit „POCO„, wie: PersonPOCO, ProductPOCO,…

  • Delegates enden mit „Handler„, wie: CommandInvokedHandler, ValueChangedHandler, SafeModeActivationChangedHandler, StreamOpeningHandler, StreamOpenedHandler, StreamClosingHandler, StreamClosedHandler, UserLoggedInHandler, UserLoggedOutHandler,…

  • Event-Handling-Methoden beginnen mit „On“ plus Event-Name, und können -wenn passend- mit „ed“ enden, also: „On<EventName>ed„, wie: OnCommandInvoked, OnValueChanged, OnSafeModeActivationChanged, OnStreamOpening, OnStreamOpened, OnStreamClosing, OnStreamClosed, OnUserLoggedIn, OnUserLoggedOut, OnMachineReplaced, OnErrorOccured,…

  • Funktionen/Methode, die etwas Berechnen und eine Zahl zurückgeben beginnen mit „Calc“ oder „Calculate“ (und NIEMALS mit „Get“!), wie: CalculateCircleSurface(…), CalculateStandardDeviation(…) oder kurz: CalcStdDeviation(…)

  • Methoden die asynchron laufen enden mit „Async“, wie: CalcStdDeviationAsync(IEnumerable pTooManyNumbers),…

  • Boolsche Properties beginnen mit „Is„, „Has„, „Use„, „Contains“ udg., wie: „IsEnabled“, „IsReady“, „IsEmpty“, „HasElement“, „ContainsError“, „ContainsNull“, „UseForceMode“, „DoBackup“,…

  • Properties die Exception werfen können, werden NICHT als Properties, sondern mit „Get…()“ und „Set…()“ implementiert (so wie es die C# Sprach-Designer Anders Hejlsberg, Bill Wagner & Co vorgesehen haben), denn ein Property sollte niemals Exceptions werfen, schon gar nicht der Getter. Ausgenommen sind die Indexer (wegen IndexOutOfRangeException).

  • „Get…“ Methoden welche Exceptions abfangen und behandeln enden mit:
    • OrNull“ wenn bei Exception NULL zurückgeliefert wird
    • OrDefault“ wenn bei Exception Default-Wert (z. B. default(int)) zurückgeliefert wird

  • Member-Variablen beginnen mit „m“ oder „m_„, wie: mFirstName oder m_FirstName

  • Parameter beginnen mit „p“ wie: CalcRectSurface(int pWidth, int pHeight), SetTopLeft(int pX, int pY),…

  • Konstanten werden GROSS_GESCHRIEBEN: DEFAULT_VALUE, MAX_VALUE,…

Der Beispiel-Code unten erfüllt die oben erwähnten Regeln.
Versuche herauszufinden: Welcher Name ist ein Property, welcher ein Parameter, welcher eine Member-Variable und welcher eine Konstante:

Konstanten, Parameter, Properties etc. sind eindeutig zu erkennen

Syntax Highlighting & Code lesen

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Die Zeiten wo alles in Grün auf schwarzem Hintergrund stand (Borland Turbo Pascal/CPP Editor udg.) sind lange vorbei.
IDEs wie Visual Studio & Co bieten von Haus aus Syntax Highlighting.
Die Einfärbung der Schlüsselwörter, Kommentare, Typen etc. machen das Lesen von Code leichter, flüssiger und angenehmer.
Für mich jedoch reichte die Einfärbung für C# in Visual Studio nicht.
Ich möchte schnell auf einem Blick, ohne zu lesen erkennen, ob bei ein Programm-Element (in C#) es sich um ein Interface, Klasse, Enum, Delegate, Event oder „Magic Number“ handelt.
Dazu habe ich für Interfaces, Delegates, Enums und Zahlen eigene Farben zugewiesen (siehe Bild 1!)

  1. Kommentare sehe ich deutlicher (Dunkelgrün statt Standard Leichtgrau)
  2. Enums sind eindeutig sofort zu erkennen (Orange)
  3. Delegates sind ebenfalls eindeutig zu erkennen (Violett)
  4. Interfaces haben eine andere Farbe als (abstrakte) Klassen oder Structs
  5. Events sind Braun
  6. Magic Numbers“ haben bei mir keine Chance, da sie sofort erkannt werden 😉
  7. Unterscheidung zwischen Enums und Konstanten sind ebenfalls leichter
Bild 1


So gelangt man zu Farben- und Font-Style von Programm-Elementen in Visual Studio (Bild 2):

Bild 2

Color Theme & Code lesen

Seit Visual Studio 2012 wird „Dark Theme“ (Tools → Options → Environment → General → Color Theme) als Standard verwendet.
Das hat den Vorteil, dass die Augen mit weniger Licht belastet werden.
Jedoch ist es so, dass „Dark Theme“ sich eher für Grafik, 3D und Animations-Editoren eignet, um die Tool-Leisten weg- und das Bild/Modell (Bild, 3D Objekt udg.) in die Mitte zu rücken (dadurch wird automatisch die Fokussierung auf das Bild/Modell geleitet). Für das Lesen und Schreiben von Text (hier Code) ist es eher ungeeignet (laut Augen-Mediziner). Die Augen müssen sich mühsam auf die hellen Buchstaben fokussieren. Vor allem, wenn durch große Fenster, was heutzutage modern ist, das Sonnenlicht auf dem Bildschirm fällt, oder über helle Flächen auf dem Bildschirm reflektiert wird.


Deshalb probiert es selbst aus!
Nachdem ich eine Zeit lang extreme Augen- und Kopfschmerzen hatte, und deshalb zum Augenarzt gehen müsste, bin ich auf dem „Light“ Theme umgestiegen. Das reflektierte Sonnenlicht ist seitdem kein Thema (für mich) mehr.
Für detaillierte Informationen, siehe hier und hier!
Anmerkung: bei OLED und AMOLED Bildschirm, wird oft Dark Theme zwecks Energiesparen eingesetzt.

Augen & Code lesen

Der Kunde zahlt fürs Code-Schreiben und nicht fürs Code-Lesen!

Wenn wir ein Buch, eine Zeitung, ein Magazin oder nur ein A4 Blatt lesen, dann halten wir es so, dass die obere Kante etwas weiter hinten ist, und die untere Kante näher zu uns ist. Beim Lesen nach Unten bewegen sich die Augen weiter nach Innen (zu Nase), und beim Lesen nach Oben bewegen sich die Augen auseinander.
Wir halten ein Buch niemals parallel vor unserem Kopf, wir neigen den Kopf etwas nach Unten.
Kurze Zeilen unter einander machen den Lese-Fluss leichter.
Lange Zeilen stören den Lese-Fluss, da die Augen sich auch horizontal bewegen müssen. Bei sehr lange Zeilen muss sich sogar noch der Kopf sich horizontal drehen.
Je mehr Augenmuskeln beansprucht werden, desto früher wird man Müde (Kopf-Schmerzen). Man kann sich schlecht konzentrieren/fokussieren.
Deshalb ist das Lesen während einer Zug- oder Busfahrt (für längere Zeit) nicht ratsam.

Das gilt auch für Code lesen. Deshalb sollte darauf geachtet werden das beim Code schreiben, die Zeilen möglichst kurz und untereinander stehen.

// Hindert den Lesefluss
if ((a > B && C < D) && !(x == y || y != z)) dataCommunicator.SomeMethod(a, b, c, true);
// Noch mehr Code...

// Macht den Lesefluss leichter
if ((a > B && C < D) && !(x == y || y != z))
    dataCommunicator.SomeMethod(a, b, c, true);
// Noch mehr Code...

Naming-Conventions

Nomen est omen
Namen spielen beim Schreiben von Code eine essenzielle Rolle.
Gut gewählte Namen können viele Grübeleien, Verwirrungen und Missverständnisse sowie Fehler vermeiden, und machen den Code leichter lesbar.
Gute Namen sind:

  • Selbsterklärend
  • Klar
  • Eindeutig
  • Unmissverständlich
  • Einprägsam
  • Sagen genau was das Ding tut oder enthält, nicht mehr und nicht weniger
  • Müssen nicht kommentiert werden

Zum Beispiel hier (C# Code):
Der Klassen-Name sagt aus „Ich enthalte Geometrie-Methoden“, die erste Methode sagt „Ich berechne eine Kreisfläche, wenn du mir die Radius-Länge gibst“, usw.
Man braucht nicht (mit F12 ins Visual Studio) sich den Code innerhalb einer Methode zu lesen, um zu wissen, was es tut.

    public class GeometryCalculator
    {
        public double CalculateCircleSurface(double radiusLength)
        {
            return radiusLength * radiusLength * Math.PI;
        }

        public double CalculateSquareSurface(double sideLength)
        {
            return sideLength * sideLength;
        }
    }

Nomen est omen… eben.

Ugly vs Clean Code (zum Aufwärmen)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ugly.Code
{
    public class GeoCalc
    {
        public double GetCS(double value)
        {

            double val1;
            double calc;


            val1 = value;
            calc = Math.Pow(val1, 2) * Math.PI;


            return calc;


        }

        public double GetSS(double value)
        {

            double val1;
            double calc;


            val1 = value;
            calc = Math.Pow(val1, 2);


            return calc;
        }

        // Some other methods...
    }
}

Warum ist der Code oben „Ugly“?

  1. Ungenutzte „using“ Zeilen
  2. Der Klassen-Name sagt nicht, was die Klasse enthält/anbietet/tut
  3. Die Methoden-Namen sagen nicht, was sie tun oder berechnen/zurückliefern
  4. Die Parameter-Namen sagen nicht, was sie enthalten
  5. Die lokalen Variable-Namen sagen nicht, was sie enthalten
  6. Lokale Variablen kaschieren die Parameter und dessen Werte, machen den Algorithmus schwer verständlich
  7. Unnötige Leerzeilen verlängern unnötig die Methode. Man muss mehr scrollen und mit den Augen rauf & runterschauen.

Die Klasse „GeoCalc“ von Oben, könnte man auch so schreiben:

using System;

namespace Clean.Code
{
    public class GeometryCalculator
    {
        public double CalculateCircleSurface(double radiusLength)
        {
            return radiusLength * radiusLength * Math.PI;
        }


        public double CalculateSquareSurface(double sideLength)
        {
            return sideLength * sideLength;
        }

        // Some other methods...
    }
}