2013-04-26 11 views
64

Nehmen wir an, es gibt eine Operation, die einen Benutzer erstellt. Dieser Vorgang kann fehlschlagen, wenn eine angegebene E-Mail-Adresse oder ein Benutzername existiert. Wenn es fehlgeschlagen ist, ist es erforderlich, genau zu wissen, warum. Es gibt drei Ansätze, dies so zu tun, wie ich es sehe, und ich frage mich, ob es einen klaren Gewinner gibt.Welches Design ist am meisten zu bevorzugen: Test-Create, Try-Create, Create-Catch?

So, hier ist ein Klasse-Benutzer:

class User 
{ 
    public string Email { get; set; } 
    public string UserName { get; set; } 
} 

Und es gibt 3 Möglichkeiten von Operation auszuführen erstellen:

Test erstellen

if (UserExists(user)) act on user exists error; 
if (UsernameExists(user)) act on username exists error; 
CreateUser(user); 

userexists und UsernameExists machen Anfrage Datenbankserver, um eine Validierung durchzuführen. Diese Aufrufe werden in CreateUser erneut wiederholt, um sicherzustellen, dass die API korrekt verwendet wird. Falls die Validierung fehlgeschlagen ist, werfe ich ArgumentOutOfRangeException in beiden Fällen aus. Es gibt also einen Leistungseinbruch.

Try-Create

enum CreateUserResultCode 
{ 
    Success, 
    UserAlreadyExists, 
    UsernameAlreadyExists 
} 

if (!TryCreate(user, out resultCode)) 
{ 
    switch(resultCode) 
    { 
     case UserAlreadyExists: act on user exists error; 
     case UsernameAlreadyExists: act on username exists error; 
    } 
} 

Dieses Muster hat die Validierung nur einmal, sondern wir greifen den so genannten Fehlercodes verwendet, die keine gute Praxis betrachtet wird.

Create-Fang

try 
{ 
    CreateUser(user); 
} 
catch(UserExistsException) 
{ 
    act on user exists error; 
} 
catch(UsernameExistsException) 
{ 
    act on username exists error; 
} 

I Fehlercodes nicht hier, aber ich habe jetzt eine separate Ausnahmeklasse für jeden Fall zu schaffen. Es ist mehr oder weniger wie Ausnahmen verwendet werden sollen, aber ich frage mich, ob es sinnvoll ist, eine separate Ausnahme anstelle des Enumerationseintrags zu erstellen.

Haben wir also einen klaren Gewinner oder ist es mehr eine Frage des Geschmacks?

+9

Sie könnten an Raymond Chens Blog-Posts interessiert sein ["Cleaner, eleganter und falscher"] (http://blogs.msdn.com/b/oldnewthing/archive/2004/04/22/118161.aspx) und ["Cleaner, eleganter und schwerer zu erkennen"] (http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx), wo er einen Fall, dass Fehlercodes macht sind besser als Ausnahmen. – ruakh

+0

Weitere externe Referenzen? Ich bin sehr interessiert. – QED

Antwort

80

Haben wir also einen klaren Gewinner oder ist es mehr eine Frage des Geschmacks?

Die erste Option hat einen grundlegenden Fehler - es ist nie sicher oder Thread-sicher gehen, wenn CreateUser auf externe Ressourcen angewiesen ist, und andere Implementierungen können zwischen Ihren Tests erstellen. Im Allgemeinen tendiere ich dazu, dieses "Muster" zu vermeiden.

Wie für die anderen beiden Optionen - es kommt wirklich darauf an, ob der Fehler erwartet zu passieren ist. Wenn der Fehler CreateUser auf einer etwas normalen Basis erwartet wird, ist das Try * -Muster meine Präferenz, da die Verwendung von Ausnahmen im Wesentlichen die Verwendung von Ausnahmen für den Kontrollfluss ist.

Wenn der Fehler wirklich ein Ausnahmefall wäre, dann wären Ausnahmen verständlicher.

+13

+1 für Thread-Sicherheitsprobleme – ken2k

+0

@Reed Ich denke mehr und mehr über eine Try * -Ansatz, aber ich bin entmutigt von all diesen Büchern und Artikeln, die behaupten, ich sollte keine Fehlercodes für die Fehlerbehandlung verwenden (zB [Ausnahme Werfen] (http://msdn.microsoft.com/en-us/library/ms229030 (v = vs.100) .aspx)). Sie sagen, dass eines der größten Probleme dort ist, wie man Fehlercodes den Call-Stack weitergibt. Irgendwelche Gedanken einer das? – andriys

+2

@andriys Es hängt davon ab, ob sie den Call-Stack übergeben müssen. Wenn Sie das brauchen, können Ausnahmen besser sein - aber wenn dies wirklich nur Kontrollfluss ist (wie Ihr Beispielcode), ist ein einzelnes Ergebnis oft einfacher und sauberer. Ich würde jedoch auf jeden Fall die separaten Prüfungen vermeiden. –

37

Test-Create kann Race Conditions verursachen, also ist es keine gute Idee. Es macht wahrscheinlich auch zusätzliche Arbeit.

Try-Create ist gut, wenn Sie erwarten, dass Fehler Teil des normalen Code-Flusses sind (z. B. bei Benutzereingaben).

Create-Catch ist gut, wenn Fehler wirklich außergewöhnlich sind (damit Sie sich keine Gedanken über die Leistung machen).

+3

+1 für Thread-Sicherheitsprobleme – ken2k

+2

Ich würde darauf hinweisen, dass in vielen Fällen wie das Treffen einer Datenbank, die Leistung Kosten von Ausnahmen ist keine große Sache. Die konzeptionellen Kosten für den Umgang mit diesen Ausnahmen sind jedoch. Wenn jemand Ihre "Create-Catch" -Logik duplizieren wollte und den "Catch" -Teil vergessen hat, würden seltsame Dinge passieren, aber im "Try-Create" -Fall macht der Rückgabewert es viel offensichtlicher und der Fehler ist leise. Vor- und Nachteile zu allem natürlich. – Guvante

9

Dies ist etwas subjektiv, aber es gibt einige konkrete Vor-und Nachteile, die es wert sind, darauf hinzuweisen.

Ein Nachteil des Test-Create-Ansatzes ist die Racebedingung. Wenn zwei Clients versuchen, denselben Benutzer ungefähr zur gleichen Zeit zu erstellen, ist es möglicherweise möglich, dass beide die Tests bestehen und anschließend versuchen, denselben Benutzer zu erstellen.

Zwischen Try-Create und Create-Catch bevorzuge ich Create-Catch, aber das ist persönlicher Geschmack. Man könnte argumentieren, dass Create-Catch Ausnahmen für die Flusskontrolle verwendet, was normalerweise verpönt ist. Auf der anderen Seite erfordert Try-Create einen etwas peinlichen output Parameter, der leichter übersehen werden könnte.

Also bevorzuge ich Create-Catch, aber hier gibt es definitiv Raum für Diskussionen.

+0

Ich bevorzuge auch Create-Catch in dieser Situation. Eine primäre Schlüsselverletzung ist sicherlich eine gültige Ausnahme. Für ungültige Benutzereingaben wäre es ein bisschen mehrdeutig, aber ich würde wahrscheinlich immer noch entscheiden, eine FormatException zu werfen. – JosephHirn

4

Sie haben angegeben, dass UserExists und UsernameExists beide DB-Aufrufe vornehmen. Ich nehme an, dass CreateUser auch einen Datenbankaufruf durchführt.

Warum lässt sich die Datenbank nicht mit Threadingproblemen umgehen? Unter der Annahme, dass für Ihre Tabelle (n) geeignete Einschränkungen bestehen, können Sie sie innerhalb eines gespeicherten Proc-Aufrufs ausschließen lassen.

Daher werde ich meine Stimme für Create-Catch abgeben.

Verwandte Themen