Виммельбух, 3, перетяжка
Виммельбух, 3, перетяжка
Виммельбух, 3, перетяжка

Современные языки программирования, которые заставят вас страдать: Часть 1, ООП

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

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

31К открытий32К показов

Прим. ред. Это перевод статьи Ильи Суздальницкого. Мнение редакции может не совпадать с мнением автора оригинала.

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

Си-подобные языки

С++

Особенности языка: C++ может многое. Слишком многое. Это попытка стать универсальным, при этом не будучи хорошим в чём-то одном. В языке есть: оператор goto, указатели, ссылки, ООП, перегрузка операторов и другие не особо полезные фичи.

Почему C++ такой? Полагаю из-за возраста. Язык был создан в далёком 1979 году, когда его создатели не знали на чём нужно фокусироваться. Добавлять в язык больше возможностей считалось хорошей идеей, ведь это увеличивало область применения.

Скорость: C++ славится долгой компиляцией. Она значительно дольше чем у Java, но не так плоха, как у Scala. С другой стороны производительность уже скомпилированных приложений и время их запуска достаточно хороши.

Экосистема\Инструментарий: описание ошибок — не сильная сторона C++, это иллюстрирует следующий твит:

Современные языки программирования, которые заставят вас страдать: Часть 1, ООП 1
В С++ мы не говорим «Пропущен символ *», мы говорим:

Сборка мусора: этой фичи никогда не было в C++. Ручное управление сборкой мусора — источник множества ошибок.

ООП, которое не получилось: во времена создания C++ ООП была крутой новой парадигмой, но при её реализации было допущено несколько критических ошибок. По крайней мере в С++ использовать ООП не обязательно (В отличие от Java).

Сложность изучения: С++ — сложный низкоуровневый язык без автоматического управления памятью. Его сложно изучать новичкам из-за чрезмерного количества функций.

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

Обработка ошибок: предпочтительный механизм обработки — выброс и обработка исключений.

Иммутабельность: поддержка отсутствует.

Поддержка NULL: все ссылки могут быть NULL.

Вердикт: неудавшаяся попытка превзойти язык Си. Вероятно, стоит использовать только для системного программирования. Однако и здесь есть лучшие альтернативы. Например, такие современные языки программирования, как Rust и Go.

Java

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

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

ООП: Подробнее моё мнение об ООП можно узнать в статье ООП — катастрофа на триллион долларов. Вместо этого я процитирую более выдающегося человека: «Мне жаль, что я придумал для этого термин “объекты”, и люди сфокусировались на побочной идее. Главная идея — сообщения» — Алан Кей, изобретатель ООП.

Скорость: Java запускается на JVM, что замедляет время старта. Я видел программы которые запускались по 30 секунд и дольше, что неприемлемо для современных приложений. Время компиляции растет на больших проектах, что влияет на продуктивность разработчиков (но всё ещё не так плохо как в Scala). Однако производительность JVM во время выполнения программы действительно хороша.

Сложность изучения: Хотя Java довольно простой язык, писать на нём хороший объектно-ориентированный код действительно сложно.

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

Поддержка NULL: все ссылки могут быть NULL.

Обработка ошибок: предпочтительный механизм обработки — выброс и обработка исключений.

Иммутабельность: поддержка отсутствует.

Вердикт: Java был неплохим современным языком программирования в момент своего появления. Его портит сосредоточенность на ООП. Язык очень многословен и страдает от шаблонного кода.

С#

Синтаксис: синтаксис C# всегда немного опережал Java. Он меньше страдает от шаблонного кода. Но хоть C# и объектно-ориентированный язык, он тоже страдает от многословности. Приятно видеть как синтаксис C# улучшается с каждым релизом, добавляются: сопоставление с образцом, кортежи и другие возможности.

ООП: C#, как и Java больше сосредоточен на ООП. И снова, вместо того чтобы рассказывать о недостатках ООП, я процитирую более выдающегося человека: «Я считаю, что недостаточное переиспользование больше относится к ООП языкам, чем функциональным. Потому что проблема ООП языков в неявной среде, которую они таскают за собой. Вы хотите банан, но получаете гориллу, держащую банан и целые джунгли» — Джо Армстронг, создатель языка Erlang.

Мультипарадигменность: разработчики утверждают, что C# — мультипарадигменный язык. В частности, говорят что C# поддерживает функциональное программирование. Я считаю, что поддержки функций первого класса не достаточно для того, чтобы считать язык функциональным. Что для этого нужно? Как минимум встроенная поддержка иммутабельных структур данных, сопоставления с образцом, конвейерный оператор для создания цепочек функций и алгебраические типы данных.

Параллелизм: аналогично с C++ и Java.

Поддержка NULL: аналогично с C++ и Java.

Обработка ошибок: аналогично с C++ и Java.

Иммутабельность: аналогично с C++ и Java.

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

Python

Экосистема: есть библиотеки почти на любой случай. В отличие от JavaScript, Python нельзя использовать для фронтенда. Это компенсируется множеством библиотек для анализа данных.

Сложность изучения: простой язык, который можно выучить за пару недель.

Типизация: имеет динамическую типизацию.

Скорость: Python — интерпретируемый язык программирования и является одним из самых медленных языков по времени выполнения программы. В случаях когда производительность важна, можно использовать Cython. Скорость запуска программ также страдает, в сравнении с нативными языками.

Инструменты: управление зависимостями в Python разочаровывает. Существует: pip, pipenv, virtiualenv, pip freeze и другие. Для сравнения — NPM в JS это всё что вам нужно.

Параллелизм: имеет только рудиментарную поддержку параллелизма.

Поддержка NULL: Все ссылки в Python могут быть NULL.

Обработка ошибок: предпочтительный механизм обработки — выброс и обработка исключений.

Иммутабельность: поддержка отсутствует.

Вердикт: увы, Python не имеет достаточной поддержки функционального программирования, которое как нельзя лучше подходит для анализа данных (лучше использовать Elixir). Язык не стоит использовать где-то кроме анализа данных (когда нет других альтернатив). Julia вероятно может быть хорошей заменой, но её экосистема ещё не такая зрелая, как у Pyhton.

Rust

Скорость: язык создавался быстрым. Компиляция на Rust занимает больше времени чем на Go. Выполнение программ немного быстрее чем на Go.

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

			// Source: https://doc.rust-lang.org/rust-by-example/std/option.html

// вернет либо значение типа T либо None
enum Option {
    Some(T),
    None,
}

// деление целого числа без "паники"
fn checked_division(dividend: i32, divisor: i32) -> Option {
    if divisor == 0 {
        // Ошибка это None
        None
    } else {
        //результат обёрнут в Some
        Some(dividend / divisor)
    }
}

// Функция деления, которая может вернуть ошибку
fn try_division(dividend: i32, divisor: i32) {
    // Option можно использовать в match, как другие enum
    match checked_division(dividend, divisor) {
        None => println!("{} / {} failed!", dividend, divisor),
        Some(quotient) => {
            println!("{} / {} = {}", dividend, divisor, quotient)
        },
    }
}
		

Обработка ошибок: Rust использует для этого подход современных функциональных языков программирования. Существует специальный тип Result, который показывает, что операция может выдать ошибку. Это очень похоже на Option, но случай None тоже хранит результат.

			// Результат либо Ок типа Т, либо ошибка типа Е
enum Result {
    Ok(T),
    Err(E),
}

// Функция, которая может вернуть ошибку
fn random() -> Result {
    let mut generator = rand::thread_rng();
    let number = generator.gen_range(0, 1000);
    if number <= 500 {
        Ok(number)
    } else {
        Err(String::from(number.to_string() + " should be less than 500"))
    }
}

// Обработка результата функции
match random() {
    Ok(i) => i.to_string(),
    Err(e) => e,
}
		

Управление памятью: нет сборщика мусора.

Параллелизм: из-за отсутствия сборки мусора параллелизм в Rust довольно сложен.

Иммутабельность: не поддерживается.

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

Вердикт: Rust хорош для системного программирования, имеет мощную систему типов, Option и современную обработку ошибок. Однако, он всё ещё менее популярен чем TS и JS, потому что не подходит для бэкенда\Web API.

TypeScript

Надмножество JS: это плюс, потому что многие уже знают JavaScript. Но с другой стороны, язык тянет за собой всё из JS.

Экосистема: здесь всё также наследуется от JS, что даёт доступ к огромной экосистеме JS. Работать с NPM очень приятно, особенно после Python. Однако, не все JS-библиотеки поддерживают TypeScript.

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

Поддержка NULL: в версии 2.0 была добавлена поддержка non-nullable типов, её можно включить с помощью флага –strictNullChecks. Однако это не подразумевается в языке.

Обработка ошибок: предпочтительный механизм обработки — выброс и обработка исключений.

Новые фичи JS: JavaScript быстрее получает клевые обновления. Используя Babel можно использовать даже экспериментальные фичи.

Иммутабельность: JS разработчики могут использовать библиотеки для этого. В TypeScript приходится полагаться на нативные массивы/оператор spread (копирование при записи):

			const oldArray = [1, 2];
const newArray = [...oldArray, 3];

const oldPerson = {
   name: {
     first: "John",
     last: "Snow"
   },
   age: 30
};

// глубокое копирование объектов выглядит громоздко
const newPerson = {
  ...oldPerson,
  name: {
     ...oldPerson.name,
     first: "Jon"
  }
};
		

К сожалению нативный оператор spread не производит глубокое копирование, а ручное расширение является громоздким. Копирование больших массивов/объектов плохо сказывается на производительности.
Ключевое слово readonly делает свойства иммутабельными. Однако это всё ещё далеко от адекватной поддержки иммутабельности.

TypeScript&React: если вы занимаетесь фронтендом, то наверняка используете React. Он не создан для работы с TS. React пригоден для функциональных языков.

Нужен ли TypeScript? Мне кажется что шумиха вокруг TS имеет ту же природу, что и популярность Java\C#. Её причина — поддержка большими корпорациями.

Вердикт: хотя TypeScript и позиционируется как “лучше чем JS”, его достоинства переоценены.

Go

Параллелизм: это киллер фича языка. Как и Erlang\Elixir, Go следует mailbox модели параллелизма. Параллелизм в Go с помощью горутины в случае ошибки убивает всю программу, когда как параллелизм в Elixir убивает один из процессов.

Скорость: Go — очень быстрый язык, как по времени компиляции, так и скорости запуска программ.

Сложность изучения: это простой язык, который можно изучить за месяц.

Обработка ошибок: Go не поддерживает исключения. Вместо этого нужно явно обрабатывать возможные ошибки. Как и Rust, он возвращает два значения: результат вызова и возможную ошибку.

Не ООП: хоть многие со мной не согласятся, я считаю отсутствие ООП фич большим достоинством.

Экосистема: нативные Gо библиотеки недостаточно стандартизированы. Одни библиотеки в случае ошибки возвращают (int, error), другие -1.

Типизация: отсутствие дженериков в Go приводит к дублированию кода.

Поддержка NULL: к сожалению, Gо использует NULL, а не более безопасные альтернативы.

Иммутабельность: не поддерживается.

Вердикт: Если вы не работаете в Google, тогда Go, вероятно, не лучший выбор. Go — простой язык, подходящий для системного программирования. Он действительно быстрый, лёгкий для изучения и отлично справляется с многопоточностью.

Javascript

Экосистема: это сильная сторона JS. Этот язык используют в вебе, CLI, data science, и даже машинном обучении.

Сложность изучения: JavaScript один из самых простых для изучения языков. Его можно освоить за пару недель.

Типизация: JS — динамически типизирован. И это иногда порождает странные вещи вроде:

			[] == ![] // -> true
NaN === NaN; // -> false
[] == ''   // -> true
[] == 0    // -> true
		

Иммутабельность: выше уже говорилось о том, что оператор spread снижает производительность. Однако, JS-библиотеки могут помочь.

React не создан для JavaScript: использование PropTypes обязательно в связке React+JS. Однако это означает, что PropTypes должны поддерживаться, что может стать вашим кошмаром.

Также возможны проблемы с производительностью в подобных моментах:

			HugeList options=[]
		

Такой невинный на первый взгляд код, может стать кошмарным, из-за того, что в JS []!=[]. Он заставит огромный список перерендериться при каждом обновлении.

Ключевое слово this: это, возможно, худшее, что есть в JS. Оно порождает неоднозначное поведение. Использование этого ключевого слова часто приводит к странным ошибкам.

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

Новые фичи JS: быстро получают поддержку (можно использовать экспериментальные).

Обработка ошибок: предпочтительный механизм обработки — выброс и обработка исключений.

Вердикт: JS — не идеален. Но при должной дисциплине может быть хорошим языком для фуллстек разработки.

Следите за новыми постами
Следите за новыми постами по любимым темам
31К открытий32К показов