О типизации ЯП

16 июня 2017г.  typings     типизация     typescript     яп   


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

Сильную и слабую типизацию рассматривать не будем, так как статья немного не об этом.


Начну статью со спойлера: лично я предпочитаю использовать ЯП со статической типизацией. Преимущества статической типизации (далее ST — static typing) перекрывают все возможные достоинства динамической типизации (далее DT — dynamic typing). После знакомства с TypeScript/Flow нет никакого желания возвращаться к обычному JS. Давайте разберем плюсы и минусы каждого подхода по пунктам, а потом сравним.

Динамическая типизация. Преимущества

Главное и основное преимущество DT — простота и упрощение. Что как правило обеспечивает низкий порог вхождения в ЯП. И вот это на самом деле самый спорный пункт, я даже сомневался, стоит ли включать его в "преимущества". С одной стороны, конечно да, удобно, можно создавать переменные без указания типа, пихать туда массивы, объекты, строки или числа, сравнивать все это друг с другом, передавать в качестве аргументов в функции и методы не будучи уверенным наверняка, какой тип имеет переменная в данный момент и какого типа результат вернется. По началу, когда кода мало, а работает над ним всего один разработчик, это все действительно выглядит просто и удобно. Но когда проект становится сложнее и в команде уже 2+ разработчиков, начинаются проблемы.

Рассмотрим в качестве пример рождение типичного амбициозного стартапа на PHP с командой из пары малоопытных разработчиков. Ибо тема эта близка многим, на самом деле.

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

Взглянув на вызов произвольного метода в листинге какого-нибудь модуля вашего когда, вы обнаружите, что не очень-то понятно, какого типа аргументы были переданы и какого типа результат вернулся. Поэтому для отладки или при рефакторинге, вам придется щедро раскидывать var_dump-ы по всему коду, а потом внимательно изучать их вывод. Что, согласитесь, весьма неудобно и затратно по времени и просто бесит. И, да, ваша навороченная IDE не будет заметно полезнее того же notepad++, в плане полезных подсказок.

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

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

Отдельная тема — приведение типов. Хоть у вас теперь все переменные подписаны "по-венгерски", не каждый из вашей команды сходу понимает, какого типа результат выдаст код, вроде такого:

/* index.php?foo=1 */ $sBar = 'some string'; $fValue = 1.00; var_dump($_GET['foo'] + ($sBar * null) == $fValue);

Приходится распечатывать шпаргалку по приведению типов и исполнять подобные куски при отладке вручную, дабы не казаться умнее интерпретатора. И самый логичный исход, перестать сравнивать горячее с мягким, а начать приводить типы к общему знаменателю (вместо $foo повсеместно (int)$iMyIntegerVar), использовать строгие сравнения (никаких "==", только "===" и "!=="), и всегда строго проверять типы (повсеместные typeof/gettype и пр. радости жизни) при любых операциях присваивания.

К чему мы в итоге пришли? Правильно, теперь мы занимаемся статической типизацией вручную, вместо того, чтобы возложить данную неблагодарную работу на компилятор/интерпретатор. Используется ли нами данное преимущество DT, "упрощающее" нам жизнь? Очевидно, нет.

Динамическая типизация. Недостатки

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

  • вы испытаете немало головной боли при рефакторинге и code review, пытаясь понять, какой подразумевался результат у вон той операции или почему в результате сравнения код выполняется не по той ветке, что планировалось;
  • изменив тип выходных данных какого-то метода, вы с легкостью пропустите где-нибудь в недрах вашего кода участок, в котором этот метод используется точно также, как и до изменения. И если покрытие кода тестами у вас не 100%, то узнаете об этом вы скорее всего уже после того, как где-то эта проблема вылезет как пользовательский баг;
  • ваша IDE вам не подскажет и не подберет подходящие для данного типа методы и вы фактически лишаетесь ее основного преимущества при разработке;
  • трата времени на попытку поддержания порядка, приходится всюду добавлять лишние ручные проверки типов
  • необходимость поддерживать документацию в стиле комментариев ***Doc, чтобы перестать путаться в многообразии ваших классов и их методов, и/или избыточные unit-тесты;
  • лишняя нагрузка на ваш интерпретатор при динамической проверки типов, а также лишние затраты памяти; это становится проблемой при разрастании ваших объектов "вглубь".

Статическая типизация. Преимущества

  • вы всегда уверены что написанный метод и результаты его работы используется вами правильно;
  • ошибки класса "сравниваем несравниваемое" или "пихаем невпихуемое" исключаются на стадии компиляции;
  • после перехода на ST (например, при переходе с JS на TS) вы с большой вероятностью обнаружите, что таких ошибок у вас дофига; и даже не ошибок, а скорее досадных опечаток, которых ваш замылившийся глаз в упор не замечает;
  • нет необходимости заниматься лишними проверками типов самостоятельно; вообще, переход на ST — это довольно дешевый способ повысить качество кода, особенно когда кодовая база растет быстро за счет большой команды;
  • ваш IDE станет намного дружелюбнее по отношению к вам; а если вы все еще пишете в блокноте, то это дополнительный стимул перейти на IDE;
  • рефакторинг перестает быть нудным и кропотливым занятием, после которого частенько появляются новые баги; поправив какой-нибудь метод вы не пропустите какое-нибудь место, где он вызывается в дебрях вашего проекта — компилятор услужливо вам напомнит;
  • читабельность кода заметно повышается.

Статическая типизация. Недостатки

  • вам предстоит самостоятельно заниматься разбором типов при получении данных из внешних источников, так как часто СУБД и прочим Rest-сервисам фиолетово до того, какие типы используете вы; но даже при динамической типизации вы вряд ли будете хранить все что пришло извне в том же самом виде, как оно пришло; поэтому недостаток незначительный;
  • порог вхождения в ЯП выше; что тоже не слишком серьезный недостаток, так как на мой взгляд, разница не столь существенна; потратив немного времени на преодоления этого "порога", вы получите существенное преимущество в дальнейшем; и это явно не сложнее, чем освоить комментарии в стиле ***Doc или покрыть проект на 100% unit-тестами;
  • для перевода проекта с DT на ST нужно много времени; это может быть правдой, если вы планируете разовый масштабный переход; но на деле, это вовсе необязательно; изначально можно пометить все не объявленные типы универсальным типом (таким как any в TypeScript); после чего постепенно заменять их по одному на что-то более осмысленное; поверьте, этот процесс очень увлекательный, и, скорее всего, вы узнаете много нового о своем проекте.

Подводя итоги сравнения

Если вы внимательно прочитали предыдущие пункты, то вам уже должно быть очевидно, почему стоит переходить на языки со ST. Конечно, вас может остановить тот факт, что ваш любимый язык не поддерживает статическую типизацию и нет подобных расширений языка. Но стоит обратить внимание, что языки со временем развиваются, в том же PHP, например, в седьмой версии появились Scalar Type Declarations. В Javascript господствуют транспайлеры и компиляторы, позволяющие писать на TypeScript или Dart. И так далее, почти всегда есть из чего выбрать. Возможности ЯП расширяются, и то, что ранее мы даже не пытались представить себе, теперь вполне обыденное дело.

Когда стоит использовать динамическую типизацию

  • если вы пишите небольшой скрипт, в котором не заложена серьезная бизнес логика, например, shell-скрипты, упрощающие рутинные операции;
  • если вы DB-developer и пишите процедуры для своей СУБД (как правило, у вас при этом нет особых вариантов);
  • если вы вчера купили книгу "Своя CMS на PHP для чайников" и вам жалко потраченных на нее денег.


Полезные материалы: