Meine Wertprüffunktion muss sowohl einen Booleschen Wert als auch eine Nachricht zurückgeben

14

Ich habe eine Wertüberprüfungsfunktion, ähnlich einer Funktion zur Überprüfung von Kreditkartennummern, die in einer Zeichenfolge übergeben wird und prüfen muss, ob der Wert das richtige Format hat.

Wenn es das richtige Format ist, muss es true zurückgeben.

Wenn es nicht das richtige Format ist, muss es false zurückgeben und uns mitteilen, was mit dem Wert nicht stimmt.

Die Frage ist, was ist der schönste Weg, dies zu erreichen?

Hier sind einige Lösungen:

1. Verwenden Sie Integer / Enum-Rückkehrcodes, um Bedeutungen zu kennzeichnen:

String[] returnCodeLookup = 
[
"Value contains wrong number of characters, should contain 10 characters",
"Value should end with 1", 
"Value should be a multiple of 3"
]

private int valueChecker(String value)
{
    /*check value*/
    return returnCode;
}

rc = checkValue(valueToBeChecked);
if rc == 0
{
    /*continue as normal*/
}
else
{
    print("Invalid value format: ") + returnCodeLookup[rc];
}

Diese Lösung gefällt mir nicht, da sie auf der Anruferseite implementiert werden muss.

2. Erstellen Sie eine returnCode-Klasse

Class ReturnCode()
{
    private boolean success;
    private String message;

    public boolean getSuccess()
    {
        return this.success;
    }

    public String getMessage()
    {
        return this.message; 
    }
}

private ReturnCode valueChecker(String value)
{
    /*check value*/
    return returnCode;
}

rc = checkValue(valueToBeChecked);
if rc.getSuccess()
{
    /*continue as normal*/
}
else
{
    print("Invalid value format: ") + rc.getMessage();
}

Diese Lösung ist aufgeräumt, aber es scheint, als würde man das Rad übertreiben / neu erfinden.

3. Verwenden Sie Ausnahmen.

private boolean valueChecker(String value)
{
    if int(value)%3 != 0 throw InvalidFormatException("Value should be a multiple of 3";
    /*etc*/
    return True;
}

try {
rc = checkValue(valueToBeChecked);
}

catch (InvalidFormatException e)
{
     print e.toString();
}

Ich bin versucht, diese Lösung zu verwenden, aber mir wurde gesagt, dass Sie keine Ausnahmen für die Geschäftslogik verwenden sollten.

Dwjohnston
quelle
'[..] Überprüfen Sie, ob der Wert das richtige Format hat.' Sollte der Name dann nicht FormatChecker sein ?
Andy
Das Richtig / Falsch-Ergebnis erscheint überflüssig. Könnte es einfach eine leere oder leere Zeichenfolge zurückgeben, um den Erfolg anzuzeigen? Das funktioniert seit ungefähr 50 Jahren für UNIX. :-)
user949300

Antworten:

14

Verwenden Sie ein komplexeres Rückgabeobjekt, in dem beide Aspekte zusammengefasst sind. Beispiel:

public interface IValidationResult {
  boolean isSuccess();
  String getMessage();
}

Dies hat mehrere Vorteile:

  1. Gibt mehrere zusammengehörige Daten in einem Objekt zurück.
  2. Platz für Erweiterungen, wenn Sie in Zukunft zusätzliche Daten hinzufügen müssen.
  3. Keine Abhängigkeit von der zeitlichen Kopplung: Sie können mehrere Eingaben validieren, und sie verstopfen die Nachricht nicht wie in der anderen Antwort. Sie können Nachrichten in beliebiger Reihenfolge sogar über mehrere Threads hinweg prüfen.

Ich habe dieses spezielle Design bereits in Anwendungen verwendet, in denen eine Validierung mehr als nur wahr oder falsch sein kann. Möglicherweise ist eine detaillierte Meldung erforderlich oder nur ein Teil der Eingabe ist ungültig (z. B. kann ein Formular mit zehn Elementen nur ein oder zwei ungültige Felder enthalten). Mit diesem Design können Sie diese Anforderungen problemlos erfüllen.


quelle
Ich muss zugeben, dass diese Lösung besser ist als meine. Meins ist nicht threadsicher.
Tulains Córdova
@ user61852 Dies ist zwar eine allgemeine Übersicht über eine Schnittstelle für ein Ergebnisobjekt, ich denke jedoch, dass das Ziel hier darin besteht, dass der Validierungscode ein eigenes Objekt ist, das keinen Status enthält. Das würde es unveränderlich machen, was viele Vorteile hat, über die wir auf dieser Seite immer wieder sprechen.
Warum ist eine Schnittstelle notwendig?
Dwjohnston
1
@dwjohnston eine schnittstelle ist nicht notwendig, aber es ist eine gute idee. Vererbung ist ein sehr starker Kopplungstyp, der nur bei Bedarf verwendet werden sollte.
Alternativ können Sie weiter vereinfachen. Der Erfolg ist nicht interessant. Deklarieren Sie daher eine Konstante IValidationResult.SUCCESS, die eine leere Fehlermeldung zurückgibt. Dann sieht deine Logik so ausif (result != SUCCESS) { doStuff(result.getMessage()); }
Morgen
2

Verwenden Sie in keinem der oben genannten Fälle eine ValueChecker-Klasse

Zuerst eine Schnittstelle, um Ihnen Flexibilität zu geben:

public interface IValueChecker {
    public boolean checkValue(String value);
    public String getLastMessage();
}

Implementieren Sie dann so viele Valuechecker, wie Sie benötigen:

public class MyVeryEspecificValueChecker implements IValueChecker {
    private String lastMessage="";
    @Override
    public boolean checkValue(String value) {
        boolean valid=false;
        // perform check, updates "valid" and "lastMessage"
        return valid;
    }
    @Override
    public String getLastMessage() {
        return lastMessage;
    }
}

Beispiel-Clientcode:

public class TestValueChecker {
    public static void main(String[] args) {
        String valueToCheck="213123-YUYAS-27163-10";
        IValueChecker vc = new MyVeryEspecificValueChecker();
        vc.checkValue(valueToCheck);
        System.out.println(vc.getLastMessage());
    }
}

Dies hat den Vorteil, dass Sie viele verschiedene Wertprüfer haben können.

Tulains Córdova
quelle
1
Ich bin nicht sicher, ob mir der Wertprüfer gefällt, der den Status beibehält, ohne die Möglichkeit zu haben, den zuletzt überprüften Wert zu sehen.
Peter K.
1

Meine Antwort erweitert den Ansatz von @ Snowman. Grundsätzlich sollte jede Validierung, jede Geschäftsregel und jede Geschäftslogik zu einer gewissen Reaktion führen können - zumindest in Webanwendungen. Diese Antwort wird wiederum einem Anrufer angezeigt. Dies brachte mich zu der folgenden Oberfläche (es ist PHP, aber die Frage ist sprachunabhängig):

interface Action
{
    /**
     * @param Request $request
     * @throws RuntimeException
     * @return Response
     */
    public function act(Request $request);
}

Das Erstellen eines Switch-Operators, der wie ein Ausdruck und nicht wie eine Anweisung wirkt, führt dazu, dass der Anwendungsservice folgendermaßen aussieht:

class MyApplicationService implements Action
{
    private $dataStorage;

    public function __construct(UserDataStorage $dataStorage)
    {
        $this->dataStorage = $dataStorage;
    }

    public function act(Request $request)
    {
        return
            (new _SwitchTrue(
                new _Case(
                    new EmailIsInvalid(),
                    new EmailIsInvalidResponse()
                ),
                new _Case(
                    new PasswordIsInvalid(),
                    new PasswordIsInvalidResponse()
                ),
                new _Case(
                    new EmailAlreadyRegistered($this->dataStorage),
                    new EmailAlreadyRegisteredResponse()
                ),
                new _Default(
                    new class implements Action
                    {
                        public function act(Request $request)
                        {
                            // business logic goes here

                            return new UserRegisteredResponse();
                        }
                    }
                )
            ))
                ->act($request)
            ;
    }
}
Zapadlo
quelle