Является ли наличие классов «Util» поводом для беспокойства? [закрыто]

14

Иногда я создаю классы 'Util', которые в первую очередь служат для хранения методов и значений, которые на самом деле не принадлежат никому другому. Но каждый раз, когда я создаю один из этих классов, я думаю: «О-о, я буду сожалеть об этом позже ...», потому что я где-то читал, что это плохо.

Но с другой стороны, кажется, есть два убедительных (по крайней мере для меня) случая для них:

  1. секреты реализации, которые используются в нескольких классах в пакете
  2. предоставляя полезную функциональность для расширения класса, не загромождая его интерфейс

Я на пути к уничтожению? Что вы скажете !! Должен ли я рефакторинг?


источник
11
Прекратите использовать Utilв именах ваших классов. Проблема решена.
Яннис
3
@MattFenwick Яннис имеет хорошую точку зрения. Присвоение имени классу SomethingUtilнемного лениво и просто затеняет истинное назначение класса - то же самое с классами с именем SomethingManagerили SomethingService. Если этот класс несет единственную ответственность, должно быть легко дать ему осмысленное имя. Если нет, то это реальная проблема для решения ...
MattDavey
1
@MDavey хорошая идея, вероятно, это был неправильный выбор слова Util, хотя, очевидно, я не ожидал, что это зафиксируется, а остальная часть вопроса проигнорирована ...
1
Если вы создаете несколько классов * Util, разделенных типом объекта, которым они манипулируют, то я думаю, что это нормально. Я не вижу проблем с наличием StringUtil, ListUtil и т. Д. Если вы не находитесь в C #, то вам следует использовать методы расширения.
Марко-Фисет
4
У меня часто есть наборы функций, которые используются для связанных целей. Они не очень хорошо отображаются в классе. Мне не нужен класс ImageResizer, мне просто нужна функция ResizeImage. Поэтому я помещу связанные функции в статический класс, такой как ImageTools. Для функций, которые еще не относятся к какой-либо группировке, у меня есть статический класс Util для их хранения, но как только у меня будет несколько, которые достаточно связаны друг с другом, чтобы поместиться в один класс, я переместлю их. Я не вижу лучшего способа с этим справиться.
Филипп

Ответы:

26

Современный ОО дизайн допускает, что не все является объектом. Некоторые вещи являются поведением или формулами, а некоторые не имеют состояния. Это хорошо, чтобы смоделировать эти вещи как чистые функции, чтобы получить выгоду от этого дизайна.

Java и C # (и другие) требуют, чтобы вы создали класс util и перепрыгнули через этот обруч, чтобы сделать это. Раздражает, но не конец света; и не очень хлопотно с точки зрения дизайна.

Telastyn
источник
Да, это то, о чем я думал. Спасибо за ответ!
Согласен, хотя следует отметить, что .NET теперь предлагает методы расширения и частичные классы в качестве альтернативы этому подходу. Если вы доведите этот элемент до крайности, вы получите миксины Ruby - язык, который мало нуждается в служебных классах.
Неонтапир
2
@neontapir - За исключением того, что реализация методов расширения в основном вынуждает вас создавать класс
утилит
Правда. Есть два места, где мешают классы Util: одно находится в самом классе, а другое - в месте вызова. Делая методы похожими на существующие объекты, методы расширения решают проблему с CallSite. Я согласен, все еще существует проблема существования класса Util.
Неонтапир
16

Никогда не говори никогда"

Я не думаю, что это обязательно плохо, это только плохо, если ты делаешь это плохо и злоупотребляешь этим.

Нам всем нужны инструменты и утилиты

Для начала, мы все используем некоторые библиотеки, которые иногда считаются почти повсеместными и обязательными. Например, в мире Java, Google Guava или некоторых из Apache Commons ( Apache Commons Lang , коллекции Apache Commons и т. Д.).

Так что в этом явно есть необходимость.

Избегайте грубых слов, дублирования и появления ошибок

Если вы думаете о них довольно много просто очень большой пучок этих Utilклассов вы описываете, за исключением кто - то прошел через большие длины , чтобы получить их (относительно) право, и они были раз - проверены и сильно глаза сжались другими.

Поэтому я бы сказал, что первое эмпирическое правило, когда возникает желание написать Utilкласс, - проверить, что Utilкласс на самом деле еще не существует.

Единственный контраргумент, который я видел для этого, - это когда вы хотите ограничить свои зависимости, потому что:

  • вы хотите ограничить объем памяти ваших зависимостей,
  • или вы хотите строго контролировать то, что разработчикам разрешено использовать (это происходит в одержимых больших командах, или когда известна конкретная структура, имеющая нечетный класс супер-дерьма, которого где-то абсолютно избегают).

Но и то, и другое можно решить, переупаковав библиотеку с помощью ProGuard или ее эквивалента, или разобрав ее самостоятельно (для пользователей Maven , maven-shade-plugin предлагает несколько шаблонов фильтрации, чтобы интегрировать это как часть вашей сборки).

Так что, если он находится в lib и соответствует вашему варианту использования, и никакие тесты не говорят вам иначе, используйте его. Если это немного отличается от того, что вы, расширьте его (если возможно) или расширьте, или, в крайнем случае, перепишите его.

Соглашения об именах

Однако пока в этом ответе я назвал их Utilтакими же как ты. Не называй их так.

Дайте им значимые имена. Возьмите Google Guava как (очень, очень) хороший пример того, что нужно делать, и просто представьте, что com.google.guavaпространство имен на самом деле является вашим utilкорнем.

Назовите свой пакет util, в худшем случае, но не классы. Если имеет дело с Stringобъектами и манипулированием строковыми конструкциями, называйте это Strings, а не StringUtils(извините, Apache Commons Lang - я все еще люблю и использую вас!). Если это что-то конкретное, выберите конкретное имя класса (например, Splitterили Joiner).

Модульный тест

Если вам нужно прибегнуть к написанию этих утилит, обязательно протестируйте их. Преимущество утилит в том, что они, как правило, представляют собой автономные компоненты, которые принимают определенные входные данные и возвращают конкретные выходные данные. Это концепция. Так что нет никаких оснований не тестировать их.

Кроме того, модульное тестирование позволит вам определить и задокументировать контракт их API. Если тесты не работают, либо вы что-то изменили неправильно, либо это означает, что вы пытаетесь изменить контракт вашего API (или что ваши первоначальные тесты были дерьмовыми - учитесь на этом, и не делайте этого снова) .

API дизайн

Решение о дизайне, которое вы примете для этих API, будет следовать за вами, возможно, долгое время. Поэтому, не тратя часов на написание Splitter-clone, будьте осторожны с подходом к решению проблемы.

Задайте себе несколько вопросов:

  • Ваш вспомогательный метод гарантирует класс сам по себе или статический метод достаточно хорош, если имеет смысл для него быть частью группы аналогично полезных методов?
  • Вам нужны фабричные методы, чтобы создавать объекты и делать ваши API более читабельными?
  • Говоря о читабельности, вам нужен Fluent API , компоновщики и т. Д.?

Вы хотите, чтобы эти утилиты охватывали широкий спектр вариантов использования, были надежными, стабильными, хорошо документированными, следовали принципу наименьшего удивления и были автономными. В идеале каждый подпакет ваших утилит, или, по крайней мере, весь ваш пакет утилит, должен быть экспортирован в пакет для простого повторного использования.

Как обычно, учитесь у гигантов здесь:

Да, многие из них делают акцент на коллекциях и структурах данных, но не говорите мне, что это не то, где или для чего вы обычно реализуете большинство своих утилит, прямо или косвенно.

haylem
источник
+1 дляIf deals with String objects and manipulation of string constructs, call it Strings, not StringUtils
Тулаинс Кордова
+1 Для разработки API в .Net прочитайте книгу архитекторов .Net. Руководство по разработке структуры:
условные обозначения
Почему вы называете это Strings вместо StringUtil (s)? Струны для меня звучат как объект коллекции.
bdrx
@bdrx: для меня объект коллекции будет либо, StringsCollectionTypeHereесли вы хотите конкретную реализацию. Или более конкретное имя, если эти строки имеют конкретное значение в контексте вашего приложения. В этом конкретном случае Гуава использует Stringsсвоих помощников, связанных со строками, в отличие от StringUtilsCommons Lang. Я нахожу это вполне приемлемым, для меня это просто означает, что классы обрабатывают Stringобъект или это класс общего назначения для управления строками.
Хайлем
@bdrx: В общем, NameUtilsвещи не дают мне покоя, потому что, если они находятся под четко обозначенным именем пакета, я бы уже знал, что это служебный класс (а если нет, то быстро это выяснил бы, посмотрев на API). Это так раздражает меня , как люди объявляя вещи , как SomethingInterface, ISomethingили SomethingImpl. Я был в порядке с такими артефактами, когда кодировал в C и не использовал IDE. Сегодня мне вообще не нужны такие вещи.
Хайлем
8

Классы Util не являются связными и, как правило, имеют плохой дизайн, потому что у класса должна быть одна причина для изменения (принцип единой ответственности).

Тем не менее, я видел «Util» классов в самом Java API, как: Math, Collectionsи Arrays.

На самом деле это служебные классы, но все их методы связаны с одной темой, у одного есть математические операции, у одного есть методы для манипулирования коллекциями, а у другого - для манипулирования массивами.

Старайтесь не нет абсолютно не связанных методов в служебном классе. Если это так, скорее всего, вы можете поместить их в другое место, где они действительно принадлежат.

Если необходимо иметь классы утилит, попробуйте разделить их по темам, подобным Java Math, Collectionsи Arrays. Это, по крайней мере, показывает некоторое намерение дизайна, даже когда они просто пространства имен.

Я, например, всегда избегаю служебных классов и никогда не нуждался в их создании.

Тулаинс Кордова
источник
Спасибо за ответ. Таким образом, даже если классы util являются пакетно-закрытыми, они все еще являются запахом дизайна?
не все принадлежит классу все же. если вы застряли с языком, который настаивает на том, что он делает, то будет использовать классы.
JK.
1
Ну, у служебных классов нет принципа проектирования, который бы давал вам знать, сколько методов слишком много, поскольку для начала не существует связности. Как ты мог знать, как остановиться? После 30 методов? 40? 100? Принципы ООП, с другой стороны, дают представление о том, когда метод не принадлежит конкретному классу, а когда класс делает слишком много. Это называется сплоченность. Но, как я уже говорил в своем ответе, те люди, которые разрабатывали Java, использовали служебные классы. Вы тоже не можете этого сделать. Я не создаю служебные классы и не сталкивался с ситуацией, когда что-то не входит в обычный класс.
Тулаинс Кордова
1
Классы Utils обычно не являются классами, это просто пространства имен, содержащие множество, как правило, чистых функций. Чистые функции настолько взаимосвязаны, насколько это возможно.
JK.
1
@jk Я имел в виду Utils ... Между прочим, придумывая имена, подобные Mathили Arraysдемонстрирующие хоть какое-то намерение дизайна.
Тулаинс Кордова
3

Вполне приемлемо иметь классы util , хотя я предпочитаю термин ClassNameHelper. .NET BCL даже содержит вспомогательные классы. Самая важная вещь, которую следует помнить, - это тщательно документировать назначение классов, а также каждого отдельного вспомогательного метода и сделать его высококачественным поддерживаемым кодом.

И не увлекайтесь вспомогательными классами.

Дэвид Андерсон
источник
0

Я использую двухуровневый подход. Класс "Globals" в пакете (папке) util. Чтобы что-то входило в класс «Globals» или пакет «util», оно должно быть:

  1. Просто,
  2. Неизменный,
  3. Не зависит от чего-либо еще в вашем приложении

Примеры, которые проходят эти тесты:

  • Общесистемные даты и десятичные форматы
  • Неизменные глобальные данные, такие как EARLIEST_YEAR (которую поддерживает система) или IS_TEST_MODE, или какое-то такое действительно глобальное и неизменное (пока система работает) значение.
  • Очень маленькие вспомогательные методы, которые обходят пробелы в вашем языке, фреймворке или инструментарии

Вот пример очень маленького вспомогательного метода, полностью независимого от остальной части приложения:

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

Глядя на это, я вижу ошибку, согласно которой 21 должен быть «21-м», 22 - «22-м» и т. Д., Но это не относится к делу.

Если один из этих вспомогательных методов растет или усложняется, его следует переместить в его собственный класс в пакете util. Если два или более вспомогательных класса в пакете util связаны друг с другом, они должны быть перемещены в их собственный пакет. Если константный или вспомогательный метод оказывается связанным с определенной частью вашего приложения, его следует перенести туда.

Вы должны быть в состоянии объяснить, почему нет лучшего места для всего, что вы прикрепляете в классе Globals или в пакете утилит, и вам следует периодически очищать его, используя приведенные выше тесты. В противном случае вы просто создаете беспорядок.

GlenPeterson
источник