Никогда не использовать строки в Java? [закрыто]


73

Я наткнулся на запись в блоге, в которой не рекомендуется использовать Strings в Java для того, чтобы в вашем коде отсутствовала семантика, предлагая вместо этого использовать классы тонкой оболочки. Это примеры «до» и «после», приведенные в данной статье для иллюстрации:

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

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


Соответствующее обсуждение оригинальной вики: "что это за toString () из?"
Кристофер Хаммарстрем

5
Ого, это сообщение в блоге заставило меня чувствовать себя физически больным
Роб

5
Я прочитал по крайней мере одну книгу (которая, возможно, была первым выпуском Code Complete), в которой рекомендовано определить все ваши примитивные типы, чтобы иметь имена из проблемного домена. Та же идея
user16764

9
утверждение, начинающееся с «никогда», «всегда» ложно!
Флоренс Целай

1
Этот парень технический директор ?!
Hyangelo

Ответы:


88

Инкапсуляция предназначена для защиты вашей программы от изменений . Будет ли изменяться представление Имени? Если нет, то вы напрасно тратите время и YAGNI подает заявку.

Изменить: я прочитал сообщение в блоге, и у него есть принципиально хорошая идея. Проблема в том, что он слишком далеко продвинулся. Что - то вроде String orderId это очень плохо, потому что по- видимому "!"£$%^&*())ADAFVF, не является допустимым orderId. Это означает, что Stringпредставляет намного больше возможных значений, чем допустимых orderIds. Однако для чего-то подобного a nameвы не можете предсказать, что может быть или не быть действительным именем, а любое Stringявляется допустимым name.

В первом случае вы (правильно) сокращаете возможные входные данные до только допустимых. Во втором случае вы не добились сужения возможных допустимых входных данных.

Изменить еще раз: рассмотрим случай неверного ввода. Если вы напишите «Гарет Гобулькок» в качестве своего имени, это будет выглядеть глупо, это не будет концом света. Если вы введете неверный OrderID, скорее всего, он просто не будет работать.


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

13
@Malcolm: Но тогда у вас нет никакого способа узнать, какие строки проверяются, кроме как проверять их снова и снова.
DeadMG

7
«[...] вы не можете предсказать, что может быть или не быть действительным именем, а любая строка является допустимым именем». Я предполагаю, что одной из сильных сторон этого метода является то, что если ваш параметр имеет тип Name, вы не можете случайно передать другое несвязанное значение String. Там, где вы ожидаете Name, Nameкомпилируется только воля, и строка «Я должен вам 5 долларов» не может быть случайно принята. (Обратите внимание, что это никак не связано с проверкой имен!)
Андрес Ф.

6
Создание типов, специфичных для конкретного использования, добавляет семантическое богатство и помогает повысить безопасность. Кроме того, создание типов для представления определенных значений очень помогает при использовании контейнера IoC для автоматического связывания ваших классов - легко найти правильный компонент, который следует использовать, когда это единственный компонент, зарегистрированный для определенного класса. Намного больше усилий требуется, когда это только одна из многих зарегистрированных строк.
Дафан

3
@DeadMG Это аргумент, а не то, что мы можем решить в комментариях. Я бы сказал, что неуместные значения примитивных типов действительно случаются, и укрепление ваших интерфейсов - один из способов улучшить ситуацию.
Андрес Ф.

46

Это просто безумие :)


2
Это тоже мое мнение, но дело в том, что я помню это предложение в «Code Complete». Конечно , не все в этой книге неоспоримо, но , по крайней мере , это заставляет меня думать дважды , прежде чем отвергнуть идею.
DPM

8
Вы только что упомянули некоторые лозунги без какого-либо реального оправдания. Можете ли вы уточнить свое мнение? Некоторые принятые шаблоны и языковые функции, например, могут выглядеть как дополнительные сложности, но предлагают что-то ценное взамен (например: статическая типизация)
Andres F.

12
Здравый смысл - это не здравый смысл.
Каз Дракон

1
+1, к этому я бы добавил, что когда язык поддерживает именованные параметры, а среда IDE хороша, как в случае с C # и VS2010, то не нужно компенсировать недостаток функций в языке с помощью сумасшедших шаблонов. , Нет необходимости в классе с именем X и классе с именем Y, если можно написать. var p = new Point(x: 10, y:20);Кроме того, Cinema не похожа на строку. Я бы понял, если бы мы имели дело с физическими величинами, такими как давление, температура, энергия, где единицы отличаются, а некоторые не могут быть отрицательными. Автору блога нужно попробовать функционал.
Работа

1
+1 за "Не переусердствуй!"
Иван

23

Я согласен с автором, в основном. Если есть какое-либо поведение, свойственное полю, такое как проверка идентификатора заказа, то я бы создал класс для представления этого типа. Его второе замечание еще более важно: если у вас есть набор полей, которые представляют какое-то понятие, например адрес, то создайте класс для этого понятия. Если вы программируете на Java, вы платите высокую цену за статическую типизацию. Вы также можете получить все, что можете.


2
Можно ли прокомментировать комментарий?
Кевин Клайн

Какую высокую цену мы платим за статическую типизацию?
Ричард Тингл

@RichardTingle: время для перекомпиляции кода и перезапуска JVM для каждого изменения кода. Время, потраченное на внесение изменений, совместимых с исходным кодом, но несовместимых с двоичным кодом, и вещей, которые необходимо перекомпилировать, не истечет, и вы получите «MissingMethodException».
Кевин Клайн

У меня никогда не было проблем с java-приложениями (что при непрерывной компиляции обычно компилируется к моменту запуска), но это справедливо для веб-WAR, для которых перераспределение кажется немного медленнее
Ричард Тингл,

16

Не делай этого; это усложнит вещи, и вам это не нужно

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


Выгоды

Главным преимуществом является возможность изменять и расширять код. Если вы используете

class Point {
    int x,y;
    // other point operations
}

вместо того, чтобы просто передавать пару целых чисел - что, к сожалению, делают многие интерфейсы, - потом становится гораздо проще добавить другое измерение. Или измените тип на double. Если вы используете List<Author> authorsили List<Person> authorsвместо List<String> authorsэтого позже, становится намного проще добавлять больше информации к тому, что представляет автор. Записывая это так, я чувствую, что я констатирую очевидное, но на практике я сам много раз виноват в использовании строк таким образом, особенно в тех случаях, когда это не было очевидно в начале, тогда мне нужно больше, чем строка.

В настоящее время я пытаюсь реорганизовать какой-либо список строк, который переплетен в моем коде, потому что мне нужно больше информации там, и я чувствую боль: \

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

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

Недостатки

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

Он может включать в себя множество избыточных сеттеров (или конструкций) и геттеров . Автор блога приводит действительно уродливый пример new Point(x(10), y(10))вместо new Point(10, 10), и я хотел бы добавить, что использование может также включать такие вещи, как Math.max(p.x.get(), p.y.get())вместо Math.max(p.x, p.y). И длинный код часто считают трудным для чтения, и это справедливо. Но, честно говоря, я чувствую, что много кода перемещает объекты, и только избранные методы создают его, и еще меньше нуждаются в доступе к его мельчайшим деталям (что в любом случае не является OOPy).

дискуссионный

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


Как и в большинстве других школ программирования, я думаю, что это вредно для здоровья. Я не вижу, чтобы я когда-либо разделял координаты x и y, чтобы они были разных типов. Я не думаю, что Countэто необходимо, когда intдолжно быть достаточно. Мне не нравится unsigned intиспользование в C - хотя теоретически это хорошо, оно просто не дает вам достаточно информации, и оно запрещает расширять ваш код позже для поддержки этого магического -1. Иногда вам нужна простота.

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

У меня глубокое отвращение к чрезмерно спроектированному коду. Я действительно делаю. Но использовал правильно, я не думаю, что это чрезмерное проектирование.


5

Хотя это отчасти излишне, я часто думаю, что большинство вещей, которые я видел, было недостаточно разработанным.

Это не просто «Безопасность». Одна из действительно приятных особенностей Java - то, что она очень помогает вам запоминать / выяснять, что нужно / что ожидает любой вызов библиотечного метода.

НАИБОЛЬШАЯ (безусловно) библиотека Java, с которой я работал, была написана кем-то, кто очень любил Smalltalk и моделировал библиотеку GUI после того, как она работала как smalltalk - проблема в том, что каждый метод брал один и тот же базовый объект, но на самом деле не мог ИСПОЛЬЗОВАТЬ все, к чему может быть приведен базовый объект, так что вы вернулись к предположению, что передать в методы, и не знали, потерпели ли вы неудачу до времени выполнения (что-то, с чем я имел дело, приходилось сталкиваться каждый раз работал в с).

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

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

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

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


3

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

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

Два параметра необходимы для указания фактического покупателя, который заказывает билеты, у вас может быть два разных фильма с одинаковыми именами (например, ремейки), у вас может быть один и тот же фильм с разными именами (например, переводы). В определенной сети кинотеатров могут быть разные филиалы, так как вы будете поступать с этим последовательно и согласованно (например, используете $chain ($city)ли вы $chain in $cityили еще что-то и как собираетесь убедиться, что это используется на худой основе. На самом деле худшее - это указание вашего покровителя с помощью двух параметров, тот факт, что указаны имя и фамилия, не гарантирует действительного клиента (и вы не можете различить два John Doe).

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

Итак, ИМХО в блоге просто говорится «убедитесь, что вы передали правильные типы», его автор просто сделал чрезмерно ограниченный выбор, чтобы выбрать в частности базовые типы данных (что является неправильным сообщением).

Предлагаемая альтернатива лучше:

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

С другой стороны, я думаю, что сообщение в блоге заходит слишком далеко, оборачивая все. Countслишком общий, я мог бы сосчитать яблоки или апельсины с этим, добавить их, и у меня все еще есть ситуация, когда система типов позволяет мне делать бессмысленные операции. Конечно, вы можете применять ту же логику, что и в блоге, определять типы CountOfOrangesи т. Д., Но это также глупо.

Что бы это ни стоило, я бы на самом деле написал что-то вроде

public Ticket bookTicket(
  Person patron,
  Film film,
  int numberOfTickets,
  Cinema cinema);

Короче говоря: вы не должны передавать бессмысленные переменные; единственный раз, когда вы на самом деле указываете объект со значением, которое не определяет фактический объект, это когда вы запускаете запрос (например public Collection<Film> findFilmsWithTitle(String title)) или когда вы собираете подтверждение концепции. Держите систему типов в чистоте, поэтому не используйте слишком общий тип (например, фильм, представленный символом a String) или слишком ограничительный / конкретный / надуманный (например, Countвместо int). Используйте тип, который определяет ваш объект однозначно и однозначно, когда это возможно и жизнеспособно.

редактировать : еще более короткое резюме. Для небольших приложений (например, подтверждение концепции): зачем беспокоиться о сложной конструкции? Просто используйте Stringили intи иди с этим.

Для больших приложений: действительно ли вероятно, что у вас есть много классов, состоящих из одного поля с базовым типом данных? Если у вас мало таких классов, у вас просто есть «нормальные» объекты, ничего особенного там не происходит.

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


Я понимаю вашу точку зрения, но я бы сказал, что вы предполагаете больше, чем заявляет автор. Насколько мы знаем, его модель имеет только строку для покровителя, строку для фильма и так далее. Эта гипотетическая дополнительная функциональность на самом деле является сутью проблемы, поэтому либо он был очень «рассеянным», чтобы пропустить это при изложении своего довода, либо он считает, что мы должны предоставить больше смысловой силы только потому, что. Опять же, насколько мы знаем, это последнее, что он имел в виду.
DPM

@Jubbat: действительно, я предполагаю больше, чем заявляет автор. Но я хочу сказать, что либо у вас есть простое приложение, и в этом случае любой способ слишком сложен. Для этого масштаба ремонтопригодность не является проблемой, и семантическое различие препятствует вашей скорости кодирования. Если, с другой стороны, ваше приложение имеет большой размер, стоит правильно определить ваши типы (но вряд ли это будут простые обертки). ИМХО, его примеры просто не убедительны или имеют серьезные недостатки в дизайне, выходящие за рамки того, что он пытается сделать.
Эгон

2

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


2
+1 для обозначения лечения симптомов, а не причины.
Работа

2

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

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


0

Было бы хорошо, если бы IF String (end Integer и ... говоря только о String) не был окончательным, так что эти классы могли бы БЫТЬ некоторой (ограниченной) String со смыслом и все же могли бы быть отправлены какому-нибудь независимому объекту, который знает, как обрабатывать базовый тип (без разговоров туда-сюда).

И "добрые дела" этого увеличиваются, когда есть, например. ограничения на все имена.

Но при создании какого-либо приложения (не библиотеки) его всегда можно реорганизовать. Поэтому я предпочитаю начинать без него.


0

В качестве примера: в нашей разрабатываемой в настоящее время системе существует множество различных сущностей, которые могут быть идентифицированы различными видами идентификаторов (из-за использования внешних систем), иногда даже однотипными сущностями. Все идентификаторы являются строками - поэтому, если кто-то смешивает, какой тип идентификатора должен быть передан в качестве параметра, ошибка времени компиляции не отображается, но программа будет взорвана во время выполнения. Это случается довольно часто. Поэтому я должен сказать, что основная цель этого принципа - не защищать от изменений (хотя это также и служит), а защищать себя от ошибок. И в любом случае, если кто-то разрабатывает API, он должен отражать концепции домена, поэтому концептуально полезно определять классы, специфичные для домена - все зависит от того, есть ли порядок в сознании разработчиков,


@ downvoter: не могли бы вы дать объяснение?
thSoft
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.