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


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


Удачной отказоустойчивости!