Czy posiadanie klas „Util” jest powodem do niepokoju? [Zamknięte]

14

Czasami tworzę klasy „Util”, które służą przede wszystkim do przechowywania metod i wartości, które tak naprawdę nie wydają się należeć gdzie indziej. Ale za każdym razem, gdy tworzę jedną z tych klas, myślę „uh, och, później będę tego żałować ...”, ponieważ czytam gdzieś, że jest źle.

Ale z drugiej strony wydaje się, że istnieją dla nich dwa przekonujące (przynajmniej dla mnie) przypadki:

  1. tajniki implementacji używane w wielu klasach w pakiecie
  2. zapewniając użyteczną funkcjonalność w celu zwiększenia klasy, bez zaśmiecania jej interfejsu

Czy jestem w drodze do zniszczenia? Co mówisz !! Czy powinienem refaktoryzować?


źródło
11
Przestań używać Utilw nazwach swoich klas. Problem rozwiązany.
yannis,
3
@MattFenwick Yannis ma rację. Nazywanie klasy SomethingUtiljest nieco leniwe i po prostu przesłania jej prawdziwy cel - podobnie jak w przypadku klas o nazwach SomethingManagerlub SomethingService. Jeśli ta klasa ma jedną odpowiedzialność, powinno być łatwo nadać jej sensowną nazwę. Jeśli nie, to jest to prawdziwy problem do rozwiązania ...
MattDavey,
1
@MDavey dobry punkt, prawdopodobnie był to zły wybór Util, ale oczywiście nie spodziewałem się, że to zostanie naprawione, a reszta pytania zostanie zignorowana ...
1
Jeśli utworzysz wiele klas * Util oddzielonych przez rodzaj obiektów, którymi manipulują, to myślę, że jest w porządku. Nie widzę żadnego problemu z StringUtil, ListUtil itp. O ile nie jesteś w C #, powinieneś użyć metod rozszerzenia.
marco-fiset
4
Często mam zestawy funkcji, które są używane do powiązanych celów. Nie bardzo dobrze odwzorowują klasę. Nie potrzebuję klasy ImageResizer, potrzebuję tylko funkcji ResizeImage. Więc umieszczę powiązane funkcje w klasie statycznej, takiej jak ImageTools. W przypadku funkcji, które nie są jeszcze w jakimkolwiek zgrupowaniu, mam klasę statyczną Util, która je utrzymuje, ale gdy tylko kilka z nich będzie wystarczająco powiązanych ze sobą, aby zmieściły się w jednej klasie, przeniosę je. Nie widzę lepszego sposobu na to, by sobie z tym poradzić.
Filip

Odpowiedzi:

26

Nowoczesne wzornictwo OO akceptuje, że nie wszystko jest przedmiotem. Niektóre rzeczy są zachowaniami lub formułami, a niektóre nie mają stanu. Dobrze jest modelować te rzeczy jako czyste funkcje, aby uzyskać korzyści z tego projektu.

Java i C # (i inne) wymagają stworzenia klasy util i przeskoczenia przez ten obręcz, aby to zrobić. Irytujące, ale nie koniec świata; i niezbyt kłopotliwe z punktu widzenia projektowania.

Telastyn
źródło
Tak, tak myślałem. Dzięki za odpowiedzi!
Zgadzam się, chociaż należy wspomnieć, że .NET oferuje obecnie metody rozszerzeń i klasy częściowe jako alternatywę dla tego podejścia. Jeśli doprowadzisz ten przedmiot do skrajności, otrzymasz mixy Ruby - język, który nie potrzebuje tak bardzo klas użytkowych.
neontapir
2
@neontapir - Z wyjątkiem implementacji metod rozszerzenia w zasadzie zmusza cię do stworzenia klasy util z metodami rozszerzenia ...
Telastyn
Prawdziwe. Istnieją dwa miejsca, w których przeszkadzają klasy Util, jedno znajduje się w samej klasie, a drugie jest stroną wywoławczą. Dzięki temu, że metody wyglądają, jakby istniały na ulepszonym obiekcie, metody rozszerzenia rozwiązują problem z witryną wywoławczą. Zgadzam się, wciąż istnieje kwestia istnienia klasy Util.
neontapir,
16

Nigdy nie mów nigdy"

Nie sądzę, że to musi być złe, tylko źle, jeśli zrobisz to źle i nadużyjesz.

Wszyscy potrzebujemy narzędzi i narzędzi

Na początek wszyscy używamy bibliotek, które czasami są uważane za prawie wszechobecne i niezbędne. Na przykład w świecie Java, Google Guava lub niektórych Apache Commons ( Apache Commons Lang , Apache Commons Collections itp.).

Tak więc wyraźnie jest taka potrzeba.

Unikaj twardych słów, powielania i wprowadzania błędów

Jeśli myślisz o nich dość dużo po prostu bardzo duże grono tych Utilklas można opisać, chyba ktoś przeszedł wiele trudu, aby je (stosunkowo) prawo, a oni byli razem - przetestowany i mocno przyciągające zaciskał przez innych.

Powiedziałbym więc, że pierwszą zasadą przy odczuciu swędzenia pisania Utilzajęć jest sprawdzenie, czy Utilklasa faktycznie nie istnieje.

Jedynym kontrargumentem, jaki widziałem, jest to, że chcesz ograniczyć swoje zależności, ponieważ:

  • chcesz ograniczyć ślad pamięci swoich zależności,
  • lub chcesz ściśle kontrolować, z czego mogą korzystać programiści (dzieje się to w obsesyjnych dużych zespołach lub gdy znany jest konkretny framework posiadający dziwną super-gównianą klasę, której absolutnie gdzieś trzeba unikać).

Ale z obydwoma tymi problemami można zająć się ponownie pakując bibliotekę przy użyciu ProGuard lub jej odpowiednika, lub osobiście ją rozłączając (dla użytkowników Maven wtyczka maven-shadow-plug oferuje pewne wzorce filtrowania, aby zintegrować to jako część twojej kompilacji).

Więc jeśli jest w wersji lib i pasuje do twojego przypadku użycia, a żadne testy porównawcze nie wskazują inaczej, użyj go. Jeśli różni się on nieco od tego, co przedłużysz, przedłuż go (jeśli to możliwe) lub przedłuż, albo w ostateczności przepisz go.

Konwencje nazewnictwa

Jednak do tej pory w tej odpowiedzi nazwałem ich Utilpodobnymi do ciebie. Nie nazywaj ich tak.

Nadaj im sensowne nazwy. Weź Google Guava jako (bardzo, bardzo) dobry przykład tego, co należy zrobić, i wyobraź sobie, że com.google.guavaprzestrzeń nazw jest tak naprawdę Twoim utilkatalogiem głównym.

Zadzwoń do swojego pakietu util, w najgorszym wypadku, ale nie do klas. Jeśli dotyczy Stringobiektów i manipulacji konstruktów smyczkowych, nazwać Strings, a nie StringUtils(przepraszam, Apache Commons Lang - nadal lubię cię i używać!). Jeśli robi coś konkretnego, wybierz nazwę konkretnej klasy (jak Splitterlub Joiner).

Test jednostkowy

Jeśli musisz użyć tych narzędzi, przetestuj je. Zaletą narzędzi jest to, że zwykle są to raczej samodzielne komponenty, które pobierają określone dane wejściowe i zwracają określone dane wyjściowe. To jest koncepcja. Nie ma więc wymówki, aby ich nie testować jednostkowo.

Ponadto testy jednostkowe pozwolą ci zdefiniować i udokumentować umowę API. Jeśli testy zakończą się, albo zmieniłeś coś w niewłaściwy sposób, albo oznacza to, że próbujesz zmienić umowę API (lub że twoje oryginalne testy były badziewne - ucz się z tego i nie rób tego ponownie) .

Projektowanie interfejsu API

Decyzje projektowe, które podejmiesz dla tych interfejsów API, prawdopodobnie podążą za tobą długo. Tak więc, nie poświęcając godzin na pisanie Splitter-klonu, uważaj na swoje podejście do problemu.

Zadaj sobie kilka pytań:

  • Czy twoja metoda użyteczności gwarantuje samą klasę, czy też metoda statyczna jest wystarczająco dobra, jeśli ma sens, aby była częścią grupy podobnie użytecznych metod?
  • Czy potrzebujesz fabrycznych metod do tworzenia obiektów i zwiększenia czytelności interfejsów API?
  • Mówiąc o czytelności, potrzebujesz płynnego interfejsu API , konstruktorów itp.?

Chcesz, aby te narzędzia obejmowały szeroki wachlarz przypadków użycia, były solidne, stabilne, dobrze udokumentowane, zgodnie z zasadą najmniejszego zaskoczenia i były niezależne. Idealnie byłoby, gdyby każdy podpakiet twoich narzędzi, lub przynajmniej cały pakiet użytkowy, mógł zostać wyeksportowany do pakietu w celu łatwego ponownego użycia.

Jak zwykle ucz się od gigantów tutaj:

Tak, wiele z nich kładzie nacisk na kolekcje i struktury danych, ale nie mów mi, że nie jest to miejsce lub po co zwykle możesz wdrożyć większość swoich narzędzi, bezpośrednio lub pośrednio.

Haylem
źródło
+1 zaIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Tulains Córdova
+1 Aby zaprojektować API w .Net, przeczytaj książkę architektów .Net. Wytyczne dotyczące projektowania ram: konwencje, idiomy i wzory dla bibliotek .NET wielokrotnego użytku
MarkJ
Dlaczego nazwałbyś to ciągami zamiast StringUtil (s)? Ciągi dla mnie brzmią jak obiekt kolekcji.
bdrx
@bdrx: dla mnie obiekt kolekcji byłby albo, StringsCollectionTypeHerejeśli chcesz konkretnej implementacji. Lub bardziej konkretna nazwa, jeśli ciągi te mają określone znaczenie w kontekście Twojej aplikacji. W tym konkretnym przypadku Guava używa Stringsswoich pomocników związanych z łańcuchami, w przeciwieństwie do StringUtilsCommons Lang. Uważam to za całkowicie akceptowalne, oznacza to dla mnie, że klasy obsługują Stringobiekt lub są klasą ogólnego przeznaczenia do zarządzania ciągami.
haylem
@bdrx: Ogólnie NameUtilsrzecz biorąc , błąd mnie bez końca, ponieważ jeśli znajduje się pod wyraźnie oznaczoną nazwą pakietu, wiedziałbym już, że jest to klasa narzędziowa (a jeśli nie, szybko to zrozumiałbym po spojrzeniu na API). To tak irytujące dla mnie jako osoby deklarując rzeczy jak SomethingInterface, ISomethinglub SomethingImpl. Nieźle sobie radziłem z takimi artefaktami, gdy kodowałem w C i nie używałem IDE. Dzisiaj generalnie nie potrzebuję takich rzeczy.
haylem
8

Klasy Util nie są spójne i generalnie mają zły projekt, ponieważ klasa musi mieć jeden powód do zmiany (zasada pojedynczej odpowiedzialności).

Jednak widziałem „util” klas w samym Java API, takich jak: Math, Collectionsi Arrays.

Są to w rzeczywistości klasy użytkowe, ale wszystkie ich metody są powiązane z jednym tematem, jedna ma operacje matematyczne, jedna ma metody manipulowania kolekcjami, a druga do manipulowania tablicami.

Staraj się nie mieć całkowicie niepowiązanych metod w klasie użyteczności. W takim przypadku istnieje szansa, że ​​równie dobrze możesz umieścić je tam, gdzie naprawdę są.

Jeśli musi mieć util klas następnie spróbuj im być oddzielone tematu jak Java Math, Collectionsi Arrays. Przynajmniej pokazuje pewien zamiar projektowania, nawet jeśli są to tylko przestrzenie nazw.

I dla jednego, zawsze unikać klas użytkowych i nigdy nie miałem potrzeby , aby utworzyć.

Tulains Córdova
źródło
Dzięki za odpowiedzi. Tak więc, nawet jeśli klasy util są prywatne dla pakietu, to nadal mają zapach projektowy?
nie wszystko jednak należy do klasy. jeśli utkniesz w języku, który upiera się, że tak, to będą odbywały się zajęcia.
jk.
1
Cóż, klasy użyteczności nie mają zasady projektowania, która poinformowałaby cię, ile metod jest za dużo, ponieważ na początku nie ma spójności. Skąd możesz wiedzieć, jak się zatrzymać? Po 30 metodach? 40? 100? Z drugiej strony zasady OOP dają wyobrażenie, kiedy metoda nie należy do konkretnej klasy, a kiedy klasa robi zbyt wiele. To się nazywa spójność. Ale, jak powiedziałem w mojej odpowiedzi, sami ludzie, którzy zaprojektowali Javę, korzystali z klas użytkowych. Ty też nie możesz tego zrobić. Nie tworzę klas użytkowych i nie byłem w sytuacji, gdy coś nie należy do normalnej klasy.
Tulains Córdova
1
Klasy Utils zwykle nie są klasami, są to po prostu przestrzenie nazw zawierające kilka, zwykle czystych funkcji. Czyste funkcje są tak spójne, jak tylko możesz.
jk.
1
@jk Miałem na myśli Utils ... Nawiasem mówiąc, wymyślanie nazw takich jak Mathlub Arrayspokazuje przynajmniej pewne zamiary projektu.
Tulains Córdova,
3

Jest całkiem akceptowalny, aby mieć klasy użytkowe , chociaż wolę ten termin ClassNameHelper. .NET BCL zawiera nawet klasy pomocnicze. Najważniejszą rzeczą do zapamiętania jest dokładne udokumentowanie celu zajęć, a także każdej metody pomocnika oraz uczynienie z niego wysokiej jakości kodu, który można utrzymać.

I nie daj się zwieść klasom pomocników.

David Anderson
źródło
0

Używam dwupoziomowego podejścia. Klasa „Globals” w pakiecie „util” (folder). Aby coś przejść do klasy „Globals” lub pakietu „util”, musi to być:

  1. Prosty,
  2. Niezmienny,
  3. Nie zależy od niczego innego w twojej aplikacji

Przykłady, które pomyślnie przejdą te testy:

  • Systemowe formaty daty i dziesiętne
  • Niezmienne dane globalne, takie jak EARLIEST_YEAR (obsługiwany przez system) lub IS_TEST_MODE lub jakieś inne prawdziwie globalne i niezmienne (podczas działania systemu) wartości.
  • Bardzo małe metody pomocnicze, które omijają luki w Twoim języku, frameworku lub zestawie narzędzi

Oto przykład bardzo małej metody pomocniczej, całkowicie niezależnej od reszty aplikacji:

public static String ordinal(final int i) {
    return (i == 1) ? "1st" :
           (i == 2) ? "2nd" :
           (i == 3) ? "3rd" :
           new StringBuilder().append(i).append("th").toString();
}

Patrząc na to, widzę błąd, w którym 21 powinno być „21”, 22 powinno być „22.” itd., Ale to nie ma sensu.

Jeśli jedna z tych metod pomocniczych rośnie lub staje się skomplikowana, należy ją przenieść do własnej klasy w pakiecie użytkownika. Jeśli dwie lub więcej klas pomocniczych w pakiecie użytkownika są ze sobą powiązane, należy je przenieść do własnego pakietu. Jeśli metoda stała lub pomocnicza okaże się związana z określoną częścią aplikacji, należy ją tam przenieść.

Powinieneś być w stanie uzasadnić, dlaczego nie ma lepszego miejsca na wszystko, co trzymasz w klasie Globals lub pakiecie użytkownika, i powinieneś czyścić je okresowo za pomocą powyższych testów. W przeciwnym razie po prostu tworzysz bałagan.

GlenPeterson
źródło