Jestem wielkim fanem sprawdzania typu statycznego. Zapobiega popełnianiu takich głupich błędów:
// java code
Adult a = new Adult();
a.setAge("Roger"); //static type checker would complain
a.setName(42); //and here too
Ale to nie przeszkadza ci popełniać tak głupich błędów:
Adult a = new Adult();
// obviously you've mixed up these fields, but type checker won't complain
a.setAge(150); // nobody's ever lived this old
a.setWeight(42); // a 42lb adult would have serious health issues
Problem pojawia się, gdy używasz tego samego typu do reprezentowania oczywiście różnego rodzaju informacji. Myślałem, że dobrym rozwiązaniem byłoby rozszerzenie Integer
klasy, aby zapobiec błędom logiki biznesowej, ale nie dodawać funkcjonalności. Na przykład:
class Age extends Integer{};
class Pounds extends Integer{};
class Adult{
...
public void setAge(Age age){..}
public void setWeight(Pounds pounds){...}
}
Adult a = new Adult();
a.setAge(new Age(42));
a.setWeight(new Pounds(150));
Czy jest to uważane za dobrą praktykę? A może występują nieprzewidziane problemy techniczne związane z tak restrykcyjnym projektem?
a.SetAge( new Age(150) )
Nadal nie będziesz się kompilować?new Age(...)
obiektu nie można niepoprawnie przypisać go do zmiennej typuWeight
w żadnym innym miejscu. Zmniejsza liczbę miejsc, w których mogą wystąpić błędy.Odpowiedzi:
Zasadniczo pytasz o system jednostek (nie, nie testy jednostkowe, „jednostka” jak w „jednostce fizycznej”, takie jak mierniki, wolty itp.).
W twoim kodzie
Age
reprezentuje czas iPounds
reprezentuje masę. Prowadzi to do takich rzeczy, jak konwersja jednostek, jednostki podstawowe, precyzja itp.Były / są próby wprowadzenia czegoś takiego do Javy, na przykład:
Wydaje się, że dwie ostatnie żyją w tym githubie: https://github.com/unitsofmeasurement
C ++ ma jednostki poprzez Boost
LabView jest dostarczany z wieloma jednostkami .
Istnieją inne przykłady w innych językach. (zmiany są mile widziane)
Jak widać powyżej, im bardziej prawdopodobne jest, że język obsługuje wartości z jednostkami, tym bardziej natywnie obsługuje jednostki. LabView jest często używany do interakcji z urządzeniami pomiarowymi. W związku z tym sensowne jest posiadanie takiej funkcji w języku i używanie jej z pewnością byłoby uważane za dobrą praktykę.
Ale w każdym języku wysokiego poziomu ogólnego przeznaczenia, gdzie popyt na tak rygor jest niski, prawdopodobnie jest to nieoczekiwane.
Moje przypuszczenie byłoby: wydajność / pamięci. Jeśli masz do czynienia z wieloma wartościami, narzut obiektu na wartość może stać się problemem. Ale jak zawsze: przedwczesna optymalizacja jest źródłem wszelkiego zła .
Myślę, że większym „problemem” jest przyzwyczajanie się do ludzi, ponieważ jednostka jest zwykle domyślnie zdefiniowana w następujący sposób:
Ludzie będą zdezorientowani, gdy będą musieli przekazać obiekt jako wartość czegoś, co z pozoru można opisać prostym
int
, gdy nie będą zaznajomieni z systemami jednostek.źródło
int
...” - dla którego mamy tutaj przewodnika najmniejszej niespodzianki. Dobry chwytW przeciwieństwie do odpowiedzi zerowej, zdefiniowanie typu dla „jednostki” może być korzystne, jeśli liczba całkowita nie wystarcza do opisania pomiaru. Na przykład masa jest często mierzona w wielu jednostkach w tym samym systemie pomiarowym. Pomyśl „funty” i „uncje” lub „kilogramy” i „gramy”.
Jeśli potrzebujesz bardziej szczegółowego poziomu pomiaru, zdefiniowanie rodzaju jednostki jest korzystne:
W przypadku „wieku” zalecam obliczanie tego w czasie wykonywania na podstawie daty urodzenia osoby:
źródło
To, czego szukasz, jest znane jako typy oznaczone . Są sposobem na powiedzenie „jest to liczba całkowita reprezentująca wiek”, podczas gdy „jest to także liczba całkowita, ale reprezentuje wagę” i „nie można przypisać jednej do drugiej”. Zauważ, że wykracza to poza jednostki fizyczne, takie jak metry lub kilogramy: w moim programie mogę mieć „wysokości ludzi” i „odległości między punktami na mapie”, obie mierzone w metrach, ale nie ze sobą kompatybilne, ponieważ przypisałem jedną do drugi nie ma sensu z punktu widzenia logiki biznesowej.
Niektóre języki, takie jak Scala, dość łatwo obsługują oznaczone typy (patrz link powyżej). W innych możesz tworzyć własne klasy opakowań, ale jest to mniej wygodne.
Walidacja, np. Sprawdzenie, czy wzrost osoby jest „rozsądny”, to inna kwestia. Możesz wstawić taki kod do swojej
Adult
klasy (konstruktora lub seterów) lub wewnątrz otagowanych klas typu / otoki. W pewien sposób wbudowane klasy, takie jakURL
lubUUID
spełniające taką rolę (między innymi, np. Zapewniając metody użytkowe).To, czy użycie oznaczonych typów lub klas opakowań rzeczywiście pomoże ulepszyć kod, będzie zależeć od kilku czynników. Jeśli twoje obiekty są proste i mają niewiele pól, ryzyko niewłaściwego przypisania ich jest niskie, a dodatkowy kod potrzebny do użycia oznaczonych typów może nie być wart wysiłku. W złożonych systemach ze złożonymi strukturami i dużą ilością pól (zwłaszcza jeśli wiele z nich ma ten sam prymitywny typ), może to być naprawdę pomocne.
W kodzie, który piszę, często tworzę klasy otoki, jeśli przekazuję mapy. Typy
Map<String, String>
same w sobie są bardzo nieprzejrzyste, więc zawijanie ich w klasy znaczącymi nazwami, na przykład, bardzoNameToAddress
pomaga. Oczywiście przy typach oznaczonych możesz pisaćMap<Name, Address>
i nie potrzebujesz opakowania dla całej mapy.Jednak w przypadku prostych typów, takich jak Strings lub Integers, stwierdziłem, że klasy opakowań (w Javie) są zbyt uciążliwe. Zwykła logika biznesowa nie była taka zła, ale pojawiło się wiele problemów z serializacją tych typów do JSON, mapowaniem ich do obiektów DB itp. Możesz pisać mapery i hooki dla wszystkich dużych frameworków (np. Jackson i Spring Data), ale dodatkowa praca i konserwacja związana z tym kodem zrekompensują wszelkie korzyści wynikające z używania tych opakowań. Oczywiście w YMMV iw innym systemie bilans może być inny.
źródło