Ostatnio czytałem wiele artykułów opisujących prymitywną obsesję jako zapach kodu.
Są dwie zalety unikania prymitywnej obsesji:
Sprawia, że model domeny jest bardziej wyraźny. Na przykład mogę porozmawiać z analitykiem biznesowym na temat kodu pocztowego zamiast ciągu zawierającego kod pocztowy.
Cała walidacja odbywa się w jednym miejscu zamiast w całej aplikacji.
Istnieje wiele artykułów opisujących zapach zapachowy. Na przykład widzę zaletę usuwania prymitywnej obsesji na punkcie takiego kodu pocztowego:
public class Address
{
public ZipCode ZipCode { get; set; }
}
Oto konstruktor ZipCode:
public ZipCode(string value)
{
// Perform regex matching to verify XXXXX or XXXXX-XXXX format
_value = value;
}
Można byłoby złamanie DRY zasadę oddanie tej logiki walidacji wszędzie jest używany kod pocztowy.
A co z następującymi obiektami:
Data urodzenia: sprawdź, czy to więcej niż umysł i mniej niż dzisiejsza data.
Wynagrodzenie: sprawdź, czy jest większa lub równa zero.
Czy stworzyłbyś obiekt DateOfBirth i obiekt wynagrodzenia? Zaletą jest to, że możesz o nich mówić, opisując model domeny. Jest to jednak przypadek nadinżynierii, ponieważ nie ma zbyt wiele uzasadnień. Czy istnieje reguła, która opisuje, kiedy i kiedy nie należy usuwać prymitywnej obsesji, czy zawsze należy to robić, jeśli to możliwe?
Myślę, że mógłbym utworzyć alias typu zamiast klasy, co pomogłoby w punkcie 1 powyżej.
DateOfBirth
, aby sprawdził w porównaniu z?Salary
iDistance
sprzeciwisz się, nie możesz przypadkowo użyć ich zamiennie. Możesz, jeśli oba są typudouble
.Odpowiedzi:
Przeciwieństwem byłoby „modelowanie domen”, a może „ponad inżynieria”.
Wprowadzenie obiektu wynagrodzenia może być dobrym pomysłem z następującego powodu: liczby rzadko występują samodzielnie w modelu domeny, prawie zawsze mają wymiar i jednostkę. Zwykle nie modelujemy niczego przydatnego, jeśli dodamy długość do czasu lub masy i rzadko osiągamy dobre wyniki, gdy mierzymy metry i stopy.
Co do DateOfBirth, prawdopodobnie - należy rozważyć dwie kwestie. Po pierwsze, utworzenie nieprymitywnej daty daje miejsce, w którym można skoncentrować wszystkie dziwne obawy związane z matematyką randkową. Wiele języków zapewnia jeden gotowy; DateTime , java.util.Date . Są to domeny agnostyk implementacje datami, ale są one nie prymitywy.
Po drugie,
DateOfBirth
nie jest tak naprawdę datą; tutaj, w Stanach Zjednoczonych, „data urodzenia” jest konstrukcją kulturową / fikcją prawną. Zwykle mierzymy datę urodzenia od lokalnej daty urodzenia osoby; Bob, urodzony w Kalifornii, może mieć „wcześniejszą” datę urodzenia niż Alice, urodzona w Nowym Jorku, mimo że jest młodszy z nich.Z pewnością nie zawsze; na granicach aplikacje nie są zorientowane obiektowo . Dość często spotyka się prymitywy używane do opisywania zachowań w testach .
źródło
java.util.Date
zjava.time.LocalDate
Szczerze mówiąc: to zależy.
Zawsze istnieje ryzyko nadmiernej inżynierii kodu. Jak szeroko rozpowszechniony będzie DateOfBirth i Wynagrodzenie? Czy użyjesz ich tylko w trzech ściśle powiązanych klasach, czy będą one używane w całej aplikacji? Czy „po prostu” zamkniesz je we własnym typie / klasie, aby wymusić to jedno ograniczenie, czy może wymyślisz więcej ograniczeń / funkcji, które faktycznie tam są?
Weźmy na przykład Wynagrodzenie: Czy masz jakieś operacje z „Wynagrodzeniem” (np. Obsługa różnych walut, a może funkcja toString ())? Zastanów się, czym jest / robi wynagrodzenie, jeśli nie postrzegasz go jako zwykłego prymitywu, i istnieje duża szansa, że wynagrodzenie będzie jego własną klasą.
źródło
Możliwa zasada może zależeć od warstwy programu. W przypadku domeny (DDD), czyli Entities Layer (Martin, 2018), może to również oznaczać „unikanie prymitywów dla wszystkiego, co reprezentuje koncepcję domeny / biznesu”. Uzasadnienia są takie, jak stwierdzono w PO: bardziej ekspresyjny model domeny, walidacja reguł biznesowych, wyraźne wyrażanie domyślnych pojęć (Evans, 2004).
Alias typu może być lekką alternatywą (Ghosh, 2017) i w razie potrzeby przekształcony w klasę encji. Na przykład, możemy najpierw wymagać
Salary
BE>=0
, a później decydują się na nie pozwolić$100.33333
, a wszystko powyżej$10,000,000
(co bankructwa klienta). UżycieNonnegative
prymitywnego przedstawieniaSalary
i innych pojęć skomplikowałoby to refaktoryzację.Unikanie prymitywów może również pomóc uniknąć nadmiernej inżynierii. Załóżmy, że musimy połączyć wynagrodzenie i datę urodzenia w strukturę danych: np. Aby mieć mniej parametrów metody lub przekazywać dane między modułami. Następnie możemy użyć krotki z typem
(Salary, DateOfBirth)
. Rzeczywiście, krotka z prymitywami,(Nonnegative, Nonnegative)
jest mało pouczająca, podczas gdy niektóre rozdęteclass EmployeeData
ukryłyby między innymi wymagane pola. Podpis w słowiecalcPension(d: (Salary, DateOfBirth))
jest bardziej skoncentrowany niż wcalcPension(d: EmployeeData)
, co narusza zasadę segregacji interfejsu. Podobnie wyspecjalizowanieclass SalaryAndDateOfBirth
wydaje się niezręczne i prawdopodobnie jest przesadą. Później możemy zdefiniować klasę danych; krotki i typy domen elementarnych pozwalają nam odroczyć takie decyzje.W warstwie zewnętrznej (np. GUI) sensowne może być „rozebranie” bytów do ich składowych prymitywów (np. Umieszczenie w DAO). Zapobiega to wyciekaniu abstrakcji domen do warstw zewnętrznych, jak zaleca Martin (2018).
Literatura
E. Evans, „Domain-Driven Design”, 2004
D. Ghosh, „Funkcjonalne i reaktywne modelowanie domen”, 2017
RC Martin, „Czysta architektura”, 2018
źródło
Lepiej cierpisz z powodu pierwotnej obsesji lub bycia astronautą architektury ?
Oba przypadki są patologiczne, w jednym przypadku masz zbyt mało abstrakcji, co prowadzi do powtórzeń i łatwo mylnie jabłko z pomarańczą, w drugim zapomniałeś już o tym przestać i zaczynasz robić rzeczy, co utrudnia wykonanie czegokolwiek .
Jak prawie zawsze chcesz umiaru, który jest dobrze przemyślanym środkiem.
Pamiętaj, że właściwość ma oprócz nazwy także nazwę. Również rozkładanie adresu na części składowe może być zbyt restrykcyjne, jeśli zawsze odbywa się w ten sam sposób. Nie cały świat jest w centrum Nowego Jorku.
źródło
Jeśli masz klasę wynagrodzeń, może ona mieć metody takie jak ApplyRaise.
Z drugiej strony klasa ZipCode nie musi mieć wewnętrznej walidacji, aby uniknąć powielania walidacji wszędzie tam, gdzie można mieć klasę ZipCodeValidator, którą można wstrzyknąć, więc jeśli twój system ma działać zarówno na adresach w USA, jak i w Wielkiej Brytanii, możesz po prostu wstrzyknąć poprawny walidator, a kiedy musisz również obsługiwać adresy AUS, możesz po prostu dodać nowy walidator.
Innym problemem jest to, że jeśli musisz zapisywać dane do bazy danych za pomocą EntityFramework, będzie musiał wiedzieć, jak obsługiwać wynagrodzenie lub kod pocztowy.
Nie ma jednoznacznej odpowiedzi na pytanie, gdzie należy wyznaczyć granicę między tym, jak inteligentne powinny być klasy, ale powiem, że mam tendencję do przenoszenia logiki biznesowej, takiej jak sprawdzanie poprawności, do klas logiki biznesowej, w których klasy danych są czystymi danymi, jak się wydaje pracować lepiej z EntityFramework.
Jeśli chodzi o użycie aliasów typów, nazwa członka / właściwości powinna zawierać wszystkie potrzebne informacje na temat zawartości, więc nie użyłbym aliasów typu.
źródło
(Jakie jest prawdopodobnie pytanie)
Kiedy użycie typu pierwotnego nie jest zapachem kodu?
(Odpowiedź)
Jeśli parametr nie zawiera reguł - użyj typu pierwotnego.
Użyj prymitywnego typu dla takich jak:
Użyj obiektu do:
Ten ostatni przykład mieć w nim zasady, czyli obiekt
SimpleDate
składa się zYear
,Month
, iDay
. Dzięki zastosowaniu Object w tym przypadku pojęcieSimpleDate
ważności może być zawarte w obiekcie.źródło
Oprócz kanonicznych przykładów adresów e-mail lub kodów pocztowych podanych gdzie indziej w tym pytaniu, w przypadku gdy uważam, że refaktoryzacja z dala od Primitive Obsession może być szczególnie pomocna, to identyfikatory jednostek (patrz https://andrewlock.net/using-strongly-typed-entity -ids-to-unikać-prymitywnej-obsesji-część-1 / na przykład, jak to zrobić w .NET).
Nie pamiętam, ile razy błąd wkradł się, ponieważ metoda miała taką sygnaturę:
Kompiluje się dobrze, a jeśli nie jesteś rygorystyczny w testowaniu jednostek, może to również przejść pomyślnie. Jednak przekształć te identyfikatory jednostek w klasy właściwe dla domeny i hej, błędy w czasie kompilacji:
Wyobraź sobie, że ta metoda przeskalowała do 10 lub więcej parametrów, wszystkich
int
typów danych (nie wspominając o zapachu kodu długiej listy parametrów ). Sytuacja staje się jeszcze gorsza, gdy używasz czegoś takiego jak AutoMapper do przełączania między obiektami domeny i DTO, i robisz refaktoryzację nie zostaje wykryty przez automatyczne mapowanie.źródło
Z drugiej strony, w kontaktach z wieloma różnymi krajami i ich różnymi systemami kodów pocztowych, oznacza to, że nie można zweryfikować kodu pocztowego, chyba że znasz dany kraj. Twoja
ZipCode
klasa musi także przechowywać kraj.Ale czy następnie oddzielnie przechowujesz kraj jako część
Address
(którego kod pocztowy jest również częścią) i część kodu pocztowego (do weryfikacji)?ZipCode
klasa, aleAddress
klasa, która znowu będzie zawierałastring ZipCode
co oznacza, że zatoczyliśmy pełne koło.Nie rozumiem twojego podstawowego twierdzenia, że gdy część informacji ma dany typ zmiennej, to w jakiś sposób masz obowiązek wspomnieć o tym typie, gdy rozmawiasz z analitykiem biznesowym.
Czemu? Dlaczego nie możesz po prostu mówić o „kodzie pocztowym” i całkowicie pomijać określony typ? Jakiego rodzaju rozmowy prowadzisz ze swoim analitykiem biznesowym (nie technicznym!), Gdzie typ nieruchomości jest kwintesencją rozmowy?
Skąd pochodzę, kody pocztowe są zawsze numeryczne. Mamy więc wybór, możemy przechowywać go jako
int
lub jakostring
. Zwykle używamy łańcucha, ponieważ nie oczekujemy operacji matematycznych na danych, ale nigdy analityk biznesowy nie powiedział mi, że musi to być łańcuch. Decyzję tę podejmuje deweloper (lub zapewne analityk techniczny, choć z mojego doświadczenia wynika, że nie zajmują się bezpośrednio drobiazgami).Analityk biznesowy nie dba o typ danych, o ile aplikacja wykonuje to, czego oczekuje.
Walidacja jest trudną bestią do rozwiązania, ponieważ zależy od tego, czego oczekują ludzie.
Po pierwsze, nie zgadzam się z argumentem walidacyjnym jako sposobem pokazania, dlaczego należy unikać prymitywnej obsesji, ponieważ nie zgadzam się z tym, że (jako uniwersalna prawda) dane muszą być zawsze weryfikowane przez cały czas.
Na przykład, co jeśli jest to bardziej skomplikowane wyszukiwanie? Zamiast zwykłego sprawdzenia formatu, co jeśli weryfikacja wymaga skontaktowania się z zewnętrznym interfejsem API i oczekiwania na odpowiedź? Czy naprawdę chcesz zmusić aplikację do wywołania tego zewnętrznego interfejsu API dla każdego
ZipCode
obiektu, który tworzysz?Może jest to ścisły wymóg biznesowy, a następnie oczywiście uzasadniony. Ale to nie jest uniwersalna prawda. Będzie wiele przypadków użycia, w których będzie to bardziej obciążenie niż rozwiązanie.
Jako drugi przykład podczas wpisywania adresu w formularzu często podajesz swój kod pocztowy przed krajem. Chociaż miło jest mieć natychmiastowe informacje zwrotne dotyczące sprawdzania poprawności w interfejsie użytkownika, w rzeczywistości byłoby przeszkodą dla mnie (jako użytkownika), jeśli aplikacja powiadomiłaby mnie o „złym” formacie kodu pocztowego, ponieważ prawdziwym źródłem problemu jest (np.) To, że mój kraj nie jest krajem wybranym domyślnie, dlatego weryfikacja nastąpiła dla niewłaściwego kraju.
To zły komunikat o błędzie, który rozprasza użytkownika i powoduje niepotrzebne zamieszanie.
Podobnie jak wieczna walidacja nie jest uniwersalną prawdą, podobnie jak moje przykłady. To jest kontekstowe . Niektóre domeny aplikacji wymagają przede wszystkim weryfikacji danych. Inne domeny nie umieszczają walidacji tak wysoko na liście priorytetów, ponieważ problemy, które niesie ze sobą, są sprzeczne z ich rzeczywistymi priorytetami (np. Wrażenia użytkownika lub możliwość początkowego przechowywania wadliwych danych, aby można je było poprawić zamiast nigdy nie pozwalać na to przechowywane)
Problem z tymi walidacjami polega na tym, że są one niekompletne, zbędne lub wskazują na znacznie większy problem .
Sprawdzanie, czy data jest większa niż umysł, jest zbędne. Umysł dosłownie oznacza, że jest to najmniejsza możliwa data. Poza tym, gdzie narysujesz linię istotności? Po co zapobiegać,
DateTime.MinDate
ale pozwalaćDateTime.MinDate.AddSeconds(1)
? Wybierasz konkretną wartość, która nie jest szczególnie zła w porównaniu do wielu innych wartości.Moje urodziny są 2 stycznia 1978 r. (Nie, ale załóżmy, że tak). Powiedzmy jednak, że dane w Twojej aplikacji są nieprawidłowe, a zamiast tego napisano, że moje urodziny to:
Wszystkie te daty są nieprawidłowe. Żaden z nich nie jest „bardziej odpowiedni” niż drugi. Ale twoja reguła sprawdzania poprawności złapie tylko jeden z tych trzech przykładów.
Całkowicie pominąłeś także kontekst korzystania z tych danych. Jeśli jest to używane np. W bocie przypominającym o urodzinach, powiedziałbym, że walidacja nie ma sensu, ponieważ nie ma szczególnych złych konsekwencji dla podania niewłaściwej daty.
Z drugiej strony, jeśli są to dane rządowe i potrzebujesz daty urodzenia, aby uwierzytelnić czyjąś tożsamość (a ich niepodanie prowadzi do złych konsekwencji, np. Odmowy zabezpieczenia społecznego), to poprawność danych jest najważniejsza i musisz w pełni sprawdź poprawność danych. Proponowana walidacja, którą masz teraz, jest nieodpowiednia.
W przypadku wynagrodzenia istnieje pewien zdrowy rozsądek, ponieważ nie może być ujemny. Ale jeśli realistycznie oczekujesz, że wprowadzane są bezsensowne dane, sugerowałbym zbadanie źródła tych bezsensownych danych. Ponieważ jeśli nie można im ufać, że wprowadzają sensowne dane, nie można im również ufać, że wprowadzą prawidłowe dane.
Jeśli zamiast tego wynagrodzenie jest obliczane przez aplikację i w jakiś sposób możliwe jest uzyskanie liczby ujemnej (i poprawnej), wówczas lepszym rozwiązaniem byłoby
Math.Max(myValue, 0)
przekształcenie liczb ujemnych w 0, zamiast nieudanej weryfikacji. Ponieważ jeśli twoja logika zdecydowała, że wynik jest liczbą ujemną, nieudana walidacja oznacza, że będzie musiała powtórzyć obliczenia, i nie ma powodu sądzić, że za drugim razem pojawi się inna liczba.A jeśli pojawi się inna liczba, ponownie podejrzewasz, że obliczenia nie są spójne i dlatego nie można im ufać.
Nie oznacza to, że sprawdzanie poprawności nie jest przydatne. Ale bezcelowa walidacja jest zła, zarówno dlatego, że wymaga wysiłku, ale tak naprawdę nie rozwiązuje problemu, i daje ludziom fałszywe poczucie bezpieczeństwa.
źródło