Moja funkcja sprawdzania wartości musi zwracać zarówno wartość logiczną, jak i komunikat

14

Mam funkcję sprawdzania wartości, coś podobnego do funkcji sprawdzania numeru karty kredytowej, która jest przekazywana w postaci ciągu i musi sprawdzić, czy wartość ma odpowiedni format.

Jeśli jest to właściwy format, musi zwrócić wartość true.

Jeśli nie jest to właściwy format, musi zwrócić false, a także powiedzieć nam, co jest nie tak z wartością.

Pytanie brzmi: jaki jest najlepszy sposób na osiągnięcie tego?

Oto kilka rozwiązań:

1. Użyj kodów powrotu liczby całkowitej / wyliczenia, aby zaznaczyć znaczenie:

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];
}

Nie podoba mi się to rozwiązanie, ponieważ wymaga implementacji rzeczy po stronie wywołującej.

2. Utwórz klasę returnCode

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();
}

To rozwiązanie jest schludne, ale wydaje się, że przesadza / wymyśla koło.

3. Użyj wyjątków.

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();
}

Kusi mnie, aby skorzystać z tego rozwiązania, ale powiedziano mi, że nie należy używać wyjątków w logice biznesowej.

dwjohnston
źródło
„[...] sprawdź, czy wartość ma odpowiedni format.” Czy nazwa nie powinna zatem brzmieć FormatChecker ?
Andy,
Wynik prawda / fałsz wydaje się zbędny. Czy może po prostu zwrócić pusty lub zerowy ciąg znaków, aby wskazać sukces? Działa to w systemie UNIX przez około 50 lat. :-)
user949300

Odpowiedzi:

14

Użyj bardziej złożonego obiektu zwrotnego, który zawiera obie obawy. Przykład:

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

Ma to kilka zalet:

  1. Zwraca wiele powiązanych danych w jednym obiekcie.
  2. Miejsce na rozbudowę, jeśli w przyszłości konieczne będzie dodanie dodatkowych danych.
  3. Brak polegania na sprzężeniu czasowym: możesz sprawdzić wiele danych wejściowych i nie blokują one komunikatu jak w drugiej odpowiedzi. Możesz sprawdzać wiadomości w dowolnej kolejności, nawet w wątkach.

Właściwie korzystałem już z tego konkretnego projektu w aplikacjach, w których walidacja może być czymś więcej niż zwykłą prawdą lub fałszem. Być może niezbędny jest szczegółowy komunikat lub tylko część danych wejściowych jest nieprawidłowa (np. Formularz z dziesięcioma elementami może mieć tylko jedno lub dwa nieprawidłowe pola). Korzystając z tego projektu, możesz łatwo dostosować się do tych wymagań.


źródło
Muszę przyznać, że to rozwiązanie jest lepsze niż moje. Mój nie jest wątkowo bezpieczny.
Tulains Córdova
@ user61852 Chociaż jest to ogólny przegląd interfejsu dla obiektu wynikowego, myślę, że celem tutaj jest, aby kod sprawdzający był własnym obiektem nie zawierającym stanu. To sprawiłoby, że był niezmienny, co ma wiele zalet, o których ciągle mówimy na tej stronie.
Dlaczego potrzebny jest interfejs?
dwjohnston,
1
@dwjohnston interfejs nie jest konieczny, ale jest to dobry pomysł. Dziedziczenie jest bardzo silnym rodzajem sprzężenia, które należy stosować tylko w razie potrzeby.
Alternatywnie możesz uprościć dalej. Sukces nie jest interesujący, dlatego zadeklaruj stałą, IValidationResult.SUCCESSktóra zwraca pusty komunikat o błędzie. Wtedy twoja logika wygląda następującoif (result != SUCCESS) { doStuff(result.getMessage()); }
Morgen,
2

Żadne z powyższych nie należy używać klasy ValueChecker

Najpierw interfejs zapewniający elastyczność:

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

Następnie zaimplementuj tyle kontrolerów wartości, ile potrzebujesz:

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;
    }
}

Przykładowy kod klienta:

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());
    }
}

Ma tę zaletę, że możesz mieć wiele różnych kontrolerów wartości.

Tulains Córdova
źródło
1
Nie jestem pewien, czy podoba mi się utrzymanie kontrolera wartości bez możliwości sprawdzenia ostatniej wartości.
Peter K.,
1

Moja odpowiedź rozszerza podejście @ Snowman. Zasadniczo każda walidacja, każda reguła biznesowa i każda logika biznesowa powinny być w stanie dać odpowiedź - przynajmniej w aplikacjach internetowych. Ta odpowiedź z kolei jest wyświetlana dzwoniącemu. To doprowadziło mnie do następującego interfejsu (to php, ale pytanie ma charakter niezależny od języka):

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

Utworzenie operatora przełącznika działającego jak wyrażenie, a nie wyrażenie powoduje, że usługa aplikacji wygląda następująco:

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)
            ;
    }
}
Zapadło
źródło