28 April 2009 ReadYou, HowToCode Robert Muehsig

image <p>Vor einer ganze Weile habe ich über eine Idee geschrieben, die wir eine ganze Weile auch in einem Projekt so verfolgt haben. Ein anderes geniales Konzept, wo ich mir nicht ganz sicher war, war das Exception Handling. Nachdem ich beide Ideen in einem Projekt umgesetzt hatte, kommt jetzt mein Fazit: Keep it simple!</p> <p> </p>

Hintergrund:
Ich und ein Kollege von mir arbeite gerade an einer kleinen (bis mittelgroßen bzw. riesigen! ;) ) Social Network Platform - bzw. geht es in diese Richtung. Nähere Infos zum konkreten Projekt folgen später. Wir haben uns hohe Ziele in das Design der Applikation gesteckt:
- 3 sauber getrennte Schichten
- Testbarkeit möglichst hoch
- Fetziges UI 
- Gute Wartbarkeit - DRY!
Eigentlich also ein Projekt, mit SOLIDen Prinzipien und schön aussehen soll es auch :)

Da ich solch ein Projekt in dieser Größe noch nie gemacht habe, habe ich das ein oder andere mal bereits darüber gebloggt. Nun möchte ich schreiben, wie es bisher weiterging. Diese Projekt ist zudem ein 100%tiges Privatprojekt, wo Zeitdruck zwar da ist, allerdings nicht in dem Maße wie bei einem Kundenprojekt.

GenericResponse<AbsoluterFußschuss> <-> GenericRequest<IDEE>
Ich hatte vor einer ganzen Zeit eine Idee, unseren ServiceLayer (der die Businesslogik bei uns übernimmt) mit "Response" & "Request" Objekten anzusprechen bzw. dass er diese zurückgibt:

image

Die Idee dahinter war, dass man im Response z.B. reinschreiben kann, dass der Aufruf erfolgreich war oder den Fehlergrund wieder zurückgibt.
Leider muss ich sagen, dass ich zu diesem Zeitpunkt das Exceptionhandling wohl noch nicht ganz geschnallt hatte. Daher hat sich bei uns jetzt folgender Spruch eingeprägt:

Was fliegt, dass fliegt!
Sinnlos Exceptions versuchen abzufangen, die man sowieso nicht behandeln kann, bringt nichts. Das klingt einfach, aber ich wette man findet an vielen Stellen Code der so aussieht:

try
{
	// Evil Things Happen Here!
}
catch(Exception ex)
{
	Logger.Log(ex);
}

Nach dem Loggen wird entweder noch ein ReturnCode zurückgegeben oder es bleibt einfach so. Allerdings ist das nicht Sinn und Zweck der Sache. Im ASP.NET Umfeld würde ich einfach empfehlen, dass man unbehandelte Exceptions im Application_Error Event loggt, dem User auf die Error.aspx umleitet und eine Mail an die Entwickler rausschickt (oder ELMAH sich mal näher anschaut).

Back to the Basics
Da der Hauptgrund für diese Responseklassen eigentlich das Exceptionhandling war und wir dies jetzt ganz leicht nutzen, brauchen wir diese nun nicht.
Wenn der Nutzer was eingibt, was nicht valide ist, z.B. Logindaten oder Registrierdaten, dann werfen wir eine Exception vom Typ "UserException" mit einem Errorcode.

Keine perfekte Lösung
Die Errorcodes sind ein großes Enum und in einer UserException können auch mehrere Errorcodes untergebracht werden. Gebraucht werden diese ErrorCodes um ein genaues Feld angeben zu können, welches fehlt oder einen Fehlerstatus zurückgegeben. Momentan sieht das Enum so aus:

    public enum ErrorCodes
    {
        DuplicateEmail,
        InvalidEmail,
        InvalidLogin,
        InvalidPassword,
        InvalidPrename,
        InvalidSurname,
        InvalidCompanyName,
        InvalidCity,
        InvalidState,
        InvalidLongitude,
        InvalidLatitude,
        InvalidTime,
        OutOfTime,
        NoEmployee
    }

In unserem (ASP.NET MVC) Controller können wir dann den Service Aufrufe in einem Try packen und der Catch sieht so aus:

 catch(UserException ex)
 	{
                if(ex.ErrorCodes.Any(er => er == ErrorCodes.InvalidEmail))
                    ModelState.AddModelError("email", "Ungültige Email-Adresse");
	...
	}

Dass das Enum sicher immer weiter anwachsen wird ist uns bewusst, aber eine andere Methode um ein definiertes Element im Servicelayer zur nächsten Schicht weiterzugeben war uns nicht eingefallen, wenn jemanden ohne große Magie was einfällt, dann nur zu :) 
Ebenso ist die generelle Verwendung von Exceptions für Usereingaben (z.B. bei der Registrierung) laut MSDN auch nicht gern gesehen, allerdings muss ich sagen, dass da die Alternativen aus meiner jetzigen Sicht nicht sehr schön sind. Natürlich sollten Exceptions nur für "Ausnahmen" genommen werden, allerdings erhöhen sie meiner Meinung nach doch etwas die Lesbarkeit, anstatt X-Returncodes im Model zu definieren.

Die UserExceptions werden allerdings auch nur dort eingesetzt, wo wir das Backend definitiv ansprechen müssen (z.B. ob ein Login schon vorhanden ist).

GenericResponse
Die "GenericResponse" Klasse blieb trotzdem noch eine ganze Weile weiter im Projekt bestehen und wurde weiterhin verwendet, auch wenn wir die Features nicht mehr wirklich gebraucht haben. Dabei war allerdings der Schreibaufwand enorm.
Wir nehmen als Beispiel eine Klasse, welche einfach einen Bool zurückgibt. Mit unserer Variante musst man daraus sowas machen:

public GenericResponse<bool> DoSomething()
{
	bool result = this.Repository.Get(BLABLABLA);
	return new GenericResponse<bool> { Value = result };
}

Das hat die Lesbarkeit nicht sonderlich erhöht und war auch sehr bescheiden zu entwickeln. Daher ist es nun auch rausgeflogen :) (nach 1,5h Refactoring) 

Ganz am Anfang, ohne die Exceptions, mussten wir den Code so schreiben:

public GenericResponse<bool> DoSomething()
{
	bool result = this.Repository.Get(BLABLABLA);
	return new GenericResponse<bool> { Value = result, Result = ServiceResult.Succeeded };
}

Da finde ich nun die Exceptions charmanter, allerdings lassen wir uns gerne eines besseren Belehren ;)

GenericRequest
Die Request Klasse ist allerdings aktuell noch drin, auch wenn wir uns derzeit über die Sinnhaftigkeit streiten. Einmal gibt es den eigentlich Parameter den die Methode haben will und wir schleifen in diesem Objekt immer noch den aktuellen Benutzer durch, sodass wir im ServiceLayer auch prüfen können, ob der Nutzer diese oder jene Aktion ausführen kann oder darf. Das Konzept steht soweit noch, allerdings werden wir das wohl etwas vereinfachen.

Mein Fazit
Je einfacher etwas ist, desto besser. Auch wenn die Konzepte und Ideen vielleicht gut sind, muss man sich immer fragen, ob es der Aufwand wert ist alles in X Objekte unterzuverschachteln, denn den puren Tippaufwand sollte man nicht vernachlässigen - selbst mit Resharper und allen Tricks ist ein "return true" schneller geschrieben als "return new GenericResponse<bool> { Value = true}".

So richtig glücklich bin ich mit unserem Exceptionhandling noch nicht, allerdings erfüllt es meine Anforderungen: Es ist einfach, von der Wartbarkeit her zu verschmerzen und ich kann zwischen den Schichten auch definierte "Fehler" weitergeben (durch die Errorcodes).


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de