Kiedy próbuję utworzyć interfejs dla określonego programu, zwykle staram się unikać zgłaszania wyjątków, które zależą od niepoprawnych danych wejściowych.
Tak więc często zdarza się, że myślałem o takim kodzie (ten przykład jest tylko przykładem, nie przejmuj się funkcją, którą wykonuje, przykład w Javie):
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, powiedzmy, że evenSize
tak naprawdę pochodzi od danych wejściowych użytkownika. Nie jestem więc pewien, czy tak jest. Ale nie chcę wywoływać tej metody z możliwością zgłoszenia wyjątku. Dlatego wykonuję następującą funkcję:
public static boolean isEven(int evenSize) {
return evenSize % 2 == 0;
}
ale teraz mam dwie kontrole, które wykonują tę samą weryfikację danych wejściowych: wyrażenie w if
instrukcji i jawne sprawdzenie isEven
. Duplikat kodu, niezbyt fajny, więc przeróbmy:
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, to rozwiązało, ale teraz dochodzimy do następującej sytuacji:
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)
teraz mamy nadmiarowe sprawdzenie: już wiemy, że wartość jest parzysta, ale padToEvenWithIsEven
nadal wykonuje sprawdzenie parametru, które zawsze zwróci true, ponieważ już wywołaliśmy tę funkcję.
Teraz isEven
oczywiście nie stanowi to problemu, ale jeśli sprawdzanie parametrów jest bardziej kłopotliwe, może to wiązać się z nadmiernymi kosztami. Poza tym wykonywanie zbędnego połączenia po prostu nie wydaje się właściwe.
Czasami możemy obejść ten problem, wprowadzając „typ zatwierdzony” lub tworząc funkcję, w której ten problem nie może wystąpić:
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();
}
ale wymaga to mądrego myślenia i dość dużego refaktora.
Czy istnieje (bardziej) ogólny sposób, w jaki możemy uniknąć zbędnych wywołań isEven
i sprawdzania podwójnych parametrów? Chciałbym, aby rozwiązanie nie wywoływało padToEven
nieprawidłowego parametru, wyzwalając wyjątek.
Bez wyjątków nie mam na myśli programowania bez wyjątków, to znaczy, że dane wprowadzone przez użytkownika nie wyzwalają wyjątku zgodnie z projektem, podczas gdy sama funkcja ogólna nadal zawiera sprawdzanie parametrów (choćby w celu ochrony przed błędami programowania).
źródło
padToEvenWithIsEven
nie wykonuje się weryfikacji danych wejściowych użytkownika. Wykonuje sprawdzenie poprawności na wejściu, aby zabezpieczyć się przed błędami programowania w kodzie wywołującym. To, jak obszerna musi być ta walidacja, zależy od analizy kosztów / ryzyka, w której koszt kontroli jest porównywany z ryzykiem, że osoba pisząca kod wywołujący przekaże nieprawidłowy parametr.Odpowiedzi:
W twoim przykładzie najlepszym rozwiązaniem jest użycie bardziej ogólnej funkcji wypełniania; jeśli dzwoniący chce wyrównać rozmiar, może sam to sprawdzić.
Jeśli wielokrotnie przeprowadzasz tę samą weryfikację wartości lub chcesz zezwolić tylko na podzbiór wartości danego typu, wówczas mikrotypy / małe typy mogą być pomocne. W przypadku narzędzi ogólnego przeznaczenia, takich jak wypełnianie, nie jest to dobry pomysł, ale jeśli twoja wartość odgrywa szczególną rolę w modelu domeny, użycie dedykowanego typu zamiast pierwotnych wartości może być wielkim krokiem naprzód. Tutaj możesz zdefiniować:
Teraz możesz zadeklarować
i nie musisz dokonywać żadnej wewnętrznej weryfikacji. W przypadku tak prostego testu owijania int wewnątrz obiektu będzie prawdopodobnie droższe pod względem wydajności w czasie wykonywania, ale użycie systemu typów na swoją korzyść może zmniejszyć błędy i wyjaśnić projekt.
Używanie tak małych typów może być przydatne nawet wtedy, gdy nie przeprowadzają one sprawdzania poprawności, np. W celu ujednoznacznienia łańcucha reprezentującego
FirstName
aLastName
. Często używam tego wzorca w językach o typie statycznym.źródło
EvenInteger size; while (size == null) { try { size = new EvenInteger(getSizeFromInput()); } catch(...){}} String result = padStringToEven(size,...);
unsafePadStringToEven
operację, która nie wykonuje żadnych kontroli, ale wydaje się, że to zły pomysł, aby uniknąć sprawdzania poprawności.Jako rozszerzenie odpowiedzi @ amon, możemy połączyć jego
EvenInteger
z tym, co społeczność programistów funkcjonalnych mogłaby nazwać „Smart Constructor” - funkcją, która otacza głupi konstruktor i upewnia się, że obiekt jest w poprawnym stanie (robimy głupie klasa konstruktora lub moduł / pakiet prywatny w językach innych niż klasy, aby upewnić się, że używane są tylko inteligentne konstruktory). Sztuczka polega na zwróceniuOptional
(lub odpowiedniku), aby uczynić funkcję bardziej złożoną.Następnie możemy łatwo użyć standardowych
Optional
metod do zapisania logiki wejściowej:Możesz także pisać
keepAsking()
w bardziej idiomatycznym stylu Java z pętlą do-while.Następnie w pozostałej części kodu możesz polegać na
EvenInteger
pewności, że naprawdę będzie on parzysty, a nasza kontrola parzystości została napisana tylko razEvenInteger::of
.źródło
Dwukrotne sprawdzenie poprawności stanowi problem, jeśli wynik sprawdzania poprawności jest taki sam ORAZ sprawdzanie poprawności odbywa się w tej samej klasie. To nie jest twój przykład. W refaktoryzowanym kodzie pierwszym sprawdzeniem jest nawet sprawdzenie poprawności danych wejściowych, awaria powoduje żądanie nowej danych wejściowych. Drugie sprawdzenie jest całkowicie niezależne od pierwszego, ponieważ odbywa się na publicznej metodzie padToEvenWithEven, która może być wywołana spoza klasy i ma inny wynik (wyjątek).
Twój problem jest podobny do problemu pomylenia przypadkowo identycznego kodu z nie-suchym. Mylicie implementację z projektem. Nie są takie same, a to, że masz jedną lub kilkanaście linii, które są takie same, nie oznacza, że służą one temu samemu celowi i można je na zawsze traktować zamiennie. Zapewne twoja klasa robi zbyt wiele, ale pomiń to, bo to prawdopodobnie tylko zabawkowy przykład ...
Jeśli jest to problem z wydajnością, możesz rozwiązać problem, tworząc prywatną metodę, która nie wykonuje żadnej weryfikacji, którą publiczna platforma PadToEvenWithEven wywołała po sprawdzeniu poprawności i którą zamiast tego wywołałaby inna metoda. Jeśli nie jest to problem z wydajnością, pozwól innym metodom wykonać kontrole wymagane do wykonania przypisanych zadań.
źródło