Пишите маленькие функции на основе примеров Кейт Брэйтуэйт

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

Например, в игре го есть положение, называемое атари, в котором фишки игрока могут быть захвачены противником: фишка с двумя и более свободными соседними клетками (называемыми степенями свободы), не находится в положении атари. Подсчитать количество степеней свободы у фишки бывает нелегко, но, когда оно известно, определить атари легко. Можно для начала написать такую функцию:

boolean atari(int libertyCount)

  libertyCount < 2

Здесь спрятано больше, чем кажется на первый взгляд. Математическую функцию можно рассматривать как множество — некоторое подмножество декартова произведения области определения (здесь это int) и области принимаемых значений (здесь — boolean). Будь эти множества одинакового размера, как в Java, в множестве int х boolean было бы 2L*(Integer.MAX_VALUE+(—1L*Integer.MIN_VALUE)+1L), или 8589934592 элементов. Половина из них принадлежит подмножеству, являющемуся нашей функцией, поэтому для полного доказательства корректности нашей функции нужно проверить около 4,3 х 109 случаев.

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

Выход подсказывает предметная область. Природа го такова, что число степеней свободы фишки является не любым целым числом, а одним из чисел {1,2,3,4}. Поэтому можно написать другой вариант кода:

LibertyCount = {1,2,3,4}

boolean atari(LibertyCount libertyCount)

  libertyCount == 1

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

Загрузка...