Статьи

Я бы хотел, чтобы у Ruby были интерфейсы

Аннотация

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

Документация для людей и машин

Во-первых, я хочу показать, как аннотации типов могут использоваться как документация для людей и машин.

Давайте посмотрим на эту функцию jQuery.ajax()и просто притворимся, что мы не знакомы с ней. Какую информацию мы можем получить только от его подписи?

    jQuery.ajax(url, settings)

Единственное, что мы можем точно сказать, это то, что функция принимает два аргумента. Мы можем угадать типы. Возможно, первый — это строка, а второй — хеш / объект конфигурации. Но это всего лишь предположение, и мы можем ошибаться. Мы понятия не имеем, какие параметры входят в объект настроек (ни их имена, ни их типы) или что возвращает эта функция.

Мы не можем вызвать эту функцию без проверки исходного кода или документации. Каждый, кто выступает за проверку исходного кода, не понимает, почему у нас есть функции и классы. Они могут быть использованы без нашего ведома об их реализации. Другими словами, мы должны полагаться на их интерфейсы, а не на их реализацию. Что подводит нас к моему первому пункту.

Типы помогают определить интерфейс функции или класса.

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

Теперь сопоставьте его с печатной версией.

Future ajax(String url, {bool async: true, Function beforeSend, bool cache: true});

Это дает нам гораздо больше информации.

  • Первый аргумент этой функции — строка.
  • Функция принимает несколько параметров, и мы видим их все в подписи. Мы можем видеть не только их имена, но также их типы и значения по умолчанию.
  • Функция возвращает экземпляр класса Future. И, конечно же, наши инструменты могут показать нам публичные методы этого класса.

Типизированная версия дает нам достаточно информации для вызова этой функции без проверки исходного кода или документации.

Можно утверждать, что хорошая документация может передавать больше информации, чем система типов. Хотя это, безусловно, правда, типы дают вам гораздо более быстрый отклик. Автозаполнение происходит мгновенно, тогда как проверка документации занимает много времени. Более того, типы служат документацией для машин, обеспечивая такие функции, как структурный поиск и рефакторинг. Другое дело, что не все проекты хорошо документированы. Удачи в поиске любой документации для приложения Rails с 200 тысячами строк, которое существует уже много лет.

Подводя итог:

Типы отлично подходят для определения API, потому что они служат документацией для людей и машин.

Типы обеспечивают концептуальную основу для программиста

Хороший дизайн — это все о четко определенных интерфейсах. И гораздо проще выразить идею интерфейса на языке, который их поддерживает.

Например, представьте приложение для продажи книг (скажем, приложение Rails), в котором покупка может быть сделана либо зарегистрированным пользователем через пользовательский интерфейс, либо внешней системой через некоторый API.

optional_type_system

Как видите, оба класса играют роль покупателя. Несмотря на то, что это чрезвычайно важно для приложения, понятие покупателя четко не выражено в коде. Там нет файла с именем purchaser.rb. И в результате, кто-то может изменить код, чтобы упустить тот факт, что эта роль вообще существует.

def process_purchase purchaser, details
#...
end

class User
end

class ExternalSystem
end

Можете ли вы, взглянув на этот код, сказать, какие объекты могут играть роль покупателя? Какие методы включает эта роль? Мы не знаем наверняка, и мы не будем получать большую помощь от наших инструментов.

Теперь сравните его с версией, в которой мы явно определяем интерфейс покупателя.

processPurchase(Purchaser purchaser, details){
    //...
}

abstract class Purchaser {
    int id();
    Address address();
}

class User implements Purchaser {}
class ExternalSystem implements Purchaser {}

В типизированной версии четко указано, что у нас есть интерфейс Покупателя, и классы User и ExternalSystem реализуют его. Более того, наши инструменты могут видеть это и, как следствие, они могут помочь нам ориентироваться. Что подводит нас ко второму пункту.

Типы обеспечивают концептуальную основу для программиста. Это полезно для определения интерфейсов / протоколов / ролей.

Я провожу большую часть своего времени в программировании на Ruby и JavaScript, и я считаю эти языки чрезвычайно мощными и гибкими. Но после работы с большими приложениями, написанными на этих языках, я пришел к выводу, что отсутствие интерфейсов толкает разработчиков к созданию тесно связанных систем.

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

Статически Типизированные Языки?

Может показаться, что я защищаю статически типизированные языки, но это не так. Аргументы против обязательной статической системы типов все еще здравы.

  1. Это усложняет язык. Написание фиктивной библиотеки на Ruby — это упражнение, которое может выполнить почти каждый. Делать подобные вещи в Java гораздо сложнее.
  2. Это сдерживает выразительность языка.
  3. Требуются аннотации типов, даже если они нежелательны (например, на доменно-ориентированном языке).

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

Системы необязательного типа

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

Другими словами,

processPurchase(Purchaser p, OrderDetails o){}

такой же как

processPurchase(p, o){}

когда он запускается в производство.

Дополнительные системы типов очень прощающие. Вам не нужно выполнять проверку типа (как в Java). Напротив, средство проверки типов поможет вам найти опечатки, поиск и рефакторинг. Вы не переписываете свою программу, чтобы заставить типы работать. Если вы что-то создаете и создаете прототипы, просто не используйте типы. Используйте их только тогда, когда они добавляют ценность.

Было бы не совсем справедливо сказать, что факультативная типизация — это всего лишь хорошая вещь без каких-либо недостатков. Это все еще добавляет некоторую сложность языку (хотя ничто по сравнению с обязательной статической системой типов). Кроме того, чтобы в полной мере увидеть преимущества такой системы, вам нужны более сложные инструменты, чем простой текстовый редактор. Однако по причинам, которые я изложил в начале этой статьи, я готов заплатить эту цену.

Языки

Распространенное заблуждение среди разработчиков заключается в том, что язык с необязательной статической типизацией «похож на Java». Наоборот, он намного ближе к Ruby, Smalltalk или JavaScript. Это динамически типизированный язык с системой типов, построенной сверху для улучшения опыта разработки.

Если вы хотите намочить ноги, я могу порекомендовать следующие языки.

Современные языки для создания веб-приложений:

И несколько для любителей Smalltalk:

  • Strongtalk — реализация Smalltalk с необязательной статической типизацией.
  • Newspeak — еще один язык в семье Smalltalk.

Есть также некоторые языки, которые не имеют необязательных систем типов в строгом смысле, но имеют нечто похожее (например, Groovy).

Прочитайте больше

  • Гилад Брача является ведущим экспертом в этой области. Я настоятельно рекомендую прочитать его статью на эту тему: «Системы сменных типов»

  • Он также работает над Dart, поэтому вы можете прочитать его мысли о системе типов Dart : «Необязательные типы в Dart»

  • Я использую дартс в течение некоторого времени. Если вам интересен этот язык, вам могут пригодиться эти скринкасты: DartCasts .