Załóżmy, że mamy metodę, foo(String bar)
która działa tylko na ciągach, które spełniają określone kryteria; na przykład musi być małymi literami, nie może być pusty lub mieć tylko białe znaki i musi pasować do wzorca [a-z0-9-_./@]+
. Dokumentacja metody podaje te kryteria.
Czy metoda powinna odrzucić wszelkie odchylenia od tych kryteriów, czy też powinna bardziej wybaczać niektóre kryteria? Na przykład, jeśli początkową metodą jest
public void foo(String bar) {
if (bar == null) {
throw new IllegalArgumentException("bar must not be null");
}
if (!bar.matches(BAR_PATTERN_STRING)) {
throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
}
this.bar = bar;
}
A drugą metodą wybaczania jest
public void foo(String bar) {
if (bar == null) {
throw new IllegalArgumentException("bar must not be null");
}
if (!bar.matches(BAR_PATTERN_STRING)) {
bar = bar.toLowerCase().trim().replaceAll(" ", "_");
if (!bar.matches(BAR_PATTERN_STRING) {
throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
}
}
this.bar = bar;
}
Czy dokumentacja powinna zostać zmieniona, aby oświadczyć, że zostanie przekształcona i ustawiona na przekształconą wartość, jeśli to możliwe, czy też metoda powinna być tak prosta, jak to możliwe i odrzucać wszelkie odchylenia? W takim przypadku bar
może zostać ustawiony przez użytkownika aplikacji.
Podstawowym przypadkiem użycia tego są użytkownicy uzyskujący dostęp do obiektów z repozytorium za pomocą określonego identyfikatora ciągu. Każdy obiekt w repozytorium powinien mieć unikalny ciąg identyfikujący go. Te repozytoria mogą przechowywać obiekty na różne sposoby (serwer sql, json, xml, binarny itp.), Więc próbowałem zidentyfikować najniższy wspólny mianownik, który pasowałby do większości konwencji nazewnictwa.
źródło
foo
funkcję, która jest rygorystyczna pod względem akceptowanych argumentów, i mieć drugą funkcję pomocniczą, która może próbować „wyczyścić” argument, z którego chcesz skorzystaćfoo
. W ten sposób każda metoda ma mniej do zrobienia sama, a ich zarządzanie i integracja są czystsze. Idąc tą drogą, prawdopodobnie pomocne byłoby również odejście od projektu ciężkiego od wyjątków; możesz użyć czegoś takiego jakOptional
zamiast, a następnie mieć funkcje, które zużywająfoo
wyjątki rzucania, jeśli to konieczne.Odpowiedzi:
Twoja metoda powinna robić to, co mówi.
Zapobiega to błędom, zarówno w użyciu, jak i późniejszym zmienianiu zachowania przez opiekunów. Oszczędza czas, ponieważ opiekunowie nie muszą poświęcać tyle czasu na zastanawianie się, co się dzieje.
To powiedziawszy, jeśli zdefiniowana logika nie jest przyjazna dla użytkownika, być może powinna zostać poprawiona.
źródło
foo
ifooForUncleanString
metody, w których ten drugi dokonuje poprawek przed przekazaniem go do pierwszego.)Jest kilka punktów:
Pamiętaj, że w trybie debugowania znajdują się stwierdzenia dotyczące debugowania, które w większości łagodzą wszelkie problemy z wydajnością.
Jeśli wdrożysz interfejs użytkownika, przyjazne komunikaty o błędach (w tym sugestie i inna pomoc) są częścią dobrego projektu.
Pamiętaj jednak, że interfejsy API są przeznaczone dla programistów, a nie użytkowników końcowych.
Prawdziwym eksperymentem polegającym na rozmyciu i pobłażaniu przy wprowadzaniu danych jest HTML.
W rezultacie wszyscy robili to nieco inaczej, a specyfikacja, teraz udokumentowana, jest gigantyczną książką pełną specjalnych przypadków.
Zobacz prawo Postela („ Bądź konserwatywny w tym, co robisz, bądź liberalny w tym, co akceptujesz od innych. ”) I krytyk dotykający tego ( Lub o wiele lepszy, o którym MichaelT mnie uświadomił ).
źródło
Zachowanie metody powinno być jasne, intuicyjne, przewidywalne i proste. Ogólnie rzecz biorąc, powinniśmy bardzo wahać się, czy wykonać dodatkowe przetwarzanie danych wejściowych osoby dzwoniącej. Takie domysły na temat tego, co zamierzał dzwoniący, niezmiennie mają wiele przypadków skrajnych, które powodują niepożądane zachowanie. Rozważ operację tak prostą jak połączenie ścieżki pliku. Wiele (a może nawet większość) funkcji łączenia ścieżek plików po cichu odrzuca wszystkie poprzednie ścieżki, jeśli jedna z łączonych ścieżek wydaje się być zrootowana! Na przykład
/abc/xyz
połączone z/evil
spowoduje tylko/evil
. Prawie nigdy nie zamierzam tego robić, dołączając ścieżki plików, ale ponieważ nie ma interfejsu, który nie zachowałby się w ten sposób, jestem zmuszony albo mieć błędy, albo napisać dodatkowy kod opisujący te przypadki.To powiedziawszy, zdarzają się rzadkie sytuacje, w których sensowna jest metoda „wybaczania”, ale osoba dzwoniąca powinna zawsze decydować, kiedy i czy te etapy przetwarzania mają zastosowanie do ich sytuacji. Kiedy więc zidentyfikujesz wspólny krok przetwarzania wstępnego, który chcesz zastosować do argumentów w różnych sytuacjach, powinieneś udostępnić interfejsy dla:
Ostatni jest opcjonalny; powinieneś go podać tylko wtedy, gdy z niego skorzysta duża liczba połączeń.
Ujawnienie surowej funkcjonalności daje dzwoniącemu możliwość korzystania z niej bez etapu wstępnego przetwarzania, gdy zajdzie taka potrzeba. Samo ujawnienie kroku preprocesora pozwala wywołującemu użyć go w sytuacjach, w których nawet nie wywołuje funkcji lub gdy chce przetworzyć niektóre dane wejściowe przed wywołaniem funkcji (na przykład, gdy najpierw chce przekazać ją do innej funkcji). Zapewnienie kombinacji umożliwia dzwoniącym wywoływanie obu bez żadnych kłopotów, co jest przydatne przede wszystkim, jeśli większość dzwoniących użyje go w ten sposób.
źródło
Jak powiedzieli inni, dopasowanie łańcucha „wybaczenie” oznacza wprowadzenie dodatkowej złożoności. Oznacza to więcej pracy przy wdrażaniu dopasowania. Na przykład masz teraz o wiele więcej przypadków testowych. Musisz wykonać dodatkową pracę, aby upewnić się, że w przestrzeni nazw nie ma semantycznie równych nazw. Większa złożoność oznacza również, że w przyszłości będzie więcej błędów. Prostszy mechanizm, taki jak rower, wymaga mniej konserwacji niż bardziej skomplikowany, taki jak samochód.
Czy więc łagodne dopasowywanie ciągów jest warte wszystkich tych dodatkowych kosztów? Zależy to od przypadku użycia, jak zauważyli inni. Jeśli ciągi są jakimś zewnętrznym wejściem, nad którym nie masz kontroli, a łagodne dopasowanie jest wyraźną zaletą, być może warto. Być może dane wejściowe pochodzą od użytkowników końcowych, którzy mogą nie być zbyt sumienni w kwestii znaków kosmicznych i wielkich liter, a Ty masz silną motywację, aby ułatwić korzystanie z produktu.
Z drugiej strony, gdyby dane wejściowe pochodziły z, powiedzmy, plików właściwości zebranych przez ludzi technicznych, którzy powinni to zrozumieć
"Fred Mertz" != "FredMertz"
, byłbym bardziej skłonny do zaostrzenia dopasowania i zaoszczędzić koszty rozwoju.W każdym razie uważam, że przycinanie i ignorowanie wiodących i końcowych spacji ma wartość - widziałem zbyt wiele godzin marnowanych na debugowanie tego rodzaju problemów.
źródło
Wspominasz o kontekście, z którego pochodzi to pytanie.
Biorąc to pod uwagę, chciałbym, aby metoda zrobiła tylko jedną rzecz: określa wymagania dla łańcucha, niech wykona się na tej podstawie - nie próbowałbym go tutaj transformować. Uprość to i wyjaśnij; udokumentuj to i staraj się zachować synchronizację dokumentacji i kodu.
Jeśli chcesz przekształcić dane pochodzące z bazy danych użytkowników w bardziej wybaczający sposób, umieść tę funkcjonalność w osobnej metodzie transformacji i udokumentuj powiązaną funkcjonalność .
W pewnym momencie wymagania funkcji muszą zostać spełnione, jasno udokumentowane, a wykonanie musi być kontynuowane. „Przebaczenie” w jego punkcie jest nieco nieme, jest to decyzja projektowa i argumentowałbym za funkcją, która nie zmienia swojego argumentu. Zmutowanie funkcji wejścia powoduje ukrycie części sprawdzania poprawności, która byłaby wymagana od klienta. Posiadanie funkcji, która powoduje mutację, pomaga klientowi zrobić to dobrze.
Duży nacisk tutaj jest klarowność i dokumentów, co kod robi .
źródło
źródło