Czy powinniśmy definiować typy dla wszystkiego?


141

Ostatnio miałem problem z czytelnością mojego kodu.
Miałem funkcję, która wykonała operację i zwróciła ciąg reprezentujący identyfikator tej operacji do przyszłego odwołania (trochę jak OpenFile w Windows zwracający uchwyt). Użytkownik użyje tego identyfikatora później, aby rozpocząć operację i monitorować jej zakończenie.

Identyfikator musiał być ciągiem losowym ze względu na obawy związane z interoperacyjnością. Stworzyło to metodę, która miała bardzo niejasny podpis:

public string CreateNewThing()

To sprawia, że ​​zamiar typu zwracanego jest niejasny. Pomyślałem, że mogę zawinąć ten ciąg w inny typ, który uczyni jego znaczenie bardziej zrozumiałym:

public OperationIdentifier CreateNewThing()

Typ będzie zawierał tylko ciąg znaków i będzie używany za każdym razem, gdy zostanie użyty.

Oczywistym jest, że zaletą tego sposobu działania jest większe bezpieczeństwo typu i wyraźniejsze zamiary, ale tworzy także znacznie więcej kodu i kodu, który nie jest bardzo idiomatyczny. Z jednej strony podoba mi się dodatkowe bezpieczeństwo, ale także tworzy dużo bałaganu.

Czy uważasz, że ze względów bezpieczeństwa dobrą praktyką jest owijanie prostych typów w klasę?


4
Możesz być zainteresowany czytaniem o Tiny Typach. Grałem z tym przez chwilę i nie poleciłbym tego, ale myślenie o tym jest zabawne. darrenhobbs.com/2007/04/11/tiny-types
Jeroen Vannevel

40
Gdy użyjesz czegoś takiego jak Haskell, zobaczysz, ile pomaga używanie typów. Rodzaje pomagają zapewnić prawidłowy program.
Nate Symer

3
Nawet dynamiczny język, taki jak Erlang, korzysta z systemu pisma, dlatego ma system pisma typowego i narzędzie do sprawdzania pisma znane Haskellersowi, mimo że jest językiem dynamicznym. Języki takie jak C / C ++, w których prawdziwym typem czegokolwiek jest liczba całkowita, którą zgadzamy się z kompilatorem, aby traktować określony sposób, lub Java, w której prawie masz typy, jeśli zdecydujesz się udawać, że klasy są typami, które stanowią albo problem z przeciążeniem semantycznym w język (czy jest to „klasa” czy „typ”?) lub niejednoznaczność typu (czy to „URL”, „String” czy „UPC”?). Na koniec użyj dowolnych narzędzi, które możesz jednoznacznie określić.
zxq9

7
Nie jestem pewien, skąd pomysł, że C ++ ma narzut na typy. W C ++ 11 żaden z twórców kompilatorów nie widział problemu z nadaniem każdej lambdzie własnego, unikalnego typu. Ale TBH, tak naprawdę nie można oczekiwać, że znajdzie się wiele wglądu w komentarzu na temat „systemu typów C / C ++”, tak jakby obie dzieliły system typów.
MSalters

2
@JDB - nie, nie ma. Nawet nie podobny.
Davor Ždralo

Odpowiedzi:


152

Prymitywy, takie jak stringlub int, nie mają znaczenia w domenie biznesowej. Bezpośrednią konsekwencją tego jest to, że możesz błędnie użyć adresu URL, gdy oczekiwany jest identyfikator produktu, lub użyć ilości, gdy spodziewasz się ceny .

Z tego powodu wyzwanie Kalistenika obiektów obejmuje zawijanie prymitywów jako jedną z jego zasad:

Reguła 3: Zawiń wszystkie prymitywy i łańcuchy

W języku Java int jest prymitywnym, a nie prawdziwym obiektem, więc podlega innym regułom niż obiekty. Jest używany ze składnią, która nie jest zorientowana obiektowo. Co ważniejsze, sama int jest tylko skalarem, więc nie ma znaczenia. Gdy metoda przyjmuje int jako parametr, nazwa metody musi wykonać całą pracę polegającą na wyrażeniu intencji. Jeśli ta sama metoda zajmuje godzinę jako parametr, o wiele łatwiej jest zobaczyć, co się dzieje.

Ten sam dokument wyjaśnia, że ​​istnieje dodatkowa korzyść:

Małe przedmioty, takie jak Godzina lub Pieniądze, również dają nam oczywiste miejsce do postawienia zachowania, które w innym przypadku byłoby zaśmiecone wokół innych klas .

Rzeczywiście, gdy stosowane są prymitywy, zazwyczaj niezwykle trudne jest śledzenie dokładnej lokalizacji kodu związanego z tymi typami, co często prowadzi do poważnego powielania kodu . Jeśli jest Price: Moneyklasa, naturalne jest sprawdzenie zasięgu w środku. Jeżeli zamiast tego do przechowywania cen produktów stosuje się int(gorzej, a double), kto powinien potwierdzić zakres? Produkt? Rabat? Wózek?

Wreszcie trzecią korzyścią niewymienioną w dokumencie jest możliwość stosunkowo łatwej zmiany rodzaju bazowego. Jeśli dzisiaj mój ProductIdma shorttyp podstawowy, a później muszę go użyć int, istnieje szansa, że ​​kod do zmiany nie obejmie całej bazy kodu.

Wadą - i ten sam argument stosuje się do każdej reguły ćwiczenia Calisthenics Object - jest to, że jeśli szybko staje się zbyt przytłaczający, aby stworzyć klasę dla wszystkiego . Jeśli Productzawiera, ProductPricektóre dziedziczy, z PositivePricektórego dziedziczy, z Pricektórego dziedziczy Money, to nie jest to czysta architektura, ale raczej kompletny bałagan, w którym aby znaleźć jedną rzecz, opiekun powinien otwierać kilkadziesiąt plików za każdym razem.

Inną kwestią do rozważenia jest koszt (pod względem linii kodu) tworzenia dodatkowych klas. Jeśli opakowania są niezmienne (jak zwykle powinny), oznacza to, że jeśli weźmiemy C #, musisz mieć w opakowaniu co najmniej:

  • Właściciel nieruchomości,
  • Jego zaplecze,
  • Konstruktor, który przypisuje wartość do pola bazowego,
  • Zwyczaj ToString(),
  • Komentarze do dokumentacji XML (która tworzy wiele wierszy),
  • A Equalsi GetHashCodezastępuje (również dużo LOC).

i ewentualnie, w stosownych przypadkach:

  • DebuggerDisplay atrybut,
  • Override ==i !=operatorów,
  • W końcu przeciążenie niejawnego operatora konwersji do płynnej konwersji do iz typu enkapsulowanego,
  • Kontrakty kodowe (w tym niezmiennik, który jest dość długą metodą z trzema atrybutami),
  • Kilka konwerterów, które będą używane podczas serializacji XML, serializacji JSON lub przechowywania / ładowania wartości do / z bazy danych.

Sto LOC na proste opakowanie sprawia, że ​​jest on zbyt wygórowany, dlatego też możesz być całkowicie pewien długoterminowej rentowności takiego opakowania. Pojęcie zakresu wyjaśnione przez Thomasa Junk jest tutaj szczególnie istotne. Pisanie setek LOC-ów, które będą reprezentować ProductIdużywane w całej bazie kodu, wygląda całkiem użytecznie. Napisanie klasy tego rozmiaru dla fragmentu kodu, który tworzy trzy wiersze w ramach jednej metody, jest znacznie bardziej wątpliwe.

Wniosek:

  • Zawijaj operacje podstawowe w klasach, które mają znaczenie w domenie biznesowej aplikacji, gdy (1) pomaga zmniejszyć liczbę błędów, (2) zmniejsza ryzyko duplikacji kodu lub (3) pomaga później zmienić typ bazowy.

  • Nie zawijaj automatycznie wszystkich prymitywów, które znajdziesz w kodzie: w wielu przypadkach użycie stringlub intjest całkowicie w porządku.

W praktyce public string CreateNewThing()zwracanie instancji ThingIdklasy zamiast stringmoże pomóc, ale możesz również:

  • Zwraca instancję Id<string>klasy, czyli obiekt typu ogólnego wskazujący, że typem bazowym jest łańcuch. Zaletą czytelności jest brak konieczności utrzymywania wielu typów.

  • Zwraca instancję Thingklasy. Jeśli użytkownik potrzebuje tylko identyfikatora, można to łatwo zrobić za pomocą:

    var thing = this.CreateNewThing();
    var id = thing.Id;
    

33
Myślę, że ważna jest tutaj zdolność do swobodnej zmiany typu podstawowego. Dzisiaj jest to ciąg znaków, ale może GUID lub jutro. Utworzenie opakowania typu ukrywa te informacje, prowadząc do mniejszej liczby zmian na drodze. ++ Dobra odpowiedź tutaj.
RubberDuck

4
W przypadku pieniędzy upewnij się, że waluta jest zawarta w obiekcie Money, lub że cały program używa tego samego (który następnie należy udokumentować).
Paŭlo Ebermann

5
@Blrfl: chociaż istnieją uzasadnione przypadki, w których konieczne jest głębokie dziedziczenie, zwykle widzę takie dziedziczenie w przeprojektowanym kodzie napisanym bez zwracania uwagi na czytelność, wynikającym z kompulsywnego podejścia do płatności według LOC. Stąd negatywny charakter tej części mojej odpowiedzi.
Arseni Mourzenko

3
@ArTs: To jest argument „potępmy narzędzie, ponieważ niektórzy ludzie używają go w niewłaściwy sposób”.
Blrfl

6
@MainMa Przykład, w którym znaczenie mogło uniknąć kosztownego błędu: en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure
cbojar

60

Użyłbym zakresu jako ogólnej zasady: im węższy zakres generowania i konsumowania values, tym mniejsze prawdopodobieństwo, że będziesz musiał stworzyć obiekt reprezentujący tę wartość.

Powiedz, że masz następujący pseudokod

id = generateProcessId();
doFancyOtherstuff();
job.do(id);

wtedy zakres jest bardzo ograniczony i nie widziałbym sensu w tworzeniu tego typu. Ale powiedzmy, że generujesz tę wartość w jednej warstwie i przekazujesz ją do kolejnej warstwy (lub nawet innego obiektu), wtedy stworzenie takiego typu byłoby idealnie uzasadnione.


14
To bardzo dobra uwaga. Jawne typy są ważnym narzędziem do komunikowania zamiarów , co jest szczególnie ważne ponad granicami obiektów i usług.
Avner Shahar-Kashtan

+1, chociaż jeśli jeszcze nie dokonałeś refaktoryzacji całego kodu doFancyOtherstuff()podprogramu, możesz pomyśleć, że job.do(id)odwołanie nie jest wystarczająco lokalne, aby można je było przechowywać jako prosty ciąg.
Mark Hurd

To jest całkowicie prawda! Dzieje się tak, gdy masz prywatną metodę, w której wiesz, że świat zewnętrzny nigdy jej nie użyje. Następnie możesz pomyśleć trochę więcej, gdy klasa jest chroniona, a potem, gdy klasa jest publiczna, ale nie jest częścią publicznego interfejsu API. Zakres zakresu i dużo sztuki, aby umieścić linię w dobrej pozycji między zbyt prymitywnymi a wieloma niestandardowymi typami.
Samuel

34

Systemy typu statycznego mają na celu zapobieganie nieprawidłowemu wykorzystaniu danych.

Istnieją oczywiste przykłady typów, które to robią:

  • nie możesz uzyskać miesiąca z UUID
  • nie można pomnożyć dwóch ciągów.

Istnieją bardziej subtelne przykłady

  • za coś nie można zapłacić za długość biurka
  • nie możesz złożyć żądania HTTP, używając czyjejś nazwy jako adresu URL.

Możemy mieć ochotę użyć doublezarówno ceny, jak i długości, lub użyć stringzarówno nazwy, jak i adresu URL. Ale zrobienie tego psuje nasz niesamowity system typów i pozwala tym nadużyciom przejść testy statyczne języka.

Pomylenie funta sekund z Newtonem sekundami może mieć złe wyniki w czasie wykonywania .


Jest to szczególnie problem z łańcuchami. Często stają się one „uniwersalnym typem danych”.

Jesteśmy przyzwyczajeni do interfejsów tekstowych głównie z komputerami, a my często rozszerzyć te ludzkie interfejsy (UIS) do interfejsów programistycznych (API). Myślimy o 34,25 jako o znakach 34,25 . Myślimy o randce jak o postaciach 05-03-2015 . UUID uważamy za znaki 75e945ee-f1e9-11e4-b9b2-1697f925ec7b .

Ale ten model mentalny szkodzi abstrakcji API.

Słowa lub język, gdy są pisane lub wypowiadane, nie odgrywają żadnej roli w moim mechanizmie myślenia.

Albert Einstein

Podobnie reprezentacje tekstowe nie powinny odgrywać żadnej roli w projektowaniu typów i interfejsów API. Uważaj na string! (i inne nazbyt ogólne „prymitywne” typy)


Typy komunikują „jakie operacje mają sens”.

Na przykład kiedyś pracowałem na kliencie z interfejsem API REST HTTP. REST, poprawnie wykonane, używaj encji hipermedialnych, które mają hiperłącza wskazujące na powiązane encje. W tym kliencie wpisano nie tylko podmioty (np. Użytkownik, Konto, Subskrypcja), ale również linki do tych podmiotów (UserLink, AccountLink, SubscriptionLink). Linki były niewiele więcej niż opakowaniami Uri, ale osobne typy uniemożliwiały próbę użycia AccountLink do pobrania użytkownika. Gdyby wszystko było jasne Uri- lub nawet gorzej string- błędy te można było znaleźć tylko w czasie wykonywania.

Podobnie w twojej sytuacji masz dane, które są wykorzystywane tylko w jednym celu: do identyfikacji Operation. Nie należy go używać do niczego innego i nie powinniśmy próbować identyfikować Operations z losowymi ciągami, które stworzyliśmy. Utworzenie osobnej klasy zwiększa czytelność i bezpieczeństwo kodu.


Oczywiście wszystkie dobre rzeczy można wykorzystać w nadmiarze. Rozważać

  1. ile przejrzystości dodaje do twojego kodu

  2. jak często jest używany

Jeśli „typ” danych (w sensie abstrakcyjnym) jest często używany, do różnych celów i między interfejsami kodu, jest bardzo dobrym kandydatem do bycia odrębną klasą, kosztem gadatliwości.


21
„ciągi często stają się„ uniwersalnym ”typem danych” - to jest pochodzenie przymkniętego w język „monikera„ języków pisanych strunowo ”.
MSalters

@MSalters, ha ha, bardzo ładne.
Paul Draper

4
Co oczywiście 05-03-2015 oznacza interpretacja jako data ?
CVn

10

Czy uważasz, że ze względów bezpieczeństwa dobrą praktyką jest owijanie prostych typów w klasę?

Czasami.

Jest to jeden z tych przypadków, w których trzeba rozważyć problemy, które mogą wyniknąć z użycia stringzamiast bardziej konkretnego OperationIdentifier. Jakie są ich nasilenie? Jakie jest ich prawdopodobieństwo?

Następnie musisz wziąć pod uwagę koszt korzystania z innego rodzaju. Jak bolesne jest stosowanie? Ile to pracy?

W niektórych przypadkach zaoszczędzisz czas i wysiłek, mając fajny konkretny typ do pracy. W innych nie będzie to warte kłopotu.

Ogólnie rzecz biorąc, uważam, że tego rodzaju rzeczy należy robić więcej niż dzisiaj. Jeśli masz podmiot, który coś znaczy w Twojej domenie, dobrze jest mieć go jako swój własny typ, ponieważ istnieje większe prawdopodobieństwo, że podmiot ten zmieni się / rozwinie wraz z firmą.


10

Ogólnie zgadzam się, że wiele razy powinieneś utworzyć typ dla prymitywów i łańcuchów, ale ponieważ powyższe odpowiedzi zalecają utworzenie typu w większości przypadków, wymienię kilka powodów, dla których / kiedy nie:

  • Występ. Muszę tu odnosić się do prawdziwych języków. W języku C #, jeśli identyfikator klienta jest krótki i zawijasz go w klasę - właśnie utworzyłeś dużo narzutu: pamięci, ponieważ ma on teraz 8 bajtów w systemie 64-bitowym i szybkość, ponieważ teraz jest przydzielany na stosie.
  • Kiedy przyjmujesz założenia dotyczące typu. Jeśli identyfikator klienta jest krótki i masz pewną logikę, która go w jakiś sposób spakuje - zwykle już przyjmujesz założenia dotyczące tego typu. We wszystkich tych miejscach musisz teraz przełamać abstrakcję. Jeśli jest to jedno miejsce, to nie jest wielka sprawa, jeśli jest wszędzie, możesz znaleźć to w połowie czasu, gdy używasz prymitywu.
  • Ponieważ nie każdy język ma typef. W przypadku języków, które tego nie robią i kodu, który jest już napisany, wprowadzenie takiej zmiany może być dużym zadaniem, które może nawet wprowadzić błędy (ale każdy ma zestaw testów z dobrym zasięgiem, prawda?).
  • W niektórych przypadkach zmniejsza czytelność. Jak to wydrukować? Jak to sprawdzić? Czy powinienem sprawdzać wartość null? Czy wszystkie pytania wymagają pogłębienia definicji typu.

3
Z pewnością jeśli typ opakowania jest strukturą, a nie klasą, wówczas short(na przykład) ma taki sam koszt zawinięty lub rozpakowany. (?)
MathematicalOrchid

1
Czy nie byłoby dobrym rozwiązaniem dla typu zawierającego własną walidację? Lub utworzyć typ pomocnika, aby go zweryfikować?
Nick Udell

1
Niepotrzebne pakowanie lub mungowanie jest anty-wzorem. Informacje powinny przepływać w sposób przejrzysty przez kod aplikacji, z wyjątkiem przypadków, w których transformacja jest wyraźnie i naprawdę konieczna . Typy są dobre, ponieważ zwykle zastępują niewielką ilość dobrego kodu, umieszczoną w jednym miejscu, dużymi ilościami złej implementacji rozpryskanymi w całym systemie.
Thomas W

7

Nie, nie powinieneś definiować typów (klas) dla „wszystkiego”.

Ale, jak podają inne odpowiedzi, często jest to przydatne. Powinieneś rozwijać - świadomie, jeśli to możliwe - uczucie zbytniego tarcia z powodu braku odpowiedniego typu lub klasy podczas pisania, testowania i utrzymywania swojego kodu. Dla mnie początek zbyt dużego tarcia występuje, gdy chcę skonsolidować wiele prymitywnych wartości jako pojedynczą wartość lub gdy muszę zweryfikować wartości (tj. Określić, która ze wszystkich możliwych wartości typu pierwotnego odpowiada prawidłowym wartościom „typ domyślny”).

Przekonałem się, że takie kwestie, które postawiłeś w swoim pytaniu, są odpowiedzialne za zbyt dużo projektowania w moim kodzie. Mam nawyk celowego unikania pisania więcej kodu niż to konieczne. Mamy nadzieję, że piszesz dobre (zautomatyzowane) testy dla swojego kodu - jeśli tak, to możesz z łatwością refaktoryzować kod i dodawać typy lub klasy, jeśli zapewnia to korzyści netto dla ciągłego rozwoju i utrzymania kodu .

Zarówno odpowiedź Telastyna, jak i odpowiedź Thomasa Junk'a bardzo dobrze podkreślają kontekst i użycie odpowiedniego kodu. Jeśli używasz wartości w jednym bloku kodu (np. Metoda, pętla, usingblok), możesz po prostu użyć prostego typu. Można nawet używać prymitywnego typu, jeśli używasz zestawu wartości wielokrotnie i w wielu innych miejscach. Ale im częściej i szerzej używasz zestawu wartości, a im rzadziej ten zestaw wartości odpowiada wartościom reprezentowanym przez typ pierwotny, tym bardziej powinieneś rozważyć kapsułkowanie wartości w klasie lub typie.


5

Na powierzchni wygląda na to, że wszystko, co musisz zrobić, to zidentyfikować operację.

Dodatkowo mówisz, co ma zrobić operacja:

aby rozpocząć operację [i] w celu monitorowania jej zakończenia

Mówisz to w taki sposób, jakby to było „tak, jak używana jest identyfikacja”, ale powiedziałbym, że są to właściwości opisujące operację. To brzmi jak definicja typu, istnieje nawet wzorzec zwany „wzorcem poleceń”, który bardzo dobrze pasuje. .

To mówi

wzorzec polecenia [...] służy do enkapsulacji wszystkich informacji potrzebnych do wykonania akcji lub wyzwolenia zdarzenia w późniejszym czasie .

Myślę, że jest to bardzo podobne w porównaniu z tym, co chcesz zrobić ze swoimi operacjami. (Porównaj wyrażenia, które pogrubiłem w obu cudzysłowach) Zamiast zwracać ciąg, zwróć identyfikator Operationw sensie abstrakcyjnym, na przykład wskaźnik do obiektu tej klasy.

Odnośnie komentarza

Byłoby wtf-y, aby nazwać tę klasę poleceniem, a następnie nie mieć w niej logiki.

Nie, nie zrobiłby tego. Pamiętaj, że wzory są bardzo abstrakcyjne , tak abstrakcyjne, że są w pewnym sensie meta. Oznacza to, że często abstrahują one od samego programowania, a nie jakiejś prawdziwej koncepcji świata. Wzorzec poleceń jest abstrakcją wywołania funkcji. (lub metoda) Jakby ktoś wcisnął przycisk pauzy zaraz po przekazaniu wartości parametrów i tuż przed wykonaniem, aby wznowić później.

Poniżej rozważa się oop, ale motywacja za nim powinna być prawdziwa dla każdego paradygmatu. Chciałbym podać kilka powodów, dla których umieszczenie logiki w poleceniu można uznać za coś złego.

  1. Gdybyś miał całą tę logikę w poleceniu, byłby nadęty i bałagan. Pomyślałbyś „no cóż, cała ta funkcjonalność powinna być w osobnych klasach”. Byś byłaby logikę z polecenia.
  2. Jeśli masz całą tę logikę w poleceniu, trudno byłoby przetestować i przeszkodzić w testowaniu. „Cholera jasna, dlaczego muszę używać tego nonsensu polecenia? Chcę tylko przetestować, jeśli ta funkcja wyrzuci 1, czy nie, nie chcę jej później wywoływać, chcę ją teraz przetestować!”
  3. Jeśli masz całą tę logikę w poleceniu, nie ma sensu przesyłać raportów po zakończeniu. Jeśli myślisz o funkcji, która ma być domyślnie wykonywana synchronicznie, nie ma sensu być powiadamianym o zakończeniu wykonywania. Być może zaczynasz inny wątek, ponieważ twoja operacja polega na renderowaniu awatara do formatu kinowego (może potrzebować dodatkowego wątku na Raspberry pi), może musisz pobrać jakieś dane z serwera ... cokolwiek to jest, jeśli istnieje powód raportowanie po zakończeniu może być spowodowane pewną asynchronicznością (czy to słowo?). Myślę, że uruchomienie wątku lub skontaktowanie się z serwerem to logika, która nie powinna znajdować się w twoim poleceniu. Jest to nieco meta argument i może być kontrowersyjny. Jeśli uważasz, że to nie ma sensu, powiedz mi w komentarzach.

Podsumowując: wzorzec poleceń pozwala na zawinięcie funkcji w obiekt, który zostanie wykonany później. Ze względu na modułowość (funkcjonalność istnieje bez względu na to, czy jest wykonywana za pomocą komendy, czy nie), testowalność (funkcjonalność powinna być testowalna bez komendy) i wszystkie inne brzęczące słowa, które zasadniczo wyrażają potrzebę napisania dobrego kodu, nie wprowadzilibyśmy logiki do polecenia


Ponieważ wzory są abstrakcyjne, może być trudno wymyślić dobre metafory świata rzeczywistego. Oto próba:

„Hej, babciu, czy mógłbyś nacisnąć przycisk nagrywania na telewizorze o godzinie 12, żeby nie przegapić simpsonów na kanale 1?”

Moja babcia nie wie, co się dzieje technicznie, kiedy naciska ten przycisk nagrywania. Logika jest gdzie indziej (w telewizji). I to dobrze. Funkcjonalność jest enkapsulowana, a informacje ukryte przed poleceniem, to użytkownik API, niekoniecznie część logiki ... och jeez znów bełkoczę brzęczące słowa, lepiej dokończę teraz edycję.


Masz rację, nie myślałem o tym w ten sposób. Ale wzorzec polecenia nie jest w 100% dobrym dopasowaniem, ponieważ logika operacji jest zawarta w innej klasie i nie mogę umieścić go w obiekcie, którym jest polecenie. Byłoby wtf-y, aby nazwać tę klasę poleceniem, a następnie nie mieć w niej logiki.
Ziv

To ma sens. Rozważ zwrócenie operacji, a nie identyfikatora operacji. Jest to podobne do licencji C # TPL, w której zwracane jest zadanie lub zadanie <T>. @Ziv: Mówisz „logika operacji jest zawarta w innej klasie”. Ale czy to naprawdę oznacza, że ​​nie możesz również dostarczyć tego stąd?
Moby Disk

@Ziv Zaczynam się różnić. Spójrz na powody, które dodałem do mojej odpowiedzi i sprawdź, czy mają one dla ciebie jakiś sens. =)
zero

5

Pomysł zawijania prymitywnych typów,

  • Aby ustalić język specyficzny dla domeny
  • Ogranicz błędy popełniane przez użytkowników, przekazując nieprawidłowe wartości

Oczywiście byłoby to bardzo trudne i niepraktyczne, aby robić to wszędzie, ale ważne jest, aby zawijać typy tam, gdzie jest to wymagane,

Na przykład, jeśli masz klasę Order,

Order
{
   long OrderId { get; set; }
   string InvoiceNumber { get; set; }
   string Description { get; set; }
   double Amount { get; set; }
   string CurrencyCode { get; set; }
}

Ważnymi właściwościami do wyszukiwania Zamówienia są głównie OrderId i InvoiceNumber. Kwota i kod waluty są ze sobą ściśle powiązane, a jeśli ktoś zmieni kod waluty bez zmiany kwoty, zamówienie nie będzie już uważane za ważne.

Tak więc tylko zawijanie OrderId, InvoiceNumber i wprowadzenie kompozytu dla waluty ma sens w tym scenariuszu i prawdopodobnie zawijanie opisu nie ma sensu. Tak preferowany wynik może wyglądać,

    Order
    {
       OrderIdType OrderId { get; set; }
       InvoiceNumberType InvoiceNumber { get; set; }
       string Description { get; set; }
       Currency Amount { get; set; }
    }

Nie ma więc powodu, żeby wszystko owijać, ale rzeczy, które naprawdę mają znaczenie.


4

Niepopularna opinia:

Zwykle nie powinieneś definiować nowego typu!

Definiowanie nowego typu do zawijania klasy podstawowej lub podstawowej jest czasem nazywane definiowaniem pseudo-typu. IBM opisuje to jako złych praktyk tutaj (koncentrują się w szczególności na nadużycia ze strony generyków w tym przypadku).

Pseudo typy sprawiają, że wspólna funkcja biblioteki jest bezużyteczna.

Funkcje matematyczne Java mogą współpracować ze wszystkimi liczbami pierwotnymi. Ale jeśli zdefiniujesz nową klasę Procent (zawijanie podwójnej liczby, która może mieścić się w przedziale 0 ~ 1), wszystkie te funkcje są bezużyteczne i muszą zostać otoczone przez klasy, które (jeszcze gorzej) muszą wiedzieć o wewnętrznej reprezentacji klasy Procent .

Pseudo typy stają się wirusowe

Podczas tworzenia wielu bibliotek często zdarza się, że te pseudotypy stają się wirusowe. Jeśli użyjesz wyżej wspomnianej klasy procentowej w jednej bibliotece, albo będziesz musiał ją przekonwertować na granicy biblioteki (tracąc wszelkie bezpieczeństwo / znaczenie / logikę / inny powód, dla którego miałeś utworzyć ten typ), albo będziesz musiał stworzyć te klasy dostępne również w innej bibliotece. Zarażanie nowej biblioteki typami, w których wystarczyłoby zwykłe podwójne.

Zabierz wiadomość

Tak długo, jak typ, który zawijasz, nie wymaga dużej logiki biznesowej, odradzałbym pakowanie go w pseudo-klasę. Powinieneś zawinąć klasę tylko wtedy, gdy istnieją poważne ograniczenia biznesowe. W innych przypadkach prawidłowe nazewnictwo zmiennych powinno mieć duży wpływ na przekazywanie znaczenia.

Przykład:

A uintmoże idealnie reprezentować identyfikator użytkownika, możemy nadal używać wbudowanych operatorów Java dla uints (takich jak ==) i nie potrzebujemy logiki biznesowej do ochrony „wewnętrznego stanu” identyfikatora użytkownika.


Zgodzić się. cała filozofia programowania jest w porządku, ale w praktyce musisz też sprawić, by działała
Ewan

Jeśli widziałeś jakieś reklamy pożyczek dla najbiedniejszych ludzi w Wielkiej Brytanii, przekonasz się, że procent zawinięcia podwójnego w przedziale 0..1 nie działa ...
gnasher729

1
W C / C ++ / Objective C możesz użyć typedef, który daje ci tylko nową nazwę dla istniejącego typu pierwotnego. Z drugiej strony, twój kod powinien działać bez względu na typ bazowy, na przykład, jeśli zmienię OrderId z uint32_t na uint64_t (ponieważ muszę przetworzyć ponad 4 miliardy zamówień).
gnasher729

Nie będę cię głosować za odwagę, ale pseudotypy i typy zdefiniowane w odpowiedzi na pytania są różne. Są to typy specyficzne dla biznesu. Psuedotypy to typy, które są nadal ogólne.
0

@ gnasher729: C ++ 11 ma również literały zdefiniowane przez użytkownika, które sprawiają, że proces owijania jest bardzo słodki dla użytkownika: Odległość d = 36,0_mi + 42,0_km; . msdn.microsoft.com/en-us/library/dn919277.aspx
damix911

3

Podobnie jak w przypadku wszystkich wskazówek, umiejętność stosowania zasad jest umiejętnością. Jeśli tworzysz własne typy w języku opartym na typach, sprawdzasz typ. Ogólnie rzecz biorąc, będzie to dobry plan.

Konwencja Naming jest kluczem do czytelności. Oba połączone mogą jasno wyrażać intencje.
Ale /// jest nadal przydatne.

Tak, powiedziałbym, że stwórz wiele własnych typów, gdy ich życie wykracza poza granice klas. Weź również pod uwagę użycie obu Structi Classnie zawsze klasy.


2
Co ///znaczy
Gabe

1
/// jest komentarzem do dokumentacji w języku C #, który pozwala intellisense na pokazanie dokumentacji podpisu w punkcie metody wywołania. Zastanów się, jak najlepiej udokumentować kod. Może być uprawiany za pomocą narzędzi i zapewniać zachowanie w zakresie inteligencji. msdn.microsoft.com/en-us/library/tkxs89c5%28v=vs.71%29.aspx
phil soady
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.