[Off Topic] Koffein
Eine Frage an ALLE Entwickler... 

Trinkt ihr Kaffee???

Das hier ist das Netz einer normalen gesunden Spinne. Unten sieht man das Netz einer Spinne unter Koffeineinfluss. 

Die Frage ist... Lassen sich hier Rückschlüsse auf Softwarestrukturen schließen???

(Quelle: Wikipedia)

Ich trinke übrigens keinen Kafffee... ;-)
StuxNet… Der neue Wurm

Ich komme soeben von einem Vortrag zu dem Wurm StuxNet, auf dem einige interessante Details genannt wurden. Wer das Thema aufmerksam in den Medien verfolgt hat, wird hier wahrscheinlich nichts neues lesen können, aber ich wollt sowieso endlich mal wieder ein wenig bloggen. Also ist das das ideale Thema.

Stuxnet ist ein Computerwurm, der vor ca. einem Jahr bekannt wurde. Es handelt sich hierbei nicht um einen normalen Wurm, sondern um einen, der ausschließlich Siemens Steueranlagen (z.B. für Atomkraftwerke) infiziert. Und auch nicht alle, nein. nur ganz bestimmte. Nämlich eine im Iran. Aber nun zu den Details.

Hier eine kurze Zusammenfassung. Für mehr Interesse unten eine erweiterte Zusammenfassung.

Kurzzusammenfassung:

Stuxnet ist ein Computerwurm, der mit sehr sehr viel Aufwand entwickelt wurde und interessanter Weise nicht normale Computer angreift, sondern exakt nur eine ganz bestimmte Anlagensteuerung von Siemens. Die Siemens Simatik 7. Aber auch nicht alle, sondern nur die eine, wo ganz bestimmte Seriennummern vorlagen. Diese Anlage steht zufällig im Iran.

Eckdaten (Teilweise Vermutungen)

  • Entwicklungsbeginn 2002 in Israel
  • Entwicklungsende ca. 2007
  • Schadcode greift nur S7 von Siemens an
  • 7 unterschiedliche Verbreitungsmöglichkeiten, um sicherzustellen, dass auch wirklich mindestens eine greift
  • Die Fernsteuerung von Stuxnet wurde deaktiviert
  • Der Entwicklungsaufwand wird auf viele Millionen $ geschätzt
  • Allein eine solche Testumgebung aufzubauen und zu betreiben liegt zwischen 0,5 und 1 Million $
  • Verteilte Management-Infrastrukturen in Dänemark und in Malaysia (Dort standen wohl einige Verbreitungs- und Update-Server)
  • Infektionsrate im Iran, ca. 60%, in USA 0,3 %, in Deutschland noch weniger
  • Großes Gefahrenpotential für die Zukunft, schätzten Diskussionsbeteiligte

Erweiterte Zusammenfassung

Neuartigkeit an diesem Computerwurm:

  • Der Schadcode infiziert ausschließlich eine Siemens Simatik 7 Prozessanlagensteuerung
  • Anhand von technischen Vorgehensweisen und auch einigen öffentlichen Aussagen lässt sich vermuten, dass Stuxnet 2002 in Israel begonnen wurde.
  • Nicht generell alle diese Prozessanlagen werden infiziert.
  • Stuxnet prüft Seriennummern der beteiligten Komponenten
  • 7 Verbreitungsmechanismen
    • USB (Stick einstecken, schon war der Rechner infiziert) (Zero Day Exploit MS10-046)
    • Fileserver (Dateien von einem Fileserver im Explorer anzeigen, schon war der Rechner infiziert)
    • Printserver (Auf einem Printserver drucken, schon war der Rechner infiziert) (Zero Day Exploit MS10-061)
    • Windows RPC
    • Siemens Projektdateien
    • WinCC-Datenbank für S7-Steuerung (Datenbank, in der Simatik 7 Daten speichert. Auf Daten zugreifen und schon war der Rechner infiziert, sofern DB-Server infiziert war)
    • Eigenes Peer-to-Peer-Netzwerk. (Der Virus hat untereinander mit einem eigenen Protokoll kommuniziert, wie weit Infizierung vorangeschritten ist und wo noch Rechner sein könnten und welche Rechte er bisher erreichen konnte)
  • Stuxnet verfügt über Fernsteuerung. Diese wurde abgeschaltet. Entweder vor der Verbreitung, weil man wusste, dass Atomanlagen eh nicht online erreichbar sind, oder im Zuge der Entdeckung von Stuxnet
  • Nach der Infizierung des Kraftwerkes macht Stuxnet erstmal gar nichts und lernt mehrere Wochen lang die Abläufe im Kraftwerk kennen. Nach einer bestimmten Zeit fängt der Wurm an, langsam die Kontrolle zu übernehmen und dem Benutzer das erwartete Verhalten vorzugaukeln, so wie es sich stuxnet Wochen vorher angelernt hat

Aufwand

  • Es wurden insgesamt 2 Softwarezertifikate von Realtek gestohlen (so kann sich der Wurm unbemerkt als Treiber installieren, weil Windows denkt, das is ne gültige Netzwerkkarte)
  • Es musste eine Simatik 7 Testanlage aufgebaut werden, um den Wurm zu testen.
  • Die Parameter der Zielkonfiguration mussten ermittelt werden (Seriennummern der Bauteile im Iran, Arbeitsfrequenzen und Arbeitsweisen) (Hoher Spionageaufwand)
  • Abschluss der Entwicklungen vermutlich 2007
  • Verteilte Management Infrastrukturen in Dänemark und Malaysia (Server)
  • Es wurden insgesamt 4 Zero DayExploits (z.B. MS10-046 und MS10-061) benutzt. Jeder dieser Sicherheitslücken kostet auf dem Schwarzmarkt ca. 250.000 $
  • Infizierungen gab es weltweit. Das Virus wollte so viele PCs wie möglich infizieren (aber ohne Auswirkungen auf PCs), um so möglichst viele Wege in das Zielkraftwerk zu haben. Höchste Infizierungsrate im Iran (mit 60% aller Rechner)

Gefahren

  • Im Code waren wohl 984 Programmierbare Speichereinheiten hinterlegt, die ausfallen sollten, und zufällig sind im Iran 984 Programmierbare Speichereinheiten in einem Kraftwerk ausgefallen
  • Stuxnet greift an, wenn bestimmte Frequenzumrichter mindestens 800 Hz hatten (Ermittlung der Zielkonfiguration im Iran)
  • Eine Gefahr geht wohl von „Social Engineering“ aus. Das bedeutet, durch symphatisches, selbstbewusstes auftreten mit einigen sowieso öffentlich zugänglichen Vorinformationen, von anderen Mitarbeitern in sensible Bereiche eingelassen werden und dort selbstständig handeln zu können.
  • Einige Diskussionsteilnehmer sagten, einfach eines dieser Kraftwerke ausschalten, hätten die Entwickler auch sehr viel günstiger haben können, in dem sie ein paar Bomben auf das Kraftwerk geworfen hätten. Der Hohe Aufwand wurde aber betrieben, um eigentlich keine Aufmerksamkeit zu erregen.
  • Erstes Auftreten von Stuxnet im Jahre 2008 (Virenscannerhersteller bemerkten unerlaubte Zugriffe auf bestimmte Windows-Sicherheitslücken)
  • Erste klare Strukturen vom Ausmaß von Stuxnet wurden 2009 erkannt. In den Medien tauchte Stuxnet 2010 auf.

Ergebnisse der Diskussion nach dem Vortrag (Wie schützt man sich davor)

  • Dinge wie Datenschleusen einbaun
  • Integritätsprüfungen
  • Einige Teilnehmer haben keine Angst und glauben nicht, dass irgendwelche Hacker-Kiddies mal eben Stuxnet nachbaun
  • Andere Teilnehmer glauben, dass sich nun jeder Hobbyhacker auf Stuxnet stürzt, das studiert und dann jeder ein eigenes Stuxnet schreibt und bald kein einziges Kraftwerk mehr auf der Welt funktioniert
  • Mitarbeiter stark gegen social Engineering sensibilisieren
  • Am besten gar nicht erst ein Kraftwerk betreiben
  • Es wird vermutet, dass Firmen wie Microsoft und Siemens mitgeholfen haben, dies wurde aber versucht zu zerstreuen, da z.B. Informationen, wie man eine Programmierbare Speichereinheit der S7 ansteuert, größtenteils von Siemens online zur Verfügung gestellt wird
  • Weiterhin is der Iran sowieso bekloppt, weil er Hightech vom Klassenfeind kauft.
  • Die Anlage sollte auf Windows-Rechnern laufen. Früher waren das extra proprietäre Betriebssysteme, die extra für solche Anlagensteuerungen entwickelt wurden. Diese waren weniger angreifbar
  • Der Iran sagt, alles kein Problem. Angriffe vom verzweifelten Feind konnten leicht und ohne Probleme abgewehrt werden. Das Stuxnet selbst teilweise vorgaukelt, „besiegt“ bzw. entfernt zu sein, interessierte die Iraner aber nich.

Ein sehr spannender Vortrag mit noch spannender Diskussionsrunde.

Fazit:

Ja, natürlich sollte ein Fazit nicht fehlen. Ich persönlich denke, dass hiermit erstmals eine Grenze überschritten wurde. Eine neue Art der Kriegsführung. Stuxnet hat nun dafür gesorgt, dass einige Zentrifugen ausgefallen sind oder andere Systeme nicht mehr oder nicht richtig funktionierten. Allerdings hätten die Entwickler stuxnet auch so entwickeln können, dass das Atomkraftwerk in die Luft fliegt und wir hätten ein zweites Tschernobyl mit unbeschreiblichen weltweiten Folgen. Bei der Diskussion wurde auch ein Beispiel aus der Fertigung angebracht. Wir können heutzutage im µ-Meterbereich fertigen. Wenn wir da auch nur unbemerkt winzige Abweichungen aufgrund eines Computerwurms hätten, so hätten wir am Anfang der Prozesskette große Qualitätsverluste, die sich später auf alle Bereiche wie Automobile, Küchengeräte oder Computerindustrie auswirken könnten. Die Fehlerfindung, ob wir einen Computerwurm haben, der sich gut verstecken kann, oder ob ein Mitarbeiter einfach zur falschen Zeit das Fenster aufgemacht hat und der Luftstoß die Abweichungen verursacht hat, könnten wieder viele Millionen $ bzw. € kosten, die man auch viel lieber in sinnvollere Bereiche investieren könnte. 

Aktuell bezweifle ich, dass Länder wie Iran oder andere Staaten, die teilweise noch im Mittelalter leben, Viren oder Würmer solcher Art entwickeln können. Die arbeiten mit viel primitiveren Waffen. Aber es ist sicherlich auch nur eine Frage der Zeit, bis erste konkrete Anstrengungen unternommen werden, die uns hier in der westlichen Welt, abhängig von vielen Maschinen und Computern, zu schädigen versuchen. (Ich möchte an dieser Stelle aber nicht in eine politische Diskussion abdriften, sowas geschieht schneller als man denkt. :-))


Quellen:

Wie gewünscht einige Quellen des Vortrages.

Symantec W32.Stuxnet Dossier 

http://www.symantec.com/content/en/us/enterprise/media/security_response/whitepapers/w32_stuxnet_dossier.pdf


OECD/IFP Project on "Future Global Shocks 

http://www.oecd.org/dataoecd/3/42/46894657.pdf


Israeli Test on Worm Called Crucial in Iran Nuclear Delay, New York Times

Visual Studio 2010 Projektarten

Mir ist gerade etwas interessantes aufgefallen.. Bevor ich es vergesse schreib ich es lieber schnell auf.

Ich habe in VS2010 ein ganz normales Projekt erzeugt (Windows-Anwendung). Ich habe eine Testklasse geschrieben mit ganz normalen UnitTest-Attributen wie [TestClass] und [TestMethod] und ich konnte machen was ich wollte, meine Testmethoden sind einfach nicht in meinem Testlisten-Editor erschienen.

Ich habe mir dann einmal von Visual Studio ein richtiges Test-Projekt erzeugen lassen und da war alles schick. Ich habe meine selbstgeschriebenen Testklassen in das Test-Projekt kopiert und sofort wurden meine Testmethoden im Testeditor aufgelistet.

Wenn ich bei meinem normalen Windows-Projekt rechte Maustaste und Neues Element hinzufügen anklicke, so erscheint mir folgende Auswahl:

image

Mache ich das gleiche bei meinem Testprojekt, erscheint folgende Auswahl:

image

Interessant. Hier bietet mir VS auf einmal viel cooles Testzeug an, dafür aber keine Fenster und Steuerelemente-Sachen mehr. So, jetzt ging die Sucherei los… Wo war der Unterschied…

Ich habe die beiden Projektdateien verglichen und folgende interessante Zeile in dem normalen Windows-Projekt gefunden:

<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

Im Testprojekt sieht die Zeile so aus:

<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

Danach habe ich mal aus Spaß den ersten GUID, der nämlich den Unterschied darstellt auch einfach mit in mein Windows-Projekt kopiert. Die Zeile sieht dann da nun so aus:

<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>

So. Und das Resultat in Visual Studio sieht dann so aus:

image

Nun hab ich alles. Yeah. Windows-Elemente sowie Testelemente. Und meine Testmethoden aus dem Projekt wurden auch sofort im Testlisten-Editor erkannt. Juhu.

Nun könnte ich mir noch viele andere GUIDs heraussuchen und ein Projekt zu einem ultimativen Monsterprojekt machen wo alles drin is… muss aber auch nicht sein. :-) Denn wenn man wie ich nun sein Windows-Projekt zu einem Testprojekt macht und es dann ausführen will, so werden auch nur die Tests gestartet, und nicht mehr das eigentliche Progrämmchen. Deswegen macht es auch viel mehr Sinn, einfach ein separates Testprojekt zu erzeugen, in dem einfach nur alle Tests ausgeführt werden.

Eine Liste von Projekttypen hab ich dann doch noch hier gefunden:

Project Type Description

Project Type Guid

Windows (C#)

{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}

Windows (VB.NET)

{F184B08F-C81C-45F6-A57F-5ABD9991F28F}

Windows (Visual C++)

{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}

Web Application

{349C5851-65DF-11DA-9384-00065B846F21}

Web Site

{E24C65DC-7377-472B-9ABA-BC803B73C61A}

Distributed System

{F135691A-BF7E-435D-8960-F99683D2D49C}

Windows Communication Foundation (WCF)

{3D9AD99F-2412-4246-B90B-4EAA41C64699}

Windows Presentation Foundation (WPF)

{60DC8134-EBA5-43B8-BCC9-BB4BC16C2548}

Visual Database Tools

{C252FEB5-A946-4202-B1D4-9916A0590387}

Database

{A9ACE9BB-CECE-4E62-9AA4-C7E7C5BD2124}

Database (other project types)

{4F174C21-8C12-11D0-8340-0000F80270F8}

Test

{3AC096D0-A1C2-E12C-1390-A8335801FDAB}

Legacy (2003) Smart Device (C#)

{20D4826A-C6FA-45DB-90F4-C717570B9F32}

Legacy (2003) Smart Device (VB.NET)

{CB4CE8C6-1BDB-4DC7-A4D3-65A1999772F8}

Smart Device (C#)

{4D628B5B-2FBC-4AA6-8C16-197242AEB884}

Smart Device (VB.NET)

{68B1623D-7FB9-47D8-8664-7ECEA3297D4F}

Workflow (C#)

{14822709-B5A1-4724-98CA-57A101D1B079}

Workflow (VB.NET)

{D59BE175-2ED0-4C54-BE3D-CDAA9F3214C8}

Deployment Merge Module

{06A35CCD-C46D-44D5-987B-CF40FF872267}

Deployment Cab

{3EA9E505-35AC-4774-B492-AD1749C4943A}

Deployment Setup

{978C614F-708E-4E1A-B201-565925725DBA}

Deployment Smart Device Cab

{AB322303-2255-48EF-A496-5904EB18DA55}

Visual Studio Tools for Applications (VSTA)

{A860303F-1F3F-4691-B57E-529FC101A107}

Visual Studio Tools for Office (VSTO)

{BAA0C2D2-18E2-41B9-852F-F413020CAA33}

SharePoint Workflow

{F8810EC1-6754-47FC-A15F-DFABD2E3FA90}

XNA (Windows)

{6D335F3A-9D43-41b4-9D22-F6F17C4BE596}

XNA (XBox)

{2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2}

XNA (Zune)

{D399B71A-8929-442a-A9AC-8BEC78BB2433}

SharePoint (VB.NET)

{EC05E597-79D4-47f3-ADA0-324C4F7C7484}

SharePoint (C#)

{593B0543-81F6-4436-BA1E-4747859CAAE2}

Silverlight

{A1591282-1198-4647-A2B1-27E5FF5F6F3B}

Tabelle 1: Projekttypen von Visual Studio 2010 (Quelle: http://www.mztools.com/Articles/2008/MZ2008017.aspx)

Eigene Templates mit Geburtsdatum für VS schreiben

Ich werde heute mal etwas nostalgisch in der Vergangenheit herumrühren und hier kurz beschreiben, wie man ein ItemTemplate für Visual Studio 2008 baut und individualisiert. Das heißt, wir legen mittels eines Templates zwei cs-Dateien an, eine resx-Datei und wir schreiben in irgendein Kommentar automatisiert das Geburtsdatum des Autors. Ja, ihr habt richtig gelesen. 2008. Asche über mein Haupt. Ich werd das aber demnächst für 2010 nachholen… :-) Aber da 2008 immer noch sehr häufig benutzt wird, dachte ich mir, ich mach das jetzt mal.

ItemTemplates oder auch ProjectTemplates sind die Vorlagen, die man in Visual Studio findet, wenn man ein Neues Projekt oder ein Neues Element hinzufügen möchte. Hier können auch eigene Templates geschrieben werden.

clip_image001

Wenn man ein neues Element hinzufügt so werden mindestens eine Datei, oder auch mehrere Dateien oder Ordner, dem Projekt hinzugefügt. In unserem Beispiel möchten wir eine Kommando-Klasse (z.B. SpeichernKommando) und ein Interface in einem separaten Use-Case-Ordner hinzufügen. Zusätzlich wollen wir noch eine Ressourcendatei anlegen. Danach wollen wir weitere Benutzereingaben entgegennehmen und ebenfalls in den Dateien hinterlegen. Hier kurz noch mal meine beiden cs-Dateien, die als Vorlage für ein Kommando dienen sollen.

SpeichernKommando.cs

   1: namespace WizardBeispiel
   2: {
   3:     public class SpeichernKommando : ISpeichernKommando
   4:     {
   5:         public SpeichernKommando()
   6:         {
   7:  
   8:         }
   9:  
  10:         public bool IsAvailable()
  11:         {
  12:             return true;
  13:         }
  14:  
  15:         public void Execute()
  16:         {
  17:             throw new NotImplementedException();
  18:         }
  19:     }
  20: }

 

ISpeichernKommando.cs

   1: namespace WizardBeispiel
   2: {
   3:     public interface ISpeichernKommando
   4:     {
   5:         bool IsAvailable();
   6:         void Execute();
   7:     }
   8: }

Zuerst erstellen wir uns eine Grundlage. Klick auf Datei –> Vorlage Exportieren

image

Dann befolgt ihr den Wizard. Wählt Symbolvorlage (für deutsche VS, englisch Item Template)

clip_image001[5]

Wählt die zu exportierende Datei aus. Obwohl hier Checkboxen sind und man ein additives Verhalten vermuten könnte, ist dem leider nicht so. Trotzdem können wir später mehr als nur eine Datei hinzufügen. Jetzt wählen wir einfach erstmal nur das Kommando.

clip_image001[7]

Wählt anschließend noch einige Assemblies, die ihr gern immer mit dabei haben möchtet, wenn ihr das Template auswählt. Auch hier könnt ihr später eigene Assemblies angeben.

clip_image001[9]

Im letzten Schritt könnt ihr ein Symbol wählen, eine Beschreibung anpassen und den Exportordner einsehen. Die Daten für das Template selbst können später alle noch verändert werden.

clip_image001[11]

Nachdem dieser Schritt ausgeführt wurde, entsteht eine zip-Datei in Eigene Dateien\Visual Studio 2008\My Exported Templates\. In meinem Beispiel beinhaltet diese 3 Dateien.

  • __TemplateIcon.ico
  • MyTemplate.vstemplate
  • SpeichernKommando.cs

Die Bilddatei zeigt das Bildchen an in Visual Studio, die cs-Datei ist die Datei, die später wirklich importiert werden soll und in der .vstemplate-Datei steht genau drin, was wohin importiert werden soll. Im Übrigen geht das hier natürlich mit VB genauso gut wie mit C#. Da es sich ja nun hier nicht mehr um das SpeichernKommando direkt handelt sondern um eine Vorlage für alle möglichen Kommandos, benennen wir die Datei einfach mal um in „Kommando.cs“. Der Name der Datei muss auch in der vsTemplate-Datei umbenannt werden. Die vsTemplate-Datei sieht nun wie folgt aus:

   1: <VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
   2:   <TemplateData>
   3:     <DefaultName>WizardBeispiel.cs</DefaultName>
   4:     <Name>WizardBeispiel</Name>
   5:     <Description>Beschreibung zu meinem Template</Description>
   6:     <ProjectType>CSharp</ProjectType>
   7:     <SortOrder>10</SortOrder>
   8:     <Icon>__TemplateIcon.ico</Icon>
   9:   </TemplateData>
  10:   <TemplateContent>
  11:     <References>
  12:       <Reference>
  13:         <Assembly>System</Assembly>
  14:       </Reference>
  15:     </References>
  16:     <ProjectItem SubType="Code" TargetFileName="$fileinputname$.cs" ReplaceParameters="true">Kommando.cs</ProjectItem>
  17:   </TemplateContent>
  18: </VSTemplate>

Wenn man sich hier unsere cs-Datei noch einmal anschaut, dann wird man feststellen, dass alle „personalisierten“ Informationen mit Platzhaltern versehen wurden. Der Namespace, der Name sowie der Konstruktor der Klasse beinhalten nun kryptische Platzhalter wie $rootnamespace$ oder $safeitemname$. Somit ist sichergestellt, dass später die Klasse im richtigen Namespace liegt, den richtigen Namen hat und auch alles wieder sofort kompilierbar ist. Der Parameter „" ReplaceParameters="true"” sorgt dafür, dass diese Platzhalter auch wirklich ersetzt werden.

Jetzt fehlen uns ja noch weitere Dateien. Wir kopieren unsere Interface-Datei auch noch in den Ordner, wo wir gerade unsere vsTemplate-Datei entpackt haben. Außerdem kopieren wir noch eine resx-Datei hinein. Wenn ihr keine habt, erzeugt in Visual Studio irgendwo schnell eine Neue und fügt diese ein. Die .Designer.cs-Datei brauchen wir hier nicht, die erzeugen wir nachher neu. Meine wird Resource.resx heißen.

Wir haben ja im Wizard gesagt, das Template soll gleich wieder nach Visual Studio importiert werden. „Importiert“ heißt kopiert, und zwar nach C:\Dokumente und Einstellungen\nfranze\Eigene Dateien\Visual Studio 2008\Templates\ItemTemplates. Befindet sich diese zip-Datei in diesem Ordner, so ist die Vorlage schon hinzugefügt, wenn man ein neues Element dem Projekt hinzufügen möchte.

clip_image001[13]

Auch das Icon findet sich wieder. Wenn wir nun dieses WizardBeispiel-Element einmal testweise hinzufügen zum Projekt, so erscheint unsere Datei, nur mit dem soeben neu eingegebenen Namen. Sollte die System-Assembly noch nicht als Verweis vorliegen, so würde sie mit hinzugefügt werden. So wie es in unserem Template angegeben ist.

Aber nun weiter in unserer vstemplate-Datei. Wir fügen nun zwei neue Zeilen ein und ändern eine vorhandene noch etwas ab, damit die Dateien in unserem UC-Ordner landen. Mit den beiden neuen Dateien möchten wir zusätzlich zum Kommando noch unsere Ressourcendatei sowie unser Interface hinzufügen. Unsere Datei sieht nun wie folgt aus.

   1: <TemplateContent>
   2:   <References>
   3:     <Reference>
   4:       <Assembly>System</Assembly>
   5:     </Reference>
   6:   </References>
   7:   <ProjectItem SubType="Code" TargetFileName="UC$fileinputname$\$fileinputname$.cs" OpenInEditor="true" ReplaceParameters="true">Kommando.cs</ProjectItem>
   8:   <ProjectItem SubType="Code" TargetFileName="UC$fileinputname$\I$fileinputname$.cs" ReplaceParameters="true">IKommando.cs</ProjectItem>
   9:   <ProjectItem SubType="Code" CustomTool="ResXFileCodeGenerator" TargetFileName="UC$fileinputname$\$fileinputname$Ressource.resx" ReplaceParameters="true">Resource.resx</ProjectItem>
  10: </TemplateContent>

Diese Zeilen besagen nun folgendes. Erstens werden alle drei Dateien in einem neuen Unterordner erstellt, der mit UC anfängt und weiterhin den Namen der Datei beinhaltet (ohne Endung). Wenn wir also ein Element hinzufügen und es „SpeichernKommando.cs“ nennen, so entsteht automatisch erstmal ein Ordner namens „UCSpeichernKommando“. Dieser Ordner enthält drei Dateien. Eine  SpeichernKommando.cs, eine ISpeichernKommando.cs sowie eine SpeichernKommandoRessource.resx.

Das OpenInEditor gibt an, welche Datei danach im Editor geöffnet werden soll. Ich finde das Kommando besser als z.B. das Interface oder die Ressourcendatei. Hier kann natürlich jeder machen was er will. Im Parameter Subtype steht drin, welcher Editor zum Öffnen benutzt werden soll.

Bei der Ressourcendatei ist es wichtig noch das CustomTool anzugeben. Dieses erzeugt automatisch die gewünschte .Designer.cs und ordnet sie der Ressourcendatei unter. Bei Ressourcendateien heißt das CustomTool, welches die Ressourcen.Designer.cs-Datei generiert „ResXFileCodeGenerator“. Solltet ihr andere Anwendungsfälle haben, so erzeugt euch einfach einmal eine entsprechende Datei ganz regulär in Visual studio und schaut euch die Dateieigenschaften an. Dort ist das CustomTool eingetragen, welches ihr hier in der Vorlage benutzen könnt.

clip_image001[15]

So, mein Ordner besteht nun aus folgenden Dateien.

  • __TemplateIcon.ico
  • MyTemplate.vstemplate
  • Kommando.cs
  • IKommando.cs
  • Ressource.resx

Bevor wir aber weitermachen müssen wir noch unser Interface anpassen. Aktuell sieht es so aus:

   1: namespace WizardBeispiel
   2: {
   3:     public interface ISpeichernKommando
   4:     {
   5:         bool IsAvailable();
   6:         void Execute();
   7:     }
   8: }

Hier ist noch nicht ein Platzhalter drin. Wir ändern es in folgendes ab

   1: namespace $rootnamespace$.UC$fileinputname$
   2: {
   3:     public interface $safeitemname$
   4:     {
   5:         bool IsAvailable();
   6:         void Execute();
   7:     }
   8: }

Nun befindet sich auch das Interface im richtigen Namespace (auch im Unterordner) und hat auch den richtigen Namen. Der gleiche Namespace sollte auch in dem Kommando selbst gewählt werden. Hier braucht man kein großes I vor den Platzhalter machen, das macht VS anscheinend automatisch.

In unserer Kommando.cs sollte das Interface aber auch benutzt werden. (Man beachte hier aber das I)

    public class $safeitemname$ : I$safeitemname$

Diese Dateien zippen wir nun und kopieren sie nach „C:\Dokumente und Einstellungen\nfranze\Eigene Dateien\Visual Studio 2008\Templates\ItemTemplates“. Schon können wir in Visual Studio „Neues Element“ hinzufügen und unsere Vorlage auswählen. Probieren wir das mal.

clip_image001[17]

Et voilà

clip_image001[19]

Und es kann sofort wieder kompiliert werden… Doll. Jetzt kommt der eigentliche Spaß. Ich möchte dass im Kommando automatisch das Geburtsdatum des Autors steht. Da Visual Studio das nicht erraten kann, muss es während des Hinzufügens irgendwo angegeben werden. Wir definieren in unserem Kommando die Stelle, wo wir das gern hinhaben möchten. Bei mir sieht das nun so aus, dass ich es über meiner Klasse eingefügt hab:

// $username$ hat Geburtstag am $geburtsdatum$

public class $safeitemname$ : I$safeitemname$

{

Den Platzhalter $username$ gibt es schon, den Platzhalter $geburtsdatum$ werden wir nachher selbst ersetzen. Im Übrigen gibt es folgende vordefinierte Platzhalter (Quelle: http://msdn.microsoft.com/en-us/magazine/cc188697.aspx):

Parameter

Beschreibung

itemname

The user-provided name from the dialog. This value is often used for naming classes and files.

safeitemname

The same as itemname but with all unsafe characters removed.

safeitemrootname

The name of the root item, which can be used for multi-item templates. For example, if you have partial classes, or a code-beside or code-behind scenario, you can use this item in the secondary files when you need the safeitemname of the main item being created.

projectname

The user-provided name of the project for a project template.

safeprojectname

The user-provided name of the project for a project template with all unsafe characters removed.

rootnamespace

The root namespace of the project for an item template, which can be used to replace the namespace in a project item source file.

guid[1-10]

A GUID that can be used as a unique identifier for uses such as project GUID in the project file. You can specify up to 10 different GUID parameters using the syntax: guidx, where x is a number between 1 and 10.

time

The current time in the format DD/MM/YYYY HH:MM:SS.

year

The current four-digit year.

username

The Windows username of the logged-in user.

userdomain

The Windows domain of the logged-in user.

machinename

The name of the machine where the template is being used.

clrversion

The version of the common language runtime being used.

registered-organization

The registered organization of the user based on the system settings.

wizarddata

A single string that can be any XML or string data included in the WizardData element in a template metadata file.

Zuerst erzeugen wir ein neues Visual Studio Projekt vom Typ Klassenbibliothek. Als Verweise fügen wir folgende ein:

  • EnvDTE.dll
  • Microsoft.VisualStudio.TemplateWizardInterface.dll

Die vorgefertigte Klasse Class1 nutzen wir gleich. Wir nennen sie „Wizard“ und implementieren das Interface „IWizard“ im namespace Microsoft.VisualStudio.TemplateWizard.

Damit diese Assembly benutzt werden kann, muss sie in den GAC. Damit sie da rein kann, muss sie signiert werden. Das geht in den Projekteigenschaften -> Tab „Signierung“. Dort Checkbox „Assembly signieren“ anhaken und in der Combobox bei Schlüsseldatei „Neu“ auswählen. In dem Fenster was erscheint, gebt ihr irgendeinen Namen für die Daten an z.B. „key“ und ein passwort braucht ihr hier nicht. Das sieht nun so aus

clip_image001[21]

Als nächstes fügen wir einen winzigen Dialog (Windows Form) hinzu. Ich nenne den Dialog „DataDialog“. Auf meinem Dialog platziere ich einfach nur einen DateTimePicker. So sieht das nun bei mir aus. Wehe hier schreibt irgendeiner ins Kommentar, dass der Dialog scheiße aussieht, oder er verzweifelt den „OK“-Button sucht… Das Ausschmücken des Dialoges überlasse ich der Phantasie des Lesers.

clip_image001[23]

Ich mache nun einfach auf die Schnelle eine Property in den Dialog, der den Wert des Datetimepickers zurückgibt. Später muss der Dialog einfach nur mittels „x“ oben rechts geschlossen werden.

   1: public DateTime Datum
   2: {
   3:     get { return dateTimePicker1.Value; }
   4:     set { dateTimePicker1.Value = value; }
   5: }

In dem Wizard entferne ich überall die throw NotImplementedExceptions und in der Methode RunStarted werden wir den Dialog anzeigen und selbst einen dieser Platzhalter ersetzen. Die Parameter der Methode „RunStarted“ sind automationObject, welches das DTE-Objekt ist, falls jemand hier noch mit dem CodeDOM oder anderen Visual Studio Features weiterarbeiten möchte. Weiterhin gibt es ein Dictionary. Dieses beinhaltet alle Platzhalter und ihre Ersetzungen. Hier brauchen wir unser Geburtsdatum nur noch hinzufügen. Mein code sieht wie folgt aus:

   1: public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
   2: {
   3: }
   4:  
   5: public void ProjectFinishedGenerating(EnvDTE.Project project)
   6: {
   7: }
   8:  
   9: public void ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
  10: {
  11: }
  12:  
  13: public void RunFinished()
  14: {
  15: }
  16:  
  17: public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
  18: {
  19:     DataDialog dialog = new DataDialog();
  20:     dialog.ShowDialog();
  21:  
  22:     replacementsDictionary.Add("$geburtsdatum$", dialog.Datum.ToShortDateString());
  23: }
  24:  
  25: public bool ShouldAddProjectItem(string filePath)
  26: {
  27:     return true;
  28: }

Ok, so, nun ham wa ja schon viel geschafft. Wir kompilieren alles und werden nun unsere Assembly in den GAC einfügen. Das geht wie folgt. Im Startmenu „Microsoft Visual Studio 2008“->“Visual Studio Tools“->“Visual Studio 2008-Eingabeaufforderung“. Wichtig ist, IM ADMINISTRATORMODUS STARTEN (rechte Maustaste als Admin ausführen). Folgende Zeile eingeben:

Gacutil /i „C:\Dokumente und Einstellungen\nfranze\Eigene Dateien\Visual Studio 2008\Projects\ItemWizard\ItemWizard\ItemWizard\bin\Debug\ItemWizard.dll“

Wobei ihr natürlich hoffentlich euren Pfad nehmt… ;-)

Als letzte kleine Änderung müssen wir unseren Wizard nun auch noch bekannt machen in unserem Template. Dafür bearbeiten wir die vstemplate-Datei wie folgt.

   1: <VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
   2:   <TemplateData>
   3:       ...
   4:   </TemplateData>
   5:   <TemplateContent>
   6:       ...
   7:   </TemplateContent>
   8:   <WizardExtension>
   9:      <Assembly>ItemWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=859062e4da6d855b</Assembly>
  10:      <FullClassName>ItemWizard.Wizard</FullClassName>
  11:   </WizardExtension>
  12: </VSTemplate>

So, nun haben wirs. Nehmt euch wieder euren Ordner mit den ganzen Dateien drin, macht wieder eine zip draus und kopiert die Datei ins ItemTemplate-Verzeichnis. Und dann probiert mal, ob das so klappt.

Solltet ihr euch manchmal wundern, dass eure Änderungen im Wizard nicht übernommen werden, wenn ihr neu kompiliert und die Datei neu dem GAC zufügt, so startet mal Visual Studio neu.

Weiterhin habe ich einige Dokumentationen gesehen, in denen bei <WizardExtension><Assembly> immer nur der Assemblyname angegeben wurde, ohne Version und PublicKeyToken. Dies kann manchmal folgenden Fehler verursachen:

„Fehler: Diese Vorlage hat versucht, die nicht vertrauenswürdige Komponente „ItemWizard“ hinzuzufügen. Weitere Informationen zu diesem Problem und zum Aktivieren dieser Vorlage finden Sie in der Dokumentation über das Anpassen von Projektvorlagen.“

clip_image001[25]

So, und nun, liebe Entwicklungsleiter. Schreibt Vorlagen für eure Mitarbeiter und lasset sie sie benutzen. Jeder hat die gleichen Startvoraussetzungen, alle arbeiten mit gleichen Schemen, dies könnte ein großer Qualitätssprung in eurem Quellcode sein.

BASTA! Scrum .NET Review Diskussion

ich bin gerade von der BASTA zurück und hatte vorgestern einen kleinen Plausch mit Neno Loje. Er hat zusammen mit Jens Korte einen Vortrag gehalten, wo das Thema Scrum vorgestellt wurde. Neno beschäftigt sich nun schon seit mehreren Monaten intensiv mit Scrum und scheint nun immer mehr ein Fan von Scrum zu werden. Er sagte, dass Scrum zwar die ganzen Probleme nicht lösen kann, die man aktuell in der Softwareentwicklung hat, aber man merkt viel früher, was man (bzw. der Kunde) nicht will. Scrum akzeptiert, dass sich Anforderungen täglich ändern können. Wer schon einmal ein Kundenprojekt durchgeführt hat, wird zustimmen, dass sich Anforderungen wirklich sehr häufig ändern. Sobald der Kunde etwas zum herumspielen hat, merkt er erst einmal, wie sich seine Idee wirklich anfühlt und es entstehen sofort neue Ideen bzw. Anpassungen.

Ein guter Freund von mir sagte, er interpretiert Scrum so, dass man eigentlich Quick and Dirty programmieren soll, weil kein Code für die Ewigkeit ist, weil sich der Code eh oft ändert, weil ständig refactored werden muss und weil man bei Scrum sowieso niemals etwas programmieren soll, was nicht gefordert ist. Sprich, man soll nicht versuchen Jahre vorauszuschauen und schon mal "Code vorzubereiten" sondern vllt. maximal ein paar Wochen, je nach Sprintlänge.

Als ich das Neno sagte, reagierte er zu meinem Erstaunen mit den folgenden Worten: "Oh, ein interessanter Ansatz". Er verzog dabei die Augenbrauen wie es Muttis machen, wenn sie erstaunt wirken wollen. Ich persönlich bin eigentlich überhaupt kein Freund von Quick and Dirty-Entwicklung (außer bei Einwegsoftware), weil ich weiß, wie fehleranfällig solch ein Code ist, unter Umständen sehr schwer erweiterbar oder schwer verständlich. Vor allem für andere Teammitglieder. Neno allerdings sah diesem Punkt offen entgegen und fand den Ansatz interessant.

Damit das hier nicht alles zu viel Text wird, füge ich einfach mal kurz ein Bild ein (quelle http://boerge.mongoclique.de)

projektmanagement

Da ich in diesem Blog eine Frage an die ganze Welt stellen möchte, kurz nochmal eine Zusammenfassung zu Scrum.

- In Scrum wird direkt davon ausgegangen, dass sich Anforderungen schnell ändern können
- Das Entwicklerteam arbeitet in kleinen Intervallen (2 - 4 Wochen) ein Set von Aufgaben ab. Diese Intervalle werden Sprints genannt
- Während eines Sprints werden keine Anforderungen angepasst, nach einem Sprint aber schon
- Neue Anforderungen werden vor einem Sprint bewertet und fließen dann ggf. gleich in den nächsten Sprint ein
- Code wird oft refactored
- Es wird nicht zu vorausschauend entwickelt, da man sagt, dass theoretisch im nächsten Sprint alle Anforderungen schon wieder ganz anders sein könnten. Daher besteht die Gefahr, Code zu schreiben, der danach in der Tonne landet.

Eines der Prinzipien von Scrum ist KISS (Keep It Stupid and Simple). Genau dieses Prinzip hat mein guter Freund einfach mal so interpretiert, dass man quick and dirty programmieren soll. Alles einfach immer schnell reinhacken, damit möglichst schnell möglichst viele Features entstehen. So kann der Kunde nach einem Sprint mit den neuen Features schon herumspielen und Feedback generieren, was sehr wichtig ist und ggf. schon den nächsten Sprint beeinflussen kann. An diesem Punkt muss ich wohl zustimmen, da Kundenfeedback wirklich wichtig ist und man davon als Entwickler nie genug bekommen kann.

Bei meinem aktuellen Kunden wurde ca. ein halbes Jahr ein Framework designed, mit dem nun alle Entwickler arbeiten. Meiner Meinung nach ist das genial. Alles hat seinen Platz, alles ist vorgegeben und klar definiert. Kommandos sind unabhängig und können wiederverwendet werden. Es gibt relativ wenig Abhängigkeiten. Alle Komponenten sind einzeln testbar und jede Klasse hat genau eine Aufgabe, so wie es clean code vorgibt. Dieses Framework wird allerdings nur intern genutzt. Die Designphase betrug wie gesagt ca. ein halbes Jahr.

Nun meine Frage an euch alle. Was meint ihr dazu?  Vllt. denke ich ja einfach teilweise etwas zu konservativ. Vllt. versteht auch unter Quick and Dirty jeder etwas anderes. Vllt. sollten lieber alle Entwickler Quick and Dirty entwickeln, damit der Kunde schneller merkt, was er alles nicht haben will. Oder sollte man interne Projekte (wie z.B. Frameworks) ordentlich designen und nur Kundenprojekte quick and dirty entwickeln, damit der Kunde möglichst schnell Features zum Spielen bekommt? Aber wie sieht dann die Software unter der Haube aus? Ist sie wartbar, testbar, erweiterbar, halbwegs verständlich (auch für andere Teammitglieder) und gibt es viele Abhängigkeiten??? Ist Scrum und Clean Code ein Widerspruch???

Schreibt mir all eure Meinungen, ich bin gespannt auf jede Einzelne!

Energize!

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…

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