Grokking Java-культура - почему все так тяжело? Для чего он оптимизирован? [закрыто]

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

Синтаксис Java не является проблемой; это просто другой язык. Но помимо синтаксиса Java имеет культуру, набор методов и методов разработки, которые считаются «правильными». И пока я полностью не могу «заманить» эту культуру. Поэтому я был бы очень благодарен за объяснения или указатели в правильном направлении.

Минимальный полный пример доступен в вопросе переполнения стека, который я начал: https://stackoverflow.com/questions/43619566/returning-a-result-with-several-values-the-java-way/43620339

У меня есть задача - разобрать (из одной строки) и обработать набор из трех значений. В Python это однострочный (кортеж), в Pascal или C - запись /структура с 5 строками.

В соответствии с ответами эквивалент структуры доступен в синтаксисе Java, и тройка доступна в широко используемой библиотеке Apache, но «правильный» способ сделать это на самом деле путем создания отдельного класса для значения , в комплекте с геттерами и сеттерами. Кто-то был очень добр, чтобы представить полный пример. Это было 47 строк кода (ну, некоторые из этих строк были пробелами).

Я понимаю, что огромное сообщество разработчиков скорее всего не ошибается. Так что это проблема с моим пониманием.

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

283 голоса | спросил Mikhail Ramendik 26 PMpWed, 26 Apr 2017 14:35:23 +030035Wednesday 2017, 14:35:23

16 ответов


234

Язык Java

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

Java изначально была создана как слегка улучшенная C с OO. Как таковая Java имеет синтаксис эпохи 70-х годов. Кроме того, Java очень консервативна в отношении добавления функций, чтобы сохранить обратную совместимость и позволить ей выдерживать проверку временем. Если бы Java добавила модные функции, такие как XML-литералы, в 2005 году, когда XML был взволнован, язык был бы раздутым призрачными функциями, которые никто не заботится и которые ограничивают его эволюцию через 10 лет. Поэтому Java просто не хватает большого синтаксиса для выражения понятий.

Тем не менее, нет ничего фундаментального, чтобы заставить Java не использовать этот синтаксис. Например, Java 8 добавила ссылки на lambdas и методы, что значительно сокращает многословие во многих ситуациях. Java также может добавить поддержку объявлений с компактными типами данных, таких как классы классов Scala. Но Java просто не сделал этого. Обратите внимание, что пользовательские типы значений находятся на горизонте, и эта функция может ввести новый синтаксис для их объявления. Полагаю, мы увидим.


Культура Java

История развития Java-бизнеса в значительной степени привела нас к культуре, которую мы видим сегодня. В конце 90-х /начале 00-х годов Java стал чрезвычайно популярным языком для серверных бизнес-приложений. В то время эти приложения были в основном написаны ad-hoc и включали в себя множество сложных проблем, таких как HTTP API, базы данных и обработка XML-каналов.

В 00-е годы стало ясно, что многие из этих приложений имеют много общего и позволяют управлять этими проблемами, такими как Hibernate ORM, парсер XML Xerces, JSP и API сервлета, и EJB стали популярными. Однако, хотя эти рамки уменьшили усилия по работе в конкретном домене, который они автоматизировали, они нуждались в настройке и координации. В то время по какой-то причине было популярно писать фреймворки, чтобы удовлетворить самые сложные варианты использования, и поэтому эти библиотеки были сложными для настройки и интеграции. И со временем они становились все более сложными, поскольку они накопили функции. Развитие Java-бизнеса постепенно становилось все более и более связанным с подключением сторонних библиотек и меньше о написании алгоритмов.

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

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

ответил Solomonoff's Secret 26 PMpWed, 26 Apr 2017 17:39:39 +030039Wednesday 2017, 17:39:39
73

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

Java оптимизирует для обнаружения ошибок компилятора, не делая никаких предположений

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

Причина этой философии заключается в том, что программист только человек. Мы пишем не всегда то, что мы на самом деле намерены делать. Язык Java пытается смягчить некоторые из этих проблем, заставляя разработчика всегда явно декларировать их типы. Это всего лишь способ двойной проверки того, что написанный код действительно делает то, что было предназначено.

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

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

В Java есть два способа сделать это:

  1. Используйте Triplet<A, B, C> как возвращаемый тип (который действительно должен находиться в java.util), и я не могу объяснить, почему это не так. Тем более, что JDK8 вводит Function, BiFunction, Consumer, BiConsumer и т. д. ... просто кажется, что Pair и Triplet, по крайней мере, будет иметь смысл, но я отвлекся)

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

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

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

Рассмотрим что-то вроде этого в Typcript (или тип потока), где вместо явного ввода используется выражение типа.

function parseDurationInMillis(csvLine){
    // here the developer intends to return a Number, 
    // but actually it's a String
    return csv.firstField();
}

// Compiler infers that parseDurationInMillis is String, so it does
// string concatenation and infers that plusTwoSeconds is String
// Developer actually intended Number
var plusTwoSeconds = 2000 + parseDurationInMillis(csvLine);

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


Обратите внимание, что после правильных объектно-ориентированных принципов и моделирования, основанных на доменах, случай синтаксического анализа длительности в связанном вопросе также может быть возвращен как объект java.time.Duration, который будет много более явным, что оба указанных случая.

ответил LordOfThePigs 26 PMpWed, 26 Apr 2017 18:39:54 +030039Wednesday 2017, 18:39:54
50

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

  • Расходы, связанные с развитием между ними, похожи на воздух в воздушном шаре с воздушным шаром. Вы можете сжать одну часть воздушного шара, а другая часть набухает. Python имеет тенденцию сжимать раннюю часть. Java сжимает более позднюю часть.
  • В Java по-прежнему отсутствуют некоторые функции, которые могли бы удалить часть этого веса. Java 8 сделала огромную вмятину в этом, но культура не полностью переварила изменения. Java может использовать еще несколько вещей, таких как yield.

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

В конкретном случае вы упоминаете, что нет кортежей. Легким решением является создание класса с общедоступными значениями. Когда Java впервые появилась, люди делали это довольно регулярно. Первая проблема заключается в том, что это головная боль обслуживания. Если вам нужно добавить некоторую логику или безопасность потоков или вы хотите использовать полиморфизм, вам, по крайней мере, нужно будет касаться каждого класса, который взаимодействует с этим объектом «tuple-esque». В Python для этого есть решения, такие как __getattr__ и т. Д., Поэтому это не так страшно.

Есть некоторые вредные привычки (IMO) вокруг этого, хотя. В этом случае, если вы хотите кортеж, я задаю вопрос, почему вы сделаете его изменчивым объектом. Вам понадобятся только геттеры (на стороне примечания, я ненавижу соглашение get /set, но это то, что есть.) Я действительно думаю, что простой класс (измененный или нет) может быть полезен в частном или пакетно-частном контексте в Java , То есть, ограничивая ссылки в проекте классом, вы можете реорганизовать позже по мере необходимости, не меняя общедоступный интерфейс класса. Вот пример того, как вы можете создать простой неизменяемый объект:

public class Blah 
{
  public static Blah blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    return new Blah(number, isInSeconds, lessThanOneMillis);
  }

  private final long number;
  private final boolean isInSeconds;
  private final boolean lessThanOneMillis;

  public Blah(long number, boolean isInSeconds, boolean lessThanOneMillis)
  {
    this.number = number;
    this.isInSeconds = isInSeconds;
    this.lessThanOneMillis = lessThanOneMillis;
  }

  public long getNumber()
  {
    return number;
  }

  public boolean isInSeconds()
  {
    return isInSeconds;
  }

  public boolean isLessThanOneMillis()
  {
    return lessThanOneMillis;
  }
}

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

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

public class Blah 
{
  public static Blah fromSeconds(long number)
  {
    return new Blah(number * 1000_000);
  }

  public static Blah fromMills(long number)
  {
    return new Blah(number * 1000);
  }

  public static Blah fromNanos(long number)
  {
    return new Blah(number);
  }

  private final long nanos;

  private Blah(long nanos)
  {
    this.nanos = nanos;
  }

  public long getNanos()
  {
    return nanos;
  }

  public long getMillis()
  {
    return getNanos() / 1000; // or round, whatever your logic is
  }

  public long getSeconds()
  {
    return getMillis() / 1000; // or round, whatever your logic is
  }

  /* I don't really know what this is about but I hope you get the idea */
  public boolean isLessThanOneMillis()
  {
    return getMillis() < 1;
  }
}
ответил JimmyJames 26 PMpWed, 26 Apr 2017 17:59:40 +030059Wednesday 2017, 17:59:40
41

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

Одна из ваших жалоб - необходимость создания класса, чтобы вернуть некоторый набор значений в качестве ответа. Это актуальная проблема, которая, как я думаю, показывает, что ваши интуиции программирования правильны! Тем не менее, я думаю, что в других ответах отсутствует метка, придерживаясь примитивной навязчивой анти-модели, которую вы посвятили. И у Java нет такой же легкости работы с несколькими примитивами, как у Python, где вы можете возвращать несколько значений изначально и легко назначать их нескольким переменным.

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

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

Например, скажем, что вы узнаете больше о том, какая точность или масштаб действительно необходимы для суммирования и сопоставления продолжительности работы, и вы обнаружите, что вам нужен промежуточный флаг для обозначения «приблизительно 32 миллисекундной ошибки» (закрыть к квадратному корню 1000, поэтому на полпути логарифмически от 1 до 1000). Если вы связали себя с кодом, который использует примитивы для представления этого, вам нужно будет найти все места в коде, где у вас есть is_in_seconds,is_under_1ms и изменить его на is_in_seconds,is_about_32_ms,is_under_1ms. Все должно было измениться повсюду! Создание класса, задачей которого является запись погрешности, чтобы его можно было потреблять в другом месте, освобождает ваших потребителей от получения сведений о том, какие значения ошибки или что-то о том, как они объединяются, и позволяет им просто указывать допустимую погрешность в данный момент. (То есть, ни один потребительский код, допустимый предел погрешности, не изменяется, когда вы добавляете новый уровень ошибки в классе, поскольку все старые поля ошибки все еще действительны).

Заявление о закрытии

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

Добавление

Я добавлю абсолютно безвозмездно и несправедливо, что автоматические свойства C # и возможность назначать свойства get-only в конструкторах еще более упрощают очистку некоторого грязного кода, который потребует «способ Java» (с явными частными полями поддержки и getter /setter):

 // Warning: C# code!
public sealed class ApproximateDuration {
   public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
      LowMilliseconds = lowMilliseconds;
      HighMilliseconds = highMilliseconds;
   }
   public int LowMilliseconds { get; }
   public int HighMilliseconds { get; }
}

Вот реализация Java из вышеперечисленного:

 public final class ApproximateDuration {
  private final int lowMilliseconds;
  private final int highMilliseconds;

  public ApproximateDuration(int lowMilliseconds, int highMilliseconds) {
    this.lowMilliseconds = lowMilliseconds;
    this.highMilliseconds = highMilliseconds;
  }

  public int getLowMilliseconds() {
    return lowMilliseconds;
  }

  public int getHighMilliseconds() {
    return highMilliseconds;
  }
}

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

В этом отношении этот класс также является достойным кандидатом на struct, тип значения. Некоторое тестирование покажет, может ли переход к структуре иметь преимущество производительности во время выполнения (возможно).

ответил ErikE 27 AMpThu, 27 Apr 2017 05:29:51 +030029Thursday 2017, 05:29:51
24

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

Python - это язык с несколькими парадигмами, который оптимизируется для ясности и простоты кода (легко читается и записывается).

Java (традиционно) является языком OO на основе одного парадигма, который оптимизируется для эксплицитности и согласованности - даже ценой более подробного кода.

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

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

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

ответил JacquesB 26 PMpWed, 26 Apr 2017 18:06:42 +030006Wednesday 2017, 18:06:42
16

Не искать методы; это обычно плохая идея, как сказано в Лучшие практики BAD, шаблоны ХОРОШО? . Я знаю, что вы не просите лучших практик, но я все еще думаю, что вы найдете там некоторые релевантные элементы.

Поиск решения вашей проблемы лучше, чем практика, и ваша проблема не является кортежем, чтобы быстро вернуть три значения в Java:

  • Существуют массивы
  • Вы можете вернуть массив в виде списка в однострочном файле: Arrays.asList (...)
  • Если вы хотите сохранить сторону объекта с меньшим количеством шаблонов (и не ломбок):

class MyTuple {
    public final long value_in_milliseconds;
    public final boolean is_in_seconds;
    public final boolean is_under_1ms;
    public MyTuple(long value_in_milliseconds,....){
        ...
    }
 }

Здесь у вас есть неизменяемый объект, содержащий только ваши данные и публикацию, поэтому нет необходимости в геттерах. Обратите внимание: однако, если вы используете некоторые инструменты сериализации или уровень сохранения, такие как ORM, они обычно используют getter /setter (и могут принимать параметр для использования полей вместо getter /setter). И именно поэтому эти методы используются много. Поэтому, если вы хотите узнать о практике, лучше понять, почему они здесь для лучшего использования.

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

ответил Walfrat 26 PMpWed, 26 Apr 2017 15:15:03 +030015Wednesday 2017, 15:15:03
10

О идиомах Java в целом:

Существуют разные причины, по которым Java имеет классы для всего. Насколько мне известно, главная причина:

Java должен быть легко освоен для новичков. Чем более явными являются вещи, тем труднее пропустить важные детали. Меньше магии происходит, что было бы трудно для начинающих понять.


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

Вы можете уменьшить шаблон с помощью инструментов, таких как Lombok:

@Value
class MyTuple {
    long value_in_milliseconds;
    boolean is_in_seconds;
    boolean is_under_1ms;
}
ответил marstato 26 PMpWed, 26 Apr 2017 15:05:18 +030005Wednesday 2017, 15:05:18
7

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

  1. Код библиотеки записывается один раз, но используется гораздо чаще. Хотя приятно минимизировать накладные расходы при написании библиотеки, вероятно, в долгосрочной перспективе, вероятно, стоит написать так, чтобы минимизировать накладные расходы на использование библиотеки.
  2. Это означает, что самодокументирующиеся типы велики: имена методов помогают понять, что происходит и что вы получаете от объекта.
  3. Статическая типизация - очень полезный инструмент для устранения определенных классов ошибок. Это, конечно, не все исправить (людям нравится шутить о Haskell, что, как только вы получите систему типов, чтобы принять ваш код, это, вероятно, правильно), но это делает очень легко сделать определенные виды неправильных вещей невозможными.
  4. Написание кода библиотеки - это указание контрактов. Определение интерфейсов для ваших аргументов и типов результатов делает границы ваших контрактов более четко определенными. Если что-то принимает или создает кортеж, нет никакого упоминания, действительно ли это тот тип кортежа, который вы должны получать или производить, и существует очень мало ограничений на такой общий тип (у него даже есть правильное количество элементов? они того типа, которого вы ожидали?).

"Struct" классы с полями

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

   class ParseResult0 {
      public final long millis;
      public final boolean isSeconds;
      public final boolean isLessThanOneMilli;

      public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
         this.millis = millis;
         this.isSeconds = isSeconds;
         this.isLessThanOneMilli = isLessThanOneMilli;
      }
   }

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

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

"Структуры" в качестве интерфейсов

Благодаря статическим методам, допускаемым в интерфейсах, на самом деле относительно легко создавать неизменяемые типы без большого количества синтаксических накладных расходов. Например, я мог бы реализовать такую ​​структуру результатов, о которой вы говорите, примерно так:

   interface ParseResult {
      long getMillis();

      boolean isSeconds();

      boolean isLessThanOneMilli();

      static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
         return new ParseResult() {
            @Override
            public boolean isSeconds() {
               return isSeconds;
            }

            @Override
            public boolean isLessThanOneMilli() {
               return isLessThanOneMill;
            }

            @Override
            public long getMillis() {
               return millis;
            }
         };
      }
   }

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

С такой структурой, как этот результат синтаксического анализа, contract вашего анализатора очень четко определен. В Python один кортеж не отличается от другого кортежа. В Java статическая типизация доступна, поэтому мы уже исключаем определенные классы ошибок. Например, если вы возвращаете кортеж в Python и хотите вернуть кортеж (millis, isSeconds, isLessThanOneMilli), вы можете случайно сделать:

return (true, 500, false)

, когда вы имели в виду:

return (500, true, false)

С таким видом интерфейса Java вы не можете скомпилировать:

return ParseResult.from(true, 500, false);

. Вы должны сделать:

return ParseResult.from(500, true, false);

Это преимущество статически типизированных языков в целом.

Этот подход также дает вам возможность ограничить то, что вы можете получить. Например, при вызове метода getMillis () вы можете проверить, является ли isLessThanOneMilli () истинным, и если это так, бросьте исключение IllegalStateException (например), поскольку в этом случае нет значимого значения миллисов.

Сделать трудным сделать неправильную вещь

В интерфейсепример выше, у вас все еще есть проблема, что вы случайно можете заменить аргументы isSeconds и isLessThanOneMilli, поскольку они имеют один и тот же тип.

На практике вы действительно можете использовать TimeUnit и продолжительность, чтобы у вас был результат, например:

   interface Duration {
      TimeUnit getTimeUnit();

      long getDuration();

      static Duration from(TimeUnit unit, long duration) {
         return new Duration() {
            @Override
            public TimeUnit getTimeUnit() {
               return unit;
            }

            @Override
            public long getDuration() {
               return duration;
            }
         };
      }
   }

   interface ParseResult2 {

      boolean isLessThanOneMilli();

      Duration getDuration();

      static ParseResult2 from(TimeUnit unit, long duration) {
         Duration d = Duration.from(unit, duration);
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return false;
            }

            @Override
            public Duration getDuration() {
               return d;
            }
         };
      }

      static ParseResult2 lessThanOneMilli() {
         return new ParseResult2() {
            @Override
            public boolean isLessThanOneMilli() {
               return true;
            }

            @Override
            public Duration getDuration() {
               throw new IllegalStateException();
            }
         };
      }
   }

Это будет больше lot , но вам нужно только написать его один раз и (при условии, что вы правильно документировали), люди, которые в конечном итоге используют ваш код не должен угадывать, что означает результат, и не может случайно делать такие вещи, как result[0], когда они означают result[1]. Вы по-прежнему получаете экземпляры create довольно лаконично, и получение данных из них также не так уж и тяжело:

  ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
  ParseResult2 y = ParseResult2.lessThanOneMilli();

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

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

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

Теперь, все сказанное, все еще есть места, где простые кортежи или списки могут иметь большой смысл. Может быть меньше накладных расходов при возврате массива чего-то, и если это так (и эти служебные данные значительны, что вы бы определили с профилированием), то использование простого массива значений internal может сделать много смысла. Очевидно, что ваш открытый API должен иметь четко определенные типы.

ответил Joshua Taylor 27 AMpThu, 27 Apr 2017 00:14:58 +030014Thursday 2017, 00:14:58
7

Проблема в том, что вы сравниваете яблоки с апельсинами . Вы спросили, как имитировать возврат более чем одного значения, что дает быстрый и простой пример python с нетипизированным кортежем, и вы фактически получили практически ответ на один лайнер .

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

Это тоже недолго. Единственное, что вам нужно написать, это определения полей. Могут генерироваться сеттеры, геттеры, hashCode и equals. Поэтому ваш фактический вопрос должен быть, почему геттеры и сеттеры не сгенерированы автоматически, но это проблема синтаксиса (например, проблема с синтаксическим сахаром), а не культурная проблема.

И, наконец, вы overthinking пытаетесь ускорить то, что не важно вообще. Время, затрачиваемое на изучение классов DTO, незначительно по сравнению с временем, проведенным для поддержания и отладки системы. Поэтому никто не оптимизирует за меньшую многословность.

ответил 27 PMpThu, 27 Apr 2017 14:44:32 +030044Thursday 2017, 14:44:32
5

Это три разных фактора, которые способствуют тому, что вы наблюдаете.

Кортежи против названных полей

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

Синтаксис языка

Может ли быть проще объявить класс? Я не говорю о том, чтобы публиковать поля или использовать карту, но что-то вроде классов классов Scala, которые обеспечивают все преимущества описанной вами настройки, но гораздо более кратки:

case class Foo(duration: Int, unit: String, tooShort: Boolean)

Мы могли бы это сделать, но есть стоимость: синтаксис становится более сложным. Конечно, это может быть полезно для некоторых случаев или даже для большинства случаев или даже для большинства случаев в течение следующих 5 лет - но это нужно судить. Кстати, это о хороших вещах с языками, которые вы можете изменить самостоятельно (например, lisp), и обратите внимание, как это становится возможным благодаря простоте синтаксиса. Даже если вы на самом деле не модифицируете язык, простой синтаксис позволяет использовать более мощные инструменты; например, много раз я пропускаю некоторые варианты рефакторинга, доступные для Java, но не для Scala.

Философия языка

Но самым важным фактором является то, что язык должен дать определенный образ мышления. Иногда это может показаться угнетающим (я часто хотел поддержать определенную функцию), но удаление функций так же важно, как и их. Можете ли вы поддержать все? Конечно, но тогда вы могли бы просто написать компилятор, который компилирует каждый отдельный язык. Другими словами, у вас не будет языка - у вас будет надмножество языков, и каждый проект будет в основном принимать подмножество.

Конечно, можно писать код, который противоречит философии языка, и, как вы заметили, результаты часто уродливы. Наличие класса с несколькими парами в Java сродни использованию var в Scala, дегенерации прологовых предикатов для функций, выполнению unsafePerformIO в haskell и т. Д. Классы Java не должны быть легкими - им не нужно передавать данные вокруг , Когда что-то кажется трудным, часто бывает полезно отступить и посмотреть, есть ли другой способ. В вашем примере:

Почему длительность отдельно от единиц? Существует множество библиотек времени, которые позволяют объявлять длительность - что-то вроде Duration (5, секунд) (синтаксис будет отличаться), что позволит вам делать все, что вы хотите, гораздо более надежным способом. Возможно, вы хотите его преобразовать - зачем проверять, является ли результат [1] (или [2]?) «Часом» и умножается на 3600? И для третьего аргумента - в чем его цель? Я бы предположил, что в какой-то момент вам придется печатать «менее 1 мс» или фактическое время - это какая-то логика, которая, естественно, принадлежит данным времени. То есть вы должны иметь такой класс:

class TimeResult {
    public TimeResult(duration, unit, tooShort)
    public String getResult() {
        if tooShort:
           return "too short"
        else:
           return format(duration)
}

}

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

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

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

ответил Thanos Tintinidis 26 PMpWed, 26 Apr 2017 21:26:13 +030026Wednesday 2017, 21:26:13
5

Насколько я понимаю, основными причинами являются

  1. Интерфейсы - это основной способ Java для абстрагирования классов.
  2. Java может возвращать только одно значение из метода - объект или массив или собственное значение (int /long /double /float /boolean).
  3. Интерфейсы не могут содержать поля, только методы. Если вы хотите получить доступ к полю, вы должны пройти через метод - следовательно, getters и seters.
  4. Если метод возвращает интерфейс, у вас должен быть действительно реализованный класс.

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

ответил Thorbjørn Ravn Andersen 27 AMpThu, 27 Apr 2017 03:01:38 +030001Thursday 2017, 03:01:38
3

Я согласен с Жаком Б. ответить, что

  

Java (традиционно) является языком OO с одним парадигмом   который оптимизирует для объяснения и согласованности

Но объяснение и последовательность не являются конечными целями для оптимизации. Когда вы говорите, что «python оптимизирован для удобочитаемости», вы сразу же указываете, что конечной целью является «ремонтопригодность» и «скорость разработки».

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

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

Или, как один мудрый парень положил его очень давно ,

  

Лучший способ судить о языке - это посмотреть на код, написанный его   Сторонники. «Radix enim omnium malorum est cupiditas» - и Java - это   наглядным примером ориентированного на деньги программирования (СС). Как начальник   сторонник Java в SGI сказал мне: «Алекс, вы должны пойти туда, где   деньги есть ». Но я не особенно хочу идти туда, где деньги -   он обычно не пахнет там.

ответил artem 26 PMpWed, 26 Apr 2017 20:30:35 +030030Wednesday 2017, 20:30:35
3

(Этот ответ не является объяснением для Java, в частности, но вместо этого обращается к общему вопросу: «Что могли бы [тяжелые] методы оптимизировать для?)

Рассмотрим эти два принципа:

  1. Хорошо, когда ваша программа поступает правильно. Мы должны упростить запись программ, которые делают правильные вещи.
  2. Плохо, когда ваша программа делает не то. Нам должно быть сложнее писать программы, которые делают не то.

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

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

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

Другие примеры, которые делают вашу систему более «тяжелой»:

  • Когда у вас большая часть кодов, над которыми многие команды работали много лет, возникает большая проблема: кто-то может неправильно использовать чужой API. Функция, вызываемая с аргументами в неправильном порядке или вызываемая без обеспечения некоторых предварительных условий /ограничений, которые она ожидает, может быть катастрофической.
  • В качестве особого случая, скажем, ваша команда поддерживает определенный API или библиотеку и хочет изменить или реорганизовать ее. Чем больше «удержаны» ваши пользователи в том, как они могут использовать ваш код, тем легче изменить его. (Обратите внимание, что здесь было бы предпочтительно иметь настоящие гарантии, чтобы никто не мог использовать его необычным способом.)
  • Если разработка разделена между несколькими людьми или командами, может показаться хорошей идеей, чтобы один человек или команда «задавали» интерфейс, а другие фактически реализовали его. Чтобы она работала, вам нужно быть уверенным, когда реализация будет выполнена, что реализация действительно соответствует спецификации.

Вот лишь некоторые примеры, чтобы дать вам представление о случаях, когда делать вещи «тяжелыми» (и усложнять вам просто написать код быстро) может быть действительно преднамеренным. (Можно даже утверждать, что если писать код требует больших усилий, это может заставить вас думать более тщательно, прежде чем писать код! Конечно, эта строка аргумента быстро становится нелепой.)

Пример: внутренняя система Python Google имеет тенденцию делать вещи «тяжелыми», поэтому вы не можете просто импортировать чужой код, вы должны объявить зависимость в BUILD file , команда, код которой вы хотите импортировать, должен иметь свою библиотеку, объявленную как видимую для вашего кода, и т. д.


Примечание . Все вышеизложенное относится к ситуации, когда вещи становятся «тяжелыми». Я абсолютно не утверждает, что Java или Python (либо сами языки, либо их культуры) делают оптимальные компромиссы для любого конкретного случая; это вам о чем подумать. Две взаимосвязанные ссылки на такие компромиссы:

ответил ShreevatsaR 27 AMpThu, 27 Apr 2017 11:33:49 +030033Thursday 2017, 11:33:49
2

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

Часть того, что влияет на рекомендацию, является тем, что считается читаемым и поддерживаемым в Python, а Java очень отличается.

  • В Python кортеж представляет собой язык .
  • В Java и C # кортеж является (или будет) функцией library .

Я упоминаю только C #, потому что стандартная библиотека имеет набор Tuple <A, B, C, .. n> классы, и это прекрасный пример того, насколько неудобны кортежи, если язык не поддерживает их напрямую. Почти во всех случаях ваш код становится более читабельным и поддерживаемым, если у вас есть хорошо выбранные классы для решения проблемы. В конкретном примере в связанном вопросе переполнения стека другие значения будут легко выражены как рассчитанные getters на возвращаемом объекте.

Интересным решением, которое сделала платформа C #, которая обеспечивает счастливую середину, является идея анонимных объектов (выпущен в C # 3.0 ), которые царапают этот зуд довольно хорошо. К сожалению, Java еще не имеет эквивалента.

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

ответил Berin Loritsch 26 PMpWed, 26 Apr 2017 15:55:05 +030055Wednesday 2017, 15:55:05
0

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

У меня было это обсуждение по-другому, о аргументах метода: Рассмотрим простой метод, который вычисляет ИМТ:

CalculateBMI(weight,height)
{
  System.out.println("BMI: " + (( weight / height ) x 703));
}

В этом случае я буду возражать против этого стиля, потому что вес и высота связаны. Метод «сообщает» это два отдельных значения, если они не являются. Когда вы вычислили ИМТ с весом одного человека и высотой другого? Это не имеет смысла.

CalculateBMI(Person)
{
  System.out.println("BMI: " + (( Person.weight / Person.height ) x 703));
}

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

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

ответил Pieter B 26 PMpWed, 26 Apr 2017 20:25:32 +030025Wednesday 2017, 20:25:32
0

Чтобы быть откровенным, культура заключается в том, что Java-программисты изначально, как правило, выходили из университетов, где преподавались принципы Object Orientated и принципы разработки программного обеспечения.

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

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

Но, как вы говорите, это не лишится недостатков: сегодня, используя Java более 10 лет, я склонен использовать либо Node /Javascript, либо Go для новых проектов, потому что они позволяют быстрее развиваться и с микросервисным стилем их часто бывает достаточно. Судя по тому, что Google сначала сильно использовал Java, но был создателем Go, я думаю, они могут делать то же самое. Но хотя я использую Go и Javascript сейчас, я по-прежнему использую многие навыки дизайна, которые я получил от лет использования и понимания Java.

ответил Tom 27 PMpThu, 27 Apr 2017 17:36:26 +030036Thursday 2017, 17:36:26

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132