Natknąłem się na wpis na blogu, który zniechęca do korzystania z napisów w Javie do powodowania braku semantyki w kodzie, sugerując, aby zamiast tego używać cienkich klas opakowań. Oto przykłady przed i po wspomnianym wpisie ilustrującym tę kwestię:
public void bookTicket(
String name,
String firstName,
String film,
int count,
String cinema);
public void bookTicket(
Name name,
FirstName firstName,
Film film,
Count count,
Cinema cinema);
Z mojego doświadczenia związanego z czytaniem blogów programistycznych doszedłem do wniosku, że 90% to nonsens, ale zastanawiam się, czy to słuszny punkt. Jakoś nie wydaje mi się to właściwe, ale nie mogłem dokładnie określić, co jest nie tak ze stylem programowania.
Odpowiedzi:
Hermetyzacja służy ochronie twojego programu przed zmianami . Czy przedstawienie nazwy zmieni się? Jeśli nie, to marnujesz swój czas i obowiązuje YAGNI.
Edycja: Przeczytałem post na blogu i ma on zasadniczo dobry pomysł. Problem polega na tym, że przeskoczył to za daleko. Coś takiego
String orderId
jest naprawdę złe, ponieważ prawdopodobnie"!"£$%^&*())ADAFVF
nie jest ważneorderId
. Oznacza to, żeString
reprezentuje o wiele więcej możliwych wartości niż są prawidłoweorderId
s. Jednak w przypadku czegoś takiego jakname
a nie można przewidzieć, która nazwa może być poprawna, a która nieString
jest poprawnaname
.Po pierwsze (poprawnie) redukujesz możliwe dane wejściowe tylko do poprawnych. W drugim przypadku nie udało się zawęzić możliwych prawidłowych danych wejściowych.
Edytuj ponownie: Rozważ przypadek nieprawidłowego wprowadzania danych. Jeśli napiszesz „Gareth Gobulcoque” jako swoje imię, będzie to wyglądać głupio, nie będzie końca świata. Jeśli wpiszesz niepoprawny OrderID, istnieje szansa, że po prostu nie zadziała.
źródło
Name
, nie możesz przypadkowo przekazać innej niepowiązanej wartości ciągu. Tam, gdzie oczekujeszName
,Name
skompiluje się tylko testament, a Ciąg „Jestem ci winien 5 $” nie może zostać przypadkowo zaakceptowany. (Uwaga: nie ma to związku z żadnym sprawdzaniem poprawności nazw!)To po prostu szalone :)
źródło
var p = new Point(x: 10, y:20);
Nie jest tak, że Cinema różni się od łańcucha. Zrozumiałbym, gdybyśmy mieli do czynienia z wielkościami fizycznymi, takimi jak ciśnienie, temperatura, energia, gdzie jednostki różnią się, a niektóre nie mogą być ujemne. Autor bloga musi wypróbować funkcjonalność.W większości zgadzam się z autorem. Jeśli istnieje jakieś zachowanie właściwe dla pola, takie jak sprawdzanie poprawności identyfikatora zamówienia, wówczas utworzyłbym klasę reprezentującą ten typ. Jego druga uwaga jest jeszcze ważniejsza: jeśli masz zestaw pól reprezentujących pewne pojęcia, takie jak adres, utwórz klasę dla tego pojęcia. Jeśli programujesz w Javie, płacisz wysoką cenę za pisanie statyczne. Równie dobrze możesz uzyskać całą wartość, jaką możesz.
źródło
Nie rób tego; to nadmiernie skomplikuje rzeczy, a Ty nie będziesz tego potrzebował
... to odpowiedź, którą napisałbym tutaj 2 lata temu. Teraz jednak nie jestem tego taki pewien; w rzeczywistości w ostatnich miesiącach zacząłem migrować stary kod do tego formatu, nie dlatego, że nie mam nic lepszego do roboty, ale dlatego, że naprawdę potrzebowałem go do wdrożenia nowych funkcji lub zmiany istniejących. Rozumiem automatyczne awersje, które inni widzą w tym kodzie, ale myślę, że jest to coś, co zasługuje na poważne przemyślenie.
Korzyści
Najważniejszą zaletą jest możliwość modyfikacji i rozszerzenia kodu. Jeśli użyjesz
zamiast po prostu przekazać kilka liczb całkowitych - co jest niestety w wielu interfejsach - znacznie łatwiej jest później dodać inny wymiar. Lub zmień typ na
double
. Jeśli użyjeszList<Author> authors
lubList<Person> authors
zamiastList<String> authors
tego później znacznie łatwiej będzie dodać więcej informacji do tego, co reprezentuje autor. Zapisując to w ten sposób, wydaje mi się, że stwierdzam to, co oczywiste, ale w praktyce byłem winny wielokrotnego używania ciągów w ten sposób, szczególnie w przypadkach, gdy na początku nie było to oczywiste, potrzebowałem więcej niż sznurek.Obecnie próbuję refaktoryzować listę ciągów, która jest przeplatana w całym kodzie, ponieważ potrzebuję tam więcej informacji i odczuwam ból: \
Poza tym zgadzam się z autorem bloga, że zawiera on więcej informacji semantycznych , co ułatwia czytelnikowi zrozumienie. Podczas gdy parametry często otrzymują znaczące nazwy i otrzymują specjalną linię dokumentacji, często nie dzieje się tak w przypadku pól lub miejscowych.
Ostatnią korzyścią jest bezpieczeństwo typu , z oczywistych powodów, ale moim zdaniem jest to drobna sprawa.
Wady
Pisanie zajmuje więcej czasu . Pisanie małej klasy jest szybkie i łatwe, ale nie wymaga wysiłku, szczególnie jeśli potrzebujesz wielu takich klas. Jeśli przestajesz pisać co 3 minuty, aby napisać nową klasę opakowań, może to być również poważna szkoda dla koncentracji. Chciałbym jednak pomyśleć, że taki stan wysiłku zwykle występuje tylko na pierwszym etapie pisania dowolnego fragmentu kodu; Zwykle mogę szybko uzyskać całkiem niezły pomysł na to, jakie podmioty będą musiały być zaangażowane.
Może obejmować wiele zbędnych seterów (lub konstrukcji) i getterów . Autor bloga podaje naprawdę brzydki przykład
new Point(x(10), y(10))
zamiastnew Point(10, 10)
, i chciałbym dodać, że użycie może również obejmować rzeczy takie jakMath.max(p.x.get(), p.y.get())
zamiastMath.max(p.x, p.y)
. Długi kod jest często uważany za trudniejszy do odczytania i słusznie. Ale szczerze mówiąc, mam wrażenie, że wiele kodu przenosi obiekty i tylko wybrane metody go tworzą, a jeszcze mniej potrzebuje dostępu do jego drobnych szczegółów (co zresztą nie jest OOPy).Sporny
Powiedziałbym, czy to pomaga w czytelności kodu jest dyskusyjne. Tak, więcej informacji semantycznych, ale dłuższy kod. Tak, łatwiej jest zrozumieć rolę każdego lokalnego, ale trudniej jest zrozumieć, co możesz z nim zrobić, chyba że przeczytasz jego dokumentację.
Podobnie jak w przypadku większości innych szkół programistycznych, myślę, że niezdrowe jest doprowadzenie tego do skrajności. Nie widzę, aby kiedykolwiek oddzielałem współrzędną xiy, aby każdy był innego typu. Nie uważam za
Count
konieczne, kiedyint
powinno wystarczyć. Nie podoba mi sięunsigned int
użycie w C - teoretycznie dobre, ale po prostu nie zapewnia wystarczającej ilości informacji i zabrania późniejszego rozszerzania kodu w celu obsługi tego magicznego -1. Czasami potrzebujesz prostoty.Myślę, że ten post na blogu jest nieco skrajny. Ale ogólnie nauczyłem się z bolesnego doświadczenia, że podstawową ideą tego są właściwe rzeczy.
Mam głęboką awersję do przerobionego kodu. Naprawdę. Ale dobrze wykorzystane, nie sądzę, aby ta nadmierna inżynieria.
źródło
Chociaż jest to rodzaj przesady, często myślę, że większość rzeczy, które widziałem, jest niedostatecznie opracowanych.
To nie tylko „bezpieczeństwo”. Jedną z naprawdę fajnych rzeczy w Javie jest to, że bardzo pomaga w zapamiętywaniu / ustalaniu, czego potrzebuje / oczekuje dana metoda biblioteki.
Biblioteka WORST (jak dotąd) Java, z którą pracowałem, została napisana przez kogoś, kto bardzo lubił Smalltalk i modelował bibliotekę GUI, aby działała bardziej jak smalltalk - problem polegał na tym, że każda metoda pobierała ten sam obiekt podstawowy, ale nie był w stanie WYKORZYSTYWAĆ wszystkiego, na co mógłby być rzutowany obiekt podstawowy, więc wróciłeś do zgadywania, co przejść do metod i nie wiedząc, czy zawiodłeś do czasu uruchomienia (Coś, z czym miałem do czynienia za każdym razem, gdy ja pracował w C).
Kolejny problem - jeśli przekazujesz ciągi, int, kolekcje i tablice bez obiektów, wszystko, co masz, to kule danych bez znaczenia. Wydaje się to naturalne, gdy myślisz w kategoriach bibliotek, z których będzie korzystać „jakaś aplikacja”, ale przy projektowaniu całej aplikacji o wiele bardziej pomocne jest przypisanie znaczenia (kodu) do wszystkich danych w miejscu, w którym dane są zdefiniowane, i myślenie tylko w warunki interakcji tych obiektów wysokiego poziomu. Jeśli przekazujesz prymitywy zamiast obiektów, to z definicji zmieniasz dane w innym miejscu niż to, w którym jest zdefiniowane (właśnie dlatego tak naprawdę nie lubię Seterów i Getterów - ta sama koncepcja, operujesz na danych, które nie są twoje).
Wreszcie, jeśli zdefiniujesz osobne obiekty dla wszystkiego, zawsze masz świetne miejsce do sprawdzania wszystkiego - na przykład, jeśli utworzysz obiekt dla kodu pocztowego, a później okaże się, że musisz upewnić się, że kod pocztowy zawsze zawiera 4-cyfrowe rozszerzenie, masz idealne miejsce do tego.
To nie jest zły pomysł. Myśląc o tym, nie jestem nawet pewien, czy powiedziałbym, że był w ogóle przeprojektowany, po prostu łatwiej jest pracować z nim pod każdym względem - jedynym wyjątkiem jest mnożenie małych klas, ale klasy Java są tak lekkie i łatwe napisać, że nie jest to nawet koszt (można je nawet wygenerować).
Byłbym bardzo zainteresowany, aby zobaczyć dobrze napisany projekt Java, w którym zdefiniowano zbyt wiele klas (tam, gdzie utrudniało to programowanie), zaczynam myśleć, że nie jest możliwe posiadanie zbyt wielu klas.
źródło
Myślę, że musisz spojrzeć na tę koncepcję z innego punktu wyjścia. Spójrz z perspektywy projektanta bazy danych: typy przekazane w pierwszym przykładzie nie definiują parametrów w unikalny sposób, nie mówiąc już o użyteczny sposób.
Potrzebne są dwa parametry, aby określić faktycznego patrona, który rezerwuje bilety, możesz mieć dwa różne filmy o identycznych nazwach (np. Przeróbki), możesz mieć ten sam film o różnych nazwach (np. Tłumaczenia). Pewna sieć kin może mieć różne oddziały, tak jak idziesz do czynienia z tym w ciąg i w spójny sposób (np używasz
$chain ($city)
lub$chain in $city
nawet coś innego i jak idziesz, aby upewnić się, że jest to konsekwentnie stosowane. Najgorsze jest określenie twojego patrona za pomocą dwóch parametrów, fakt, że podane jest zarówno imię, jak i nazwisko, nie gwarantuje ważnego klienta (i nie można rozróżnić dwóchJohn Doe
).Odpowiedzią na to jest deklarowanie typów, ale rzadko będą to cienkie opakowania, jak pokazałem powyżej. Najprawdopodobniej będą one służyć do przechowywania danych lub będą połączone z pewnego rodzaju bazą danych. Więc
Cinema
obiekt prawdopodobnie będzie miał nazwę, lokalizację ... i w ten sposób pozbędziesz się takich dwuznaczności. Jeśli są cienkimi opakowaniami, są przypadkiem.Tak więc w blogu IMHO jest tylko powiedzenie „upewnij się, że przekazujesz poprawne typy”, jego autor właśnie dokonał zbyt ograniczonego wyboru, aby wybrać w szczególności podstawowe typy danych (co jest złym komunikatem).
Proponowana alternatywa jest lepsza:
Z drugiej strony myślę, że post na blogu idzie zbyt daleko, by wszystko opakować.
Count
jest zbyt ogólny, mógłbym z tym policzyć jabłka lub pomarańcze, dodać je i nadal mieć na rękach sytuację, w której system typów pozwala mi wykonywać bezsensowne operacje. Możesz oczywiście zastosować tę samą logikę, co na blogu i zdefiniować typyCountOfOranges
itp., Ale jest to również głupie.Za to, co jest warte, napisałbym coś takiego
Krótko mówiąc: nie powinieneś przekazywać nonsensownych zmiennych; Jedynym momentem, w którym faktycznie określasz obiekt o wartości, która nie określa rzeczywistego obiektu, jest uruchomienie zapytania (np.
public Collection<Film> findFilmsWithTitle(String title)
) lub przygotowanie dowodu koncepcji. Utrzymuj swój system typów w czystości, więc nie używaj typu, który jest zbyt ogólny (np. Film reprezentowany przez aString
) lub zbyt restrykcyjny / specyficzny / wymyślony (np.Count
Zamiastint
). Użyj typu, który definiuje Twój obiekt w sposób unikalny i jednoznaczny, gdy tylko jest to możliwe i wykonalne.edycja : jeszcze krótsze podsumowanie. W przypadku małych aplikacji (np. Weryfikacja koncepcji): po co zawracać sobie głowę skomplikowanym projektem? Po prostu użyj
String
lubint
i kontynuuj.W przypadku dużych aplikacji: czy naprawdę jest tak prawdopodobne, że masz wiele klas, które składają się z jednego pola o podstawowym typie danych? Jeśli masz niewiele takich klas, po prostu masz „normalne” przedmioty, nic specjalnego się tam nie dzieje.
Uważam, że pomysł na enkapsulację łańcuchów ... to po prostu projekt, który jest niekompletny: zbyt skomplikowany dla małych aplikacji, niewystarczająco kompletny dla dużych aplikacji.
źródło
Dla mnie to robi to samo, co przy użyciu regionów w C #. Ogólnie rzecz biorąc, jeśli uważasz, że jest to konieczne, aby twój kod był czytelny, masz większe problemy, na które powinieneś poświęcić swój czas.
źródło
Powiedziałbym, że to naprawdę dobry pomysł w języku z silnie typowaną czcionką.
W Javie tego nie masz, więc utworzenie zupełnie nowej klasy dla tych rzeczy oznacza, że koszt prawdopodobnie przewyższa korzyść. Możesz także uzyskać 80% korzyści, zachowując ostrożność przy nazywaniu zmiennych / parametrów.
źródło
Byłoby dobrze, JEŻELI Łańcuch (koniec liczby całkowitej i ... mówiąc tylko o łańcuchu) nie był końcowy, więc te klasy mogłyby być pewnym (ograniczonym) łańcuchem o znaczeniu i nadal mogą być wysyłane do jakiegoś niezależnego obiektu, który wie, jak sobie z tym poradzić typ podstawowy (bez konwersacji tam iz powrotem).
A „dobroci” tego rosną, gdy są np. ograniczenia dotyczące wszystkich nazwisk.
Ale podczas tworzenia aplikacji (nie biblioteki) zawsze można ją refaktoryzować. Więc staram się zacząć bez niego.
źródło
Na przykład: w naszym obecnie opracowanym systemie istnieje wiele różnych podmiotów, które można zidentyfikować za pomocą różnego rodzaju identyfikatorów (ze względu na wykorzystanie systemów zewnętrznych), czasami nawet tego samego rodzaju. Wszystkie identyfikatory są ciągami - więc jeśli ktoś pomyli, jaki identyfikator powinien zostać przekazany jako parametr, nie pojawia się błąd czasu kompilacji, ale program wysadzi się w czasie wykonywania. Zdarza się to dość często. Muszę więc powiedzieć, że podstawowym celem tej zasady nie jest ochrona przed zmianami (choć służy to również), ale ochrona się przed błędami. W każdym razie, jeśli ktoś projektuje API, musi odzwierciedlać koncepcje domeny, więc koncepcyjnie przydatne jest definiowanie klas specyficznych dla domeny - wszystko zależy od tego, czy w programistach panuje porządek,
źródło