Меня зовут Денис, я тимлид команды R&D в Naumen Service Management Platform.

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

Вот для сравнения один и тот же код: один написан на Java 17, второй — на Java 21. 

Код, написанный на Java 17:

public Double convert(Object value) {
    if (value == null) {
        return null;
    }

    if (value instanceof Double) {
        return (Double) value;
    } else if (value instanceof String) {
        String stringValue = (String) value;
        if (stringValue.trim().isEmpty()) {
            return null;
        }
        try {
            return Double.parseDouble(stringValue);
        } catch (NumberFormatException e) {
            throw new AdvImportException("Cannot convert value '" + stringValue + "' to Double", e);
        }
    } else if (value instanceof Number) {
        return ((Number) value).doubleValue();
    }

    throw new AdvImportException("Cannot convert value '" + value + "' to Double");
}

Код, написанный на Java 21:

public Double convert(@Nullable Object value) {
    return switch (value) {
        case null -> null;
        case Double doubleValue -> doubleValue;
        case String stringValue when stringValue.isBlank() -> null;
        case String stringValue -> Double.parseDouble(stringValue);
        case Number numberValue -> numberValue.doubleValue();
        default -> throw new IllegalArgumentException("Can't convert value " + value + " to double");
    };
}

Чтобы осознать первый код и понять, что тут происходит, нужно пробраться сквозь все if’ы, instanceof’ы и закопаться во внутреннюю логику. Тот, что написан на Java 21, выглядит гораздо лучше: его и читать приятнее, и понимать быстрее. 

В общем, преимущества Java 21 очевидны. Но оказался ли путь миграции систем на Java 21 таким же простым и приятным? Если кратко: нет. 

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

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

Обновление на Java 20: вызов первый — изменение final static полей не работает через рефлексию

Итак, август 2023 года: Java 20 уже давно вышла, и мне пришла идея попробовать обновить наш продукт, который к тому времени уже несколько лет был на Java 17. Хотелось убедиться, что никаких проблем нет, и через пару месяцев, когда релизнится Java 21, обновление пройдет спокойно. 

Обновление на Java 20 должно было состоять из шести довольно простых шагов: 

  • sdk install java 20.x.x

  • sdk d java 20.x.x

  • change pom.xml

  • mvn clean install

  • tomcat -> cargo:run

  • profit

План был такой: устанавливаю Java 20 на свою локальную машину, делаю ее дефолтной, вношу изменения в файл pom.xml. Затем собираю проект на Java 20, запускаю его и радуюсь, что обновление прошло успешно. 

Все шло по плану до того момента, пока я не начал собирать проект на Java 20. На этом этапе сборка проекта закрашилась. Я понял, что настало время вспомнить о главных помощниках программиста, логах. Собственно, сюда я и полез. Так нашел виновника — изменение final static полей через рефлексию.

Кратко объясню, зачем нам нужно. Мы, как и многие, используем популярное ORM-решение Hibernate, но немного его хачим. А именно: докидываем во внутренний class Environment собственную реализацию BytecodeProvider. Это нужно не просто так. Наше приложение может генерировать огромное количество коротких запросов, например, getUUID, toString, hashCode и другие. Для них нам не нужно делать обращения в базу, пытаться распроксировать объект и выполнять другую потенциально тяжелую работу. Достаточно сконструировать ответ из данных, которые уже есть в аргументах запроса. Это позволяет справляться с очень большой нагрузкой в подобных местах. 

Поэтому мы написали собственную реализацию BytecodeProvider’а и подкинули ее в это поле классическим способом через рефлексию. Для этого мы временно исключили модификатор final и записали нужное нам значение в переменную. Все это прекрасно работало на Java 8, 11 и 17. На Java 20 вдруг перестало работать.  

Можно ли это сделать не через рефлексию, например, внутри самого Hibernate осуществить подобную замену, найти более простой способ? Нет, увы, на 4 и 5 Hibernate таких способов нет. 

Ниже реализация получения BytecodeProvider’а в Hibernate 4. Даже если вы подкините ему собственный providerName, Hibernate его проигнорирует и вернет свою реализацию BytecodeProvider’а. В 5 и начальных версиях 6 Hibernate ситуация аналогичная. Только вместо javassist’а теперь используется byte-buddy. Кстати, в свежих версиях 6 Hibernate эту проблему пофиксили — снова появилась возможность подкидывать свою реализацию BytecodeProvider’а. Вот тут и вот тут на GitHub можно узнать подробнее.

public static BytecodeProvider buildBytecodeProvider(Properties properties) {
    String provider = ConfigurationHelper.getString(BYTECODE_PROVIDER, properties, defaultValue: "javassist");
    LOG.bytecodeProvider(provider);
    return buildBytecodeProvider(provider);
}

private static BytecodeProvider buildBytecodeProvider(String providerName) {
    if ("javassist".equals(providerName)) {
        return new org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl();
    }
    LOG.unknownBytecodeProvider(providerName);
    return new org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl();
}

Итак, отправился в интернет искать причину — почему наш обходной путь на Java 20 перестал работать. Виновником оказался JEP 416: Reimplement Core Reflection with Method Handles. Его комитер Мэнди Чанг в обсуждении к ее пул-реквесту заявила, что возможность править final static поля через рефлексию была хаком, который не доходили руки починить. 

Более того, этот JEP закатился еще в Java 18. Так что начиная с Java 18 обращаться к final static полям через рефлексию больше нельзя. В итоге решили не искать еще более изощренные пути, а просто взяли и форкнули Hibernate, выкинув тот самый модификатор final из этого поля. Мы практически не изменили наш код, но проблема была решена. Пересобрав Hibernate и внеся очередные небольшие изменения в pom-файл, я продолжил двигаться по плану.

Обновление на Java 20: вызов второй — JEP 418 сломал систему

Снова попытался собрать проект, снова словил «‎синий экран» и снова полез в логи. На этот раз наткнулся на следы JEP 418: Internet-Address Resolution SPI. Этот JEP тоже закатился в Java 18. Здесь была проведена большая оптимизация механизма резолвинга адресов. Понадобилось это для виртуальных потоков, в частности, для проекта Loom, потому что до этого JEP’а работа с нативными вызовами была не совсем корректна. В ходе этой оптимизации ребята отломали наш древний хак. У нас был вот такой код:

private void initAddressResolver() {
    try {
        Class<?> clazz = Class.forName("java.net.InetAddressImplFactory");
        Method create = clazz.getDeclaredMethod("create");
        create.setAccessible(true);
        addressResolver = create.invoke(null);
        Class<?> inetAddressClass = addressResolver.getClass();
        getHostMethod = inetAddressClass.getMethod("getHostByAddr", byte[].class);
        getHostMethod.setAccessible(true);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Мы брали внутренний InetAddressImplFactory class, дергали его метод create, чтобы получить текущий addressResolver — у него вызывали нативный метод getHostByAddr:

final class Inet4AddressImpl implements InetAddressImpl {

    public native String getLocalHostName() throws UnknownHostException;

    public InetAddress[] lookupAllHostAddr(String hostname, LookupPolicy lookupPolicy) throws UnknownHostException {
        if ((lookupPolicy.characteristics() & IPV4) == 0) {
            throw new UnknownHostException(hostname);
        }
        return lookupAllHostAddr(hostname);
    }

    private native InetAddress[] lookupAllHostAddr(String hostname) throws UnknownHostException;

    public native String getHostByAddr(byte[] addr) throws UnknownHostException;

    private native boolean isReachable0(byte[] addr, int timeout, byte[] ifaddr, int ttl) throws IOException;

}

Зачем мы так делали? Все просто — этот код был написан чуть ли не на заре проекта, в году 2010, вероятно, еще на Java 5. А в то время сделать подобную логику иным способом, видимо, не получилось. Поэтому этот старый код спокойно кочевал из версии в версию Java и прекрасно работал более 10 лет.   

Но пришел новый JEP и все нам поломал. Благо исправление оказалось простейшим. Мы не только Java обновили, но еще и от легаси-кода избавились. 

Вместе с JEP 418 появилась возможность дернуть статистический метод getHostName, внутри которого дернется lookupByAddress — в нем вызывается нужный нам getHostByAddr. Так что мне оставалось выкинуть пару десятков строк нашего старого кода и заменить его на одну строчку:

InetAddress inetAddress = ...
inetAddress.getHostName();

Обновление на Java 20: вызов третий — Degrade Thread.stop() крашнул код

Наконец, после этих двух исправлений проект на Java 20 собрался. Но через пару секунд после запуска меня снова ждал «‎синий экран»‎. 

Причина оказалась в улучшении, которое закатилось в Java 20, —  Degrade Thread.stop(). В этом фиксе ребята из JDK сделали то, что давно обещали. Теперь все вызовы, которые позволяли управлять тредами из Java приложения стали forRemoval и выбрасывают UnsupportedOperationException, что делает работу с ними абсолютно бесполезной. 

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

После исправления этой проблемы проект на Java 20 запустился. Оставалось лишь закатить в продукт те исправления, которые я сделал, и ждать релиза Java 21.  

Обновление на Java 21: вызов первый — Ignite 2.15 не поддерживает новую версию

19 сентября состоялся релиз Java 21, но в этот день, конечно, мы обновляться не стали. Решили дождаться, когда Java 21 выйдет на всех платформах, появятся Docker-образы и релизнится версия Java 21.0.1. За это время мы подготовили свою тестовую систему и инфраструктуру. В общем, потратили еще примерно 2-3 недели на ожидание. 

Когда все было готово, я приступил к продуктовому обновлению нашего продукта на Java 21. Ориентировался на чек-лист, который использовал при обновлении на Java 20. Думал, в этот раз никаких проблем не будет. Так что установил Java 21, сделал ее дефолтной, внес изменения в pom-файл, собрал проект, начал его запускать, и… снова «‎синий экран»‎.

Все дороги снова вели в логи. Нашел виновника — тонкий клиент Ignite версии 2.15. Оказалось, что он пока еще не поддерживает Java 21. 

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

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

Ignite 2.16, кстати, релизнулся в декабре 2023 года.

В итоге я удалил пять строчек из pom-файла и без проблем запустил проект. Пришло время его тестировать. Отправил ветку в тестирующую систему, ожидая, что спустя некоторое время, получу заветную зеленую галочку, но нет, снова «‎синий экран»‎.

Обновление на Java 21: вызов второй — java.util.SequencedCollection#getFirst завалил GWT

Снова полез в логи и обнаружил вот такую «‎прелесть»‎ — StackOverflowException из глубин GWT кода.

GWT или Google Web Toolkit, если кто не в курсе, — это фреймворк, который позволяет писать фронтендовую часть приложения на чистой Java. На Java пишем логику, которая должна отрабатывать в браузере, в момент компиляции Java-код трансформирует в JavaScript, и уже он отрабатывает в браузере. А так как весь наш фронт в тот момент был написан как раз на GWT, получить такую ошибку из его глубин было неприятно. Ведь, напомню, на Java 20 все отлично работало. 

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

В Java 21, как вы знаете, появился новый интерфейс SequencedCollection, который принес с собой несколько удобных и полезных методов. Один из них, — getFirst() — и оказался тем самым виновником, что закрашил GWT. Вместе с ишью уже был готов баг-фикс, причем состоящий буквально из нескольких строк.

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

После чего я пересобрал GWT, внес очередные изменения в pom-файл и продолжил тестировать.

Обновление на Java 21: вызов третий — PMD зафейлился

В этот раз результат был гораздо лучше. Еще не успех, но уже значительное продвижение к нему. Вместо зеленой галочки от тестовой системы я получил «‎оранжевую»‎, значит большинство тестов пройдено, но проблемы все еще есть. И этой проблемой оказался PMD — крутейший статический анализатор кода. Главная особенность — возможность его расширения. Вы можете самостоятельно написать кастомные правила, которые будут покрывать особенности вашего кода. Мы активно используем PMD: примерно 20-30 кастомных правил чекают нужные нам особенности.  

Итак, я начал разбираться, почему PMD зафейлился. Выяснил, что Java 21 еще не поддерживается самой последней на тот момент версией 6.55. Но, что очень удачно, уже был заявлен релиз-кандидат 7 версии, в котором есть поддержка Java 21.

Однако для запуска PMD мы используем maven-pmd-plugin. И его последняя на тот момент версия 3.21.2, к огромному сожалению, еще не была адаптирована под работу с 7 версией PMD. То есть поддержка Java 21 вроде как есть, потому что появился релиз-кандидат PMD, который обещал, что все будет работать, но запустить его нормально в проекте текущими средствами невозможно. 

Тут замаячила неприятная перспектива делать непростой форк этого плагина, затачивать его под наш продукт и под новый PMD, затем долго тестировать и разбираться, что не работает. Но практически ничего из этого мне делать не пришлось. Разработчик из Мюнхена сделал все за меня. Андреас Дэнжел — основной комитер PMD и Maven PMD Plugin — еще летом 2023 года сделал экспериментальную ветку Maven PMD Plugin’а с поддержкой 7 PMD. Так что мне оставалось просто взять эту веточку и адаптировать ее под наши условия.

В результате сам PMD отработал: запустил свои встроенные правила и попробовал проверить наш код. Но абсолютно все наши кастомные правила отказались работать. В конце концов, изучив объемный Migration Guide с 6 на 7 PMD и разобравшись со всеми изменениями в AST, мне удалось справиться и с этой проблемой.

Но, надо сказать, борьба с PMD оказалась самым серьезным вызовом на пути обновления нашей платформы с Java 17 на 21. Когда же, наконец, все тесты были пройдены, наш проект был полностью адаптирован под Java 21.

Обновление на Java 20 и Java 21: итоги

Вот, что мы сделали, обновляя нашу платформу с Java 17 на Java 21:

  • форкнули Hibernate ради хака с BytecodeProvider; 

  • избавились от legasy со InetAddress Resolution;

  • переписали работу с потоками; 

  • выкинули Ignite; 

  • обновили несколько библиотек: byte-buddy, spotbugs и др.; 

  • форкнули Maven PMD Plugin, обновили PMD и переписали все наши правила. 

Но последнее к настоящему времени снова изменилось. Весной 2024 релизнулась официальная версия 7 PMD и официальный Maven PMD Plugin, который поддерживает работу 7 PMD. Так что в текущей версии мы уже используем нативную реализацию плагина вместо собственного форка.

Ну и пришло время главного вопроса: стоит ли обновляться? Или можно спокойно жить на Java 17, 11 или даже 8 и ничего не делать? 

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

На весеннем Naumen Java Meetup #3 я подробнее рассказал о пути миграции на Java 21.

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

Комментарии (26)


  1. Samhuawei
    18.06.2024 14:21

    Хорошие бюджеты в  Naumen Service Management Platform. У Ситибанка переход на следующую версию Java занимает по пять лет по причине отстутствия денег на оплату работы программистов вне беклога.


  1. rsashka
    18.06.2024 14:21
    +4

    А зачем переходить, если все и так работает?


    1. blognaumen Автор
      18.06.2024 14:21
      +2

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

      В финальной части доклада на ютубе более подробно рассказал, почему полезно обновление. 


      1. rsashka
        18.06.2024 14:21

        позволяют проводить подобные обновления

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


  1. panzerfaust
    18.06.2024 14:21
    +4

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

    Тут еще такой момент, что становится тяжело искать желающих работать в откровенно легасевом проекте. Лично я тупо не хочу идти в "молодой и динамично развивающийся продукт" на java 8. Я в своей карьере пережил много миграций: с 8 на 11, с 11 на 17, с 17 на 21, со Spring Boot 2 на 3. И еще с AngularJS на Angular9. Это выматывает. Приходить на java 8 и повторять этот квест нет желания. А пользоваться адекватными фичами инструмента все равно хочется.

    Кстати, в моем текущем проекте переход с 19 на 21 и на второй компилятор Kotlin прошел на удивление быстро и спокойно за 1 спринт. Но это потому, что у нас зависимостей минимум.

    Так что респект, что находите силы и время на это.


    1. Sigest
      18.06.2024 14:21

      А расскажите про опыт перехода на котлин 2, если там есть что-то интересное конечно


    1. Semenych
      18.06.2024 14:21

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

      Тот самый последный дремучий микросервис который не меняется 8 лет, можно на 7-ой яве оставить. Фиг с ним


  1. agoncharov
    18.06.2024 14:21

    Все что было после релиза java 8 - косметика


    1. tuxi
      18.06.2024 14:21

      Вот да, переход на 8 был эпичным, а на 11-ю уже детсад по сути


    1. Skipy
      18.06.2024 14:21
      +1

      Честно говоря, я уже от восьмерки плакал. Чтобы так откровенно начать убивать язык - это надо постараться. Все молятся на "Чистый код" Мартина, но требования второй главы в 8 нарушены чуть более чем полностью.

      Что, кстати, проявляется и в этой статье - по мнению автора код под 21 читать проще и приятнее, а по мне это трэш и содомия. Полное несоответствие исходной семантике языка. Это даже не речь мастера Йоды, это хаотично добавленные слова, которые ты знаешь, но смысл всего предложения от тебя ускользает. Смотришь и не понимаешь, приходится прилагать усилия для перевода, несмотря на использование языка с 1996 года.

      А код под 17 читать сложно не потому, что это 17, а потому что руки надо оторвать тому, кто его так отформатировал. В норме этот код читается намного проще:

      public Double convert(Object value){
          if (null == value){
              return null;
          }
          if (value instanceof Double){
              return (Double)value;
          }
          if (value instanceof String){
      	    String sValue = (String) value;
      		return (sValue.trim.isEmpty()) ? null : Double.parseDouble(sValue)
          }
      	if (value instanceof Number){
              return ((Number)value).doubleValue();
          }
          throw new AdvImportException("Can't convert value " + value + " to double");
      }


      1. agoncharov
        18.06.2024 14:21
        +2

        Так более того, в 17 джаве можно избавиться от явного каста после instanceof с объявлением переменной


      1. Lewigh
        18.06.2024 14:21

        Честно говоря, я уже от восьмерки плакал. Чтобы так откровенно начать убивать язык - это надо постараться. Все молятся на "Чистый код" Мартина, но требования второй главы в 8 нарушены чуть более чем полностью.

        Не могли бы уточнить к каким изменениям у Вас такие претензии?


        1. aleksandy
          18.06.2024 14:21
          +2

          Судя по

          использование языка с 1996 года

          основная претензия в том, что "раньше трава была зеленее".


        1. Skipy
          18.06.2024 14:21

          Лямбды. Анонимные функции. Всё, что привело к потере именования кода. Вторая глава "Чистого кода" - она про именование. Если коротко - "именуйте всё, что можно именовать". Когда в метод передается функция двух целых параметров - ты принципиально не можешь сделать предположение о том, что она делает. Это надо целенаправленно разбираться. Если передается Comparator<Integer> - разбираться не надо, тут всё очевидно. Разница в том, что во втором случае надо написать целых лишних несколько слов:

          new Comparator<Integer>{
              public int compare(Integer i1, Integer i2){
                  // код функции
              }
          }

          Аж 8 штук. Один раз. Это такая затрата времени! Быстрее написать (i1, i2) -> // код функции

          А о том, сколько лишнего времени уходит на разбор при поддержке этого кода - никто не думает. И SLA на поставку патча в течение двух часов со штрафом в 2млн тоже мало кто встречал.


          1. panzerfaust
            18.06.2024 14:21
            +1

            И SLA на поставку патча в течение двух часов со штрафом в 2млн тоже мало кто встречал.

            У вас ПТСР? Я правильно понял, что вся индустрия должна отказаться от ФП потому, что конкретный человек в конкретной организации с конкретным SLA не осилил синтаксис лямбд?


          1. agoncharov
            18.06.2024 14:21
            +1

            Назначение лямбды обычно очевидно из её кода и из имени функции, в которую она передаётся


            1. aleksandy
              18.06.2024 14:21

              Поддерживаю. @skipy, а тем, кому это неочевидно, могут просто сначала объявить переменную нужного типа.

              Comparator<Integer> integerComparator = Integer::compareTo;
              


      1. valery1707
        18.06.2024 14:21
        +3

        В Java 17 можно и так:

        public Double convert(Object value) {
            if (null == value) {
                return null;
            } else if (value instanceof Double v) {
                return v;
            } else if (value instanceof String v) {
                return (v.trim().isEmpty()) ? null : Double.parseDouble(v);
            } else if (value instanceof Number v) {
                return v.doubleValue();
            }
            throw new AdvImportException("Can't convert value " + value + " to double");
        }
        

        А вариант Java 21 просто делает код более выразительным выделяя паттерны из кучи if в один switch:

        public Double convert(Object value) {
            return switch (value) {
                case null -> null;
                case Double v -> v;
                case String v -> v.trim().isBlank() ? null : Double.parseDouble(v);
                case Number v -> v.doubleValue();
                default -> throw new IllegalArgumentException("Can't convert value" + value + " to double");
            };
        }
        

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


        1. Skipy
          18.06.2024 14:21

          просто теперь внутри case разрешено ставить не только константу, но и "паттерн" (фактически "условие")

          Тут не просто условие. Тут во-первых, совершенно неочевидный instanceof. В case всегда стояло значение того, что указано в switch, это важно. А тут неявно проверяется тип. Напишите value.class - и такой case будет интуитивно понятен.

          Пусть у вас value типа Class. А передается туда Class<Number>. И есть два case - case Class и case Number. По типу (неявный instanceof) это Class, по значению Number. Какой case сработает? У вас приблизительно полсекунды на понимание. Дальше это уже "запнулся и потерял скорость восприятия".

          Во-вторых, совершенно очевидное объявление переменной, но неочевидная связь этой переменной с тем, что стоит в switch. По отдельности это можно было бы понять, но вместе это уже вызывает ровно тот эффект, о котором я писал - смотришь и не понимаешь. Уйдет этот switch за пределы экрана - и теряется нить восприятия, надо проматывать код назад и искать, а что же там было, откуда присваивание идет

          Императивные языки - они более-менее одинаковые по выразительности. Я могу читать код на C#, С++, питоне, Го и еще много чем. Это не вызывает сложностей, это как диалекты одного языка, слова чуть разные, но правила построения фраз совпадают. А тут нарушены именно правила построения. Не получается читать логику, надо читать синтаксис. Отдельные слова.


          1. agoncharov
            18.06.2024 14:21
            +1

            "А тут неявно проверяется тип" - вполне себе явно, синтаксис другой, неоднозначностей нет.

            У вас приблизительно полсекунды на понимание

            Специально сложно сконструированный кейс, где мы разбираем и Class, и Number. С if-ми где делается instanceof вы ещё больше запнетесь

            неочевидная связь этой переменной с тем, что стоит в switch

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


      1. panzerfaust
        18.06.2024 14:21
        +2

        Все молятся на "Чистый код" Мартина, но требования второй главы в 8 нарушены чуть более чем полностью

        Ну вот кто такие заявления делает - тот, видимо, и молится. Адекватные люди понимают, что "Чистый код" - не заповеди на скрижалях, а просто набор советов. Даже если дизайн джавы действительно что-то там нарушает, то Оракл имеет на это полное право.


        1. Skipy
          18.06.2024 14:21

          Эти "советы" написаны кровью. Не успел за два часа разобраться в чужом коде, найти ошибку, исправить, протестировать, собрать патч и поставить заказчику - получил штраф в 2 млн. Работали в таких условиях? Я работал. Там нет времени на понимание очередного синтаксического сахара ради экономии двух слов в месяц. Кстати, у нас лямбды были запрещены.

          А Oracle на всё имеет право. В том числе и на снижение использования языка. Что мы и наблюдаем по индексу TIOBE. С 2020 на первое место Java не поднялся ни разу, за всю историю индекса такого не было.


          1. panzerfaust
            18.06.2024 14:21
            +1

            Работали в таких условиях? Я работал

            Напомнило логику "Я служил! А ты служил?".

            С 2020 на первое место Java не поднялся ни разу, за всю историю индекса такого не было

            The index is calculated from the number of search engine results for queries containing the name of the language

            Очень полезный и адекватный индекс. Осталось узнать, была ли у Оракла цель пушить джаву в поисковых выдачах. Держите в курсе.


          1. agoncharov
            18.06.2024 14:21

            Эти "советы' написаны теми, кому надо было продавать книгу


          1. aleksandy
            18.06.2024 14:21

            у нас лямбды были запрещены

            Интересно, а кто принял такое решение?


  1. Semenych
    18.06.2024 14:21
    +5

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

    Сейчас да есть несовместимости если "вы делаете странное" (давайте называть вещи своими именами).Но вроде фиксится не очень ужасно, за Яву скорее рад чем наоборот.

    Все же развития языка для меня важнее, чем совместимость сломанная на private static reflection.