Verwandlung zum ersten Architektur-Dojo mit Ralf und Stefan

Ich berichte noch heute von dem Ereignis der Verwandlung. Es war als Coding-Dojo geplant gewesen. Weiß jeder, was ein Coding-Dojo ist? Zu Beginn gibt es eine Kata, eine kleine Aufgabe, die es zu lösen gilt. Dazu gehören alle Schritte von der Planung und dem Durchdenken der gesamten Aufgabe bis hin zum entwickelten Code. Einige japanische Begriffe kannte ich noch von meinen früheren Judo-Kursen.

Es war nicht das erste mal, dass Ralf und Stefan ein solches Coding-Dojo durchführten. Es war aber das erste mal, dass wir eigentlich gar nicht kodierten, sondern uns viel mehr über die Architektur unterhielten. Abgesehen von einer Person, die total heiß drauf war mal wieder was zu kodieren, waren alle sehr zufrieden mit dem Ergebnis, auch ohne eine Zeile Code geschrieben zu haben. Selbst Ralf und Stefan waren begeistert. Die beiden lernen auch bei jedem Dojo immer wieder etwas Neues.

Bei der Aufgabe ging es darum, spezielle Textdateien einzulesen und zu konvertieren (sie nannten es BankOCR). Mehr verrat ich aber nicht, da ich Ralf und Stefan sowie auch den Teilnehmern den Spaß an der Aufgabe nicht verderben möchte.

Im Endeffekt dauerte unsere Architektur natürlich viel zu lange. 20 Leute brauchten für die Architektur ca. 3 Stunden. Das war aber auch gar nicht schlimm, weil es nicht um die Zeit ging, sondern um die Flut an Erkenntnissen und Sichtweisen der einzelnen Teilnehmer. Es wurde schnell klar, dass eines der Kernprobleme weder die .NET oder C#-Fähigkeiten waren, sondern eher viel mehr die unterschiedlichen Ansatzpunkte und Verständnissschwierigkeiten. Einer sagte, “das könnte man Zahl nennen”, dann sagte der nächste, “ja aber wie nennen wir dann das hier?” Allein die Begrifflichkeiten zu ermitteln dauerte seine Zeit. Es ging dabei aber nicht, wie auch später einstimmig erkannt wurde, um ein stupides Glossar. Es ging um einheitliche Begriffe, die jeder benutzen konnte sowie die Verbindungen zwischen den Begriffen untereinander. Dies bildete eine wunderbare Grundlage für spätere Objekte bzw. Objektbezeichnungen im Code (wenn man denn entwickelt hätte… :-)).

Ralf bemerkte gegen Ende, dass ja theoretisch anhand unserer Begriffsbildung ganz zu Beginn auch der Lösungsansatz sehr nah an diese Begriffe und den Zusammenhängen angelehnt war. Er stellte voller Erstaunen fest, dass, hätte man am Anfang andere Begriffe gewählt und somit auch andere Zusammenhänge, oder vllt. auch deteilliertere Begriffe, so wäre unter Umständen eine gänzlich andere Lösung am Ende entstanden.

Für mich bleibt als Fazit zu sagen. Mir hat es Spaß gemacht alle unterschiedlichen Denkansätze zu hören und nachzuvollziehen. Ich bin fast gar nicht hinterhergekommen mit mitschreiben, somit konnte ich mich selbst leider nicht so sehr beteiligen… Das hat aber auch niemanden gestört.

In ein paar Wochen geht es weiter, hier in Berlin… Man darf gespannt sein.

Microsoft Dynamics NAV 2009 –> SP1

Ich richte gerade meinen tollen neuen NAV-Server ein und habe erst 2009 installiert und danach erst das SP1 (ja, es ist schon spät und dunkel draußen).

Auf jeden Fall hatte ich einen lustigen Fehler beim Starten des rllenbasierten Clients. Die Pages wurden nicht richtig dargestellt und es entstand folgender Fehler:

Fehler: Die Metadaten für das Objekt vom Typ 'Page' mit der ID '9060' befinden sich in einem
fehlerhaften Zustand, der auf eine frühere Ausnahme zurückzuführen ist:

Fehler beim Kompilieren von Assembly 'C:\ProgramData\Microsoft\Microsoft Dynamics
NAV\60\Server\MicrosoftDynamicsNavServer\assembly\Page9060.dll'. Dies kann auf
Unterschiede zwischen Binärdateien in der Installation oder der Datenbank zurückzuführen
sein. Stellen Sie sicher, dass alle Installationskomponenten konsistent und auf dem neuesten
Stand sind. Fehlerdetails: 'c:\ProgramData\Microsoft\Microsoft Dynamics
NAV\60\Server\MicrosoftDynamicsNavServer\source\Page\Page9060.cs(63,9) : error CS1501:
Keine Überladung für die Methode RegisterSourceExpression erfordert 5-Argumente...

So, nun, nach ca. zwei Stunden herumexperimentieren ist die Lösung einmal wieder ganz einfach. Ab in den klassischen Client und einmal im Objekt Designer einfach alles komplett durchkompilieren… Und siehe da.. es geht. So komisch hier sah das aus…

RTC error

Die spinn bei Philips…

Heute mal etwas technischer und fern von .NET. Ich habe mir vor ca. einem Jahr kabellose Kopfhörer von Philips gekauft. In den Kopfhörern selbst sind Philips-Akkus, und wenn man die Kopfhörer in so eine Ladestation stellt, dann laden sie sich wieder auf. Ok, soviel zur Vorgeschichte.

Vor einer Woche stellte ich fest, dass die Kopfhörer außerhalb der Ladestation nur noch ca. 10 Minuten funktionierten. Die Akkus sind also im Eimer. Nun gut. Nach einem Jahr nich so doll, aber meinetwegen.

image

So sehen die Originalen Akkus aus. In bzw. auf dem Gerät steht, man soll natürlich nur Philips-Akkus kaufen.Ich bin losgerannt zum nächsten Rewe und habe mir irgendwelche NoName-Akkus gekauft.  Hab sie eingesteckt und dann ging der Kopfhörer zwar kurzzeitig an, aber die Statuslampe blinkte und der Kopfhörer war nach ca. 5 Sekunden wieder aus. Ok, ich hab die Daten der Akkus verglichen und gemerkt, dass die Akkus von Rewe 100 mA/h weniger haben. Ja, also eigentlich ist das ja kein Problem, aber nun gut dachte ich, wer weiss was die da wieder verbaut haben in China und habe also nun Originale Akkus bestellt bei Amazon.

So sehen dann die neuen Akkus aus:

image

So, nun dachte ich, ich kann endlich mal wieder loslegen und Musik hören. Aber nein. Auch mit diesen Akkus blinkte die Statuslampe kurz und nach 5 Sekunden waren die Kopfhörer wieder aus. So, spätestens jetzt hatte ich die … voll und wollte der Sache auf den Grund gehen. Ich wurde auch sehr schnell fündig. Wenn man sich oben nochmal die originalen Akkus ansieht, so sieht man am Minus-Pol, dass die Folie, die normaler Weise den gesamten Batteriekörper überzieht, relativ weit oben aufhört und der untere Teil also die blanke metallische Aussenhülle des Akkus ist. Nun werfen wir mal kurz einen Blick in die Kopfhörer selbst.

image

Man sieht an den Einsteckbuchsen für die Akkus, dass nicht nur ein Kontakt für den Minuspol vorhanden ist, wie man es kennt, sondern gleich zwei (siehe rote Ellipsen). Diese Affen von Philips haben also mit Absicht diesen Quatsch da eingebaut, damit man immer nur ihre dummen Akkus nimmt, die natürlich nach spätestens einem Jahr im Eimer sind. Und selbst, nachdem ich laut Bezeichnung die originalen Akkus bestellt habe, funktioniert es nicht, weil diese Deppen bei Philips wohl ihre Super-Kontakte da vergessen haben und dass normale Akkus nicht gehen. Man bedenke, seit dem Tag wo ich keine Musik mehr länger als ne viertel Stunde hören konnte bis jetzt sind schon fast eine ganze Woche vergangen… Super.

So. Ich habe mir nun einen der Akkus genommen und einfach unten die Folie etwas abgepopelt. Generell besteht bei Batterien und Akkus die gesamte Außenseite aus dem Minus-Pol. Die Folie umhüllt den Metallkörper und kann relativ leicht abgezogen werden. Das sieht dann so aus:

image

Wenn man dieses Akku nun wieder einsteckt, mit der freigelegten metallischen Seite, so dass die beiden Kontakte das Metall berühren, oh wunder, geht es wieder. Diese Aktion kostete mich einmal umsonst dumme Akkus von Philips und n Haufen Wartezeit. Es hätte alles soo einfach sein können, aber nein.. Warum auch?

Solltet ihr auch zufällig diese Kopfhörer Philips SHD9100 gekauft haben, dann kauft keine Akkus von Philips. Das nützt eh nichts. Popelt schnell das bisschen Folie ab von irgendwelchen anderen Akkus und schon hat Philips kein Geld mit den Akkus verdient… Jeah!

Man kann hier sicherlich auch irgendwas mit .NET machen, aber ich wollte einfach nur Musik hören…

Nochmal ein Bild aller Komponenten als Zusammenfassung

image

Die Chronik eines Projektes

Es war ein sehr aussichtsreiches Projekt. Vielversprechend, anspruchsvoll, innovativ und zukunftssicher, alles in .NET. Die entstehende Software sollte weltweit eingesetzt werden. Klasse... Wer träumt nicht von solch einem Projekt. Die beteiligten Entwickler sind fähige Leute im .NET-Umfeld. Sie können jedes Problem lösen. Das Projekt begann auf einer grünen Wiese. Sprich, man konnte alles selbst designen, planen und entwickeln. Es gab kaum technische Vorgaben.

Heute redet man über dieses Projekt nur noch mit Anwälten und versucht so viel Schadensersatz wie möglich herauszuholen. Alle sind enttäuscht, jeder sucht bei jedem die Schuld und alle sind unzufrieden… Was war passiert????

Die Entwickler sind fähige Leute, die in .NET alle möglichen Probleme lösen können. Technisch gesehen war das Projekt also handhabbar. Bei diesem Projekt arbeiten die Entwickler direkt mit dem “Kunden”. Diese Entwickler bzw. Programmierer sind normalerweise Angestellte, die von irgendeinem Projektleiter gesagt bekommen, was sie machen sollen und vllt. auch wie sie es machen sollen. Das oben angesprochene Projekt war ein Projekt auf selbstständigen Basis mit direktem Kundenkontakt… Und da ist auch schon das erste Problem.. Der “Kunde”. Eine ominöse Person oder Institution, die viele tolle Ideen hat aber leider oft nicht selbst genau weiß, was sie will. Wenn dann noch die eigentliche Leitung des Projektes gänzlich versagt, ist das Projekt zum Scheitern verurteilt… Egal wie gut die Entwickler sind. Ab einer bestimmten Größenordnung eines Projektes ist eine gute strukturierte Planung einfach unabdingbar. Ansonsten weiss niemand, was er machen soll. Die Entwickler sind Anfangs super motiviert und bauen und machen was das Zeug hält.. Obwohl der Kunde mehrfach pro Monat ein Release erhält, kommt erst nach langer langer Zeit heraus, dass das meiste, was die Entwickler tolles gebaut haben, gar nicht das ist, was der Kunde wollte. Wie kann sowas passieren? Hat sich der Kunde das alles nicht angeschaut? Oder gab es einfach nur zu wenig oder falsche Kommunikation? Hat der Kunde gedacht, “Na die Entwickler da machen das schon…”?

Ich wurde in das oben genannte Projekt geholt, weil die Anforderungen die Entwicklungskapazitäten überstiegen. Schon nach ein paar Tagen habe ich moniert, dass die Prozesse in diesem Projekt äußerst ungünstig ablaufen und dass das später zu großen Problemen führen kann. Der Kunde, der eigentlich nur Mittelkunde ist, redet ab und zu mit dem echten Endkunden (eine große Firma) und redet dann wieder ab und zu mit den Entwicklern. Natürlich alles via MSN. Ich sagte, Leute, lasst uns Dokumente erstellen, wo wir explizit aufschreiben, was wir entwickeln werden. Lasst uns aufschreiben, wie wir die Aufgabenstellung verstanden haben. Wenn der Kunde das nochmal gegenliest, dann kommen vllt. hier und da noch Dinge heraus, die wir übersehen oder falsch verstanden haben. Ich bekam als Antwort: “Nico, für Dokumente haben wir keine Zeit. Wir arbeiten agile. Da braucht man keine Dokumente. Die sind im Moment des Schreibens schon veraltet.” Ja. Da ich nur nebenläufiger Entwickler war und kein Projektleiter, habe ich zwar immer wieder betont, dass ich denke, dass diese Arbeitsweise irgendwann ein Nachspiel haben wird, habe mich aber noch den Prozessen (falls man es so nennen kann) untergeordnet und habe einfach nach besten Wissen und Gewissen entwickelt.

Ich schreibe diesen Blog-Eintrag, weil ich euch allen einen Rat aufgrund dieser Erfahrungen geben möchte. Es mag vllt. komisch klingen, aber es ist ein gewisser Lernprozess, mit dem Phänomen “Kunde” umzugehen. Das bedeutet: es gibt unterschiedliche Arten von Kunden. Die einen kommen mit einem 800-Seiten Pflichtenheft an, was entweder die IT-Abteilung oder andere IT-Spezialisten entwickelt haben. Dieses Pflichtenheft beantwortet alle Fragen und man braucht einfach nur noch losentwickeln. Dies ist sehr schön, aber auch sehr selten der Fall.

Die gänzlich andere Art von Kunden stellt sich hin und sagt: “Herr Franze, ich habe hier dieses Problem. Machen sie, dass es geht!” Dieses Satz “Machen Sie, dass es geht” habe ich schon öfter gehört. An dieser Stelle muss man wieder unterscheiden.

Die einen meinen damit, dass sie nur das Problem kennen, aber keine Ahnung haben, wie die Lösung aussehen könnte. Dafür bin ich ja dann da. Ich (respektive ihr, die das hier lest) seit die Entwickler der Lösung. Das bedeutet, dass ihr nicht einfach nur irgendein kleines Progrämmchen programmiert, ihr versetzt euch in die Lage des Kunden, versteht ihn und sein Problem und entwickelt mit ihm gemeinsam eine Lösung.

Die anderen meinen mit diesem Satz, dass sie glauben, wie die Lösung aussieht, und wenn sie selbst ein wenig C# oder VB könnten, könnten se das schnell selbst programmieren. Dass da vllt. ein Jahr Entwicklungszeit dahinter steht und man nebenbei noch Client-Server-Wissen sowie Datenbank-Knowhow braucht, weiß natürlich am Anfang keiner. Der Kunde steht eigentlich schon am Ziel und denkt, dass er uns Entwickler mit einigen kurzen Sätzen auch zum Ziel bringen kann. Damit wir die entsprechende Lösung genauso bauen können, wie er sich das vorstellt. Leider klappt das nur oft so nicht, weil der Kunde vllt. keine technische Erfahrung hat oder oft aneinander vorbeigeredet wird. Der Kunde ist am Ziel und wundert sich, dass wir (der Entwickler) da nicht auch einfach ankommen…

Zusammenfassung:

Was ich damit sagen möchte ist das Folgende. Wenn ihr ein Projekt für einen Kunden entwickelt, (ich rede hier nicht von Hello-World-Applikationen, sondern vllt. mit Client, Server und Datenbank und mit einer Dauer von ca. einem Mann-Jahr), dann versucht strukturiert vorzugehen. Das bedeutet, setzt euch hin und plant und entwickelt das Projekt, vorerst so weit es geht auf dem Papier (oder in Word oder so). Diese Dokumente bilden eine gemeinsame Basis. Der Kunde erklärt euch irgendwas. Ok, nehmt das mit ins Dokument auf, so wie ihr es verstanden habt. Wenn der Kunde das später noch einmal liest, dann sagt er vllt. “Ähh, so hab ich das aber nicht gemeint… Das sollte doch so und so sein..”. Wenn der Kunde keine Lust hat, das Dokument nochmal zu lesen, dann kann euch niemand einen Vorwurf machen. Ich sage nicht, dass ein solches Dokument immer 100%ig vollständig und korrekt ist (das ist es nie), aber es gibt doch schon viele Dinge, die im Vorfeld auffallen können und die man auch einfach schon auf dem Papier durchdenken kann. Vor allem Dinge, die nur nebenbei erledigt werden müssen, aber trotzdem essentiell sind. Zum Beispiel Tests, oder ein ordentliches Setup-Paket. Wie wird die Software später installiert? und wie kommen Initialdaten in die Datenbank? Wie ist der Ablauf im Programm. Von welchen Masken aus kommt man auf welche und wann sind wie welche Kommandos möglich?

Dieses Dokument verleiht euch auch sehr viel Sicherheit. Später, wenn der Kunde sagt, dass er das so alles gar nicht gemeint hat, könnt ihr das Dokument herausholen und sagen: “Guck mal, so steht’s hier, und so hab ichs gebaut…” Wenn dem Kunden später auf einmal einfällt, dass die Software doch mandantenfähig sein soll, und ihr somit in alle 500 Tabellen nochmal eingreifen müsst und einiges an der Businesslogik der Software ändern müsst, dann könnt ihr ebenfalls das Dokument herausholen und sagen: “Äh, ich hab in dem Dokument gerade mal nach dem Wort Mandant gesucht und es nicht gefunden. Es wurde im Vorfeld nichts von Mandanten gesagt. Deswegen kostet diese Anpassung nun leider xxx € mehr und von der Zeit her verschiebt sich das Ende des Projektes um zwei Wochen”. Bei solchen Aussagen ein Schwarz-auf-Weiss-Dokument zur Hand zu haben ist echtes Gold wert. Es gibt Kunden, die haben ganz viele tolle Ideen, Träume und Wünsche, die man auch alle gerne umsetzen kann, aber leider wird dabei immer der Kosten- und auch der Zeitrahmen unangetastet gelassen. Sprich, die Entwickler sollen mal eben schnell nebenbei hier ein Haufen Features einbauen, aber es darf keine Zeit und somit auch kein Geld kosten. Das Resultat ist, dass die Qualität leidet. Und wenn die Software den ganzen Tag nur abstürzt, hat auch niemand gewonnen.

So. Hier unten zeige ich nochmal das “magische Dreieck”, welches symbolisiert, dass Zeit, Kosten und Qualität immer zusammenhängen. Man kann nicht eines verändern, ohne die anderen Punkte unangetastet zu lassen. Erhöht man die Qualität, so kostet das Zeit und somit Geld. Spart man an Zeit, weil alles schnell schnell fertig werden muss, so verringert das die Qualität, und im Endeffekt steigert das sogar die Kosten. Weil später einen Bug finden, dauert länger, als es gleich richtig zu machen. Natürlich ist niemand mehr in der Lage, diesen Fehler den Problemen zu Beginn des Projektes zuzuschreiben. Es ist halt einfach nur ein fataler Bug in der Software.

magisches-dreieck

Magisches Dreieck für Kosten – Zeit – Qualität:
Quelle: http://www.realtime-solutions.de/softwareentwicklung/projektmanagement/index.php

wirkliche Zusammenfassung:

Ich appelliere an alle Softwareentwickler und Programmierer, die etwas von sich halten. Nehmt euch die folgenden Sätze zu herzen.

“Qualität zahlt sich aus”

“Qualität bemerkt man immer erst dann, wenn sie nicht da ist”

Arbeitet ordentlich, macht nichts schnell schnell, das geht eh nur nach hinten los (wie gesagt, wir reden hier nicht von einem winzigen hello-world-Projekt) und versucht hier und da immer mal auch ein wenig Dokument zu produzieren, wo drin steht, was ihr wie macht. So ist es auch anderen Entwickler möglich, sich ggf. in eure Software einzuarbeiten.

Fehlerbehandlung… eine kleine Zusammenfassung

Ich habe hier viel Zeug geschrieben. Falls du gerade keine Zeit oder Lust meine geistigen Ergüsse wirklich durchzulesen, dann spring doch einfach nach unten zur Zusammenfassung… :-)

So. Ich habe nun ewig nicht mehr gebloggt, weil unendlich viele Leute Software haben wollen. Aber genug der Vorrede, los gehts.

Ich habe immer noch mein kleines Persönlichkeitsproblem in Bezug auf Fehlerbehandlung in .NET und Exceptions. Ich habe ja hier Restriktiv vs. Robuste Entwicklung eine Umfangreiche Diskussion gestartet, in wie weit man Exceptions einsetzen könnte.

Fangen wir doch mal beim Urschleim an. Früher gab es Rückgabewerte. Die gibt es übrigens immer noch, denn auf Windows-API-Ebene gibt es keine Exceptions. Die Windows-APIs geben meistens einen int-Wert zurück, der hilft, die ganzen Windows-Fehlercodes auszuwerten, die man übrigens hier finden kann (Windows Fehlercodes). Es handelt sich hier um 16.000 der unterschiedlichsten Standard-Windowsfehler. “Fehler” 0 bedeutet meist, dass alles in Ordnung ist. Jeder API-Bereich hat dann noch seine ganz eigenen Fehlercodes, die speziell auf den jeweiligen Bereich zugeschnitten sind (hier zum Beispiel die Windows Netzwerksicherheits-Fehlermeldungen. die dazugehörigen Nummern befinden sich jeweils in den entsprechenden C++-Headerdateien… leider)

Die Rückgabewerte können ausgewertet werden. Und das ist auch schon ein relevanter Knackpunkt. Sie können ausgewertet werden, müssen aber nicht. Es folgt ein kleines Beispiel, welches Daten auf eine Chipkarte schreibt. Ohne error-Handling.

   1: private void WriteWithoutErrorHandling()
   2: {
   3:     IntPtr handleReader;
   4:     IntPtr handleCard;
   5:     uint dwDLLVersion = 0;
   6:     IntPtr cardReaderContext;               // Context for interact with cardreaders
   7:  
   8:     WinSCardFunctions.SCardEstablishContext(0, IntPtr.Zero, IntPtr.Zero, out cardReaderContext);
   9:     WinSCardFunctions.MCardInitialize(cardReaderContext, "MyCardReader", out handleReader, out dwDLLVersion);
  10:     WinSCardFunctions.MCardConnect(handleReader, 1, 0, out handleCard);
  11:  
  12:     byte[] data = new byte[5] { 0x17, 0x1A, 0x17, 0x50, 0xFF };
  13:     int uiBytes2Write = data.Length;
  14:     WinSCardFunctions.MCardWriteMemory(handleCard, 0, 0, data, out uiBytes2Write);
  15:  
  16:     WinSCardFunctions.MCardDisconnect(handleCard, 0);
  17:     WinSCardFunctions.MCardShutdown(handleReader);
  18: }

Sieht doch eigentlich ganz gut aus. Oben ein paar Variablen, danach schön in Reih und Glied ein paar API-Calls, dann die Daten auf die Chipkarte schreiben und noch zum Abschluss wieder ein paar API-Calls. Alle diese APIs liefern einen int-Wert zurück, der mich aber in diesem Beispiel in keinster Weise interessiert. Keiner dieser APIs wirft eine Exception. Sprich, meine Methode stürzt niemals ab… juhu..

Nun das gleiche Beispiel nochmal mit annähernd kompletter Fehlerbehandlung.

   1: private void WriteNormal()
   2: {
   3:     IntPtr handleReader;
   4:     IntPtr handleCard;
   5:     uint dwDLLVersion = 0;
   6:     IntPtr cardReaderContext;               // Context for interact with cardreaders
   7:  
   8:     SmartCardErrorCode ec = WinSCardFunctions.SCardEstablishContext(0, IntPtr.Zero, IntPtr.Zero, out cardReaderContext);
   9:     if (ec != SmartCardErrorCode.None)
  10:         throw new COMException("Error while establishing context.", (int)ec);
  11:  
  12:     ec = WinSCardFunctions.MCardInitialize(cardReaderContext, "MyCardReader", out handleReader, out dwDLLVersion);
  13:     if (ec != SmartCardErrorCode.None)
  14:         throw new COMException("Error while initialize the card reader.", (int)ec);
  15:  
  16:     ec = WinSCardFunctions.MCardConnect(handleReader, 1, 0, out handleCard);
  17:     if (ec != SmartCardErrorCode.None)
  18:     {
  19:         WinSCardFunctions.MCardShutdown(handleReader);
  20:         throw new COMException("Error while connect to the card.", (int)ec);
  21:     }
  22:  
  23:     byte[] data = new byte[5] { 0x17, 0x1A, 0x17, 0x50, 0xFF };
  24:     int uiBytes2Write = data.Length;
  25:     ec = WinSCardFunctions.MCardWriteMemory(handleCard, 0, 0, data, out uiBytes2Write);
  26:     if (ec != SmartCardErrorCode.None)
  27:     {
  28:         WinSCardFunctions.MCardShutdown(handleReader);
  29:         throw new COMException("Error while writing data to the card. Byets written: " + uiBytes2Write.ToString(), (int)ec);
  30:     }
  31:  
  32:     WinSCardFunctions.MCardDisconnect(handleCard, 0);
  33:     WinSCardFunctions.MCardShutdown(handleReader);
  34: }

Man sieht, dass sich der Code fast verdoppelt hat. Jeder API-Call (abgesehen von den letzten paar), sind mit einer Fehlerbehandlungsroutine abgesichert. Der Rückgabewert jeder API wird geprüft, und wenn irgendwas nicht stimmt, wird eine entsprechende Exception mit individuellem Text und der Fehlernummer geworfen. Man sieht an dieser Stelle schön, dass es sehr aufwendig sein kann, ordentliche Fehlerbehandlung zu betreiben. Ich glaube, bei API-Calls hat man oft nicht die Chance, sich um die entstandenen Probleme selbst zu kümmern. Man muss sie einfach nach oben reichen mittels einer Exception und oben muss man sich drum kümmern. Würde man hier z.B. einfach nur einen boolschen Rückgabewert machen, der im Fehlerfall einfach nur false zurückliefert, geht die Fehlerinformation verloren. Niemand weiß dann, welche Funktion eigentlich nicht funktioniert hat. Man weiß nur, “es geht nicht.” Und wir alle wissen ja, wie sehr wir von Fehlermeldungen der Art “Unbekannter Fehler” begeistert sind.

In einer “normalen” Methode ohne APIs sollte man nicht bei jeder Kleinigkeit eine Exception werfen, aber das ist wieder situationsabhängig und wie mein anderer oben genannter Blogeintrag bewiesen hat, auch eine Charakterfrage.

Im Folgenden wird das Prinzip “Deep Nested” beschrieben (Ja, ein oder zwei cool klingende englische Worte, um wesentlich professioneller zu klingen… ;-))

   1: private void ShowDeepNested(int a, int b, int c, int d, int e)
   2: {
   3:     if (a > 5)
   4:     {
   5:         RunMethodWitha(a);
   6:         if (b == 0)
   7:         {
   8:             RunMethodWithab(a, b);
   9:             if (c == -1)
  10:             {
  11:                 RunMethodWithc(c);
  12:                 if (d < -5)
  13:                 {
  14:                     RunMethodWithabcd(a, b, c, d);
  15:                     if (e == int.MaxValue)
  16:                     {
  17:                         RunMethodWithabe(a, b, e + c);
  18:                     }
  19:                 }
  20:             }
  21:         }
  22:     }
  23: }
  24:  

Es handelt sich um eine Methode mit 5 int-Parametern. Alle 5 müssen bestimmte Bedingungen erfüllen. Je nach Bedingungs-Konglomerat werden unterschiedliche Dinge ausgeführt bzw. errechnet (Bitte an dieser Stelle keine Aussagen wie: “Mhh, man is der blöd, da kann man doch super nen Strategy-Pattern oder irgendein anderes cooles Konstrukt anwenden, dann hat man solche Probleme nicht.. Und so komisch verschachtelte Methoden schreibt man doch eh nicht…”. Es geht hier um Beispiele, und in der echten Welt kann man nicht immer auf ner grünen Wiese anfangen und alles von Anfang an super strukturiert durchdenken und alle Ausnahmen von vorn herein berücksichtigen! Stichwort “Historisch gewachsen”)

Was man wohl auf Anhieb sieht, ist, dass es relativ unübersichtlich ist. Die zu starke Verschachtelung bedeutet, dass man an jeder Zeile wissen muss, welche Bedingung denn hier nun eigentlich gilt. Um so näher wir dem Höhepunkt der if-Verschachtelung kommen (in Zeile 17), desto “richtiger” scheint der Code zu werden, weil anscheinend immer mehr Bedingungen erfüllt sind.

Was man aber wohl erst auf dem zweiten Blick erkennt, ist, dass man die ganzen Negativ-Fälle eigentlich einfach nur umschifft hat. Denn was ist denn, wenn nun doch die erste Bedingung falsch ist? Oder die dritte? Dann gehen wir gekonnt über alles drüber und es passiert nix oder nur die Hälfte. Klar gibt es Fälle, wo das exakt so gewünscht ist, aber oft merkt man erst beim Negativtest, dass man da ja doch noch irgendwas machen muss…

Nun möchte ich eine weitere Vorgehensweise zeigen, die mit den coolen Worten “Early Out” bezeichnet wird.

   1: private void ShowEarlyOut(int a, int b, int c, int d, int e)
   2: {
   3:     if ((a > 5) == false)
   4:     {
   5:         return;
   6:     }
   7:     RunMethodWitha(a);
   8:     if ((b == 0) == false)
   9:     {
  10:         return;
  11:     }
  12:     RunMethodWithab(a, b);
  13:     if ((c == -1) == false)
  14:     {
  15:         return;
  16:     }
  17:     RunMethodWithc(c);
  18:     if ((d < -5) == false)
  19:     {
  20:         return;
  21:     }
  22:     RunMethodWithabcd(a, b, c, d);
  23:     if ((e == int.MaxValue) == false)
  24:     {
  25:         return;
  26:     }
  27:     RunMethodWithabe(a, b, e + c);
  28: }

Man sieht wieder die gleiche Methode mit 5 int-Parametern. Diesmal ist es aber so, dass einfach die Bedingungen negiert werden, und man sofort den Fehlerfall behandelt. Wenn man über ein if hinweg kommt, so befindet man sich immer noch in seiner ganz normalen Methodenebene, und hat trotzdem seine Parameter geprüft. Dies sieht nicht nur übersichtlicher aus, es verdeutlicht auch sehr stark, was eigentlich passiert, wenn mal eine Bedingung nicht stimmt. Hier ist es viel einfacher, in einem Fehlerfall eine Variable vllt. zurückzusetzen o.ä. Man beachte aber auch in diesem Beispiel, dass die Fehlerinformation verloren geht.

Nun mal wieder zu Exceptions. Man kann sie nicht einfach ignorieren und die Behandlung kann sehr strukturiert erfolgen. Nachteile sind, dass Exceptions, wenn sie geworfen werden, sehr sehr langsam sind. Das Zusammenbauen des Stacktraces erfordert sehr viel Performance. In einer Schleife immer schön die ganze Zeit eine Exception werfen, kann die Ausführung von Millisekunden auf Minuten erhöhen. Dass man eine Exception behandeln muss und nicht ignorieren kann, kann aber auch ein Nachteil sein, weil in der echten Welt oft die Anforderung kommt, dass ein Kunde bei einer Stapelausführung nicht genervt den gesamten Prozess abbrechen möchte, wenn mal ein Wert nicht hingehauen hat.. Schauen wir uns dazu mal die nächsten Snippets an.

   1: private void RunSzenario()
   2: {
   3:     try
   4:     {
   5:         LoadDataInMemory();
   6:         CalculateSumOnData();
   7:         GroupDataInMemory();
   8:         CreateReportOnData();
   9:         SendReportToAllUser();
  10:         FreeTheMemory();
  11:     }
  12:     catch (Exception)
  13:     {
  14:         // Was hier? Loggen? Oder MessageBox.Show("Geht nicht");
  15:     }
  16: }

Dieses kleine Szenario, was Daten lädt, etwas mit ihnen macht und dann die Daten wieder freigibt, wird in diesen 6 Methoden gekapselt. Diese Methode werfen unterschiedliche Exceptions. Ich habe einen try-Catch-Block drum gemacht, damit mir das ganze nicht gänzlich um die Ohren fliegt. Der Stacktrace der Exception ist für die Entwickler interessant, und die Message wahrscheinlich auch für den Entwickler, vlt. auch für den Enduser. Aber trotzdem könnte die Fehlerauswertung an dieser Stelle detaillierter sein. Wird eine Exception ausgelöst werden alle folgenden Methoden nicht aufgerufen. Das kann so gewollt sein. Manchmal entstehen aber auch Situationen, wo es Probleme bei der Ausführung gibt, selbst wenn man eine Aufräum-Methode in den Finally-Block tut.

Das folgende Beispiel muss niemand von euch lesen. Wichtig ist eigentlich nur mal die Zeilenanzahl. Ich habe mir jetzt mal ein paar Exceptions ausgedacht, die all diese Methoden werfen können, und ich hab versucht, alle ordnungsgemäß zu behandeln (hier nur mit dem Anzeigen einer “richtigen” Meldung für den User)…

   1: private void RunSzenario()
   2: {
   3:     try
   4:     {
   5:         LoadDataInMemory();
   6:     }
   7:     catch (AccessDeniedException)
   8:     {
   9:         MessageBox.Show("Speicherbereich geschützt.");
  10:         try
  11:         {
  12:             LogException();
  13:         }
  14:         catch (LogException)
  15:         {
  16:             MessageBox.Show("Beim Protokollieren eines Fehlers beim Laden ist ein Fehler aufgetreten.");
  17:         }
  18:     }
  19:     catch (DataNotFoundException dEx)
  20:     {
  21:         MessageBox.Show("Die Daten konnten nicht geladen werden. Grund: " + dEx.Message);
  22:     }
  23:     catch (Exception)
  24:     {
  25:         MessageBox.Show("Unbekannter Fehler beim Laden... Sorry");
  26:     }
  27:     try
  28:     {
  29:         CalculateSumOnData();
  30:     }
  31:     catch (SecurityException)
  32:     {
  33:         MessageBox.Show("Sie haben keine Berechtigung, Berechnungen auf diesen Daten auszuführen");
  34:     }
  35:     catch (SumIsTooBigException)
  36:     {
  37:         MessageBox.Show("Das geht aber so nich.. Die Summe is viel zu groß.");
  38:     }
  39:     catch (Exception)
  40:     {
  41:         MessageBox.Show("Unbekannter Fehler beim Summieren... Sorry");
  42:     }
  43:     try
  44:     {
  45:         GroupDataInMemory();
  46:     }
  47:     catch (MonsterGroupException mEx)
  48:     {
  49:         MessageBox.Show("Gruppierung ist aufgrund des folgenden Wertes nicht möglich: " + mEx.InvalidValue);
  50:     }
  51:     catch (Exception)
  52:     {
  53:         MessageBox.Show("Unbekannter Fehler beim gruppieren... Sorry");
  54:     }
  55:     try
  56:     {
  57:         CreateReportOnData();
  58:     }
  59:     catch (OutOfPaperException)
  60:     {
  61:         MessageBox.Show("Dieser Bericht würde ihre Kapazität an Papier übersteigen.");
  62:     }
  63:     catch (FileNotFoundException fEx)
  64:     {
  65:         MessageBox.Show("Die Berichtsvorlagedatei " + fEx.File + " konnte nicht gefunden werden.");
  66:     }
  67:     catch (Exception)
  68:     {
  69:         MessageBox.Show("Unbekannter Fehler beim Erstellen der Berichte... Sorry");
  70:     }
  71:     try
  72:     {
  73:         SendReportToAllUser();
  74:     }
  75:     catch (UsersAreAtHomeException)
  76:     {
  77:         MessageBox.Show("Keine Benutzer gefunden.");
  78:     }
  79:     catch (SendingNotSupportedException)
  80:     {
  81:         MessageBox.Show("Das Versenden von was acuh immer ist zur Zeit nicht erlaubt.");
  82:     }
  83:     catch (Exception)
  84:     {
  85:         MessageBox.Show("Unbekannter Fehler... Sorry");
  86:     }
  87:     finally
  88:     {
  89:         try
  90:         {
  91:             FreeTheMemory();
  92:         }
  93:         catch (MemoryNotFreeException)
  94:         {
  95:             MessageBox.Show("Der Speicher kann nun auch nciht mehr freigegeben werden");
  96:         }
  97:         catch (OutOfMemoryException)
  98:         {
  99:             MessageBox.Show("Der Speicher ist zu voll zum Freigeben");
 100:         }
 101:         catch (Exception)
 102:         {
 103:             MessageBox.Show("Unbekannter Fehler... Sorry");
 104:         }
 105:     }
 106: }

Man beachte die 16 Zeilen vorher zu diesen 106 Zeilen. An dieser Stelle sei übrigens mal erwähnt, dass “gute” Software, bzw. sagen wir mal ausgereifte Software, generell MEHR Fehlerbehandlungscode beinhaltet als normalen Ausführungscode, da der Benutzer vor dem Rechner bekanntlich viel mehr Falsch als Richtig macht, und die Software ihn bei seiner täglichen Arbeit unterstützt, indem sie seine Fehler korrigiert oder ihn dezent auf den Fehler hinweist und ihm Hinweise zur Korrektur gibt (langer Satz). Hier sind nun aber auch keine Strukturen wie Vererbung von Exceptions und Aufgliedern in verschiedene Gruppen vorhanden. Exceptions können ja vom Fehlertext her selbst schon immer detaillierter werden, wenn man sie richtig einsetzt und selbst geschriebene Exception einer gut strukturierten Vererbungshierarchie unterlegt. In dem Beispiel is auch noch nicht mal ne Schleife drin… Das wäre ja sonst ein reines Chaos.

Die Vorteile liegen klar auf der Hand, man weiss zu jedem Zeitpunkt so gut wie exakt, was genau wer wo warum falsch gemacht hat. So, schaut man aber nun doch nochmal erneut hin, so sieht man in Zeile 12, dass eine Methode im Catch-Block ausgeführt wird, die aber nun wiederum eine Exception werfen kann. Die Methode in Zeile 91, die sich im finally-Block befindet, kann ebenfalls wieder Exceptions werfen, um die sich auch wieder gekümmert werden muss. Exceptions, die in einem catch-Block oder finally-Block auftreten, sind nicht automatisch abgesichert. Es können so Exceptions durchrutschen, und es entsteht ein großes geschachteltes WirrWarr von try- und catch-Blöcken. Weiterhin können in ungünstigen Konstellationen Exceptions sogar überschrieben werden, wenn man nicht explizit immer alle Exceptions beibehält und als InnerException mit weiterreicht.

So, und nun??? Ich stelle zum Thema Exceptions mal folgendes fest.

Grundsätzlich gilt die Devise, Exceptions vermeiden wo es geht (z.B. mit if(!File.Exists(…)) return; vorher einer FileNotFoundException aus dem Weg gehen).

Exceptions sollten nur dann gefangen werden, wenn sie auch behandelt werden können. Ansonsten werden sie einfach durchgelassen und jemand anders, der sie auch behandeln kann, kümmert sich drum. (Nur loggen ist z.B. keine Behandlung)

Selbst sollte nur in wirklichen Ausnahmefällen Exceptions geworfen werden, und nicht, weil man lange keine mehr geworfen hat. Und was genau “Ausnahmefälle” sind, ist wieder situations- bzw. sogar charakterabhängig…

Manchmal möchte man einfach auch mal ohne Exceptions arbeiten wie früher. Deswegen sind auch heutzutage Rückgabewerte nichts böses. Denkt man nur mal an die “TryParse”-Methoden der simplen Datentypen. Die funktionieren ohne Exception, was einfach auch mal sehr schön ist. Es muss ja nicht immer wegen jeder Kleinigkeit die ganze Welt untergehen.

Wenn man diese Grundlagen beherzigt, ist das Leben vllt. schon etwas strukturierter. Aber nun hat man wieder das Problem, dass man eigentlich immer mal wissen müsste, wann überhaupt welche Exception geworfen werden kann??? Nicht nur Methoden können Exceptions werfen, auch Operatoren können das. Oder Konvertierungen, oder auch Konstruktoren. Das wiederum  bedeutet, dass ich meine Instanz vllt. in einem try-Block erzeugen sollte, was wiederum heißt, dass sie außerhalb dieses Blockes nicht zur Verfügung steht… Hierzu ein weiteres kleines Beispiel:

   1: private double Devide(int a, int b)
   2: {
   3:     try
   4:     {
   5:         return a / b;
   6:     }
   7:     catch (DivideByZeroException)
   8:     {
   9:         return double.NaN;
  10:     }
  11: }

In diesem Beispiel kann eine DeviceByZeroException auftreten, aber wo genau kommt die denn eigentlich her? Ja, der /-Operator wirft diese Exception. aber wo steht denn das eigentlich? Und wie viele sonstige, nicht soo offensichtliche Exception sind in unseren alltäglichen Programmen versteckt? Exceptions entstehen immer besonders gern auf den Rechnern von Kunden. Zur Sicherheit kann man ja generell überall einen riesigen try-catch-Block drum machen, so ist man immer auf der sicheren Seite (Dieser Satz war zu einem Großteil Ironie! Einfach überall ein try-catch-Block sieht nicht danach aus, als hätte man sich Gedanken gemacht, vor allem nicht, wenn man nur mach “catch(Exception)”.).

Dann gibt es noch.. relativ neu, Code Contracts. Ein kurzer Blick auf folgendes Beispiel:

   1: private double Devide(int a, int b)
   2: {
   3:     Contract.Requires(b != 0);
   4:  
   5:     return a / b;
   6: }

Das wirklich schöne an dieser Sache ist, dass ich mich als Entwickler nun nicht um diesen Fall kümmern muss (sofern sichergestellt ist, dass alle anderen Team-Mitglieder auch die Contracts nutzen und achten). Es sieht übersichtlich aus und die Methode wird nicht überschwemmt von Fehlerbehandlungscode. Das erfordert aber auch das Wissen darüber, was überhaupt alles schief gehen kann. Sprich, ich muss also Wissen, was ich tue. und zwar am besten Immer, in jeder Zeile!.

Zusammenfassung:

Fakten!!!

1. Fehlerbehandlung ist ein essentieller Teil einer jeden guten Software.

2. Im Allgemeinen ist die Fehlerbehandlung genauso aufwendig, wie die eigentlichen Prozesse im Workflow einer Software. Das merkt man aber auch erst, wenn man wirklich mal intensive und gut durchdachte sowie strukturierte Fehlerbehandlung betreibt.

3. Fehlerbehandlungscode übersteigt von der Menge her oft den eigentlichen Code, in dem der Fehler auftreten kann.

4. Keine Fehlerbehandlung zu betreiben ist schlecht!

5. Mit eines der größten Probleme bei der Fehlerbehandlung ist die Frage: “Ja und was mach ich im Fehlerfall???”

6. Viele Exceptions treten nur beim Kunden auf, nicht auf dem Entwicklungsrechner… Warum auch. Der Kunde glaubt dann immer, wir Entwickler bauen irgendwas ein wie: “if(Rechner == Kunde) throw new KundenException();”

7. Egal was eine Methode macht, ob sie eine Exception wirft oder im Fehlerfall z.B. einfach nur string.Empty zurückliefert. Es muss im Methodenkopf dokumentiert sein!

Früher gab's unsere Rückgabewerte. Heute gibt es Exceptions, was schon wesentlich besser ist, aber auch seine Probleme mit sich bringt. Sofern es möglich ist, denke ich, sollte man CodeContracts benutzen. Sie bieten meiner Meinung nach bisher das Beste “Preis/Leistung”-Verhältnis. Microsoft hat extra in .NET 4.0 die CodeContracts an sehr vielen Stellen nachgezogen. Leider heißt auch CodeContract nicht, dass wir nie wieder try-catch-Blöcke oder Exceptions brauchen.

Zum Thema Exceptions sei folgendes gesagt. Exceptions sollten sehr sparsam eingesetzt werden. Nicht bei jeder Kleinigkeit sofort eine Exception werfen. Exceptions sollten vermieden werden wo immer es geht und keines Falls im Standardablauf der Software notwendig sein (abgesehen von sehr wenigen Ausnahmen, wie z.B. ThreadAbortException). Exceptions sollten unbedingt gefangen werden, und zwar nur an den Stellen, wo sie auch behandelt werden können. Spätestens als qualifizierte Fehlermeldung in der GUI sollte eine Exception erscheinen und dem Benutzer eine Meldung mit vielen detaillierten Informationen liefern oder ihn nach einer Entscheidung fragen. Aber auch ein boolscher Rückgabewert ist kein Verbrechen…

Auswertung Restriktiv vs. Robuste Entwicklung

Auf meinen Blogeintrag hin zu "Rebuste vs. Restriktive Programmierung", wo ich die Behauptung aufgestellt habe, dass es eine Frage des Charakters des Entwicklers sei, gab es mittlerweile mehr als 30 Fremdkommentare. Ein spannendes Thema, dass ich nun hier einmal zusammen fassen möchte.

Ausgangsthese war die Betrachtung des eigenen Programmierstils in Bezug auf Robustheit bzw. Restriktivheit von API-Methoden. Am Beispiel der Methode LoadCustomer(string alphaNumber), wobei die Kundennummer alphanumerisch sei, bedeutet Restriktiv (für mich wohlgemerkt), dass eine Methode nur dann einen Kunden findet, wenn die Kundennummer exakt übereinstimmt. Also keine Leerzeichen, kein null, Groß- und Kleinschreibung stimmt, alle Bindestriche und Sonderzeichen sind exakt eingegeben. In allen anderen Fällen wird eine Exception geworfen und der Kunde konnte nicht gefunden werden. Robust bedeutet, dass z.B. einfach mal Leerzeichen Links und rechts abgeschnitten werden, weil so die Chance, mehr Eingaben als gültig zu erkennen höher ist. Bei null gibts natürlich auch da ein Problem, aber Bindestriche könnte man z.B. auch ersetzen oder versuchen zu behandeln, wenn das Format der Nummer klar ist.

Es sei noch kurz erwähnt, dass ich oft mit Client-Entwicklung zu tun habe und deswegen oft von dem Szenario ausgehe, dass unter anderem Benutereingaben (bzw. ungeprüfte Eingaben woher auch immer) direkt an die API übergeben werden. Als Beispiele wurde zum Beispiel genannt, dass Controls.Add(null) oder label1.Text=null einfach nichts macht. Es stürzt nicht ab und wirft keine Exception, eine meiner Meinung nach sehr robuste und gute Entwicklungsweise.

Andere Beispiele für Restriktive Entwicklung wurden genannt, so zum Beispiel "simpler Datentyp".Parse oder auch alles was mit Sicherheit zu tun hat.

Zusammenfassend kann folgendes gesagt werden:

1. Alle beteiligten sind sich einige, dass, egal was auch immer eine Methode macht. Es muss irgendwo stehen und dokumentiert sein.

2. Eine Methode sollte im Namen schon soo viel wie möglich darüber aussagen, was sie auch tut. Als Beispiel hierfür gibt es die ganzen coolen ExtensionMethods, die da z.B. heissen First<>() oder FirstOrDefault<>(), wobei First eine Exception werfen kann (restriktiv) und FirstOrDefault einfach den Defaultwert zu dem Datentyp zurückgibt (robust). Namen wie "LoadCustomerByNumderOrDefaultAndTrimAndCaseSensitiveAndOtherSignsAreHandledToo" (ich übertreibe leicht), sind natürlich ungünstig.

3. Ein grober Nachteil meines Favoriten, nämlich der robusten Entwicklungsmethodik, ist der Fakt, dass unter Umständen Fehler "verloren" gehen könnten. Dabei sind sich auch alle einig, dass dies niemals der Fall sein sollte. Leere catch-Blöcke sind nur in Ausnahmesituationen ok und ansonsten sollte eine Exception, wenn sie schon nicht nach aussen weitergereicht wird, zumindest irgendwie irgendwo protokolliert werden. Andere Entwickler erkennen Fehler schneller und besser, wenn eine Exception geworfen und nach aussen gereicht wird.

4. Oft ist es eine Frage der Umgebung und der Anforderung, wie und wo eine API eingesetzt wird und ob sie sich an dieser Stelle restriktiv und robust verhalten soll. In Sachen Sicherheit sollte eine API immer restriktiv sein. Also hier kann nicht mal eben versucht werden, dem Benutzer unter die Arme zu greifen und seine Eingabe doch noch richtig zu interpretieren. Wenn ein Kennwort nicht exakt stimmt, dann ist es falsch. Das ist klar.

5. Es wurden einige Vorgehensweisen und Situationen genannt, die, wenn sie richtig durchgeführt und eingehalten werden, viele Fragen von vorn herein beantworten, damit nachher niemand da steht und nicht weiss, was er genau machen soll (und so vllt. eine Exception wirft oder auch nicht...). Ein Ansatz war immer vorher gut durchdachte Kontrakte zu erstellen, und wenn diese klipp und klar sind, dann stellen sich auch später keine Fragen wie :"Ja soll ich dem Benutzer nun unter die Arme greifen und Leerzeichen wegmachen bei der Eingabe einer Kundennummer???". Weiterhin wurde gesagt, dass, wenn alle Teammitglieder gut, oft, offen und ordentlich mit einander kommunizieren, es dann auch einfacher ist, Methoden die man geschrieben hat, für andere Entwickler bereitzustellen. Leider ist dieser Perfektionissmuss, der notwendig ist, damit wirklich immer alles glatt läuft, so wie es im Buche steht, oft nur Theorie. Die echte Praxis unterscheided sich aus vielerlei Gründen oft, was wohl jeder bestätigen kann, der schon in unterschiedlichen Projekten mit unterschiedlichen Kunden und unterschiedlichen Anforderungen gearbeitet hat...

6. Es kam heraus, dass die Mehrheit der Entwickler eher zur restriktiven Gruppe zählen würden, als zur robusten. Sie würden viel eher hier und da mal eine Exception mehr werfen, als eine zu wenig. Eine gute Alternative ist es aber, z.B. in seiner API einstellen zu können, ob man lieber ne Exception haben will oder nicht. So verhält sich die API entweder robust oder restriktiv.

7. So, im Endeffekt könnte man da bestimmt noch weiter diskutieren. Ich selbst habe mit meiner robusten Enwicklungsart mehr positive Erfahrungen gemacht als mit der restriktiven Variante und meine Kunden auch (Endbenutzer sowie auch andere Entwickler). Deswegen werde ich wohl dabei bleiben. Andere widerum haben genau andere Erfahrungen gemacht. Deswegen bleiben diese bei ihren Methoden. Wieder Andere sagen, wenn man sich überhaupt zwischen restriktiv und robust entscheiden muss (das macht man meiner Meinung nach aber auch oft im Unterbewusstsein), dann is schon von vorn herein irgendwas falsch.

Also die eine klare Endlösung gibt es nicht. Die Meinungen gehen auseinander aufgrund der Erfahrungen der Entwickler hier. Beide Versionen haben Vor- und Nachteile, wie Alles in der Welt. Wichtig ist nur, dass man sich überhaupt mal bewusst ist, dass es da einen Unterschied gibt und in welche Richtung der eigene Charakter aufgrund von Erfahrungen geprägt wurde...

Restriktiv vs. Robust

Hat sich schon mal jemand von euch Gedanken über Restriktive Programmierung vs. Robuste Programmierung gemacht? Dahinter stecken keine Pattern oder sowas, sondern Charaktere von Entwicklern.

Hinter restriktiver Programmierung steht ein Charakter der aussagt: "Ich will diese schlechte Welt verbessern".

Hinter robuster Programmierung steht ein Charakter der aussagt: "Ich lebe mit dieser schlechten Welt und mache trotz Fehlern weiter".

Wenn ihr also eine Methode entwickelt, die einige Parameter bekommst, zu welcher Art von Entwicklern zählt ihr euch? Seit ihr Entwickler, die bei jeder noch so kleinen Ungereimtheit sofort eine Exception werfen (das wäre die restriktive Variante) oder nehmt ihr die Parameterwerte, analysiert sie und versucht trotzdem, egal was da kommt, das beste draus zu machen (das ist die robuste Variante)?

Natürlich kann dies nie pauschalisiert werden, und natürlich spielen auch die Anforderungen eine Rolle, aber dennoch kann man, so denke ich, seinen Charakter einer dieser beiden Varianten zuordnen. Ich persönlich bevorzuge die Robuste Variante, denn so kann meine Methode von mehreren Entwicklern genutzt werden, und in den meisten Fällen kommt trotzdem das erwartete Ergebnis heraus. Und nur selten ertönt ein Stöhnen und die Worte "Oh man, was is denn nun schon wieder mit der Scheiß Methode? Kann die nich einfach mal funktionieren??” hehe

Ein Paradebeispiel is übrigens ein Parameterwert vom Typ string. Alle meine Methoden, die einen string erhalten, stürzen niemals ab, wenn z.B. ein null kommt (Es sei denn es ist so gewollt oder sowas). Ein null interpretiere ich eigentlich immer einfach als Leerstring. Aber es gibt auch viele Entwickler, die dann ne ArgumentNullException werfen (oder es erst gar nicht prüfen und in eine NullReferenceException laufen)... Das ist in meinen Augen eine falsche Einstellung. So nach dem Motto: "Och nö, ich mag jetzt nicht nachdenken, ich werf einfach mal ne Exception, sollen die anderen mal drüber nachdenken, und wenn ich nich exakt genau das bekomme, was ich haben will, ja dann bin ich halt bockig..." hehe

 

Was meint ihr dazu? Schreibt mal eure Meinung dazu. Ich denke, in dieser Sache einige andere Sichtweisen könnte meine eigene Sichtweise stark erweitern…

.NET Tipps (Teil 1/n)

Nun wo ich endlich meinen neuen Blog habe, möchte ich meine .NET-Tipps-Sammlung erneut aufleben lassen. Selbst wenn man jahrelang mit dem .NET-Framework gearbeitet hat, gibt es immer wieder Ecken, wo man vorher noch nie drin war. Tipps und Tricks, die man im Laufe eines Entwicklerlebens mal so nebenbei herausfindet, sind oft mit die wertvollsten Informationen, weil sie entweder die tägliche Arbeit erleichtern, oder Antworten geben auf Dinge, "die man schon immer mal wissen wollte"...

Ich hab mir mal ein paar Kleinigkeiten herausgesucht, die vllt. noch nicht jeder weiß, und die man einfach wissen sollte. Leider merkt man, wenn man jahrelang als Entwickler arbeitet, manchmal schon gar nicht mehr, was man vllt. so noch nicht wissen könnte.

Zusammenfassung am Anfang

Falls jemand meine ganzen Aus- und Abschweifungen oder Sonstiges nicht lesen mag, für den hier kurz und knackig die Tipps zusammengefasst. Für Erklärungen musste dann aber doch unten lesen :-)

 

System.Reflection.MethodBase.GetCurrentMethod();
newItem = oldItem ?? new ItemClass();
using WCF = System.ServiceModel.Channels;
string.IsNullOrEmpty()
System.Threading.Interlocked
System.Security.SecureString
GC
employee.Work += employee_Work;
System.Math.Max(myIntValue, 1);

 

So, nun mal noch zu den einzelnen Punkten:

Infos zur Klasse MethodBase

Habt ihr gewusst, dass es in der Klasse MethodBase eine statische Methode gibt, die da heißt "GetCurrentMethod()"??? So kann man nicht nur die aktuelle Methode und das Objekt herausfinden, in der oder in dem man sich gerade befindet, das coole an dieser Methode ist, dass sie auch für statische Klassen funktioniert.

Der ??-Operator

Kennt ihr schon den ??-Operator? Dieser Operator prüft den linksstehenden Term auf null. Ist der null, so wird der rechte Term zurückgegeben. Ist in dem kleinen Beispiel hier oldItem = null, dann wird new ITemClass() zurückgegeben, ansonsten, wenn oldItem ein gültiger Wert ist, wird oldItem zurückgegeben...

newItem = oldItem ?? new ItemClass();

DateTime.Now

Das wissen wahrscheinlich die meisten, aber ich sags trotzdem. Wenn nur ein einziger dabei ist, ders noch nicht weiß, hat es sich gelohnt. In DateTime.Now findet man die aktuelle Uhrzeit mit dem aktuellen Datum.. :-)

Using-Alias

Manche Entwickler binden mit Absicht die usings nicht ein, weil “man so ja sofort sieht, woher die Klasse kommt”. Diese Argumentation kann ich teilweise nachvollziehen, deswegen könnte man anstatt immer den gesamten WCF-Namespace auszuschreiben, einfach das hier nutzen: “using WCF = System.ServiceModel.Channels;”

IsNullOrEmpty in System.string

Die Klasse string beinhaltet eine statische Methode namens "IsNullOrEmpty". Viele wissen nicht, dass strings auch null sein können. Wenn man z.B.

string name;

name.Lenght

macht, so schmiert einem sein Progrämmchen ab. Und einfach nur auf "" prüfen is auch nich so dolle. Viel sicherer ist if(string.IsNullOrEmpty(name)), dann... erst weitermachen. Im übrigen gibt es auch string.Empty. Das ist ein Leerstring, vergleichbar mit "", aber sauberer ist es, string.Empty zu nutzen.

Atomare Methoden der Interlocked-Klasse

Wer viel mit Threads programmiert, sollte die Interlocked-Klasse kennen. Sie beinhaltet atomare Methoden, also Methoden, die nicht unterbrochen werden können. Es ist ja so, dass so ein simples "i++" keine atomare Methode ist. Es sind mehrere Registerumräumungen nötig (sichern, inkrementieren, zurückschreiben). Wenn dabei ein anderer Thread dazwischenhaut, arbeitet der vllt. auf einem falschen Wert. Deswegen lieber das Inkrement der Interlocked-Klasse nutzen, damit passiert das nicht.

SecureString für sichere Strings im Speicher

Ihr wollt eine Anwendung schreiben, wo man auch mal ein Passwort im Speicher halten möchte? Um es möglichen Angreifern wenigstens ein wenig schwerer zu machen, gibt es die Klasse "SecureString" im Security-Namespace. Damit wird der String verschlüsselt im Speicher abgelegt... Kann manchmal bestimmt nicht schaden.

Ein wenig Garbage Collection 

Mit "GC" kann man einfach mal eben so auf den GarbageCollector zugreifen. Der sammelt die alten toten Objekte ein. Hier kann man das Einsammeln manuell starten, oder auch festlegen, dass Objekte nicht eingesammelt werden sollen.

1.0 == 0.9Periode9 = true... ???.   <-- Hää?

Zu guter letzt. Habt ihr gewusst, dass die beiden folgenden Ausdrücke genau das Gleiche sind?
"eployee.Work += new EventHandler( employee_Work );"
und
"employee.Work += employee_Work;"
Ja, doll. 1 ist ja auch exakt das gleiche wie 0,9Periode9... aber wir wollen ja nicht mit Mathematik anfangen...

Na wenn wa eh gerade bei Mathe sind

Schaut euch mal um in der Klasse “System.Math”. Die beinhaltet sehr viele statische Methoden, die auch nützlich sein können, wenn man keine mathematische Anwendung schreiben will. Zum Beispiel habe ich eine int-Property, die aufgrund von Konventionen immer mindestens 1 zurückgeben muss. Also return System.Math.Max(myIntValue, 1);

Mehr Beiträge Nächste Seite »
Nico Franze Herzlich Willkommen auf meinem Blog. Ich bin Nico, freier Softwareentwickler sowie Autor für Fachzeitschriften. Hab mit .NET Version 1.0 begonnen (damals noch VB.Net) und bin dann schlussendlich bei C# gelandet. Mehr Infos gibts unter www.nfranze.de


Suche

Los

Translator Widget

Dieser Blog

Syndikation


Locations of visitors to this page