В последние годы в интернете появилась тенденция сулить все блага мира за использование сильной типизации. Самые настойчивые ораторы даже приходят к таким выводам, как «когда у вас есть типы, вам не нужны тесты». Мне кажется, это не самый аккуратный посыл. Существуют, очевидно, ситуации, когда сильная типизация может помочь выловить человеческие ошибки на ранних стадиях, но обязательное внедрение оной буквально везде — приносит больше хлопот, чем пользы.
Все, что написано ниже — не более, чем скромное мнение автора (и так-то не самого умного человека в мире, а вдобавок — и посредственного разработчика). Но это мнение основано на более чем 30-летнем профессиональном опыте и карьере, обеспечивавшей надежные и отказоустойчивые решения для бизнеса на протяжении всего этого срока. Кроме того, я убедительно прошу каждого евангелиста сильной типизации сначала написать парсер markdown
, чтобы увидеть своими глазами ситуацию, в которой типы не приносят вообще никакой пользы.
Стандартным аргументом адептов сильной типизации будет что-то наподобие: «рассмотрим, например, рефакторинг». Вот смотрите: когда разработчик попытается позвать get_user_by_id
, передавая в качестве параметра — экземпляр user
вместо целочисленного идентификатора, компилятор сразу пожалуется, и ошибка не просочится в рантайм. И это правда! Но знаете, что? Я пишу код в течение нескольких десятилетий, и я никогда не ожидал, что бездушная машина буквально напишет за меня все приложение. Я чувствую себя отчасти ответственным за то, чтобы избежать передачи всякого малорелевантного мусора в аргументы функции. «Но было бы здорово свести наши усилия к минимуму?» — Я буквально слышу этот аргумент. Ну, как бы, и да, и нет.
Присвоение идентификатора чему-то, что предназначено для использования в качестве экземпляра, безусловно, не является самой трудной для отладки ошибкой. Присвоение куска текста тому, что должно быть меткой времени, означает всего лишь, что требуется больше внимания, а не больше сильных типов. Проблемы, которые действительно трудно поймать и устранить, подстерегают нас в другом болоте. Давайте рассмотрим некоторые примеры (я намеренно использую псевдоязык, чтобы избежать религиозных споров на тему синтаксиса).
Неверная ветка
Давайте представим, что у нас есть функция, которая обрабатывает вызовы HTTP к стороннему сервису. Мы должны ее модифицировать, чтобы добавить охватить различные варианты ответов. У нас было что-то вроде вот такого.
function call_3rd_party(uri) {
case call(uri) {
200 => handle_success()
else => handle_error()
}
}
Теперь мы хотим лучше обработать отказы.
function call_3rd_party(uri) {
case call(uri) {
200 => handle_success()
500 => handle_error(UNAVAILABLE_SERVER_ERROR)
503 => handle_error(INTERNAL_SERVER_ERROR)
else => handle_error(UNKNOWN)
}
}
Видите тут проблему? Ну разумеется, да, мы перепутали коды ошибок. Может ли любая суперсложная система суперстрогих типов сделать это для нас? К сожалению, нет.
Доступ не по тому полю
Давайте представим, что у нас есть СУБД, а там живет таблица пользователей, из которой нам надо достать запись по переданному в качестве аргумента имени. И мы пишем что-то очень простое, наподобие этого.
function get_user(name) {
db_load_user_or_die(name)
}
Даже если параметр name
— сто раз строго усилен типом, переданная в качестве аргмента фамилия вместо полного имени — сделает эту функцию неработоспособной примерно всегда.
Зависимые типы и формальное доказательство
Да, в Idris
, Agda
, и множество подобных языков, опирающихся на Теорию категорий
, каждый может доказать со стопроцентной вероятностью, что результат операции всегда будет правильным. Это звучит несколько волшебно, но это так.
Вышесказанное не означает, что у нас в руках серебряная пуля; мы просто сместили проблему вниз по цепочке. Теперь вместо того, чтобы писать правильный код на языке Foonctional, мы не должны ошибиться в языке Typoo. А это во многих случаях сложнее. Полнота, хотя и является обязательной в аксиоматике, может быть реальной проблемой для быстрого продуктивного развития продукта. Рассмотрим следующий код, показывающий имя автора рядом с сообщением в блоге.
function show_author(name) {
user = get_user(name)
if ok?(user) {
show(user)
} else {
log("Something wrong with " + user)
show(ADMIN)
}
}
Если с автором что-то пошло не так, нет причин беспокоиться. Вполне уместно просто отбросить ошибку. Конкретно здесь — все, что нам нужно, это показать чёртов пост. Не удалось дотянуться до имени автора? — Во многих случаях это не критично. Главной целью было бы все-таки показать контент, и мы должны сделать это любой ценой. С сильными типами такой код даже не скомпилируется. И нам придется править базу данных, обновлять всех не заполненных авторов, или изобрести новый монадический тип для возможно указанного автора.
Да, последнее гораздо надежнее. Да, пример довольно надуманный. Но с моим условным подходом это было бы в продакшене в одну минуту. Не поленитесь, сходите к директору, и спросите у него: «Что для бизнеса лучше?».
Панацея от всех бед
Один из главных разумных аргументов для использования сильных типов — «мне не нужно читать код, чтобы понять, что делает эта функция, я могу проверить типы». — Да ладно, это же 2020 год. Хорошие мальчики уже давно пишут и поддерживают подробную документацию, с соответствующими doctest и прочими плюшками. Назначьте совещание, пригласите всех своих коллег и попросите их проголосовать, куда они предпочитают заглядыать, чтобы понять, как работать с интерфейсом: в типы или в документацию.
И да, типы на пригодных для использования языках — часть документации. Рассуждения о том, что она может устареть, — абсурдны. Код тоже может устареть, вообще-то, почему этого никто не боится? Просто сделайте первосортную документацию для вашей кодовой базы, это совершенно не сложно. Безжалостно отклоняйте CR, даже если хоть одна функция осталась недокументированной. А еще написание документации — существенно облегчает ваше собственное понимание того, что (и как) вы там на самом деле реализуете. Да и люди — пока еще — в среднем лучше понимают простой английский язык, чем теорию категорий.
Может ты перестанешь ныть и предложишь что-нибудь?
Я мог бы привести еще тонну примеров кода, где сильная типизация только мешает. Но да, я могу вместо этого предложить и готовый рецепт. Он, вероятно, не будет работать для всех случаев, но это определенно лучше, чем слепая вера в Сильнейшего Святого Супертипизированного Спасителя.
Внешний, не встроенный в компилятор инструмент для статической проверки и анализа кода может помочь. Умный паттерн матчинг в аргументах функций. Guards. Готовность к обработке недопустимого ввода (всеядные заключительные function clauses). Разумеется, иммутабельность и разделяемая память.
Разработчик по-прежнему обязан думать, прежде чем открывать IDE текстовый редактор. Основная ответственность по-прежнему лежит на нас. И мы будем продолжать приносить баги в продакшн. Такова жизнь, это неизбежно, к сожалению.
Озаботиться 100% отказоустойчивостью в рантайме и немедленно сообщать обо всех проблемах — по-настоящему помогает. Сильные типы… Ну, судя по моему опыту — когда как. Но, вы, разумеется, можете думать иначе, да и опыт у всех разный, не говоря о вкусовых предпочтениях.
Удачной отказоустойчивости!
JustDont
Я нахожу несколько забавным, что раньше в комментах автор высказывал своё «фе» в адрес типов в угоду тестам, но в статье о тестах уже предпочёл умолчать.
А кто вам, собственно, сказал, что устаревания кода никто не боится? Вы вообще, например, в курсе, какой объем айтишной экономики ушел на то, чтоб слезть с флеша? Это всё потому, что никто не боится устаревания кода, ага.
Ну и касательно документации — это как с бекапами. Есть люди, которые еще не работали с устаревшей, неактуальной, или попросту плохой документацией, и есть люди, которые уже работали. Удивляться тому, что последние предпочитают при малейших сомнениях глянуть в код — как минимум странно. Код не врёт (особенно если вы его сами собрали), а установить, врёт ли документация — не представляется возможным без существенных расходов.
chapuza Автор
Диффамация, я такого в общем случае никогда не утверждал. А даже если бы и утверждал — почему я должен говорить о тестах в заметке, посвещенной типам? А если и должен — то вот же внезапно оно: «подробную документацию, с соответствующими doctest».
А зачем вообще добровольно использовать инструменты, которым вы не доверяете? Документация в тулчейне, которым я пользуюсь, — не врет. Никогда.
JustDont
Зачем быть бедным и больным, если можно быть богатым и здоровым?
Ну и вы передёргиваете: если у меня есть возможность заглянуть в код — то я вполне могу доверять инструментам (код хороший) и при этом не доверять документации (документация плохая).
chapuza Автор
Я черным по белому написал: инструмент может быть признан хорошим тогда и только тогда, когда у него идеальная документация.
JustDont
Да, когда вы по разным не относящимся к делу причинам миллиардер — то жильё может быть вами признано хорошим тогда, и только тогда, когда оно в 300 кв.м. и идёт в комплекте со штатом прислуги.
Я ж говорю, это аргумент из серии «ну вот смотрите, я здоровый и богатый, чё б вам всем такими не быть»?
chapuza Автор
Неправда. Мы обсуждаем код (и типы, и документацию) — написанную нами самими. Никаких проблем создавать документацию, тут не нужно быть миллиардером.
JustDont
Действительно, никаких проблем: вот тебе многомегабайтный легаси-код, который по чисто экономическим причинам мы несколько лет поддерживали силами одного полупрофессионала, а теперь наверни нам на него модную обёртку. И, опять же, по чисто экономическим причинам, времени на то, чтоб написать/обновить документацию легаси у тебя не будет.
Действительно, и при чём тут экономика и миллиардеры?
ЗЫ: Всё вышенаписанное — это, к слову, не абстракция, а личный опыт.
chapuza Автор
Там и типов нет, наверное ведь. Давайте сравнивать сравнимое, пожалуйста.
JustDont
На яве типы есть даже в дремучем легаси. А вот актуальной документации может и не быть, если несколько лет никто не занимался её поддержанием.
lair
Это ваш собственный критерий, и не все могут себе позволить ему следовать.
sshikov
>А зачем вообще добровольно использовать инструменты, которым вы не доверяете?
Хм. Вот у меня инструменты допустим Hadoop и Spark. Ну, там еще с десяток других наверное наберется, но это уже неважно. Вы думаете, у меня есть много альтернатив? Я бы сказал, что нет — ни одной вменяемой не наблюдается. И даже если бы они были, стоимость перехода на любую из них будет совершенно запредельна.