Так ли плоха динамическая типизация?

10 апреля 2013

В последнее время наблюдается большой интерес к языкам программирования со статической типизацией. Зачастую тот факт, что в неком языке (Scala, Haskell, OCaml, …) используется статическая типизация, преподносится в качестве неоспоримого преимущества этого языка над языками программирования с динамической типизацией (Erlang, Clojure и другие лиспы, Perl, Python, Ruby, …). Здесь имеет место явная подмена понятий, манипуляция неокрепшим сознанием неопытных программистов, троллинг, пропаганда и другие нехорошие вещи.

Обратим внимание, что в программировании есть две огромные ниши.

Первую нишу можно условно назвать «низкоуровневой». Сюда относится разработка драйверов, операционных систем, приложений для микроконтроллеров на хим-заводах, виртуальных машин, архиваторов/кодеков, большинства СУБД, часто — компиляторов и отладчиков, а также разработка веб-браузеров и движков трехмерных компьютерных игр. В общем, это все те области, где неистово рулят C/C++ и в обозримом будущем переход с них на что-то другое не предвидится. Поскольку эти языки являются слишком низкоуровневыми и все равно не обладают правильной статической типизацией, «низкоуровневая» ниша в контексте данного поста нам не интересна.

Есть и совершенно другая ниша, которую, как вы, скорее всего, уже догадались, мы условно назовем «высокоуровневой». Сюда относятся разработка большинства десктоп-приложений, сайтиков (включая фронтенд), мобильных приложений, игр типа Angry Birds, демонов, мало занимающихся вычислениями, некоторых СУБД, различного рода искусственных интеллектов, и так далее. В этой нише выбор языков программирования намного богаче, а потому интересно порассуждать о применимости языков со статической и динамической типизацией.

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

Поговорим о скорости. Вообще-то говоря, нет связи между скоростью работы программы и тем, используется ли в языке, на котором эта программа написана, статическая или динамическая типизация. За примерами далеко ходить не надо — взять хотя бы SBCL (это такой компилятор Common Lisp), PyPy (JIT-компилятор Python’а в машинный код) или V8 (почти как PyPy, только для JavaScript). Программы на Common Lisp могут дробить числа не хуже Java, в связи с чем первый аргумент за статическую типизацию, как минимум, сомнителен. Отметим, что некорректно сравнивать быстродействие программ, написанных на CL и C++, поскольку эти языки используются для решения задач, относящихся к разным нишам.

Также аргумент относительно скорости сомнителен по совершенно другой причине. Действительно ли в «высокоуровневой» нише часто требуется умение быстро дробить числа? На самом деле, десктопные приложения все равно большую часть времени ожидают ввода каких-то данных пользователем или ответа от СУБД. Большинство сайтов написаны на чем-то вроде PHP или Perl, и, по всей видимости, скорость этих языков всех устраивает. Опять же, потому что большую часть времени они всего лишь парсят формочки и работают с базой данных.

Хорошо, а что на счет статической проверки типов? На самом деле, она и вправду весьма удобна. Но проблема в том, что помимо класса ошибок, от которых нас избавляет статическая типизация, есть еще великое множество ошибок. Дэдлоки, состояние гонки, ошибки округления, целочисленное переполнение, утечки памяти, выход за границу массива, sql-injection и многие другие. Да и элементарные ошибки при реализации бизнес-логики никто не отменял. Чтобы обезопасить приложение от этих ошибок, его нужно тестировать. А поскольку тестировать вручную достаточно большое приложение занимает много времени, тесты должны быть автоматизированы. Возникает закономерный вопрос. Если нам все равно необходимо писать тесты, зачем нужна статическая проверка типов?

Примечание: Занятный вопрос — может ли статическая типизация повысить качество продукта, разрабатываемого командой программистов, которые не пишут тесты? Лично я в этом сомневаюсь. Культура разработки либо есть, с code review, тестированием, непрерывной интеграцией и другими хорошими практиками, и тогда продукт получается неплохим, либо ее нет. Если команда не пишет «эти глупые тесты в стиле 2 + 2 = 4», зато практикует «тестирование в бою», релизы раз в год, имена переменных X1, X2, …, XN, а также другие ужасные вещи, на выходе получится что получается. В обоих случаях не играет никакой роли, пишется продукт на Scala или на PHP.

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

Языки с динамической типизацией обычно легче в изучении. С точки зрения бизнеса это означает, что вы можете нанять хорошего программиста без опыта программирования на используемом вами языке. Всего за несколько недель из хорошего программиста без знания языка X он превратится в хорошего программиста с неплохим знанием языка Х.

Статическая типизация обременительна с точки зрения разработчика языка программирования. Решение использовать в языке статическую типизацию приводит к усложнению поддержки и развития языка программирования. Почитайте TAPL, и вы поймете, насколько сложно создать язык с правильной статической типизацией. Также я бы советовал ознакомиться с замечательной заметкой о том, какие забавные баги находят в компиляторе Scala и сколько времени порой уходит на их исправление.

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

  • Языки с динамической типизацией быстрее компилируются, ежели компиляция вообще предусмотрена, что ощутимо ускоряет разработку;
  • Почему-то языки с динамической типизацией часто имеют более читаемые сообщения об ошибках, нежели языки со статической типизацией (вроде «программа упала, потому что класс Int не имеет метода toLower, файл string_parser.code, строка 193, переменная StrVariable, стектрейс: …»);
  • Обычно в языках с динамической типизацией также предусмотрен удобный мокинг, что существено упрощает написание тестов;
  • В Erlang, например, вы можете подсоединиться к работающему приложению и выполнить в нем некий отрывок кода с целью исправить что-то или запустить трассировщик с целью разобраться в обнаруженной баге;

Хотя, строго говоря, нет причин, по которым в языке со статической типизацией нельзя реализовать все эти пункты.

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

Дополнение: Так ли плоха статическая типизация?

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.