Piszę implementację Java gry karcianej, więc stworzyłem specjalny typ kolekcji, którą nazywam Strefą. Wszystkie metody modyfikacji kolekcji Java są nieobsługiwane, ale w API strefy istnieje metoda move(Zone, Card)
, która przenosi kartę z danej strefy do siebie (osiągane przy pomocy technik pakietowych). W ten sposób mogę zapewnić, że żadne karty nie zostaną wyjęte ze strefy i po prostu znikną; można je przenieść tylko do innej strefy.
Moje pytanie brzmi: jak konieczne jest tego rodzaju kodowanie obronne? Jest „poprawny” i wydaje się, że jest to właściwa praktyka, ale nie jest tak, że Zone API będzie kiedykolwiek częścią biblioteki publicznej. To jest tylko dla mnie, więc to trochę tak, jakbym chronił swój kod przed samym sobą, gdy prawdopodobnie mógłbym być bardziej wydajny, korzystając ze standardowych kolekcji.
Jak daleko powinienem zająć się tą strefą? Czy ktoś może mi doradzić, jak bardzo powinienem pomyśleć o zachowaniu umów na zajęciach, które piszę, szczególnie dla tych, które tak naprawdę nie będą publicznie dostępne?
źródło
Odpowiedzi:
Nie zamierzam rozwiązywać problemu projektowego - wystarczy pytanie, czy robić rzeczy „poprawnie” w niepublicznym interfejsie API.
Właśnie o to chodzi. Być może istnieją koderzy, którzy pamiętają niuanse każdej klasy i metody, jaką kiedykolwiek napisali, i nigdy nie mylą się przy nich z niewłaściwą umową. Nie jestem jednym z nich. Często zapominam, jak napisany kod powinien działać w ciągu kilku godzin od napisania. Po tym, jak pomyślisz, że raz dobrze to zrozumiałeś, twój umysł będzie miał tendencję do zmiany biegów na problem, nad którym teraz pracujesz .
Masz narzędzia do walki z tym. Narzędzia te obejmują (w nieokreślonej kolejności) konwencje, testy jednostkowe i inne testy automatyczne, sprawdzanie warunków wstępnych i dokumentację. Ja sam przekonałem się, że testy jednostkowe są nieocenione, ponieważ oba zmuszają do przemyślenia, w jaki sposób zostanie wykorzystana twoja umowa i dostarczenia dokumentacji później, w jaki sposób interfejs został zaprojektowany.
źródło
Zwykle przestrzegam kilku prostych zasad:
IllegalArgumentException
.).assert input != null
.).Jeśli klient naprawdę się w to angażuje, zawsze znajdzie sposób, aby sprawić, że Twój kod będzie źle działał. Zawsze mogą to zrobić przynajmniej poprzez refleksję. Ale to piękno projektowania na podstawie umowy . Nie akceptujesz takiego wykorzystania swojego kodu, więc nie możesz zagwarantować, że będzie on działał w takich sytuacjach.
Jeśli chodzi o konkretny przypadek, jeśli
Zone
nie ma być używany i / lub dostępny dla osób postronnych, należy uczynić pakiet klasowym prywatnym (i ewentualniefinal
), lub najlepiej użyć kolekcji, które już udostępnia Java. Są przetestowane i nie trzeba wymyślać koła na nowo. Zauważ, że nie przeszkadza to w używaniu asercji w całym kodzie, aby upewnić się, że wszystko działa zgodnie z oczekiwaniami.źródło
Programowanie obronne jest bardzo dobrą rzeczą.
Aż zacznie przeszkadzać w pisaniu kodu. To nie jest taka dobra rzecz.
Mówiąc trochę bardziej pragmatycznie ...
Wygląda na to, że jesteś o krok od posunięcia się za daleko. Wyzwanie (i odpowiedź na twoje pytanie) polega na zrozumieniu, jakie są reguły biznesowe lub wymagania programu.
Korzystając z interfejsu API gry karcianej jako przykładu, istnieją środowiska, w których wszystko, co można zrobić, aby zapobiec oszustwom, ma kluczowe znaczenie. Mogą być w to zaangażowane duże kwoty prawdziwych pieniędzy, dlatego warto wprowadzić dużą liczbę czeków, aby upewnić się, że oszustwo nie będzie możliwe.
Z drugiej strony musisz pamiętać o zasadach SOLID, a zwłaszcza o pojedynczej odpowiedzialności. Poproszenie klasy kontenera o skuteczne sprawdzenie, dokąd idą karty, może być trochę za dużo. Lepiej może być warstwa audytu / kontrolera między kontenerem karty a funkcją odbierającą żądania przeniesienia.
W związku z tymi obawami musisz zrozumieć, które elementy interfejsu API są publicznie udostępnione (a tym samym narażone) w porównaniu z tym, co jest prywatne i mniej narażone. Nie jestem zwolennikiem „twardej powłoki zewnętrznej z miękkim wnętrzem”, ale najlepszym zwrotem twojego wysiłku jest utwardzenie zewnętrznej strony twojego API.
Nie sądzę, że zamierzony użytkownik końcowy biblioteki jest tak krytyczny wobec ustalenia, ile programów obronnych wprowadziłeś. Nawet w przypadku modułów, które piszę na własny użytek, wciąż staram się sprawdzić, aby upewnić się, że w przyszłości nie popełnię jakiegoś niezamierzonego błędu w wywołaniu biblioteki.
źródło
Kodowanie obronne to nie tylko dobry pomysł na kod publiczny. To świetny pomysł na każdy kod, który nie jest natychmiast wyrzucany. Jasne, wiesz, jak ma się teraz nazywać , ale nie masz pojęcia, jak dobrze będziesz pamiętać te sześć miesięcy, kiedy wrócisz do projektu.
Podstawowa składnia języka Java zapewnia dużo wbudowanej obrony w porównaniu do języka niższego poziomu lub języka interpretowanego, takiego jak odpowiednio C lub JavaScript. Zakładając, że nazywasz jasno swoje metody i nie masz zewnętrznego „sekwencjonowania metod”, prawdopodobnie możesz uniknąć podania argumentów jako poprawnego typu danych i uwzględnienia rozsądnego zachowania, jeśli poprawnie wpisane dane mogą być nadal nieprawidłowe.
(Nawiasem mówiąc, jeśli karty zawsze muszą znajdować się w strefie, myślę, że otrzymujesz lepszy huk za to, że wszystkie karty w grze są powiązane przez kolekcję globalną z twoim obiektem gry, a strefa jest własnością każdej karty. Ale ponieważ nie wiem, co robią Twoje Strefy poza trzymaniem kart, trudno jest ustalić, czy jest to właściwe.)
źródło
CardDescriptor
zawierającą kartę, jej lokalizację, status odkryty / zakryty, a nawet rotację w przypadku gier, które o to dbają. Są to wszystkie zmienne właściwości, które nie zmieniają tożsamości karty.Najpierw stwórz klasę, która prowadzi listę Stref, abyś nie stracił Strefy ani zawartych w niej kart. Następnie możesz sprawdzić, czy transfer znajduje się w Twojej ZoneList. Ta klasa prawdopodobnie będzie rodzajem singletonu, ponieważ będziesz potrzebować tylko jednego wystąpienia, ale możesz chcieć później zestawów Stref, więc zachowaj opcje.
Po drugie, nie wprowadzaj kolekcji Zone ani ZoneList ani niczego innego, chyba że spodziewasz się, że będzie to potrzebne. Oznacza to, że jeśli strefa lub ZoneList zostaną przekazane do czegoś, co oczekuje kolekcji, a następnie wdrożyć go. Możesz wyłączyć kilka metod, każąc im zgłaszać wyjątek (UnimplementedException lub coś w tym rodzaju) lub zmuszając je po prostu do niczego. (Zastanów się naprawdę, zanim użyjesz drugiej opcji. Jeśli to zrobisz, ponieważ łatwo zauważysz, że brakuje Ci błędów, które mogłeś wcześniej wykryć).
Istnieją prawdziwe pytania dotyczące tego, co jest „prawidłowe”. Ale kiedy zrozumiesz, co to jest, będziesz chciał robić rzeczy w ten sposób. Za dwa lata zapomnisz o tym wszystkim, a jeśli spróbujesz użyć kodu, zirytujesz się facetem, który napisał go w tak sprzeczny z intuicją sposób i niczego nie wyjaśnił.
źródło
Defensywne kodowanie w projektowaniu API ogólnie polega na sprawdzaniu poprawności danych wejściowych i starannym wyborze odpowiedniego mechanizmu obsługi błędów. Warto również zwrócić uwagę na inne odpowiedzi, o których wspominają.
W rzeczywistości nie o to chodzi w twoim przykładzie. Ograniczasz swoją powierzchnię API z bardzo konkretnego powodu. Jak wspomina GlenH7 , kiedy zestaw kart ma być używany w rzeczywistej grze, na przykład z talią („używaną” i „nieużywaną”), na przykład stołem i rękami, zdecydowanie chcesz umieścić czeki, aby upewnić się, że każda karta z zestawu jest obecna tylko raz.
To, że zaprojektowałeś to z „strefami”, jest arbitralnym wyborem. W zależności od implementacji (strefa może być tylko ręką, talią lub stołem w powyższym przykładzie) może to być bardzo dokładny projekt.
Jednak ta implementacja brzmi jak wyprowadzony typ bardziej
Collection<Card>
podobnego zestawu kart z mniej restrykcyjnym interfejsem API. Na przykład, jeśli chcesz zbudować kalkulator wartości kart lub sztuczną inteligencję, z pewnością chcesz mieć swobodę wyboru, które karty i ile kart iterujesz.Warto więc ujawnić taki restrykcyjny interfejs API, jeśli jedynym celem tego interfejsu API jest upewnienie się, że każda karta zawsze znajduje się w strefie.
źródło