Рубрики
Без рубрики

Мы должны писать Java-код по-другому

прекратите писать устаревший код. С пометкой java, новички.

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

Как и любой другой язык, Java развивается с течением времени. То же самое относится и к стилю, в котором написан Java-код. Код, написанный вокруг Y2K, значительно отличается от кода, написанного после 2004-2006 годов, когда были выпущены Java 5, а затем Java6. Дженерики и аннотации сейчас настолько распространены, что трудно даже представить Java-код без них.

Затем появилась Java 8 с лямбдами, Поток и Необязательно . Эти функциональные элементы должны революционизировать Java-код, но в основном они этого не делают. В каком-то смысле они определенно повлияли на то, как мы пишем Java-код, но революции не произошло. Довольно медленная эволюция. Почему? Давайте попробуем найти ответ.

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

Первая причина заключается в том, что даже авторы Java чувствовали неуверенность в том, как новые функциональные элементы вписываются в существующую экосистему Java. Чтобы увидеть эту неопределенность, достаточно прочитать Необязательно JavaDoc:

Примечание API: Необязательный в первую очередь предназначен для использования в качестве возвращаемого типа метода, где существует явная необходимость представления “без результата” и где использование null может привести к ошибкам.

API также показывает то же самое: наличие метода get() (который может вызывать NPE), а также пары методов orElseThrow() являются явным уважением к традиционному императивному стилю кодирования Java.

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

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

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

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

Держу пари, что так оно и есть. Бизнес подталкивает разработчиков к более быстрой доставке приложений. В идеале проекты, над которыми мы работаем, должны быть написаны, протестированы и развернуты еще до того, как бизнес поймет, что на самом деле необходимо реализовать. Шучу, конечно, но дата доставки “вчера” – мечта многих деловых людей.

Таким образом, нам определенно необходимо повысить производительность разработки. Каждая отдельная платформа, IDE, методология, подход к проектированию и т. Д. и т. Д. Направлены на повышение скорости внедрения и развертывания программного обеспечения (конечно, с соблюдением необходимых стандартов качества). Тем не менее, несмотря на все это, видимых прорывов в производительности разработки не наблюдается.

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

С моей точки зрения, большинство попыток повысить производительность разработки предполагают, что написание меньшего количества кода (и меньшего количества кода в целом) автоматически означает повышение производительности. Популярные библиотеки и фреймворки, такие как Spring, Lombok, Feign – все они пытаются уменьшить объем кода. Даже Kotlin был создан с одержимостью краткостью, в отличие от “многословия” Java. История много раз доказывала ошибочность этого предположения (Perl и APL, пожалуй, наиболее заметные примеры), тем не менее оно все еще живо и стимулирует большинство усилий.

Любой разработчик знает, что написание кода – это крошечная часть деятельности по разработке. Большую часть времени мы читаем код . Является ли чтение меньшего количества кода более продуктивным? Первое намерение состоит в том, чтобы сказать да , но на практике объем кода и его читабельность едва ли связаны. Чтение и запись одного и того же кода часто имеют разный “импеданс” в виде умственных затрат.

Вероятно, лучшими примерами этой разницы в “импедансе” являются регулярные выражения. Регулярные выражения довольно компактны и в большинстве случаев довольно просты в написании, особенно с использованием бесчисленных специализированных инструментов. Но чтение регулярных выражений обычно доставляет боль и отнимает гораздо больше времени. Почему? Причина в потерянном контексте . Когда мы пишем регулярное выражение, мы знаем контекст: чему мы хотим соответствовать, какие случаи следует учитывать, как может выглядеть возможный ввод и так далее и тому подобное. Само выражение представляет собой сжатое представление этого контекста. Но когда мы их читаем, контекст теряется или, если быть точным, сжимается и упаковывается с использованием очень компактного синтаксиса. И попытка “распаковать” его из регулярного выражения – довольно трудоемкая задача. В некоторых случаях переписывание с нуля занимает значительно меньше времени, чем попытка понять существующий код.

Приведенный выше пример дает один важный намек: сокращение объема кода имеет смысл только до тех пор, пока контекст остается сохраненным. Как только сокращение кода приводит к потере контекста, это начинает быть контрпродуктивным и наносит ущерб производительности разработки .

Итак, если размер кода не так важен, то как мы действительно можем повысить производительность?

Очевидно, путем сохранения и/или восстановления утраченного контекста. Но когда и почему теряется контекст?

Пожиратели контекста

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

Обнуляемые переменные

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

String value = service.method(parameter);

Просто взглянув на этот код, вы не сможете определить, может ли значение | быть нулевым или нет. Другими словами, часть контекста теряется. Чтобы восстановить его, нужно заглянуть в код service.method() и проанализировать его. Переход к этому методу, чтение его кода, возврат - все это отвлекает от текущей задачи. И постоянная необходимость помнить о том, что переменная может быть нулевой , вызывает умственные издержки. Опытные разработчики хорошо помнят о таких вещах, но это не значит, что эти умственные издержки не влияют на производительность их разработки.

Давайте подведем итоги:

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

Исключения

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

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

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

Резюме:

Бизнес-исключения – это пожиратели контекста, убийцы производительности разработки и источники ошибок.

Фреймворки как пожиратели контекста

Поскольку фреймворки обычно специфичны для конкретного проекта, проблемы, вызванные ими, также зависят от конкретного проекта. Тем не менее, если у вас возникла идея потери/сохранения контекста, вы можете заметить, что популярные фреймворки, такие как Spring и другие, которые используют сканирование путей к классам, идиому “соглашение о конфигурации” и другую “магию”, намеренно удаляют большую часть контекста и заменяют его неявным знанием настройки по умолчанию (т.Е. умственные издержки). При таком подходе приложение разбивается на набор слабо связанных классов. Без поддержки IDE даже трудно перемещаться между компонентами, настолько они разобщены. Помимо потери огромной части контекста, существует еще одна существенная проблема, которая негативно влияет на производительность: значительное количество ошибок переносится со времени компиляции на время выполнения. Последствия разрушительны:

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

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

Прагматичный Функциональный Способ Java

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

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

Оригинал: “https://dev.to/siy/we-should-write-java-code-differently-210b”