Пограбування культури Java - чому речі такі важкі? Для чого це оптимізовано? [зачинено]


288

Я багато кодував у 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), але це дуже слабке здогад.


1
не відповім на технічні аспекти питання, але все ще в контексті: softwareengineering.stackexchange.com/a/63145/13899
ньютопійський

74
Можливо, вам сподобається виконання
полковник Паніка


3
Ось що я вважаю правильною відповіддю. Ви не обробляєте Java, оскільки думаєте про програмування як sysadmin, а не програміст. Для вас програмне забезпечення - це те, що ви використовуєте для досягнення певного завдання найбільш доцільним чином. Я пишу Java-код вже 20 років, і деякі проекти, над якими я працював, потребують команди на 20, 2 роки. Java не є заміною Python, ні навпаки. Вони обидва роблять свою роботу, але їхня робота зовсім інша.
Річард

1
Причина того, що Java є де-факто стандартом, полягає в тому, що вона просто права. Його створили правильно, вперше, модними були серйозні люди, які мали бороди до бороди. Якщо ви звикли збивати сценарії оболонки для обробки файлів, Java здається нестерпно роздутою. Але якщо ви хочете створити подвійний надлишковий серверний кластер, здатний обслуговувати 50 мільйонів людей на день, підкріплений кластеризованим кешуванням Redis, 3 білінговими системами та кластером баз даних 20-фунтових баз даних Oracle. Ви не збираєтеся використовувати python, навіть якщо матимете доступ база даних в Python на 25 рядків менше коду.
Річард

Відповіді:


237

Мова Java

Я вважаю, що всі ці відповіді не мають сенсу, намагаючись приписувати намір способу роботи Java. Багатослівність Java не випливає з того, що вона орієнтована на об'єкт, оскільки Python та багато інших мов надто ще мають синтаксис. Багатослівність Java також не походить від його підтримки модифікаторів доступу. Натомість це просто те, як розроблялася та розвивалася Java.

Java спочатку була створена як дещо вдосконалений C з OO. Як така Java має синтаксис 70-х років. Крім того, Java дуже консервативна щодо додавання функцій, щоб зберегти відсталу сумісність і дати їй можливість витримати тест на час. Якби Java додала модні функції, такі як XML-літерали, у 2005 році, коли XML був усією гнівом, мова була б роздута особливостями привидів, про які ніхто не піклується, і які обмежують її еволюцію через 10 років. Тому Java просто не вистачає багато сучасного синтаксису, щоб висловлювати поняття терміново.

Однак, нічого принципового не заважає Java прийняти цей синтаксис. Наприклад, Java 8 додала лямбда та посилання на методи, що значно зменшує багатослівність у багатьох ситуаціях. Java може аналогічно додати підтримку компактних оголошень типу даних, таких як регістри Scala. Але Java просто цього не зробила. Зауважте, що власні типи значень знаходяться на горизонті, і ця функція може ввести новий синтаксис для їх оголошення. Я думаю, ми побачимо.


Культура Java

Історія розвитку Java Java в значній мірі привела нас до культури, яку ми бачимо сьогодні. В кінці 90-х - початку 00-х років Java стала надзвичайно популярною мовою для бізнес-додатків на стороні сервера. Тоді ці програми були значною мірою написані спеціально і включали багато складних проблем, таких як API HTTP, бази даних та обробка каналів XML.

У 00-ті роки стало зрозуміло, що багато з цих додатків мають багато спільного та рамки для управління цими проблемами, як Hibernate ORM, Xerces XML-аналізатор XSP, JSP та сервлет API та EJB. Однак, хоча ці рамки зменшували зусилля для роботи в тій галузі, яку вони встановили для автоматизації, вони вимагали конфігурації та координації. У той час з будь-якої причини було популярно писати рамки для задоволення найскладнішого випадку використання, і тому ці бібліотеки були складними для створення та інтеграції. І з часом вони ставали все складнішими, оскільки вони накопичували риси. Розвиток підприємства Java поступово ставав дедалі більше з’єднанням сторонніх бібліотек і менше про написання алгоритмів.

Врешті-решт, втомлива конфігурація та управління інструментами підприємства стали досить болючими, що рамки, особливо передусім Spring Framework, прийшли разом із управлінням. Ви можете помістити всю конфігурацію в одне місце, теорія пішла, і інструмент конфігурації потім налаштував би шматки та з'єднав їх. На жаль, ці «каркасні рамки» додали більшої абстракції та складності поверх усієї кулі воску.

За останні кілька років популярність зросла більш легким бібліотекам. Тим не менше, ціле покоління Java-програмістів визріло під час зростання важких корпоративних рамок. Їх моделі для наслідування, ті, хто розробляє рамки, писали фабричні фабрики та навантажувачі бобів для налаштування проксі. Їм доводилося щодня конфігурувати та інтегрувати ці жахливі місця. В результаті культура громади в цілому наслідувала приклад цих рамок і схильна до надмірної інженерії.


15
Найцікавіше - це те, що інші мови, схоже, набирають певних "явамізмів". Функції PHP OO сильно надихають Java, і в даний час JavaScript часто критикують за величезну кількість фреймворків і те, як важко зібрати всі залежності і запустити нову програму.
marcus

14
@marcus, можливо, тому, що деякі люди нарешті дізналися справу "не винаходити колесо"? Залежності - це ціна, щоб не винаходити колесо врешті-решт
Walfrat

9
"Terse" часто перекладається на таємничий, "розумний" , кодовий гольф-іш однопластовий.
Тулен Кордова

7
Продумана, добре написана відповідь. Історичний контекст. Відносно непомітний. Чудово зроблено.
Чемзмайстер

10
@ TulainsCórdova Я погоджуюся, що нам слід "уникати ... розумних хитрощів, як чума" (Дональд Кнут), але це не означає писати forцикл, коли mapадекватніше (і читабельніше). Є щасливий засіб.
Брайан Маккотчон

73

Я вважаю, що у мене є відповідь на один із питань, які ви піднімаєте, які не були порушені іншими.

Java оптимізує для виявлення помилок програміста під час компіляції, не роблячи жодних припущень

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

Причина цієї філософії полягає в тому, що програміст - лише людина. Те, що ми пишемо, - це не завжди те, що ми насправді маємо намір робити. Мова Java намагається пом’якшити деякі з цих проблем, змушуючи розробника завжди чітко заявляти про їх типи. Це лише спосіб подвійної перевірки, що написаний код насправді робить те, що було призначено.

Деякі інші мови підштовхують цю логіку ще більше, перевіряючи передумови, пост-умови та інваріанти (хоча я не впевнений, що вони роблять це під час компіляції). Це ще більш крайні способи для програміста змусити компілятора двічі перевірити власну роботу.

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

У Java є два способи зробити це:

  1. Використовуйте в Triplet<A, B, C>якості повертається типу (який на самому справі має бути java.util, і я не можу пояснити , чому це не так . Тим більше JDK8 ввести Function, BiFunction, Consumer, BiConsumerі т.д. ... Це тільки здається , що Pairі по Tripletкрайней мере , буде мати сенс. Але я відволікся )

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

В обох цих випадках компілятор може гарантувати, що ваша функція повертає декларовані типи і що абонент усвідомлює тип кожного повернутого поля та використовує їх відповідно.

Деякі мови надають одночасно статичну перевірку типу І висновок типу, але це залишає відкритими двері для тонкого класу проблем невідповідності типу. Якщо розробник мав намір повернути значення певного типу, але насправді повертає інший, і компілятор STILL приймає код, оскільки трапляється, що за спільної випадковості і функція, і абонент використовують лише методи, які можна застосувати як до призначеного та фактичні типи.

Розглянемо щось подібне в Typescript (або тип потоку), де використовується умовивід типу замість явного введення тексту.

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об'єкт, що було б набагато більш чітко, ніж обидва вищевказані випадки.


38
Заявивши, що Java оптимізує правильність коду, може бути достовірним моментом, але мені здається (програмування безлічі мов, останнім часом трохи Java), що мова або виходить з ладу, або вкрай неефективна при цьому. Зрозуміло, Java стара, і багато речей тоді ще не існувало, але є сучасні альтернативи, які, напевно, забезпечують кращу перевірку правильності, як, наприклад, Haskell (і наважуся сказати? Іржа чи Іди). Вся незворушність Java для цієї мети непотрібна. - І до того ж , це не зовсім пояснити культуру, але Соломонів показав , що культура є BS в будь-якому випадку.
tomsmeding

8
Цей зразок не демонструє, що висновок типу є проблемою лише в тому, що рішення JavaScript дозволити неявні перетворення на динамічній мові є дурним. Будь-яка нормальна динамічна мова або мова з статичною рекламою з типовою різницею не дозволила б цього зробити. Як приклад для обох видів: python би викинув час виконання, за винятком, Haskell не збирається.
Voo

39
@tomsmeding Варто зазначити, що цей аргумент особливо нерозумний, тому що Haskell був на 5 років довше, ніж Java. У Java може бути система типів, але вона навіть не мала перевірити правильність правильності, коли вона була випущена.
Олексій Король

21
"Java оптимізує для виявлення помилок у часі компіляції" - Ні. Він надає це, але, як зазначають інші, він, безумовно, не оптимізує його в жодному сенсі цього слова. Крім того, цей аргумент абсолютно не пов'язаний з питанням ОП, оскільки інші мови, які насправді оптимізуються для виявлення помилок під час компіляції, мають набагато більш легкий синтаксис для подібних сценаріїв. Завершене багатослів’я Java не має абсолютно нічого спільного з його перевіркою під час компіляції.
Конрад Рудольф

19
@KonradRudolph Так, ця відповідь є абсолютно безглуздою, хоча я гадаю, що це емоційно привабливо. Я не впевнений, чому у нього стільки голосів. Haskell (або, можливо, навіть значно більше, Idris) оптимізує набагато більше для коректності, ніж Java, і має кортежі з легким синтаксисом. Більше того, визначення типу даних є однолінійним, і ви отримуєте все, що отримала б версія Java. Ця відповідь є приводом для поганого дизайну мови та непотрібного багатослів’я, але це звучить добре, якщо вам подобається Java та не знайомі з іншими мовами.
Олексій Король

50

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

  • Витрати навколо розвитку між двома подібні повітрям у довгій повітряній кулі для тварин. Можна видавити одну частину повітряної кулі, а іншу частину набрякнути. Пітон прагне здавити ранню частину. Java видавлює більш пізню частину.
  • Java все ще не має деяких функцій, які б зняли частину цієї ваги. Java 8 зробила велику кількість зубців у цьому, але культура не повністю засвоїла зміни. Java може використовувати ще кілька речей, таких як yield.

Java "оптимізує" для високоцінного програмного забезпечення, яке буде підтримуватися багато років великими колективами людей. У мене був досвід писати речі на Python і дивитись на це через рік, і мене спантеличив власний код. На Java я можу подивитися на крихітні фрагменти коду інших людей і миттєво знати, що це робить. У Python ви не можете цього реально зробити. Це не те, що краще, як вам здається, вони просто мають різні витрати.

У конкретному випадку, який ви згадуєте, кортежі відсутні. Просте рішення - створити клас із суспільними цінностями. Коли вперше вийшла Java, люди робили це досить регулярно. Перша проблема з цим полягає в тому, що це головний біль. Якщо вам потрібно додати певну логіку або безпеку потоку або хочете скористатися поліморфізмом, вам, як мінімум, потрібно торкнутися кожного класу, який взаємодіє з цим об'єктом 'tuple-esque'. У Python для цього є такі рішення, як __getattr__etc., тому це не так страшно.

Однак навколо цього є деякі шкідливі звички (ІМО). У цьому випадку, якщо ви хочете кортеж, я сумніваюся, чому ви зробили б його об'єктом, що змінюється. Вам потрібні лише геттери (зі сторони, я ненавиджу конвенцію 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;
  }
}

Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
maple_shaft

2
Я можу запропонувати вам також поглянути на Scala щодо "сучасніших" функцій Java ...
MikeW

1
@MikeW Я фактично розпочав свою роботу з Scala ще в ранніх випусках і деякий час був активним на форумах Scala. Я думаю, що це велике досягнення в мовному дизайні, але я прийшов до висновку, що це насправді не те, що я шукав, і що я був прямим прив’язкою до цієї громади. Мені варто ще раз поглянути на це, тому що воно, мабуть, значно змінилося з того часу.
JimmyJames

Ця відповідь не стосується аспекту приблизного значення, піднятого ОП.
ErikE

@ErikE Слова "приблизне значення" не відображаються в тексті запитання. Якщо ви можете вказати на конкретну частину питання, до якого я не звертався, я можу спробувати це зробити.
JimmyJames

41

Перш ніж ви занадто злий на Яву, будь ласка, прочитайте мою відповідь в іншому дописі .

Однією із ваших скарг є необхідність створити клас просто для повернення деякого набору значень як відповіді. Це поважне занепокоєння, на мою думку, показує, що ваші інтуїції програмування є правильними! Однак я думаю, що інші відповіді не вистачають позначки, дотримуючись примітивної анти-моделі одержимості, яку ви взяли на себе. І у 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, що він є ціннісним типом. Деяке тестування покаже, чи має перехід на структуру перевагу від продуктивності роботи (можливо).


9
Я думаю, це моя улюблена відповідь. Я вважаю, що коли я просуваю подібний шалений утримувач такого класу в щось доросле, воно стає домом для всіх пов’язаних з цим обов'язків і зингу! все стає чистішим! І зазвичай водночас я дізнаюся щось цікаве про проблемний простір. Очевидно, є вміння уникати надмірної інженерії ... але Гослінг не був дурним, коли він залишив синтаксис кортежу, як і у GOTO. Ваше заключне слово чудове. Дякую!
SusanW

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

@neontapir Так, дякую! Я знав, що це ім’я! :-) Хоча я мушу сказати, що я віддаю перевагу, коли концепція домену органічно з'являється із натхненного трохи рефакторингу (як це!) ... це трохи схоже на те, коли ви вирішите свою проблему з гравітацією 19 століття, відкривши Нептун .. .
SusanW

Рефакторинг , щоб видалити примітивну одержимість може бути відмінним способом , щоб розкрити поняття предметної області!

Я не на всіх чарівному синтаксичної цукру в C # , так що якщо щось НЕ вистачає, дайте мені знати.
JimmyJames

24

Обидва Python і Java оптимізовані для супроводу відповідно до філософією своїх дизайнерів, але вони мають дуже різні уявлення про те, як досягти цього.

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

Java (традиційно) є мовою ОО на основі єдиної парадигми, яка оптимізує для чіткості та послідовності - навіть ціною більш багатослівного коду.

Кортеж Python - це структура даних із фіксованою кількістю полів. Такої ж функціональності можна досягти звичайним класом із чітко оголошеними полями. У Python природно надати кортежі як альтернативу класам, оскільки це дозволяє значно спростити код, особливо завдяки вбудованій синтаксичній підтримці кортежів.

Але це насправді не відповідає культурі Java для надання таких ярликів, оскільки ви вже можете використовувати явні декларовані класи. Не потрібно вводити різного роду структуру даних лише для збереження деяких рядків коду та уникнення деяких декларацій.

Java надає перевагу єдиній концепції (класам), що послідовно застосовується з мінімальним синтаксичним цукром для особливих випадків, тоді як Python пропонує безліч інструментів і безліч синтаксичних цукрів, щоб ви могли вибрати найбільш зручне для будь-якої конкретної мети.


+1, але як ця сітка зі статично типовими мовами з класами, наприклад, Scala, яка все-таки знайшла необхідність вводити кортежі? Я думаю, що кортежі - це лише влучне рішення в деяких випадках, навіть для мов з класами.
Андрес Ф.

@AndresF. Що ви маєте на увазі під mesh? Scala - це мова, що відрізняється від Java та має різні принципи дизайну та ідіоми.
ЖакB

Я мав на увазі це у відповідь на ваш 5-й абзац, який починається з "але це насправді не відповідає культурі Java [...]". Дійсно, я погоджуюся, що багатослів’я є частиною культури Java, але бракувати кортежі не може, тому що вони "вже використовують явно оголошені класи" - адже Scala також має явні класи (і вводить більш стислі класи справ ), але це також дозволяє кортежі! Зрештою, я думаю, що, ймовірно, Java не вводила кортежі не лише тому, що класи можуть досягати того ж (з більшою безладністю), а й тому, що його синтаксис повинен бути знайомий програмістам C, а у C не було кортежів.
Андрес Ф.

@AndresF: Scala не має відрази до кількох способів поведінки, вона поєднує в собі функції як функціональної парадигми, так і від класичної ОО. Це ближче до філософії Пітона.
ЖакБ

@AndresF: Так, Java почалася з синтаксису, близького до C / C ++, але багато спростила. C # почався як майже точна копія Java, але додав багато функцій протягом багатьох років, включаючи кортежі. Тож це дійсно різниця у філософії мовного дизайну. (Пізніші версії Java здаються менш догматичними. Функція вищого порядку спочатку була відхилена, оскільки ви могли робити те саме, що і класи, але тепер вони були введені. Тож, можливо, ми спостерігаємо зміну філософії.)
JacquesB

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). І саме тому ці практики багато використовуються. Тож якщо ви хочете дізнатися про практику, краще зрозуміти, чому вони тут для кращого використання їх.

Нарешті: я використовую геттери, тому що використовую багато інструментів серіалізації, але я їх також не пишу; Я використовую lombok: я використовую ярлики, надані моїм IDE.


9
Ще є цінність у розумінні загальних ідіом, які ви зустрічаєте на різних мовах, оскільки вони стають доступнішим фактичним стандартом. Ці ідіоми, як правило, підпадають під заголовок "кращі практики" з будь-якої причини.
Берін Лорич

3
Гей, розумне рішення проблеми. Здається, досить просто написати клас (/ структуру) з кількома громадськими членами замість повномасштабного надпроектованого рішення OOP з [gs] ефірами.
tomsmeding

3
Це призводить до розквіту, оскільки, на відміну від Python, пошук або коротше і елегантніше рішення не рекомендується. Але перевага полягає в тому, що роздуття буде більш-менш однаковим для будь-якого розробника Java. Тому існує ступінь вищої ремонтопридатності, оскільки розробники взаємозамінні, і якщо два проекти приєднуються або дві команди ненавмисно розходяться, є менша різниця стилів для боротьби.
Михайло Рамендік

2
@MikhailRamendik Це компроміс між YAGNI та OOP. Православний ООП каже, що кожен окремий об'єкт повинен бути замінним; Єдине, що має значення, це публічний інтерфейс об'єкта. Це дозволяє писати код, менш орієнтований на конкретні об'єкти, а більше на інтерфейси; а оскільки поля не можуть бути частинами інтерфейсу, ви їх ніколи не виставляйте. Це може значно полегшити підтримку коду в довгостроковій перспективі. Крім того, це дозволяє запобігти недійсним станам. У вашому оригінальному зразку можливий об'єкт, що є "<ms" і "s", які взаємовиключні. Це погано.
Луань

2
@PeterMortensen Це бібліотека Java, яка робить багато досить непов’язаних речей. Хоча це дуже добре. Ось функції
Майкл

11

Про ідіоми Java взагалі:

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

Ява повинна бути легкою для вивчення початківців. Чим явніші речі, тим важче пропустити важливі деталі. Буває менше магії, яку новачкам буде важко зрозуміти.


Що стосується вашого конкретного прикладу: аргумент для окремого класу такий: якщо ці три речі досить сильно пов'язані між собою, що вони повертаються як одне значення, варто назвати цю "річ". А введення назви для групи речей, структурованих загальнодоступним способом, означає визначення класу.

Можна зменшити котельну плиту за допомогою таких інструментів, як Lombok:

@Value
class MyTuple {
    long value_in_milliseconds;
    boolean is_in_seconds;
    boolean is_under_1ms;
}

5
Також є проект Google AutoValue Google: github.com/google/auto/blob/master/value/userguide/index.md
Іван

Ось чому програми Java можна відносно легко підтримувати!
Thorbjørn Ravn Andersen

7

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

  1. Код бібліотеки пишеться один раз, але використовується набагато частіше. Хоча приємно мінімізувати накладні витрати на написання бібліотеки, мабуть, доцільніше в довгостроковій перспективі писати таким чином, що мінімізує накладні витрати на використання бібліотеки.
  2. Це означає, що типи самодокументування чудові: назви методів допомагають зрозуміти, що відбувається і що ви отримуєте з об’єкта.
  3. Статичне введення тексту - дуже корисний інструмент для усунення певних класів помилок. Це, звичайно, не виправляє все (люди люблять жартувати з приводу Haskell, що як тільки ви отримаєте систему типу прийняти ваш код, це, мабуть, правильно), але це робить дуже легко зробити певні види неправильних речей неможливими.
  4. Написання коду бібліотеки стосується конкретизації договорів. Визначення інтерфейсів для аргументів та типів результатів дозволяє чіткіше визначати межі ваших контрактів. Якщо щось приймає або виробляє кортеж, там не сказано, чи це такий кортеж, який ви насправді повинні отримувати чи виготовляти, і існує дуже мало способів обмеження такого загального типу (чи має він навіть потрібну кількість елементів? вони того типу, якого ви очікували?).

"Структуруйте" класи з полями

Як згадували інші відповіді, ви можете просто використовувати клас із загальнодоступними полями. Якщо ви зробите ці остаточні, то ви отримаєте незмінний клас, і ви ініціалізуєте їх з конструктором:

   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 і milis завжди мають мати деяке значення, навіть якщо 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;
            }
         };
      }
   }

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

З такою структурою, як результат цього розбору, контракт вашого аналізатора дуже чітко визначений. У 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();
            }
         };
      }
   }

Це стане набагато більшим кодом, але його потрібно написати лише один раз, і (припускаючи, що ви правильно задокументовані речі), люди, які в кінцевому підсумку використовують ваш код, не повинні здогадуватися, що означає результат, і не можу випадково робити такі речі, як result[0]коли вони означають result[1]. Ви все одно можете створювати екземпляри досить лаконічно, і витяг з них даних теж не так важкий:

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

Зауважте, що ви насправді можете зробити щось подібне і підходом до класу. Просто вкажіть конструктори для різних випадків. У вас все ще виникає питання про те, до чого ініціалізувати інші поля, і ви не можете перешкодити доступу до них.

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

Ви пишете ці структури лише один раз, але ви створюєте їх багато разів, тому ви все одно хочете цього стислого створення (яке ви отримуєте). Статичне введення гарантує, що дані, які ви отримуєте з них, - це те, що ви очікуєте.

Тепер, все, що сказано, все ще є місця, де прості кортежі чи списки можуть мати багато сенсу. Можливо, буде менше витрат на повернення масиву чогось, і якщо це так (і цей наклад є значущим, що ви визначите при профілюванні), то використання простого масиву значень всередині може мати багато сенсу. У вашому загальнодоступному API все-таки повинні бути чітко визначені типи.


Я використовував власний перелік замість TimeUnit наприкінці, тому що я просто додав UNDER1MS разом із фактичними одиницями часу. Тому зараз у мене просто два поля, а не три. (Теоретично я міг би зловживати TimeUnit.NANOSECONDS, але це було б дуже заплутано.)
Михайло Рамендік,

Я не створив геттер, але тепер я бачу, як геттер дозволив би мені винести виняток, якщо, з originalTimeUnit=DurationTimeUnit.UNDER1MS, абонент намагався прочитати значення мілісекунд.
Михайло Рамендік

Я знаю, що ви це згадали, але пам’ятайте, що ваш приклад з кортежами пітона дійсно стосується динамічних порівняно зі статично типізованими кортежами; це насправді не говорить багато про самі кортежі. Ви можете мати статично набрані кортежі, які не вдасться зібрати, як я впевнений, що ви знаєте :)
Andres F.

1
@Veedrac Я не впевнений, що ти маєш на увазі. Моя думка полягала в тому, що нормально писати код бібліотеки так багатослівно (хоча це займає більше часу), оскільки на використання коду буде витрачено більше часу, ніж на його написання .
Джошуа Тейлор

1
@Veedrac Так, це правда. Але я стверджую , що є це відмінність між кодом , який буде осмислено використовувати повторно, і код , який не буде. Наприклад, у Java-пакеті деякі класи є загальнодоступними та звикають з інших місць. Ці API повинні бути продуманими. Всередині є класи, з якими, можливо, потрібно буде лише поговорити один з одним. Ці внутрішні муфти - це місця, де швидкі та брудні конструкції (наприклад, об'єкт [], який, як очікується, має три елементи), може бути добре. Для публічного API зазвичай слід віддавати перевагу чомусь більш значущому та явному.
Джошуа Тейлор

7

Проблема полягає в тому, що ви порівнюєте яблука з апельсинами . Ви запитали, як імітувати повернення більш ніж одного значення, даючи швидкий та брудний приклад пітона з нетипізованим кортежем, і ви насправді отримали відповідь практично з одного лайнера .

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

Це теж зовсім недовго. Єдине, що вам потрібно написати, - це визначення поля. Можуть бути створені сетери, getters, hashCode та рівні. Тож має бути власне питання, чому геттери і сетери не генеруються автоматично, але це синтаксис (питання, синтаксичний цукор, можна сказати), а не культурний.

І, нарешті, ти задумуєшся над тим, щоб пришвидшити щось, що зовсім не важливо. Час, витрачений на написання класів DTO, незначний порівняно з витраченим часом на підтримку та налагодження системи. Тому ніхто не оптимізує для меншої багатослівності.


2
"Ніхто" фактично невірний - є безліч інструментів, які оптимізують для меншої багатослівності, регулярні вирази - крайній приклад. Але ви, можливо, мали на увазі "ніхто у світі Java", і читати відповідь має багато сенсу, дякую. Моя власна турбота про багатослівність - це не час, витрачений на написання коду, а час, витрачений на його читання; IDE не економить час читання; але здається, ідея полягає в тому, що в 99% разів один читає лише визначення інтерфейсу, ТО ЧОМО слід бути стислим?
Михайло Рамендік

5

Це три різні фактори, які сприяють тому, що ви спостерігаєте.

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

Мабуть, самий тривіальний - в інших мовах ви використовували кортеж. Дебати про те, чи кортежі є гарною ідеєю, насправді не в цьому суть, але в Java ви використовували більш важку структуру, тому порівняно трохи несправедливі: ви могли використовувати масив об'єктів та кастинг деяких типів.

Синтаксис мови

Чи може бути простіше оголосити клас? Я не говорю про оприлюднення полів або використання карти, але щось подібне до класів випадків Scala, які забезпечують усі переваги описаних вами налаштувань, але набагато більш стислими:

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

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

Мовна філософія

Але найважливішим фактором є те, що мова повинна забезпечувати певний спосіб мислення. Іноді це може відчувати себе гнітюче (я часто хотів підтримати певну функцію), але видаляти функції так само важливо, як і мати їх. Чи могли б ви підтримати все? Звичайно, але тоді ви також можете просто написати компілятор, який збирає кожну мову. Іншими словами, у вас не буде мови - у вас буде набір мов, і кожен проект в основному буде приймати підмножину.

Звичайно, можна написати код, який суперечить філософії мови, і, як ви зауважили, результати часто некрасиві. Навчання класу з декількома полями на Java схоже на використання var у Scala, дегенерація прологічних предикатів для функцій, виконання небезпечного PerPerformIO в haskell і т.д. Класи Java не мають бути легкими - вони не є для передачі даних навколо . Коли щось здається важким, часто плідно відступити назад і подивитися, чи є інший шлях. У вашому прикладі:

Чому тривалість відокремлена від одиниць? Існує багато бібліотек часу, які дозволяють вам оголосити тривалість - щось на зразок Тривалість (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, полягає в тому, що ви повинні використовувати їх не як контейнери даних, а як самостійні логічні комірки.


Те, що я хочу зробити з тривалістю, насправді полягає в тому, щоб скласти їх суму, а потім порівняти з іншою. Вихід не задіяний. Для порівняння потрібно враховувати округлення, тому мені потрібно знати оригінальні одиниці часу на момент порівняння.
Михайло Рамендік

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

Більш важливою частиною цього конкретного питання була більш загальна сторона речей, і з усіх відповідей картина, схоже, збирається разом.
Михайло Рамендік

@MikhailRamendik, можливо, якийсь акумулятор був би варіантом - але ти краще знаєш проблему. Дійсно, справа не в вирішенні цього конкретного питання, вибачте, якщо я трохи затримався - моя головна думка полягає в тому, що мова перешкоджає використанню "простих" даних. інколи, це допомагає вам переосмислити свій підхід - інший раз інт є просто
інтом

@MikhailRamendik Приємна річ, що стосується способу бойлера - це те, що коли ти маєш клас, ти можеш додати йому поведінку. І / або зробіть це непорушним, як ви робили (це гарна ідея більшість часу; ви завжди можете повернути новий об’єкт як суму). Врешті-решт, ваш "зайвий" клас може укласти все необхідне вам поведінку, і тоді вартість для людей, що отримують життя, стає незначною. Більше того, вони можуть вам або не знадобляться. Подумайте про використання lombok або autovalue .
maaartinus

5

Наскільки я розумію, основні причини є

  1. Інтерфейси є основним способом Java для абстрагування відібраних класів.
  2. Java може повернути лише одне значення з методу - об'єкта або масиву або нативного значення (int / long / double / float / boolean).
  3. Інтерфейси не можуть містити поля, лише методи. Якщо ви хочете отримати доступ до поля, ви повинні пройти метод - отже, геттери та сетери.
  4. Якщо метод повертає інтерфейс, ви повинні мати клас реалізації, щоб фактично повертатися.

Це дає вам "Ви повинні написати клас, щоб повернутися до будь-якого нетривіального результату", який, в свою чергу, досить важкий. Якщо ви використовували клас замість інтерфейсу, ви можете просто мати поля та використовувати їх безпосередньо, але це прив'язує вас до конкретної реалізації.


Додамо до цього: якщо вам це потрібно лише у невеликому контексті (скажімо, приватний метод у класі), добре використовувати голий запис - в ідеалі незмінне. Але це насправді насправді ніколи не повинно просочуватися в державний договір класу (або ще гірше, бібліотека!). Ви можете вирішити проти записів просто, щоб переконатися, що цього ніколи не відбудеться при майбутніх змінах коду; це часто простіше рішення.
Луань

І я погоджуюсь з переглядом "Кортежі не дуже добре працюють на Java". Якщо ви використовуєте дженерики, це дуже швидко виявиться неприємним.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Вони досить добре працюють у Scala, статично набраній мові з дженериками та кортежами. То, може, в цьому винна Java?
Андрес Ф.

@AndresF. Зверніть увагу на частину "Якщо ви використовуєте дженерики". Те, як робить це Java, не піддається складним конструкціям, на відміну від Haskell. Я не знаю Scala досить добре, щоб зробити порівняння, але чи правда, що Scala допускає кілька повернених значень? Це може допомогти дуже багато.
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Функції Scala мають єдине повернене значення, яке може бути типу кортежу (наприклад def f(...): (Int, Int), це функція, fяка повертає значення, що трапляється в наборі цілих чисел). Я не впевнений, що дженерики на Java - це проблема; зауважте, що Haskell також робить тип стирання, наприклад. Я не думаю, що є технічна причина, чому у Java немає кортежів.
Андрес Ф.

3

Я згоден з відповіддю ЖакБ на це

Java (традиційно) є мовою ОО на основі однієї парадигми, яка оптимізує для чіткості та послідовності

Але чіткість і послідовність - не кінцеві цілі, для яких слід оптимізувати. Коли ви говорите, що "python оптимізований для читабельності", ви відразу згадуєте, що кінцевою метою є "ремонтопридатність" та "швидкість розвитку".

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

Іншими словами, культура Java оптимізована для того, щоб змусити менеджерів вважати, що вони розуміють розробку програмного забезпечення.

Або, як сказав один мудрець дуже давно ,

Найкращий спосіб судити про мову - це переглянути код, написаний його прихильниками. "Radix enim omnium malorum est cupiditas" - а Java явно є прикладом програмування, орієнтованого на гроші (MOP). Як сказав мені головний прихильник Java в SGI: "Алекс, ти повинен їхати туди, де гроші". Але мені не особливо хочеться їхати туди, де гроші - там зазвичай не пахне приємно.


3

(Ця відповідь не є поясненням для Java, а натомість стосується загального питання "Для чого можуть бути оптимізовані [важкі] практики?")

Розглянемо ці два принципи:

  1. Добре, коли ваша програма робить все правильно. Ми повинні полегшити написання програм, які роблять правильно.
  2. Це погано, коли ваша програма робить неправильну справу. Нам слід ускладнити написання програм, які роблять неправильно.

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

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

Наприклад, якщо виникла помилка чи кілька годин у роботі вашої програми може призвести до загибелі людей (медичні системи, повітроплавання) або навіть просто грошей (як мільйони доларів у рекламних системах Google), ви зробите різні компроміси (не тільки на вашій мові, але і в інших аспектах інженерної культури), ніж ви б одноразово написали: це, швидше за все, схиляється до "важкої" сторони.

Інші приклади, які, як правило, роблять вашу систему "важкою":

  • Коли у вас є велика база кодів, над якою працювали багато команд протягом багатьох років, одна велика стурбованість полягає в тому, що хтось може неправильно використовувати чужий API. Функція виклику з аргументами в неправильному порядку або виклик без забезпечення певних передумов / обмежень, які вона очікує, може бути катастрофічною.
  • Як окремий випадок цього, скажімо, що ваша команда підтримує певний API чи бібліотеку, і хотіла б змінити або змінити її. Чим більше "обмежених" ваших користувачів у тому, як вони могли б використовувати ваш код, тим простіше його змінити. (Зауважте, що тут було б краще мати фактичні гарантії, що ніхто не може ним користуватися незвично.)
  • Якщо розробка розділена між декількома людьми або командами, може здатися, що одна людина чи команда "вкажуть" інтерфейс, а інші реально реалізують його. Щоб воно працювало, вам потрібно мати змогу набути певної міри впевненості, коли впровадження виконано, що реалізація насправді відповідає специфікації.

Це лише декілька прикладів, щоб дати вам уявлення про випадки, коли зробити «важкими» речі (а також важче просто швидко виписати якийсь код) може бути справді навмисним. (Можна навіть стверджувати, що якщо написання коду вимагає великих зусиль, це може змусити вас подумати більш ретельно, перш ніж писати код! Звичайно, цей рядок аргументів швидко стає смішним.)

Приклад: внутрішня система Python Google, як правило, робить такі «важкими», що ви не можете просто імпортувати чужий код, ви повинні оголосити залежність у файлі BUILD , команді, код якого ви хочете імпортувати, потрібно, щоб їх бібліотека була оголошена як видимий у вашому коді тощо.


Примітка . Все вищесказане стосується лише того, коли речі мають тенденцію "важко". Я абсолютно не стверджую, що Java або Python (або самі мови, або їх культура) роблять оптимальні компроміси для будь-якого конкретного випадку; це для вас думка. Два пов'язані посилання на такі компроміси:


1
Що з читанням коду? Там є ще один компроміс. Код, зважений на химерні випадки та рясні ритуали, важче читати; із табличкою та непотрібним текстом (та ієрархіями класів!) у вашому IDE стає важче зрозуміти, що насправді робить код. Я можу бачити аргумент, що код не обов'язково повинен бути легким для запису (не впевнений, чи згоден я з цим, але він має певні заслуги), але його, безумовно, слід легко читати . Очевидно, що складний код може бути і побудований з Java - було б нерозумно стверджувати інакше - але це було завдяки його багатослівності, чи незважаючи на це?
Андрес Ф.

@AndresF. Я відредагував відповідь, щоб зрозуміти, що мова не йде конкретно про Java (я не є великим шанувальником). Але так, компроміси під час читання коду: з одного кінця ви хочете мати можливість легко читати «що насправді робить код», як ви вже говорили. З іншого боку, ви хочете мати можливість легко бачити відповіді на такі питання: як цей код стосується іншого коду? Що найгірше може зробити цей код: який може бути його вплив на інший стан, які його побічні ефекти? Від яких зовнішніх факторів може залежати цей код? (Звичайно, в ідеалі ми хочемо швидких відповідей на обидва запитання.)
ShreevatsaR

2

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

Частина того, що впливає на рекомендацію, - це те, що вважається читабельним та підтримуваним у Python та Java, дуже відрізняється.

  • У Python кортеж - це мовна особливість.
  • І в Java, і в C # кортеж є (або буде) функцією бібліотеки .

Я згадую лише C #, тому що у стандартній бібліотеці є набір класів Tuple <A, B, C, .. n>, і це прекрасний приклад того, наскільки непрості кортежі, якщо мова не підтримує їх безпосередньо. Практично в кожному випадку ваш код стає більш читабельним і доступним для обслуговування, якщо ви добре обрали класи для вирішення проблеми. У конкретному прикладі зв'язаного питання про переповнення стека, інші значення легко виражатимуться як обчислені гетери на об'єкті повернення.

Цікавим рішенням, яке зробила платформа C #, яка забезпечує щасливу середину, є ідея про анонімні об'єкти (випущені в C # 3.0 ), які досить добре подряпають цей свербіж. На жаль, у Java ще немає еквівалента.

Поки мовні функції Java не будуть змінені, найбільш читабельним і доступним рішенням є створення виділеного об'єкта. Це пов’язано з обмеженнями в мові, які сягають її початку 1995 року. У оригінальних авторів було заплановано набагато більше мовних особливостей, які ніколи не були зроблені, а зворотна сумісність - одне з головних обмежень, пов'язаних із розвитком Java з часом.


4
В останній версії c # нові кортежі - це громадяни першого класу, а не бібліотечні класи. Щоб потрапити туди, знадобилося занадто багато версій.
Ігор Солойденко

Останні 3 абзаци можна узагальнити як "Мова X має ту особливість, яку ви шукаєте, щоб Java не мала", і я не бачу, що вони додають у відповідь. Чому згадувати C # у темі Python на Java? Ні, я просто не бачу сенсу. Трохи дивно від 20k + реп.
Олів'є Грегоар

1
Як я вже говорив, "я згадую лише C #, тому що Tuples є функцією бібліотеки", аналогічно тому, як це працювало б на Яві, якби в основній бібліотеці були Tuples. Використовувати це дуже непросто, і кращою відповіддю є майже завжди мати спеціальний клас. Хоча анонімні об'єкти чухають схожий свербіж на те, для чого створені кортежі. Без підтримки мови для чогось кращого, ви, по суті, повинні працювати з тим, що є найбільш ретельним.
Берін Лорич

@IgorSoloydenko, можливо, є надія на Java 9? Це лише одна з тих особливостей, які є логічним наступним кроком до лямбда.
Берін Лорич

1
@IgorSoloydenko Я не впевнений, що це цілком правда (громадяни першого класу) - вони, схоже, є розширеннями синтаксису для створення та відкручування екземплярів класів з бібліотеки, а мають підтримку першого класу в CLR як клас, структура, перерахунок.
Піт Кіркхем,

0

Я думаю, що одним із основних моментів використання класу в цьому випадку є те, що те, що йде разом, має залишатися разом.

Я обговорював це обговорення щодо аргументів методу: Розгляньте простий метод, який обчислює ІМТ:

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

У цьому випадку я б заперечував проти цього стилю, оскільки вага та зріст пов'язані. Метод "повідомляє" це два окремих значення, коли їх немає. Коли ви обчислили ІМТ з вагою однієї людини та зростом іншої? Це не мало б сенсу.

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

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

Те саме стосується повернення кількох значень. Якщо вони чітко підключені, тоді поверніть акуратний невеликий пакет і використовуйте об'єкт, якщо вони не повертають декілька значень.


4
Гаразд, але припустимо, у вас тоді (гіпотетичне) завдання показати графік: як ІМТ залежить від ваги для певної фіксованої висоти. Тоді ви не можете цього зробити, не створивши Особу для кожної точки даних. І, доводячи це до крайності, ви не можете створити екземпляр класу Person без дійсної дати народження та номера паспорта. А тепер що? Я не спростував btw, є поважні причини поєднання властивостей разом у якомусь класі.
Артем

1
@artem - це наступний крок, коли інтерфейси починають грати. Отже, інтерфейс з вагою людини може бути чимось подібним. Ви потрапляєте в приклад, який я дав, щоб зробити висновок про те, що все разом має бути разом.
Пітер Б

0

Відверто кажучи, культура полягає в тому, що програмісти Java спочатку прагнули виходити з університетів, де викладалися об'єктно-орієнтовані принципи та принципи стійкого програмного забезпечення.

Як Ерік у своїй відповіді говорить більше слів, ви, здається, не пишете стійкого коду. Що я бачу з вашого прикладу, є дуже незручне заплутаність.

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

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


1
Зокрема, інструмент набору foobar, який використовує Google, дозволяє використовувати два мови для вирішення: java та python. Мені цікаво, що Go - це не варіант. Я трапився на цій презентації на Go, і у неї є цікаві моменти щодо Java та Python (а також інших мов.) Наприклад, одна точка, яка виділялася: "Можливо, в результаті непрофесійні програмісти заплуталися" простота використовувати "з інтерпретацією та динамічним набором тексту". Варто прочитати.
JimmyJames

@JimmyJames щойно потрапив до слайда, що каже: "в руках експертів, вони чудові" - гарний підсумок проблеми в питанні
Том
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.