В последние годы в интернете появилась тенденция сулить все блага мира за использование сильной типизации. Самые настойчивые ораторы даже приходят к таким выводам, как «когда у вас есть типы, вам не нужны тесты». Мне кажется, это не самый аккуратный посыл. Существуют, очевидно, ситуации, когда сильная типизация может помочь в��ловить человеческие ошибки на ранних стадиях, но обязательное внедрение оной буквально везде — приносит больше хлопот, чем пользы.
Все, что написано ниже — не более, чем скромное мнение автора (и так-то не самого умного человека в мире, а вдобавок — и посредственного разработчика). Но это мнение основано на более чем 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% отказоустойчивостью в рантайме и немедленно сообщать обо всех проблемах — по-настоящему помогает. Сильные типы… Ну, судя по моему опыту — когда как. Но, вы, разумеется, можете думать иначе, да и опыт у всех разный, не говоря о вкусовых предпочтениях.
Удачной отказоустойчивости!
