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

Это последняя статья, если вам интересно: https://medium.com/@rayshih771012/how-to-program-without-fear-fad4fb6dffd8

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

Человек, написавший код, ушел, но в процессе проектирования он оставил некоторые подсказки в коде - типы и средство проверки типов всегда здесь, чтобы проверить целостность с помощью подсказок, и, поскольку он существует с самого начала, это лучший вариант, которому можно доверять. Как мы слышали, код - это документ. То есть мы можем использовать систему типов как охрану, чтобы не попасть в пропасть. Рассмотрим следующий сценарий:

Есть функция, возвращающая имена пользователей: getName.

public String getName() {
  return this.firstName;
}

И вам нужно изменить его, чтобы вернуть полное имя.

public String getName() {
  return this.firstName + " " + this.lastName;
}

Обратите внимание, что в обоих случаях возвращаемыми типами являются String, и это две функции с одинаковым именем. Обратите внимание, что может быть больше других функций с таким же именем, например: функция, которая возвращает имя продукта, функция, которая возвращает имя заказа. Наивное решение - использовать функцию поиска проекта IDE, чтобы найти всю строку getName и внимательно изучить все их, одну за другой. А затем обновите вызывающие объекты, чтобы адаптировать изменение, если оно действительно относится к объекту User. Это кажется обременительным, но так быть не должно, поскольку мы программисты, и мы должны уменьшить количество повторяющихся / похожих работ. Но как? Ответ - Тип!

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

class FirstName {
  private String name;
  public FirstName(String name) {
    this.name = name;
  }
}
class FullName {
  private String name
  public FullName(String name) {
    this.name = name;
  }
}

Хотя структуры этих двух классов абсолютно одинаковы, типы не совпадают, потому что невозможно поместить значение с typeFirstName в функцию, которая требует FullName, не говоря уже о том, чтобы поместить String в.

public void printFullName(FullName name) { ... }
// somewhere
FirstName name = FirstName("Ray");
printFullName(name); // compile error here
printFullName("Ray"); // compile error here

Поскольку указанная выше переменная name не имеет типа FullName или String, компилятор просто скажет вам, что тип не совпадает.

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

public FirstName getName() { ... }
// file A in same module/package
String name = person.getName(); // compile error here
// file B in some other packages that seem unrelated
String anotherName = person.getName(); // compile error here

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

// file A in same module/package
FirstName name = person.getName();
// file B in some other packages that seem unrelated
FirstName anotherName = person.getName();

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

Следующим шагом будет одновременное изменение тела функции getName и типа возвращаемого значения на FullName. Опять же, компилятор поможет вам проверить каждое появление, все зависимости. Все, что вам нужно сделать, это исправить ошибки.

Так что это нам говорит? Средство проверки типов / компилятор может не только проверить, совпадают ли подписи типов, но также может помочь нам проверить, согласованы ли наши обещания внесенных нами изменений во всей базе кода. И это действительно помогает, потому что страх исходит из-за непоследовательности в себе или в отношениях между коллегами. А использование большего количества типов означает создание большего количества контрактов между всеми этими людьми. И средство проверки типов позаботится о том, чтобы этот контракт не был нарушен.

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

newtype FirstName = FirstName String
newtype FullName = FullName String

newtype в начале строк - это способ объявления типа данных, очень похожий на ключевое слово class в Java. И после этого идет имя типа, за которым следует =, за которым следует конструктор с одним аргументом, который является просто функцией с одним аргументом, принимающим String.

Видите ли, в отличие от Java, в котором для создания нового class вам необходимо реализовать конструктор, объявить частный член и присвоить значение частному члену. Напротив, создание новых типов в PureScript настолько естественно. Более того, эта конструкция не требует затрат времени выполнения! Это означает, что эта конструкция работает только до проверки типа, после проверки типа она будет удалена, и в среде выполнения не будет никакого представления.

Резюме

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

Закодировав больше логики в систему типов, вы можете начать замечать, что повторяете себя. Можно подумать, что уменьшить дублирование в системе типов невозможно. Для некоторых языков это неизбежно, но многие языки поддерживают функции, позволяющие предотвратить это. Эта функция обычно называется Generic. Мы рассмотрим это в следующей статье «Без страха».

Я бы хотел поговорить о других сериях в будущем. Вот список запланированных тем:

  • Эпизод: Generic - это тип высшего порядка
  • Эпизод: Ты не можешь конфликтовать с самим собой - Фантомный Тип
  • Расширенный эпизод: закодируйте побочный эффект в системе типов
  • Расширенный эпизод: кодирование логики в системе типов - более сильные типы
  • Продвинутый Эпизод: Программирование на уровне типов - генерация кода
  • Дополнительный эпизод: Возможно, этого недостаточно - разрешимость
  • Extra Episode: Your Totality is Not Total - проблема остановки реальна

Спасибо за прочтение! Любые комментарии приветствуются: D