Erstes Softwareprojekt? Ich hab von Anfängern gelesen, die von Begriffen wie BusinessLayer oder REST höchstens mal gelesen haben
Du arbeitest mit C#? Sieht zumindest so aus, könnte aber auch Java sein.
Aber schau dir doch mal die Code-Tags von der Entwickler-Ecke an, die machen das Lesen viel einfacher
Vorweg: Bei dem Thema hat jeder seine eigene Meinung, das ist nur Meine
Außerdem kann sich das sinnvolle Verhalten je nach Situation und Projekt stark ändern.
In den meisten Fällen ist Erfahrung wichtiger als Bilderbuch-Wissen, oft liegt das aber relativ nahe beieinander.
Fehlt die Erfahrung, kann es sinnvoll sein, die Fehler bewusst zu machen um daraus zu lernen. Ich bin aber faul, ich arbeite dann lieber nach dem Bilderbuch, das spart Fehlschläge
Weil Du mit Error-Messages herum hantierst:
Bau dir kein eigenes Exception-Handling!!!
Es gibt immer wieder Fälle, wo jemand von irgendeiner alten Sprache kommt und Fehler-Codes einführen will.
Da gibt's dann auf einmal zig Methoden, die ein int zurück geben und der Code muss erst mal mit einer langen Liste von Fehler-Codes abgeglichen werden um zu wissen, wie man reagieren sollte.
Auch strings sind kein guter Exception-Ersatz.
Für die meisten modernen Sprachen und dazu gehörigen Frameworks (C# und .NET) sollte es ein integriertes Exception-Handling geben.
Nutze das, damit lebst Du viel sicherer
Was Du mit Exceptions tun sollst: Erst einmal gar nichts.
Eine Exception bezeichnen eine Ausnahme, die sollte prinzipiell gar nicht erst auftreten.
Welche Arten von Exceptions sollte man behandeln?
Wenn es um Exceptions geht, die Du selber wirfst:
Versuche sie vorher zu verhindern, z.B. mit Validierung der Nutzereingaben.
Wenn eine Exception nicht sinnvoll validiert werden kann, dann fange sie ab und zeige eine verständliche Meldung an, nicht einfach nur Exception.ToString()
Vergiss aber nicht, dem Nutzer z.B. bei der Validierung den Grund mitzuteilen, sonst wird die Validierung schnell als Fehler empfunden.
Wenn es um Exceptions geht, die nicht von dir ausgehen, aber die Du prüfen kannst:
Prüfe immer vorher, bevor Du etwas ausführst. Findet ein Datei-Zugriff statt, dann prüfe, ob der möglich ist und zeige das entsprechend an.
Oder beobachte die Bedingungen permanent und deaktiviere z.B. Buttons, wenn Du voraussagen kannst, dass die Funktion nicht klappen würde.
Auch hier solltest Du dem Nutzer mitteilen, warum etwas nicht geht. Tooltips bieten sich da an.
Wenn es um Exceptions geht, die nicht von dir ausgehen und die Du nicht prüfen kannst:
Schau dir an, welche Exceptions in einer Situation fliegen können und entscheide separat, welche Du fängst und welche nicht.
Netzwerkverbindungen lassen sich z.B. nur sehr schwer im Voraus prüfen, da fliegt schnell mal eine Exception, wenn kurz die Verbindung weg ist.
In dem Fall musst Du immer im Einzelfall entscheiden.
Z.B. kann so eine Netzwerk-bezogene Exception zum Crash der Anwendung führen und auch sinnvoll sein.
Oder Du fängst sie ab, startest ab dem Zeitpunkt einen ständigen Ping um zu prüfen, wann die Verbindung wieder da ist und deaktivierst solange alle Funktionen.
Oder Du fängst sie nur ab und teilst dem Nutzer mit, was schief gelaufen ist.
Du hast also viele verschiedene Exception-Typen.
Wirfst Du eine selber, solltest Du darauf achten, dass der Exception-Typ auch zum Fehler passt, das erleichtert später das Fangen der Exception je nach Typ.
.NET hat da selber einige Exception-Typen mit geliefert, es kann aber auch sinnvoll sein, Eigene zu definieren.
Wo sollte man die Exception behandeln?
Grundsätzlich gilt: fange eine Exception nur dort, wo Du sie auch behandeln kannst.
Wenn Du z.B. eine Klasse hast, die für die Netzwerk-Kommunikation zuständig ist, indem sie Eingang und Ausgang entsprechend in Objekte übersetzt, dann nützt es dir nichts, wenn diese Klasse Netzwerkbezogene Exceptions fängt.
Die Präsentationsschicht dagegen kann da was tun. Sie kann die Exception fangen und den Fehler anzeigen.
Oder Du sagst im Business-Layer, dass der Verbindungsversuch drei mal wiederholt wird, bevor die Exception durch gelassen wird. In dem Fall würde sie zwei mal gefangen werden, beim dritten mal muss die View gerade stehen.
Wenn Du z.B. Datei-Zugriffe hast und die Exception sagt, die Datei gibt's schon: Ändere den Namen und probiere es erneut
Sagt die Exception, die Datei gibt's noch nicht: Erzeuge sie und probiere es erneut
In diesen beiden Fällen kannst Du das aber auch im Voraus prüfen.
Oder ein aktuelles Beispiel von mir: Eine Schleife, die viele Aufgaben von Außen entgegen nimmt und auf irgendeine asynchrone Weise ausführen soll.
Verursacht eine dieser Aufgaben einen Fehler, würde diese Schleife abbrechen, was nicht passieren darf.
Also wird ausnahmslos jede Exception gefangen und irgendwo registriert, damit derjenige, der die Aufgabe gesendet hat, darüber informiert werden kann.
So ist z.B. unser Server aufgebaut. Der bekommt über WCF im Sekundentakt X Commands übermittelt und kümmert sich darum, dass die asynchron ausgeführt werden.
Nur weil ein Command nicht läuft, darf unter keinen Umständen der Server beendet werden, die anderen Commands laufen ja trotzdem weiter.
Es gibt (bei C# bzw. .NET) aber noch einen anderen Weg zu behandeln:
Exception fangen, irgendwas tun und erneut werfen bzw. durch lassen.
Im catch kannst Du einfach nur throw; schreiben, dann bleibt auch der Stacktrace erhalten.
So kannst Du z.B. Logging ermöglichen oder irgendwas anderes tun, kannst dir aber trotzdem sicher sein, dass die Exception nicht unbeachtet bleibt.
Und am Ende gibt's (zumindest unter .NET) immer noch das UnhandledException-Event, je nach Art des Projektes an einer anderen Stelle, aber es ist immer da.
Darüber kannst Du global jede Exception fangen und behandeln.
Darüber haben wir z.B. ein globales Letzte-Instant-Handling gebaut.
Schafft es eine Exception bis dahin, wird eine entsprechende Meldung angezeigt, aber das Programm schmiert nicht ab.
Gleichzeitig senden wir dort aber auch eine Info an uns, damit wir den Fehler beheben können.
Und nein, das Programm ist intern und rechtlich ist da auch alles im grünen Bereich
Oder Du schreibst den Fehler in's Log und lässt das Programm trotzdem abstürzen.
Wie läuft das Logging ab?
(Beispiele sind für .NET)
DAS ist immer noch aufwendig, da gibt's kein tolles Konzept gegen
Es gibt aber Frameworks, die das vereinfachen, wie z.B. Log4Net.
Auf der anderen Seite gibt's aber auch das Pattern "Aspect-Oriented Programming", kurz "AOP", wofür - wie könnte es anders sein - natürlich auch Frameworks (z.B. PostSharp) existieren.
Damit kannst Du den Aufruf einer Methode automatisch loggen lassen, beim Kompilieren würde der Code dann mit eingebaut werden.
Auf der anderen Seite gibt's aber auch die Variante, die zur Laufzeit umgesetzt wir. Auch hier gibt's Frameworks, wie z.B. Unity.Interception von Microsoft.
Das ist gleichzeitig eine Implementierung vom "Inversion of Control" (lies dazu auch mal "DependencyInjection" nach) Pattern. Du registrierst ein Interface und eine Factory, die die Instanz erzeugt.
Unity kann zur Laufzeit einen Proxy bauen, die alle Aufrufe an deine eigene Instanz durch gibt, aber vorher und nachher noch andere Dinge wie Logging erledigt.
Bei AOP musst Du aber beachten: Du bist schnell in der "Mit Kanonen auf Spatzen"-Situation.
Mit der Compiler-Variante (PostSharp) holst Du dir einen Monoliten ins Haus, der irre viel automatisch und "magisch" macht, der dadurch aber auch so schnell ist, wie als würdest Du alles selber schreiben.
Unity.Intercetion ist da nicht anders, nur dass es mMn. etwas einfacher bzw. nachvollziehbarer ist. Die Runtime-Variante ist aber auch langsamer, das bauen von Proxies frisst viel Zeit, allerdings nur einmal, danach sollte Unity das cachen können.
Wie wir Logging umgesetzt haben:
Wir haben ja unsere Commands und der CommandDispatcher (die Schleife, die ich erwähnt habe) schreibt vor dem Aufruf die Parameter detailliert ins Log.
Beim Ausführen wird so Zeug mit geloggt, wie Laufzeit, die SQL-Commands, die abgeschickt werden (NHibernate machts möglich, EntityFramework müsste das aber auch können), etc.
Zwischen drin für kritische Bereiche haben wir noch eine Art InCode-Umsetzung, der das gleiche tut, aber auf den kleinen eingekreisten Bereich bezogen.
Die winzigen Details wie Methoden-Aufrufe loggen wir explizit nicht mit. Durch debuggen müssen wir sowieso, da hilft ein irre komplexes Log nicht und wir haben ja alle Daten, die wir brauchen.
Eventuell könnte man noch einbauen, dass besondere Fehler von bestimmten Commands automatisch zu einem ein Backup der Datenbank führen würden, damit wir die zum Testen haben