Найти в Дзене
Сергей Стеничкин

Плюсы и минусы программирования на Java .

Не так много технологий могут похвастаться тем, что они актуальны уже более 20 лет. Однако в этом году Java заняла пятое место в списке самых популярных технологий, уступив только неоспоримым лидерам: JavaScript, HTML, CSS и SQL. Java занимает 18-е место в рейтинге любимых технологий (по результатам опроса StackOverflow) и не попадает в рейтинги ненавистных. Сегодня обсудим плюсы и минусы Java — близкого и дорогого для многих программистов языка, проверенной временем технологии с узнаваемым логотипом, в виде чашки горячего кофе. Что такое Java-программирование: история и вклад Java — это язык программирования общего назначения, который следует парадигме объектно-ориентированного программирования и подходу «Написать один раз и использовать везде» . Java используется для настольных, сетевых, мобильных и корпоративных приложений. Подробная информация: Java — это не только язык программирования, но и экосистема инструментов, охватывающая почти все, что может понадобиться при программир
Оглавление

Не так много технологий могут похвастаться тем, что они актуальны уже более 20 лет. Однако в этом году Java заняла пятое место в списке самых популярных технологий, уступив только неоспоримым лидерам: JavaScript, HTML, CSS и SQL. Java занимает 18-е место в рейтинге любимых технологий (по результатам опроса StackOverflow) и не попадает в рейтинги ненавистных. Сегодня обсудим плюсы и минусы Java — близкого и дорогого для многих программистов языка, проверенной временем технологии с узнаваемым логотипом, в виде чашки горячего кофе.

Что такое Java-программирование: история и вклад

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

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

  • Java Development Kit (JDK) — комплект разработчика Java. С помощью JDK и стандартного блокнота можно писать и запускать/ компилировать код на Java;
  • Java Runtime Environment (JRE) — исполняющая система Java. Механизм распространения программного обеспечения, состоит из автономной виртуальной машины Java, стандартной библиотеки Java (Java Class Library) и инструментов настройки.
  • Integrated Development Environment (IDE) — интегрированная среда разработки. Инструменты, которые помогают запускать, редактировать и компилировать код. Самые популярные из них — IntelliJ IDEA, Eclipse и NetBeans.

Java можно найти везде. Это основной язык разработки для Android. Он используется в веб-приложениях, правительственных веб-сайтах и ​​технологиях обработки больших данных, таких как Hadoop и Apache Storm. Java подходит и для научных проектов, особенно в области обработки естественного языка. Язык Java преобладал и в программировании для мобильных устройств, задолго до появления смартфонов — первые мобильные игры в начале 2000-х годов были написаны на Java. Java, благодаря своей долгой истории, заработал свое место в Зале славы программирования. Индекс TIOBE, один из самых авторитетных индексов популярности программ в мире, при составлении рейтинга использует результаты поисковой выдачи. Несмотря на растущую популярность Go и Python, Java остается на вершине списка уже более десятилетия.

Все началось в начале 1990-х, когда команда Sun Microsystems начала разрабатывать улучшенную версию C ++ — независимую от конкретной платформы, удобную для начинающих и с автоматическим управлением памятью. Исследование привело к созданию совершенно нового языка. Название Java — одно из десятков других, предложенных командой. Сегодня логотип кофейной чашки с паром — это неприметный, но узнаваемый символ программирования. И уже неясно, что было первым: одержимость программистов кофеином или ассоциация с Java.

Как Java изменила мир программирования:

Гибкость. Java доказала, что C — процедурный, управляемый вручную и зависящий от платформы код — это не предел совершенства . Благодаря Java, все больше людей начали применять объектно-ориентированное программирование, которое сейчас используется повсеместно.

Апплеты. Еще до появления JavaScript, в Java добавили апплеты — небольшие веб-программы, которые предоставляют интерактивные элементы для визуализации и обучения. Они не используются ни для чего, кроме простой анимации, однако апплеты привлекли внимание многих программистов и подтолкнули их к разработке HTML5, Flash и JavaScript.

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

Плюсы программирования на Java

Java — уже не единственный официально поддерживаемый язык для разработки на Android. Java далеко не единственный выбор в веб-программировании. Тем не менее, Java идет в ногу со временем. Давайте рассмотрим, какие преимущества предлагает Java.

+ Объектно-ориентированное программирование

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

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

В чем плюсы ООП?

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

+ Java — язык высокого уровня с простым синтаксисом и плавной кривой обучения

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

Синтаксис Java основан на C ++, поэтому Java похожа на C. Тем не менее, синтаксис Java проще, что позволяет новичкам быстрее учиться и эффективнее использовать код для достижения конкретных результатов.

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

+ Стандарт для корпоративных вычислительных систем

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

+ Безопасность

Существует мнение, что Java — безопасный язык, однако это не совсем так. Сам язык не защищает вас от уязвимостей, но некоторые его функции устраняют распространенные уязвимости. Во-первых, в отличие от C, в Java нет указателей. Указатель — это объект, который сохраняет адрес ячейки памяти другого значения, что может вызвать несанкционированный доступ к памяти. Во-вторых, в Java есть Security Manager, созданная для каждого приложения политика безопасности, в которой можно указать правила доступа. Это позволяет запускать приложения Java в «песочнице» и устранять таким образом уязвимости.

+ Независимость от платформы («Написать один раз и использовать везде»)

«Написать один раз и использовать везде» (WORA) — популярная в IT-сфере фраза, с помощью которой Sun Microsystems описывает кросс-платформенные возможности Java. Можно создать Java-приложение на Windows, скомпилировать его в байт-код и запустить его на любой другой платформе, поддерживающей виртуальную машину Java (JVM). Таким образом, JVM служит уровнем абстракции между кодом и оборудованием.

Все основные операционные системы, включая Windows, Mac OS и Linux, поддерживают JVM. Если ваша программа не опирается на специфичные для платформы функции и пользовательский интерфейс, ее можно с легкостью перенести: по крайней мере, большую ее часть.

+ Язык для распределенного программирования и комфортной удаленной совместной работы

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

В других языках нужно использовать внешний API для дистрибуции. В Java эта технология встроена. Специфическая для Java методология распределенных вычислений называется Remote Method Invocation (RMI). RMI позволяет использовать все преимущества Java: безопасность, независимость от платформы и объектно-ориентированное программирование для распределенных вычислений. Кроме того, Java также поддерживает программирование сокетов и методологию распределения CORBA для обмена объектами между программами, написанными на разных языках.

+ Автоматическое управление памятью

Разработчикам Java не нужно вручную писать код для управления памятью благодаря автоматическому управлению памятью (AMM). AMM также используется в языке программирования Swift и при очистке памяти приложениями, которые автоматически обрабатывают распределение и освобождение памяти. Что именно это означает?

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

+ Многопоточность

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

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

+ Стабильность и сообщество

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

Сообщество разработчиков Java не имеет себе равных. Около 45% респондентов опроса StackOverflow 2018 используют Java. У Java чрезвычайно большая экосистема хорошо протестированных библиотек и фреймворков для любых задач. Начинающий разработчик, скорее всего, выберет Java: на тему Java-программирования существует более 1000 курсов на Udemy и более 300 на Coursera.

Минусы программирования на Java

Рассмотрим недостатки Java-программирования.

— Платное коммерческое использование

Недавно Oracle объявила, что с 2019 года компания начнет взимать плату за использование Java Standard Edition 8 в «коммерческих целях». За все новые обновления и исправления ошибок придется заплатить. Плата зависит от количества пользователей или компьютеров.

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

— Низкая производительность

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

— Отсутствие нативного дизайна

Для создания графического интерфейса пользователя (GUI) разработчики используют различные инструменты, ориентированные для конкретного языка. Для Android-приложений есть Android Studio, которая помогает создавать приложения с нативным дизайном. Однако, когда дело доходит до пользовательского интерфейса на ПК, Java-инструмента для создания нативного дизайна нет.

Есть несколько инструментов для разработки GUI для Java: самые популярные из них — Swing, SWT, JavaFX, JSF. Библиотека Swing — это старый, но надежный кросс-платформенный инструмент, интегрированный в различные Java-IDE, в том числе Eclipse и NetBeans. Однако, если вы не используете шаблоны, вы заметите несоответствия интерфейса. SWT использует собственные компоненты, но не подходит для сложного интерфейса. JavaFX — лаконичный и современный, но слишком новый. В целом, перед созданием GUI на Java нужно подробнее изучить инструменты.

— Многословный и сложный код

Многословность кода может показаться преимуществом, которое поможет при изучении языка. Однако, длинные, чрезмерно сложные предложения затрудняют чтение и просмотр кода. Как и естественные языки, многие языки программирования высокого уровня содержат лишнюю информацию. Java — это более легкая версия неприступного C ++, которая вынуждает программистов прописывать свои действия словами из английского языка. Это делает язык более понятным для неспециалистов, но менее компактным.

Сравним Java и Python и увидим, в чем преимущество лаконичного кода Python. В Python не используются точка с запятой, круглые и фигурные скобки. Вместо «и», «или» и «нет» в качестве операторов используются «&&», «||» и «!».

В заключение: где используется Java?

Большинство организаций так или иначе используют Java. Широкий спектр вариантов использования Java делает ее практически незаметной в использовании: поэтому часто возникает вопрос «где используется Java?». Давайте посмотрим, в каких сферах используется Java:

Приложения для Android. Несмотря на активный рост Kotlin, Java по-прежнему остается де-факто основным языком Android-приложений. Таким образом, все разработчики Java очень легко могут стать Android-программистами. Хотя Android использует Android SDK вместо JDK, тем не менее, код написан на Java.

Программные продукты. Помимо уже упомянутых Hadoop и Apache Storm, Java использовалась для создания Eclipse, OpenOffice, Gmail, Atlassian и других.

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

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

Торговые системы. На Java написана Murex, популярная программа управления банками для фронтальной и обратной связи.

Программы для работы с большими данными. Hadoop написан на Java. Scala, Kafka и Spark используют JVM. Кроме того, Java предоставляет доступ к множеству проверенных библиотек, инструментов отладки и мониторинга.

Перевод статьи The Good and the Bad of Java Programming

Экспоненциальное распределение

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

1. Почему мы изобрели экспоненциальное распределение?

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

Например, мы хотим предсказать следующее:

  • Время, по истечении которого клиент закончит поиск и закажет что-то в магазине (успех).
  • Время, по истечении которого оборудование AWS EC2 выйдет из строя (отказ).
  • Время ожидания автобуса (прибытие).

Следующий вопрос такой: почему λ * e^(−λt) — это плотность вероятности времени до следующего события?

И следующий вопрос: что значит X~EXP(0,25)?Параметр 0,25 означает 0,25 минут, часов или дней, а, может, 0,25 событий?

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

X~EXP(λ) ➡ Экспоненциальный параметр λ тот же самый, что и λ в распределении Пуассона?

Важная вещь, которая позже поможет вам не запутаться с X~EXP(0,25).0,25 — это не временной период, а число событий, совпадающее с параметром λ в процессе Пуассона.

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

Однако при моделировании времени между событиями удобнее использовать термины времени, а не количества. Например, число лет, в течение которых компьютер может включаться без ошибок — 10 лет (это удобнее, чем говорить “0,1 ошибка в год”), новый покупатель приходит каждые 10 минут, крупные ураганы возникают каждые 7 лет и так далее.

Путаница возникает, когда вы видите термин “затухание”, или еще хуже, “скорость затухания”, которые часто используются в экспоненциальном распределении. Затуханиевыражается через время (каждые 10 минут, каждые 7 лет и т.д.) и является обратной величиной параметра (λ) в распределении Пуассона. Смотрите: если у вас 3 посетителя в час, значит у вас 1 посетитель каждую треть часа.

Итак, мы можем ответить на вопрос:
Что значит “X ~ EXP(0,25)”?

Это означает, что параметр Пуассона будет равен 0,25. В течение единицы времени (неважно, в минутах, часах или годах) событие происходит в среднем 0,25 раз. Переводя в термины времени — пройдет 4 часа, прежде чем событие произойдет, если за единицу времени принят 1 час.

* Чтобы не запутаться: параметр экспоненциального распределения λ тот же самый, что в процессе Пуассона (λ).

2. Вывод плотности вероятности

Наш первый вопрос был: почему λ * e^(−λt) — это плотность вероятности времени до следующего события?

Определение экспоненциального распределения — это распределение вероятности времени *между* событиями в процессе Пуассона.

Смотрите: в период ожидания не происходит ни одного события. Другими словами, Пуассон (X=0).

Пуассон (X=0): первый этап экспоненциального распределения

Есть важная вещь, которую стоит помнить о пуассоновской плотности вероятности: период времени, в течение которого возникают пуассоновские события (X=k), составляет только одну (1) единицу времени.

Как смоделировать распределение вероятности не просто в течение одной единицы времени, а “ничего не произошло в период времени t”?

P(ничего не произошло в течение t единиц времени)

= P(X=0 в первую единицу времени)
* P(X=0 во вторую единицу времени)
* … * P (X=0 в
t-ую единицу времени)
= e^−λ * e^−λ * … * e^−λ =
e^(-λt)

Распределение Пуассона предполагает, что события возникают независимо друг от друга. Следовательно, можно посчитать вероятность нулевого успеха в течение t единиц времени, умножив P(X=0 в единицу времени) на t раз.

P(T > t) = P(X=0 в t единиц времени) = e^−λt
*
T : случайная переменная времени до первого успешного события
*
X : количество событий
*
P(T > t) : вероятность того, что время ожидания события больше,чем t единиц времени
*
P(X=0 в t единиц времени): вероятность нулевого успеха в t единиц времени

Плотность вероятности — это производная от кумулятивной функции распределения вероятности.

Поскольку у нас уже есть кумулятивная функция распределения вероятности экспоненциального распределения, 1 — P(T > t), мы можем получить плотность вероятности, продифференцировав ее.

Плотность распределения вероятности — это производная от кумулятивной функции распределения вероятности

3. Отсутствие последействия

Определение:
P(X > a + b | X > a) = P(X > b)

Это означает:

Доказательство:

Отсутствие последействия — полезный параметр?

Рационально ли моделировать долговечность механического устройства, используя экспоненциальное распределение?

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

P(X > 12|X > 9) = P(X > 3)

Это уравнение кажется вам разумным?

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

Так когда же стоит применять экспоненциальное распределение (постоянную интенсивность отказов)?

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

Где еще есть отсутствие последействия?

Экспоненциальное распределение — это единственное непрерывное распределение с отсутствием последействия (или распределение с постоянной интенсивностью отказов). Геометрическое распределение, его дискретный аналог, является единственным дискретным распределением с отсутствием последействия.

4. Применение в реальной жизни 🔥

a) Моделирование времени ожидания

У значений экспоненциальной случайной величины есть много маленьких значений и немного крупных значений. Автобус, который вы ждете, скорее всего приедет в течение 10 минут нежели в течение 60 минут.

Используя экспоненциальное распределение, можно ответить на следующие вопросы:
1. Автобус в среднем приезжает каждые 15 минут (предположим, что время между прибытием автобусов имеет экспоненциальное распределение, значит, количество автобусов, прибывающих в течение часа имеет распределение Пуассона). И я только что пропустила автобус! Водитель был злой. Как только я пришла, он закрыл двери и уехал. Если следующий автобус не приедет в течение десяти мнут, я вызову Uber, иначе опоздаю. Какова вероятность того, что ожидание следующего автобуса займет меньше 10 минут?
2. Девяносто процентов автобусов прибывают через сколько минут после предыдущего?
3. В течение какого времени в среднем прибывают два автобуса?

b) Моделирование отказа

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

Количество часов, которое AWS оборудование способно проработать без перезагрузки, соответствует экспоненциальному распределению со средним значением 8 000 часов в год.
1. У вас нет резервного сервера, а вам нужна бесперебойная работа в течение 10 000 часов. Какова вероятность того, что вы сможете выполнить эту задачу без перезагрузки сервера?
2. Какова вероятность того, что сервер не потребует перезагрузки между 12 и 18 месяцами?

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

c) Моделирование времени обслуживания (Теория очередей)

Время обслуживания (например, как долго в кафе готовят мне буррито) тоже можно смоделировать как экспоненциально распределенные переменные.

Общая длина процесса — последовательность нескольких независимых задач — соответствует распределению Эрланга: распределению суммы нескольких независимых экспоненциально распределенных переменных.

5. Вкратце: связь между экспоненциальным и пуассоновским распределениями 

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

Предположим, что на период времени между событиями не влияют предыдущие события (то есть они независимы), тогда число событий в единицу времени соответствует распределению Пуассона со значением λ = 1/μ.

6. Упражнение

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

  • Пусть U — случайная величина, имеющая равномерное распределение между 0 и 1. Тогда экспоненциальная случайная переменная будет выглядеть так

X = -1/λ * ln(U)

Докажите почему.

2. Максимальное значение плотности распределения вероятности на оси y — λ. Почему?

Плотность вероятности экспоненциального распределения

3. X1 и X2 — независимые экспоненциальные случайные переменные со значением λ.

X1~EXP(λ)
X2~EXP(λ)

Пусть Y=X1+X2.

Какова плотность вероятности Y? 
Где может быть использовано это распределение?

Как не лажать с JavaScript. Часть4

Часть 1Часть 2, Часть 3, Часть 4

Декларативный код  —  популярное понятие, но что оно означает на самом деле? Это что-то хорошее? Давайте разберёмся.

Если вы программируете, то скорее всего в императивном стиле. Вы пишете множество инструкций для достижения результата. В декларативном стиле вы описываете желаемый результат, но не инструкции в деталях. SQL, HTML, JSX  —  декларативные языки. В SQL вы пишете не то, как извлекаете данные, а то, что хотите извлечь:

SELECT * FROM Users WHERE Country='USA';

Это может быть грубо представлено в императивном JavaScript:

let user = null;

for (const u of users) {
if (u.country === 'USA') {
user = u;
break;
}
}

Или в декларативном JavaScript с экспериментальным конвейерным оператором:

import { filter, first } from 'lodash/fp';

const filterByCountry =
country => filter( user => user.country === country );

const user =
users
|> filterByCountry('USA')
|> first;

Второй вариант чище и читается лучше.

Возвращайте значение. Избегайте операторов

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

const calculateStuff = input => {
if (input.x) {
return superCalculator(input.x);
}

return dumbCalculator(input.y);
};

Сделаем код декларативным:

const calculateStuff = input => {
return input.x
? superCalculator(input.x)
: dumbCalculator(input.y);
};

И теперь он может быть лямбда-выражением:

const calculateStuff = input =>
input.x ? superCalculator(input.x) : dumbCalculator(input.y);

Операторы вызывают побочные эффекты и мутации, склонные к недетерминизму. Это снижает читаемость и надёжность кода. 

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

Декларативное программирование требует усилий

Декларативное программирование  —  не то, что можно выучить за ночь. Особенно учитывая, что большинство людей училось императивному программированию. Декларативное программирование требует дисциплины и умения мыслить совершенно иначе. Удачный первый шаг  —  научиться программировать без изменяемого состояния. Например, не используя ключевое слово let. Наверняка я могу сказать одно: если вы попробуете декларативный стиль, то удивитесь элегантности кода.

Конфигурация ESLint:

rules:
fp/no-let: warn
fp/no-loops: warn
fp/no-mutating-assign: warn
fp/no-mutating-methods: warn
fp/no-mutation: warn
fp/no-delete: warn

Не более двух параметров в функции

JavaScript типизирован динамически. Нет никаких гарантий, что функция вызывается с корректными параметрами. ES6 привносит множество функций, в том числе деструктурирование объектов, которое можно использовать для аргументов функций. Код ниже интуитивно понятен? Вы можете рассказать, чем являются параметры? Я  —  нет.

const total = computeShoppingCartTotal(itemList, 10.0, 'USD');

А в таком примере?

const computeShoppingCartTotal = ({ itemList, discount, currency }) => {...};

const total = computeShoppingCartTotal({ itemList, discount: 10.0, currency: 'USD' });

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

rules:
max-params:
- warn
- 2

Возвращайте объекты из функций

Сколько следующий фрагмент кода расскажет вам о сигнатуре функции? Что она возвращает? Возвращает ли она объект пользователя, его идентификатор или статус операции? Трудно сказать.

const result = saveUser(...);

Возврат объектов из функций проясняет намерения разработчика, код читается значительно лучше:

const { user, status } = saveUser(...);

...

const saveUser = user => {
...

return {
user: savedUser,
status: "ok"
};
};

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

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

Исключения нарушают безопасность типов

Исключения нарушают безопасность типов даже в статически типизированных языках. Согласно своей сигнатуре, функция fetchUser (id: number): User должна вернуть пользователя. Сигнатура не говорит, что будет брошено исключение, когда пользователь не найден. Если ожидается исключение, то более подходящей сигнатурой будет: fetchUser (…): User | throws UserNotFoundError. Конечно, такой синтаксис недопустим независимо от языка.

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

Исключения ломают функциональную композицию

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

const fetchBlogPost = id => {
const post = api.fetch(`/api/post/${id}`);

if (!post) throw new Error(`Post with id ${id} not found`);

return post;
};

const html = postIds |> map(fetchBlogPost) |> renderHTMLTemplate;

А если сообщение было удалено, но пользователь пытается получить доступ к нему из-за какой-то неясной ошибки? Такое значительно ухудшает пользовательский опыт.

Кортежи для обработки ошибок

Не вдаваясь в функциональное программирование, простой способ обработки ошибок  —  возврат кортежа, содержащего результат и ошибку. Да, JS не поддерживает кортежи, но их можно легко эмулировать массивом [error, result]. Кстати, это стандартный метод обработки ошибок в Go:

const fetchBlogPost = id => {
const post = api.fetch(`/api/post/${id}`);

return post
// null в ошибке, если пост найден.
? [null, post]
// null в результате, если пост не найден.
: [`Post with id ${id} not found`, null];
};

const blogPosts = postIds |> map(fetchBlogPost);

const errors =
blogPosts
|> filter(([err]) => !!err) // только элементы с ошибками.
|> map(([err]) => err); /* деструктурирует кортеж и возвращает ошибку. */

const html =
blogPosts

Иногда исключения необходимы

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

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

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

Избегайте обработки исключений

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

Спросите себя: кто несёт ответственность за ошибку? Если это пользователь, то обработайте её изящно. Покажите приятное сообщение, а не ошибку сервера.

Исключение из правила  —  использование сторонних API. Но даже тогда лучше использовать вспомогательную обёртку, чтобы возвращать кортеж [error, result]. Для этого вы можете использовать Saferr. Подробнее о нём в следующем разделе. 

К сожалению, у ESLint нет правила no-try-catch. Ближайшее по сути  —  no-throw. Конфигурация:

rules:
fp/no-throw: warn

Частичное применение функций

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

В следующем примере оборачивается библиотека Axios, печально известная тем, что вместо возврата ошибочного ответа она бросает исключение. Работать с такими библиотеками неоправданно сложно, особенно при использовании async/await.

Каррируем и частично применим функцию, чтобы сделать небезопасный метод безопасным:

// Оборачиваем axios в безопасный вызов API без исключений.
const safeApiCall = ({ url, method }) => data =>
axios({ url, method, data })
.then( result => ([null, result]) )
.catch( error => ([error, null]) );

// Частично применим универсальную функцию выше для работы с API.
const createUser = safeApiCall({
url: '/api/users',
method: 'post'
});

// Безопасный вызов API.
const [error, user] = await createUser({
email: 'ilya@suzdalnitski.com',
password: 'Password'
});

Обратите внимание, что функция safeApiCall записывается как func = (params) => (data) => {…}. Эта техника называется каррирование и она идёт рука об руку с частичным применением функций. Это означает, что func при вызове с params возвращает другую функцию, которая и выполняет работу. Другими словами, функция частично применяется с параметрами:

const func = (params) => (
(data) => {...}
);

Обратите внимание, что зависимости (params) передаются как первый параметр, а данные  —  как второй. Чтобы упростить задачу, вы можете использовать npm пакет Saferr, который также работает с промисами и async/await:

import saferr from "saferr";
import axios from "axios";

const safeGet = saferr(axios.get);

const testAsync = async url => {
const [err, result] = await safeGet(url);

if (err) {
console.error(err.message);
return;
}

console.log(result.data.results[0].email);
};


// Печатает: zdenka.dieckmann@example.com
testAsync("https://randomuser.me/api/?results=1");

// Печатает: Network Error
testAsync("https://shmoogle.com");

Несколько маленьких трюков

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

Немного безопасности типов

Да, JavaScript типизирован не статически, но мы можем сделать код устойчивее, обозначив аргументы функции. Код ниже бросает ошибку, когда значение не было передано. Это не работает для null, но защищает от undefined.

const req = name => {
throw new Error(`The value ${name} is required.`);
};

const doStuff = ( stuff = req('stuff') ) => {
...
}

Оптимизации логических выражений

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

const getUserCity = user =>
user && user.address && user.address.city;

const user = {
address: {
city: "San Francisco"
}
};

// Возвращает "San Francisco"
getUserCity(user);

// Оба undefined
getUserCity({});
getUserCity();

Они могут обеспечить альтернативное значение выражению:

const userCity = getUserCity(user) || "Detroit";

Двойное отрицание

Двойное отрицание  —  прекрасный способ преобразовать какое-то значение в логическое. Но любое ложное значение будет преобразовано в ложное. А это не всегда то, чего мы хотим. Поэтому не используйте двойное отрицание с 0.

const shouldShowTooltip = text => !!text;

// возвращает true
shouldShowTooltip('JavaScript rocks');

// всё возвращает false
shouldShowTooltip('');
shouldShowTooltip(null);
shouldShowTooltip();

Отладка с логированием на месте

Пример использования оптимизации логических выражений для отладки в React:

const add = (a, b) =>
console.log('add', a, b)
|| (a + b);

const User = ({email, name}) => (
<>
<Email value={console.log('email', email) || email} />
<Name value={console.log('name', name) || name} />
</>
);

Итоги

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

Не пытайтесь применить все правила сразу. Это очень сложно. Поместите статью в закладки, выберите одну из частей и сосредоточьтесь, включив соответствующие правила ESLint, чтобы помочь себе в путешествии по коду. Удачи!