Monday, March 27, 2017

C# - 2



    Inhaltsverzeichnis


    Vorwort
    Das vorliegende Dokument soll dafür sorgen, dass der durch das Team produzierte Code einheitlich aussieht. Es ist dabei nicht als Selbstzweck zu sehen, sondern soll dabei helfen, dass sich jeder Entwickler auch in dem Code eines Andere „zu Hause“ fühlt und diesen schnell erfassen und ggf. Fehler korrigieren bzw. die Funktionalität erweitern kann.
    Bei jeder Arbeit am Programmcode sollte sich jeder Entwickler immer wieder vor Augen führen:
           Code is much more read than it is written. - Raymond Chen
    Das gilt natürlich für den Entwickler selbst, der nach einem Jahr in seinem eigenen Sourcecode Erweiterungen vornehmen oder Fehler korrigieren soll. Das gilt aber vor allem auch für andere Entwickler, die mit dem für sie fremden Code in Berührung kommen.
    Grundsätzlich muss also darauf geachtet werden, dass der Code jederzeit von jedem Entwickler in kürzester Zeit erfassbar ist, d. h.
    • sauber strukturiert ist, so dass alle Typen leicht aufgefunden werden können. (Strukturierungsregeln)
    • präzise formuliert ist. Es wurden also sprechende und vor allem treffende Namen verwendet. (-> Namensregeln)
    • sauber formatiert und strukturiert ist, um die lesbar zu erhöhen. (Layoutregeln)
    • ausreichend und treffend kommentiert ist. (Kommentierungsregeln)
    Die in diesem Dokument zusammengefassten Regeln entstammen im Wesentlichen der MSDN Empfehlung.


    Projekteinstellungen
    TODO
     
    Strukturierungsregeln
    Eine Klasse pro Datei
    Für jeden Typ soll eine CS-Datei erstellt werden. Typen sind
    • Klassen
    • Enums
    • Interfaces
    Die Datei soll exakt so heißen wie der Typ selbst. Auf diese Weise ist es leicht möglich, die betreffenden Typen im Projektmappenexplorer zu finden.

    Eine Datei pro Typ
    Es dürfen keine partiellen Klassen verwenden, um eine Klasse auf mehrere Dateien zu verteilen. Dieses Konstrukt ist nur für generierten Code von VS gedacht (und auch notwendig). Sollte die Verteilung auf Grund der Codelänge angedacht sein, sollte eher das Klassendesign überprüft  werden und die Klasse in mehrere Klassen aufgeteilt werden.
    Einzige Ausnahme
    Einzige Ausnahme für partielle Klassen sind die Klassen, die von einem Tool generiert werden (z.B. Telerik, Entitiy Framework, etc.) und dessen Klassen um entsprechende Funktionen erweitert werden sollen. In diesem Falle ist auf der gleichen Ebene wie die generierten Klassen ein Ordner "Extension" anzulegen und die Erweiterungsdateien dort abzulegen. Der Namespacezusatz "Extension" muss dabei zwangsläufig entfallen.


    Namespaces auf Ordner mappen
    Innerhalb eines Projektes sollen die Codedateien anhand Ihres Namesspaces in Ordner aufgeteilt werden, die genauso heißen, wie der Namespace selbst.

    Namensregeln

    Treffende Namen für Variablen
    Variablen sollten so benannt werden, dass klar ist welchen Nutzen sie hat und/oder was die Variable speichert. Auf diese Weise werden Kommentare für Variablen überflüssig und der Code lässt sich schneller erfassen.

    Beispiel
    // --- Dieses Beispiel ist schlecht, weil zwar korrekt kommentiert,
    //     aber der Bezug zur Einheit fehlt
    // Alter in Jahren
    int age = 42;

    // --- Besser:
    int ageInYears = 42;


    Sprache
    Alle fachspezifischen Namen werden in deutscher Sprache verfasst. Allgemeine Begriffe hingegen in Englisch. Beispiel GetBerechtigungsnummerForNutzermedium()
    TODO!
    Möglichkeiten:
    • Deutsch: insbesondere Fachbegriffe sind gut verständlich. Es ensteht aber zwangsläufig eine Mischung mit englischen Ausdrücken (z. B. Get…)
    • Englisch: durchgängig mit allen Befehlen verwendbar. Übersetzung der Fachbegriffe jedoch sehr schwierig. In diesem Fall muss ein Projektweites Wörterbuch geführt werden.


    Keine Abkürzungen verwenden
    Es sollten keine Abkürzungen verwendet werden, da diese zwar für den Verfasser, nicht aber für andere Entwickler verständlich sind. Ausnahmen bilden allgemeingültige Abkürzungen, wie z. B. Id für Identifier und fachlich definierte Abkürzungen wie OrgID für Organisationsidentifikator. 
    Die Verwendung von Akronymen ist erlaubt. Es werden grundsätzlich keine Präfixe für die Kennzeichnung von primitiven Datentypen verwendet. Ausnahmen bilden Controls auf Frontendseiten (z.B. ASP.NET, Winforms).

    Exkurs: Abkürzung vs. Akronym
    Eine Abkürzung ist die verkürzte Schreibweise eines Begriffs, in dem nur eine bestimmte Anzahl aufeinanderfolgender Zeichen genutzt wird. Beispiel: Id für Identifier.
    Ein Akronym ist eine verkürzte Schreibweise für einen zusammengesetzten Begriff, in dem von jedem Begriff der Anfangsbuchstabe verwendet wird. Beispiel: Kfz für Kraftfahrzeug.
    Ausnahmen
    • Interfaces wird ein I vorangestellt (z.B. IRefresh)
    • privaten Variablen, die für Properties verwendet werden, wird ein Unterstrich vorangestellt
    // für den Fall, dass der Getter oder Setter ausprogrammiert werden muss, kann eine private Variable
    // angelegt werden. Name = "_" + <Property-Name>

    private int _myProperty;

    public int MyProperty
    {
        get { return _myProperty; }
        set { _myProperty = value; }
    }

    // Oder Kurzform der Property (der "Normalfall")
    public int MyProperty{ get; set; }

    TODO: Tabelle mit Kürzeln der Controls?

    Besonderheiten bei Namensgebung
    Get vs. Find
    Methode: GetCustomer();  --> Wirft bei Fehlschlag eine Exception (wenn kein Objekt zurückgegeben wird)
    Methode: FindCustomer();  --> Wirft bei Fehlschlag keine Exception, sondern liefert NULL zurück.
    Ein "Getter" impliziert immer, dass man in irgendeiner Form etwas bekommt. Falls dies nicht der Fall ist, ist dies eine Ausnahme, die kenntlich gemacht werden muss. "Find" hingegen bedeutet man versucht etwas zu finden, das Ergebnis ist aber ungewiss.

    Try-Methoden
    Methoden, die das Wort Try beinhalten werfen keine Exceptions, sondern liefern ein Ergebnis über den Returnwert. Dies ist auch die gängige Konvention aus dem .NET Framework
    Beispiel .NET Framework
    //wirft im Fehlerfall eine Exception
    int.Parse(value);

    //liefert true zurück, wenn 'value' erfolgreich geparsed werden konnte
    bool successfullyParsed = int.TryParse(value);


    Events und Delegates
    Die Definition eines Events erfolgt immer über die folgende Konvention:
    Art
    Namenskonvention
    Bemerkung
    Beispiel
    Event
    Beschreibend was das Event macht bzw. liefert
    ein Verb (z.B. Closing) oder Tätigkeit (MessageReceived), die möglichst genau beschreibt
    public event EventHandler<ClosingEventArgs> Closing;
    Eventargumente
    <EVENTNAME> + "EventArgs" ()
    nur wenn eigene Argumente erforderlich, IMMER abgeleitet von System.EventArgs
    public class ClosingEventArgs : ClosingEventArgs
    Eventhandler
    <OBJEKTNAME>_<EVENTNAME>
    Wird von Visual Studio auch so vorgeschlagen
    alte Schreibweise:
    aboutWindow.Closing += new ClosingEventHandler(aboutWindow_Closing);
    ab .NET 2.0
    aboutWindow.Closing += aboutWindow_Closing;
    Event bei Vererbung oder Ausführung
    On + <EVENTNAME>
    Dient ausschließlich der Bereitstellung des Events für abgeleitete Klassen oder Ausführung in der eigenen Klasse.
    protected void OnClosing(ClosingEventArgs e)

    Layoutregeln
    Schreibweisen
    In .Net Programmen gibt es im Prinzip drei unterschiedliche Schreibweisen.
    • CamelCase: Kleiner Anfangsbuchstabe, jedes verbundene Wort wird groß geschrieben. -> private int alterInJahren;
    • lokale Variablen
    • Namen von Parametern für Methoden
    • Felder für Properties mit führendem Unterstrich
    • PascalCase: Großer Anfangsbuchstabe, jedes verbundene Wort wird groß geschrieben. -> public void AbrechnungStarten();
    • Methoden
    • Klassen
    • Properties
    • gängige Abkürzungen
    • Akronyme
    • Alles was sonst nicht unter CamelCase oder UpperCase genannt wird
    • UpperCase: Alle Zeichen werden groß geschrieben.
    • Wenn ein Akronym aus nur zwei Buchstaben besteht, werden beide groß geschrieben, ansonsten keine Verwendung von UpperCase
    • Xml nicht XML
    • Html nicht HTML
    Überflüssige Usings entfernen
    Überflüssige usings müssen entfernt werden. Visual Studio bietet dafür eine Funktion „Sortieren und entfernen“ im Kontextmenü. Zur besseren Lesbarkeit sollten Leerzeilen eingefügt werden, wenn die Hauptnamespaces wechseln.
    usings formatieren
    using System.Collections.Generic;
    using System.Data;
    using System.Diagnostics;

    using Microsoft.CSharp;
    using Microsoft.SqlServer;


    Einrückung von Code
    Grundsätzlich sind zum Einrücken Leerzeichen zu nutzen und keine Tabs. Visual Studio ersetzt in der Grundkonfiguration die Tabs durch Leerzeichen. Diese Einstellung soll beibehalten werden.
    Tipp
    Visual Studio: Tastenkombination STRG + K + D korrigiert die Formatierung des aktuellen Dokuments.


    Geschweifte Klammern
    .Net Konvention ist, dass geschweifte Klammern immer in einer neuen Zeile stehen. Visual Studio setzt diese Konvention automatisch um.
    //gut
    if (c > 6)
    {
        MachWas();
    }

    //schlecht
    if (c > 6) {
        MachWas();
    }

    Leerzeilen
    Leerzeilen sollen überall dort eingesetzt werden, wo ein logischer Bruch besteht, aber keine Klammer steht. Sie werden eingefügt
    • in Using-Blöcken zur Trennung von Namespace-Gruppen
    • zwischen zwei Methoden
    • innerhalb einer Methode zur Strukturierung von Codeabschnitten. Oft wird dieser logische Block mit einem Kommentar ergänzt, der für den gesamten Block gilt.

    Geschweifte Klammer ersetzen Leerzeilen, d. h. dort, wo eine geschweifte Klammer in einer Zeile steht, folgt keine weitere Leerzeile. Es wird immer nur eine Leerzeile eingefügt, nie mehrere nacheinander. Im Gegensatz zu Leerzeichen kann Visual Studio falsche Leerzeilen nicht automatisch korrigieren. Hier ist also Sorgfalt vom Programmierer gefragt. Bei der Codierung müssen unnötige Leerzeilen entfernt werden.


    Leerzeichen
    Mehrfach aufeinander folgende Leerzeichen müssen vermieden werden. Vor und nach runden Klammer werden keine Leerzeichen geschrieben. Nur wenn mehrere Parameter aufeinander folgen wird nach den Komma jeweils ein Leerzeichen eingefügt. Variablendeklaration wird nicht „tabellarisch“ vorgenommen.
    Auch hier sollen die Defaulteinstellungen von Visual Studio genutzt werden.
    //falsch
    int  tag           = 15;
    int  monat         = 10;
    int  jahr          = 2015;
    bool istSchaltjahr = false;

    //richtig
    int tag = 15;
    int monat = 10;
    int jahr = 2015;
    bool istSchaltjahr = false;


    Deklarationen von Variablen
    Es erfolgt immer eine Variablendeklaration je Zeile. Lokale Variablen sind außerdem erst unmittelbar vor der Verwendung zu deklarieren und nicht am Anfang einer Methode.
    //falsch
    int var1, var2, var3 = 42;

    //richtig
    int var1 = 0;
    int var2 = 0;
    int var3 = 42;


    Explizite Angaben von Zugriffsmodifizierern
    Die Sichtbarkeit von Klassen und Methoden muss immer explizit gekennzeichnet sein, um Missverständnissen vorzubeugen. Leider führt Visual Studio die Kennzeichnung nicht automatisch aus.
    //schlecht
    class Test
    {
        int GetValue()
        {
            return 42;
        }
    }

    //gut – muss vom Programmierer eingetragen werden!! VS macht das nicht automatisch!
    public class Test
    {
        private int GetValue()
        {
            return 42;
        }
    }


    Anweisungen
    Die Regeln sind für alle Anweisungen (if, while, foreach usw.) gleich: In den runden Klammern werden keine Leerzeichen benutzt, auch dann nicht, wenn sich nichts darin befindet. Geschweifte Klammern werden zwecks besserer Lesbarkeit und Wartbarkeit immer benutzt, auch wenn der jeweilige Block nur aus einer Anweisung besteht.
    // falsch
    if (op1 > 1)
       op1--;
    else
       op1 = 0;

    // richtig
    if (op1 > 1)
    {
       op1--;
    }
    else
    {
       op1 = 0;
    }


    Kommentierungsregeln
    Hinweis
    Beim Kommentieren von Programmcode sollte man sich immer folgendes vor Augen halten: Eine von IBM durchgeführte, sechsmonatige Studie ergab, dass Wartungsprogrammierer die meisten Schwierigkeiten dabei hatten, die Absicht des Autors zu verstehen.


    Typen von Kommentaren
    Kommentare sollen helfen, sich einen schnellen Überblick über den Code zu verschaffen und möglichst schnell die gesuchte Codestelle zu finden, in die man ggf. eingreifen kann. Sehr viel trägt dazu natürlich lesbarer Programmcode bei, der aus der Anwendung der in den vorangegangenen Abschnitten festgelegten Regeln entsteht.
    Für die Orientierung ist aber ein zusammenfassender Kommentar erforderlich, damit nicht der gesamte Programmcode gelesen und verstanden werden muss, obwohl er u. U. gar nicht relevant ist. Der Leser soll erkennen können, was der Programmierer mit dem Code bezweckt. Dazu sind insbesondere der „XML-Kommentar für öffentliche Elemente“ und die „Inline-Kommentare“ wichtig. Darüber hinaus sollten gerade bei der Wartung des Codes „Änderungsmarkierungen“ gesetzt werden, die auf den Grund der Änderung hinweisen und auf die Quelle verweisen.
    Zu vermeiden sind Kommentare, die
    • den Programmcode wiederholen
    • den Programmcode erklären
    In letzterem Falle ist der Code offenbar so verwirrend, dass der Programmierer selbst bemerkt hat, dass er ihn erklären muss, damit ihn anderen verstehen können. In diesem Fall sollte eher über eine Codeverbesserung nachgedacht werden.
    Schlechter Kommentar -> Wiederholen von Programmcode
    //schlecht
    private int Summe(int op1, int op2)
    {
        // Parameter addieren und lokaler Variable zuweisen
        int sum = op1 + op2;
        return sum;
    }


    Inline-Kommentare
    • Kommentare gehören immer über die zu kommentierende Zeile.

    Inline-Kommentare
    //falsch
    int age = 42; // Alter in Jahren

    //richtig
    //Alter in Jahren
    int age = 42;

    //noch besser, da der Kommentar überflüssig ist
    int ageInYears = 42;

    • In .Net werden keine Blockkommentare mehr verwenden, auch wenn diese syntaktisch noch zur Verfügung stehen (stammen aus C++)
    Blockkommentare
    /* falsch
     * dieses ist ein Kommentar für die Funktion GetValue,
     * der sich über mehrere Zeilen erstreckt.
     * Der Blockkommentar wird nicht mehr verwendet*/
    private int GetValue()
    {
        return 42;
    }

    // richtig
    // In .Net wird jede Zeile einzeln mit den Kommentar-
    // kennzeichen versehen. Zum Auskommentieren eines
    // Blocks bietet VS eine Funktion in der Toolbar.
    private int GetValue()
    {
        return 42;
    }
    XML-Kommentar für öffentliche Elemente
    Für alle öffentlichen Elemente (Methoden, Klassen usw.) werden XML Kommentare verwendet, die auch Tags für bestimmte Elemente bieten. Aus diesen Kommentaren kann eine Dokumentation generiert werden.
    Kommentare für Methoden beginnen mit einem Verb. Gedanklich kann zur Formulierung ein „die Methode…“ ergänzt werden, welches aber nicht geschrieben wird. Für die Dokumentation der Parameter gilt das gleiche. Das „returns“ ist bereits ein Verb, daher bildet dieses Attribut eine Ausnahme. Visual Studio fügt diese Kommentare automatisch ein, wenn über ein öffentliches Element der XML-Kommentarzeichen "///" geschrieben wird.
    XML-Kommentare
    /// <summary>
    /// addiert zwei Operanden
    /// </summary>
    /// <param name="op1">repräsentiert den ersten Operanden</param>
    /// <param name="op2">repräsentiert den zweiten Operanden</param>
    /// <returns>die addierten Operanden</returns>
    public int addValues(int op1, int op2)
    {
        return op1 + op2;
    }

    Damit die Kommentare sinnvoll eingesetzt werden können, ist auf eine korrekte Rechtschreibung und Interpunktion zu achten. Die Formulierung ist so zu wählen, dass man sie auch einem Kunden zeigen kann. Sie enthält also keine Unmutsäußerungen oder ähnliches.


    Unfertiger Code und Hacks

    Visual Studio bietet einige Tags, die Code gesetzt werden können, damit gewisse Stellen leicht wiedererkannt werden können. Wird ein solches Tag in einen Kommentar geschrieben, erscheint dieser Kommentar in der Visual Studio Aufgabenliste.
    private int Bla()
    {
        //TODO: MH 27.12.14: hier fehlt noch die Berechnung
        return 42;
    }
     private float KomplizierteBerechnung()
    {
        float erg = CalcSpecialFactor() * GetPrice();

        //HACK: MH 27.12.14: CalcSpecialFactor liefert noch falsches Ergebnis, vorübergehend als 1 annehmen
        return GetPrice();
    }

    Diese Tags können in Visual Studio konfiguriert werden unter TOOLS / OPTIONEN und dort unter Umgebung / Aufgabenliste

     
    Änderungsmarkierungen
    Durchgeführte Änderungen am Programmcode sollten im TFS dokumentiert werden, damit ein geändertes Programmverhalten nachvollziehbar bleibt.
    TODO: Definieren wie dies zutun ist

    Weitere Programmierregeln
    Konstruktoren immer explizit angeben
    In C# werden standardmäßig leere Konstruktoren erstellt, sollte die Klasse keinen eigenen Konstruktor haben. Wird nun ein neuer, zusätzlicher Konstruktor erstellt, so nimmt C# diesen als Standardkonstruktor an und erstellt keinen leeren mehr. Dies erzeugt überall, wo sonst der leere Konstruktor verwendet wurde, einen "breaking-change", der dafür sorgt, dass man den Code anpassen muss. In neuen Klassen muss deswegen, wenn kein spezieller Konstruktor verwendet werden soll, der leere Konstruktor explizit angegeben werden. Auf diese Weise beugt man diesem Fehler vor.

    Validierung von Eingaben
    Bei der Codierung ist immer davon auszugehen, dass der Benutzer Eingaben tätigt, die so vom System nicht erwartet wurden. Daher wird jede Benutzereingabe validiert, bevor sie weiter verarbeitet wird.

    Prüfen von Parametern
    Bei Methoden, die als public definiert sind, erfolgt eine Validierung der übergebenen Parameter. Der Entwickler geht immer davon aus, dass übergebene Parameter nicht sinnvoll oder unvollständig sein können. Parameter müssen daher auf null, zulässige Wertebereiche, unerwartete Inhalte u. ä. geprüft werden, bevor sie weiter verwendet werden. Die Parameterprüfung löst eine Exception aus, wenn die Validierung nicht erfolgreich war.

    Hardcodierte Werte
    Keine Zahlen ("magic numbers"), Konfigurationsparameter (z. B. Pfade) oder sonstige konstante Werte im Code. Diese werden immer aus Konfigurationsdateien / Systemtabellen gelesen oder als Konstanten im Code definiert (z.B. als Enums).

    Methodenausstieg
    Zur besseren Übersicht besitzen Methoden nach Möglichkeit nur einen einzigen Ausstiegspunkt (return). Mehrere return-Kommandos in einer Methode sind allerdings im Zuge von Ausnahmebehandlungen möglich. Weiterhin wird der Rückgabewert einer Methode wird nicht zur Fehler-Anzeige mißbraucht. Es darf z.B. kein null-Wert zurückgegeben, um der aufrufenden Methode mitzuteilen, dass die Methode nicht fehlerfrei durchgeführt werden konnte. Für solche Fehlermeldung werden Ausnahmen verwendet, die von der aufrufenden Methode in entsprechender Weise aufgefangen und behandelt werden müssen.

    Zugriff auf Membervariablen
    Membervariablen werden immer private, bzw. protected deklariert. Ein Zugriff von Außen erfolgt immer über public Properties (get/set). Klassen-Konstanten dürfen public sein.

    Todo-List
    • IDisposeable beschreiben
    • Technische Dokumentation im Projekt verwalten
    • Returns vs. Exceptions
    • Umgang mit TFS
    • UnitTest
    • Ablauf: wann wird eingecheckt, wann werden Testmethoden erstellt, wann wird dokumentiert,   wann wird der Arbeitspunkt aktualisiert.

No comments:

Post a Comment