Я слышал дискуссии о том, почему статическая типизация лучше тестирования (и наоборот) как способ уменьшить количество ошибок в системах, и я думаю, что такие дискуссии бесполезны, поскольку они сравнивают яблоки и апельсины.
Рассмотрим следующий фрагмент программы (на Python с подсказками типов).
def foo(i: int, j: int) -> bool: return i < j def bar(i: int, j: int) -> bool: return i == j
Если рассматривать только сигнатуры типов этих функций, то имеем следующее.
foo: (int, int) -> bool bar: (int, int) -> bool
Используя только сигнатуры типов, мы можем пометить следующие вызовы как ошибочные.
foo("1", 3) # first argument is not of int type bar(3, [1,3]) # second argument is not of int type
Однако сигнатур типов недостаточно, чтобы пометить следующие вызовы как ошибочные.
if foo(3, 3): print("val1 is equal to val2") if bar(3, 5): print("val1 is less than val2")
Теперь мы могли бы подумать, что эти ошибки не произошли бы, если бы функции были названы как equals
и less_than
. При переименовании обратите внимание, что мы не полагаемся на статическую типизацию, чтобы избежать ошибок :)
Тем не менее, давайте продолжим и переименуем функции.
def equals(i: int, j: int) -> bool: return i < j def less_than(i: int, j: int) -> bool: return i == j if equals(3, 3): print("val1 is equal to val2") if less_than(3, 5): print("val1 is less than val2")
Даже сейчас фрагмент кода неисправен.
Проблема в том, что существует (по крайней мере) два типа типов, а мы указали только один тип. Для другого типа мы полагаемся на связь между именами функций и ожидаемым поведением функций.
Чтобы понять проблему, рассмотрим описание функции equals
:
- Принимает два целых значения i и j и возвращает логическое значение.
- Возвращает true, если входные данные равны; ложно в противном случае.
Первая часть описания ограничивает значения, допустимые в качестве входных данных и выходных данных функции. Это описание является типом значения (домена) функции, которую мы указали с помощью подсказок типа (например, : int
).
Вторая часть описания ограничивает поведение функции. Этот вид описания представляет собой поведенческий тип функции (такой, который может быть реализован по-разному). В приведенном выше примере мы не указывали такого рода информацию о типе.
Статическая типизация, доступная сегодня в большинстве языков программирования, предназначена только для указания типов значений (а не поведенческих типов).
Если вы все еще не уверены в поведенческих типах, рассмотрите возможность полагаться только на сигнатуру функции (List[int]) -> List[int]
и выбрать функцию, которая сортирует заданный список целых чисел в порядке возрастания.
Как насчет тестирования?
В отличие от статической типизации (которая сегодня общедоступна), тестирование не касается типов значений.
Тестирование — это определение и проверка типов поведения.
В частности, тестовый пример фиксирует конкретное поведение функции (например, equals(3,3)
должно оцениваться как истинное, equals(3,5)
должно оцениваться как ложное), набор тестов фиксирует поведенческий тип (т. е. набор ожидаемых поведений) функции, а тестирование проверяет, соответствует ли реализация функции правильному типу поведения (т. е. функция демонстрирует ожидаемое поведение).
Чтобы провести параллели со статической типизацией, наборы тестов похожи на подсказки типов, а тестирование похоже на проверку типов.
Хотя тестирование может показаться лучше, чем статическая типизация, это не так. В отличие от статической типизации, тесты не являются ни краткими, ни исчерпывающими в определении возможного поведения тестируемой функции (в общем). Это верно даже для расширенных методов тестирования, таких как тестирование на основе свойств. Кроме того, использование тестирования как способа обеспечения правильности типа значения в целом недопустимо.
Итак, где это нас оставляет?
До тех пор, пока у нас не появятся системы статических типов, которые позволят нам легко и лаконично указывать как значения, так и поведенческие типы и использовать их для проверки правильности типов, нам понадобятся как статическая типизация, так и тестирование, чтобы уменьшить количество ошибок в системах.
Короче говоря, статическая типизация и тестирование дополняют друг друга. А сейчас нам нужно иметь возможность указывать и анализировать типы, а также создавать и выполнять тесты.