Все статьи Программирование по контракту в языке C++
Design by Contract — принцип программирования. Его иногда ошибочно считают технологией и стремятся делать строго-как-в-учебниках, но это непрактичный подход.
Контракты функций
При написании функций, имеющих математический смысл, следует помнить про необходимость вводить и соблюдать контракты, то есть спецификации функций и проверки этих спецификаций. Вот пример плохого кода (без контрактов):
// файл GameMath.h
#pragma once
struct Math
{
Math() = delete;
static int Random(int a, int b);
}
// файл GameMath.cpp
#include "GameMath.h"
int Math::Random(int a, int b)
{
return a + rand() % (a - b);
}
// файл, использующий структуру Math
#include "GameMath.h"
void foo()
{
const int MIN_SPEED = 1;
const int MAX_SPEED = 4;
int sparkleSpeed = Math::Random(MIN_SPEED, MAX_SPEED);
// ...
}
Читатель, анализирующий код функции foo()
, будет задаваться вопросами:
- Что означают a и b, почему туда передаются
MIN_SPEED
иMAX_SPEED
? - Могу ли я через
Math::Random(1, 10)
получить число 10? - Что вернёт
Math::Random(0, -7)
?
В итоге читателю для анализа foo()
придётся изучить код других функций. Проблема легко устраняется, если ввести:
- интуитивно понятные названия параметров
- контракт в виде комментария в заголовочном файле
- проверку соблюдения контракта с помощью макроса assert
// файл GameMath.h
#pragma once
struct Math
{
Math() = delete;
// Returns value in range [min, max].
// 'max' must be more than 'min'.
static int Math::Random(int min, int max);
}
// файл GameMath.cpp
int Math::Random(int min, int max)
{
assert(min < max);
return min + rand() % (max - min + 1);
}
Инварианты объектов и структур данных
Проверка инвариантов, постусловий и предусловий у объектов изучается подробнее в курсе ООП. Это не мешает использовать инварианты, описанные в документации STL, например, на cppreference.com.
Примеры инвариантов в STL:
- у
std::vector vec;
всегда0 <= vec.size() <= vec.capacity()
- у
std::string str;
методstr.c_str()
всегда возвращает указатель на последовательность символов с нулевым символом'\0'
на конце.
Примеры предусловий в STL:
- у
std::vector vec;
при запросе по индексуvec[index]
индекс должен быть в диапазоне[0, size)
Примеры постусловий в STL:
- после изменения
std::string str;
все инварианты сохраняются
Немного теории
Контракты — часть формальных методов проверки корректности программ.
Опытные разработчики могут использовать другие реализации идеи контрактов (но перечисленные ниже способы не рекомендуются для новичков):
- для объектно-ориентированного кода: модульное тестирование (unit testing) и mock-объекты с контрактами
- для статического анализа кода: контракты из стандарта C++ 2017
- для метапрограммирования: static_assert, концепты из стандарта C++ 2017
- грамотное создание API с учётом ООП, как пример — сокрытие данных с private/public и идиомой PIMPL
- грамотное создание API с учётом ФП, как пример — optional и другие алгебраические типы данных
Читать далее
- макрос assert: http://en.cppreference.com/w/cpp/error/assert
- Design by Contract на википедии.
- контракты в c++ 2017