Wenn ich versuche, eine Schnittstelle für ein bestimmtes Programm zu erstellen, versuche ich im Allgemeinen, Ausnahmen zu vermeiden, die von nicht validierten Eingaben abhängen.
Was also oft passiert, ist, dass ich an einen solchen Code gedacht habe (dies ist nur ein Beispiel für ein Beispiel, egal welche Funktion er ausführt, Beispiel in Java):
public static String padToEvenOriginal(int evenSize, String string) {
if (evenSize % 2 == 1) {
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
OK, sagen wir also, dass dies evenSize
tatsächlich aus Benutzereingaben abgeleitet wird. Ich bin mir also nicht sicher, ob es gerade ist. Ich möchte diese Methode jedoch nicht mit der Möglichkeit aufrufen, dass eine Ausnahme ausgelöst wird. Also mache ich folgende Funktion:
public static boolean isEven(int evenSize) {
return evenSize % 2 == 0;
}
Jetzt habe ich zwei Überprüfungen, die dieselbe Eingabevalidierung durchführen: den Ausdruck in der if
Anweisung und das explizite Einchecken isEven
. Doppelter Code, nicht schön, also lasst uns umgestalten:
public static String padToEvenWithIsEven(int evenSize, String string) {
if (!isEven(evenSize)) { // to avoid duplicate code
throw new IllegalArgumentException("evenSize argument is not even");
}
if (string.length() >= evenSize) {
return string;
}
StringBuilder sb = new StringBuilder(evenSize);
sb.append(string);
for (int i = string.length(); i < evenSize; i++) {
sb.append(' ');
}
return sb.toString();
}
OK, das hat es gelöst, aber jetzt kommen wir in die folgende Situation:
String test = "123";
int size;
do {
size = getSizeFromInput();
} while (!isEven(size)); // checks if it is even
String evenTest = padToEvenWithIsEven(size, test);
System.out.println(evenTest); // checks if it is even (redundant)
Jetzt haben wir eine redundante Prüfung: Wir wissen bereits, dass der Wert gerade ist, führen aber padToEvenWithIsEven
dennoch die Parameterprüfung durch, die immer true zurückgibt, wie wir diese Funktion bereits aufgerufen haben.
Das ist isEven
natürlich kein Problem, aber wenn die Parameterprüfung umständlicher ist, kann dies zu hohe Kosten verursachen. Außerdem fühlt sich ein redundanter Anruf einfach nicht richtig an.
Manchmal können wir dies umgehen, indem wir einen "validierten Typ" einführen oder eine Funktion erstellen, bei der dieses Problem nicht auftreten kann:
public static String padToEvenSmarter(int numberOfBigrams, String string) {
int size = numberOfBigrams * 2;
if (string.length() >= size) {
return string;
}
StringBuilder sb = new StringBuilder(size);
sb.append(string);
for (int i = string.length(); i < size; i++) {
sb.append('x');
}
return sb.toString();
}
Dies erfordert jedoch ein kluges Denken und einen ziemlich großen Refaktor.
Gibt es eine (allgemeinere) Methode, mit der wir redundante Aufrufe isEven
und doppelte Parameterprüfungen vermeiden und durchführen können? Ich möchte, dass die Lösung nicht padToEven
mit einem ungültigen Parameter aufruft und die Ausnahme auslöst.
Ohne Ausnahmen meine ich keine ausnahmefreie Programmierung, ich meine, dass Benutzereingaben keine Ausnahme von Natur aus auslösen, während die generische Funktion selbst noch die Parameterprüfung enthält (wenn auch nur zum Schutz vor Programmierfehlern).
quelle
padToEvenWithIsEven
keine Überprüfung der Benutzereingaben durchgeführt wird. Es führt eine Gültigkeitsprüfung seiner Eingabe durch, um sich vor Programmierfehlern im aufrufenden Code zu schützen . Wie umfangreich diese Validierung sein muss, hängt von einer Kosten- / Risikoanalyse ab, bei der Sie die Kosten der Prüfung gegen das Risiko stellen, dass die Person, die den aufrufenden Code schreibt, den falschen Parameter übergibt.Antworten:
In Ihrem Beispiel besteht die beste Lösung darin, eine allgemeinere Auffüllfunktion zu verwenden. Wenn der Anrufer eine gerade Größe auffüllen möchte, kann er dies selbst überprüfen.
Wenn Sie wiederholt dieselbe Validierung für einen Wert durchführen oder nur eine Teilmenge von Werten eines Typs zulassen möchten, können Mikrotypen / winzige Typen hilfreich sein. Für allgemeine Dienstprogramme wie das Auffüllen ist dies keine gute Idee. Wenn Ihr Wert jedoch eine bestimmte Rolle in Ihrem Domänenmodell spielt, kann die Verwendung eines dedizierten Typs anstelle primitiver Werte ein großer Fortschritt sein. Hier könnten Sie definieren:
Jetzt können Sie deklarieren
und müssen keine interne Validierung durchführen. Für einen so einfachen Test ist das Einwickeln eines Int in ein Objekt im Hinblick auf die Laufzeitleistung wahrscheinlich teurer. Die Verwendung des Typsystems zu Ihrem Vorteil kann jedoch Fehler reduzieren und Ihr Design klarstellen.
Die Verwendung derart winziger Typen kann sogar dann nützlich sein, wenn sie keine Validierung durchführen, z. B. um eine Zeichenfolge, die a darstellt,
FirstName
von a zu unterscheidenLastName
. Ich verwende dieses Muster häufig in statisch typisierten Sprachen.quelle
EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
unsafePadStringToEven
Operation, die keine Überprüfungen durchführt, aber es scheint eine schlechte Idee zu sein, nur um eine Validierung zu vermeiden.Als Erweiterung von @ amons Antwort können wir seine
EvenInteger
mit dem kombinieren, was die funktionale Programmiergemeinschaft als "Smart Constructor" bezeichnen könnte - eine Funktion, die den dummen Konstruktor umschließt und sicherstellt, dass sich das Objekt in einem gültigen Zustand befindet (wir machen den dummen Konstruktorklasse oder in nicht klassenbasierten Sprachen (Modul / Paket privat, um sicherzustellen, dass nur die intelligenten Konstruktoren verwendet werden). Der Trick besteht darin, einOptional
(oder ein Äquivalent) zurückzugeben, um die Funktion komponierbarer zu machen.Wir können dann leicht Standardmethoden
Optional
verwenden, um die Eingabelogik zu schreiben:Sie können auch
keepAsking()
in einem idiomatischeren Java-Stil mit einer Do-While-Schleife schreiben .Dann können Sie sich im Rest Ihres Codes
EvenInteger
mit Sicherheit darauf verlassen, dass es wirklich gleichmäßig sein wird und unser gerader Scheck nur einmal in geschrieben wurdeEvenInteger::of
.quelle
Eine zweimalige Validierung ist ein Problem, wenn das Ergebnis der Validierung dasselbe ist UND die Validierung in derselben Klasse durchgeführt wird. Das ist nicht dein Beispiel. In Ihrem überarbeiteten Code ist die erste isEven-Prüfung eine Eingabevalidierung. Ein Fehler führt dazu, dass eine neue Eingabe angefordert wird. Die zweite Prüfung ist völlig unabhängig von der ersten, da es sich um eine öffentliche Methode padToEvenWithEven handelt, die von außerhalb der Klasse aufgerufen werden kann und ein anderes Ergebnis hat (die Ausnahme).
Ihr Problem ähnelt dem Problem, dass versehentlich identischer Code für nicht trocken verwechselt wird. Sie verwechseln die Implementierung mit dem Design. Sie sind nicht gleich, und nur weil Sie ein oder ein Dutzend Zeilen haben, die gleich sind, bedeutet dies nicht, dass sie denselben Zweck erfüllen und für immer als austauschbar angesehen werden können. Wahrscheinlich lässt du deine Klasse auch zu viel tun, aber überspringe das, da dies wahrscheinlich nur ein Spielzeugbeispiel ist ...
Wenn dies ein Leistungsproblem ist, können Sie das Problem lösen, indem Sie eine private Methode erstellen, die keine Validierung durchführt, die Ihr öffentliches padToEvenWithEven nach der Validierung aufgerufen hat und die Ihre andere Methode stattdessen aufruft. Wenn es sich nicht um ein Leistungsproblem handelt, lassen Sie Ihre verschiedenen Methoden die Überprüfungen durchführen, die sie zur Ausführung der ihnen zugewiesenen Aufgaben benötigen.
quelle