Почему такая ограниченная поддержка Design by Contract существует в большинстве современных языков программирования?

40

Недавно я открыл для себя Design by Contract (DbC) и считаю, что это чрезвычайно интересный способ написания кода. Среди прочего, казалось бы, предложить:

  • Лучшая документация. Поскольку договор является документацией, он не может быть устаревшим. Кроме того, поскольку в контракте конкретно указывается, что делает подпрограмма, это помогает поддерживать повторное использование.
  • Упрощенная отладка. Поскольку выполнение программы прекращается в тот момент, когда контракт не выполняется, ошибки не могут распространяться, и конкретное нарушенное утверждение, вероятно, будет выделено. Это предлагает поддержку во время разработки и во время обслуживания.
  • Лучший статический анализ. DbC - это просто реализация логики Хоара, и должны применяться те же принципы.

Затраты, по сравнению с ними, кажутся довольно небольшими:

  • Дополнительный отпечаток пальца. Поскольку контракты должны быть прописаны.
  • Требуется некоторое обучение, чтобы освоиться с написанием контрактов.

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

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

Цезарь Баутиста
источник
Звучит как слишком сложный способ программирования, основанного на тестировании, без необходимости тестирования вашей программы.
Дан
3
@ Дан, не совсем, я думаю об этом скорее как о расширении системы типов. например, функция не просто принимает целочисленный аргумент, она принимает целое число, которое по контракту должно быть больше нуля
Carson63000
4
Контракты @Dan code значительно сокращают количество тестов, которые вам необходимо выполнить.
Рей Миясака
24
@ Дэн, я бы скорее сказал, что TDD - это контракты бедняков, а не наоборот.
SK-logic
В динамическом языке вы можете «украшать» ваши объекты контрактами на основе необязательного флага. У меня есть пример реализации, который использует флаги среды для необязательного соединения существующих объектов с контрактами. Да, поддержка не является родной, но ее легко добавить. То же самое относится и к тестовым жгутам, они не являются родными, но их легко добавлять / писать.
Райнос

Ответы:

9

Возможно, они поддерживаются практически на каждом языке программирования.

То, что вам нужно, это «утверждения».

Они легко кодируются как «если»:

if (!assertion) then AssertionFailure();

При этом вы можете писать контракты, размещая такие утверждения в верхней части кода для ограничений ввода; те в точках возврата являются выходными ограничениями. Вы даже можете добавить инварианты в свой код (хотя на самом деле они не являются частью «дизайна по контракту»).

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

Вы можете сделать их немного более эффективными в большинстве языков, определив логическую константу во время компиляции «check» и немного изменив операторы:

if (checking & !Assertion) then AssertionFailure();

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

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

Большинство современных языков не дают вам «временных» утверждений (над произвольными предыдущими или последующими состояниями [временный оператор «в конце концов»)], которые вам нужны, если вы хотите написать действительно интересные контракты. Операторы IF не помогут ты здесь.

Ира Бакстер
источник
Проблема, с которой я сталкиваюсь, имея только доступ к утверждениям, заключается в том, что не существует эффективного способа проверки постусловий в командах, поскольку вам часто приходится сравнивать состояние постусловия с состоянием предусловия (Eiffel вызывает это «старое» и автоматически передает его в подпрограмму постусловия). .) В Python эта функциональность может быть тривиально воссоздана с использованием декораторов, но она заканчивается, когда приходит время отключать утверждения.
Цезарь Баутиста
Сколько из предыдущего состояния на самом деле сохраняет Эйфелева? Поскольку он разумно не может знать, к какой части вы можете получить доступ / изменить ее, не решив проблему остановки (путем анализа вашей функции), он должен либо сохранить полное состояние машины, либо в качестве единой детализации, только некоторую очень мелкую его часть. Я подозреваю последнее; и они могут «моделироваться» с помощью простых скалярных заданий перед предварительным условием. Я был бы рад узнать, что Эйфель делает иначе.
Ира Бакстер
7
... только что проверил, как работает Эйфелева. «old <exp>» - это значение <exp> при входе в функцию, поэтому он делает мелкие копии при входе в функцию, как я и ожидал. Вы можете сделать их тоже. Я согласен, что компилятор реализует синтаксис для pre / post / old более удобно, чем делать все это вручную, но дело в том, что это можно сделать вручную, и это действительно не сложно. Мы вернулись к ленивым программистам.
Ира Бакстер
@IraBaxter Нет. Код становится проще, если вы можете отделить контракт от реальной логики. Кроме того , если компилятор может сказать контракт и код обособленно, он может уменьшить дублирование много . Например, в D вы можете объявить контракт на интерфейсе или суперклассе, и утверждения будут применены ко всем реализующим / расширяющим классам, независимо от кода в их функциях. Например, для Python или Java вы должны вызвать весь superметод и, возможно, выбросить результаты, если хотите, чтобы контракты проверялись без дублирования. Это действительно помогает реализовать чистый LSP-совместимый код.
Марстато
@marstato: я уже согласился с тем, что поддержка языка - это хорошо.
Ира Бакстер
15

Как вы говорите, Design by Contract - это функция в Eiffel, которая долгое время была одним из тех языков программирования, который пользуется уважением в сообществе, но никогда не завоевывал популярность.

DbC не существует ни в одном из самых популярных языков, потому что только относительно недавно сообщество основных программистов пришло к выводу, что добавление ограничений / ожиданий в их код является "разумной" вещью, которую ожидают программисты. В настоящее время программисты обычно понимают, насколько ценным является модульное тестирование, и это приводит к тому, что программисты все чаще принимают код для проверки своих аргументов и видят преимущества. Но десятилетие назад, вероятно, большинство программистов сказали бы: «Это просто дополнительная работа для вещей, которые, как вы знаете, всегда будут в порядке».

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

Так что я думаю, что еще очень далеко до того, как DbC будет широко принят.

Ларри Обриен
источник
OTOH, обычные программисты давно уже привыкли писать утверждения. Отсутствие пригодного для использования препроцессора в большинстве современных основных языков сделало эту полезную практику неэффективной, но она все еще распространена для C и C ++. Теперь он возвращается с Microsoft Code Contracts (на основе AFAIK, переписывание байт-кода для сборок релиза).
SK-logic
8

Моя интуиция подсказывает мне, что отсутствие поддержки должно быть результатом какого-то отказа от практики ...

Ложь.

Это практика дизайна . Он может быть явно воплощен в коде (стиль Эйфелевой) или неявно в коде (большинство языков) или в модульных тестах. Практика проектирования существует и работает хорошо. Языковая поддержка по всей карте. Однако он присутствует на многих языках в рамках модульного теста.

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

Это дорого. А также. Что еще более важно, есть некоторые вещи , которые не могут быть доказаны в данном языке. Например, завершение цикла не может быть доказано на языке программирования, оно требует возможности проверки «более высокого порядка». Так что некоторые виды контрактов технически невыразимы.

Или это просто устарело из-за экстремального программирования и других методологий?

Нет.

Мы в основном используем модульные тесты, чтобы продемонстрировать, что DbC выполняется.

Для Python, как вы заметили, DbC работает в нескольких местах.

  1. Результаты испытаний документации и документации.

  2. Утверждения для проверки входов и выходов.

  3. Модульные тесты.

Дальше.

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

С. Лотт
источник
Вы можете доказать тривиальные случаи завершения цикла, такие как итерация по фиксированной конечной последовательности. Это обобщенный цикл, который нельзя показать тривиальным завершением (поскольку он может искать решения «интересных» математических гипотез); В этом вся суть проблемы остановки.
Donal Fellows
+1. Я думаю, что вы единственный, кто охватил самый критический момент - there are some things which cannot be proven. Формальная проверка может быть хорошей, но не все поддается проверке! Так что эта функция на самом деле ограничивает возможности языка программирования!
Дипан Мехта
@DonalFellows: Поскольку общий случай не может быть доказан, трудно включить набор функций, которые (а) дороги и (б) известны как неполные. Моя точка зрения в этом ответе состоит в том, что легче избежать всех этих особенностей и избегать ложных ожиданий формальных доказательств корректности в целом, когда есть ограничения. В качестве упражнения на проектирование (вне языка) можно (и нужно) использовать множество методов доказательства.
С.Лотт
Это совсем не дорого в языке, таком как C ++, где проверка контракта компилируется в сборке релиза. А использование DBC приводит к выпуску более легкого кода сборки, потому что вы выполняете меньше проверок во время выполнения, чтобы программа находилась в допустимом состоянии. Я потерял счет количества ужасных кодовых баз, которые я видел, когда многочисленные функции проверяют недопустимое состояние и возвращают ложное, когда оно НИКОГДА не должно находиться в этом состоянии в правильно протестированной сборке выпуска.
Кайтаин
6

Просто угадай. Возможно, одна из причин того, что он не так популярен, потому что «Дизайн по контракту» является торговой маркой Eiffel.

DPD
источник
3

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

Другой возможный вывод состоит в том, что популярность «управляемых языков» является текущим доказательством поддержки разработки по контракту для этих выбранных управляемых функций (ограничения массивов по контракту и т. Д.)

hotpaw2
источник
> Масса самих контрактов может стать такой же глючной и трудной для отладки, или даже более того, чем только программный код, которого я никогда не видел.
Кайтаин
2

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

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

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

Я также хотел бы отметить, что концепции, которые в значительной степени являются контрактами для шаблонов и, следовательно, в некоторой степени связаны с DbC, были исключены из новейшего стандарта C ++, поскольку даже после нескольких лет работы над ними было подсчитано, что им все еще нужны годы работы. Такие большие, широкие и радикальные изменения в языках слишком сложно реализовать.

JK.
источник
2

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

Без поддержки компилятора «DbC» - это просто другое имя для «проверки инвариантов / предположений и выдачи исключения в случае нарушения».

Инго
источник
Разве это не сталкивается с проблемой остановки?
Цезарь Баутиста
@Ceasar Это зависит. Некоторые предположения могут быть проверены, другие нет. Например, существуют системы типов, которые позволяют избежать передачи пустого списка в качестве аргумента или его возврата.
Инго
Приятный момент (+1), хотя Бертран Мейер в своей части «Мастеров программирования» упомянул также свою систему создания случайных классов и проверки на предмет нарушения контрактов. Так что это смешанный подход во время компиляции / во время выполнения, но я сомневаюсь, что этот метод работает в любой ситуации
Макси
Это в некоторой степени верно, хотя это должно быть скорее катастрофическим провалом, чем исключением (см. Ниже). Ключевым преимуществом DBC является методология, которая на самом деле приводит к более качественным программам, и гарантия того, что любой данный метод ДОЛЖЕН находиться в допустимом состоянии при входе, что упрощает большую часть внутренней логики. Как правило, вы не должны бросать исключения в случае нарушения договора. Исключения должны использоваться, когда программа МОЖЕТ НАСТОЯЩИМ в состоянии ~ X, и клиентский код должен это обрабатывать. В контракте будет сказано, что ~ X просто незаконно.
Кайтаин
1

У меня есть простое объяснение, большинство людей (включая программистов) не хотят дополнительной работы, если они не считают это необходимым. Программирование авионики, где безопасность считается очень важной, я не видел большинство проектов без нее.

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

Вероятно, это причина, по которой я думаю, что Ada никогда не выбиралась за пределы индустрии авиационного программирования, потому что она требует больше работы в области кодирования, хотя Ada - потрясающий язык, и если вы хотите создать надежную систему, это лучший язык для работы (за исключением SPARK, который является частной язык на основе ада).

Проектирование контрактных библиотек для C # было экспериментальным для Microsoft, и они очень полезны для создания надежного программного обеспечения, но они никогда не набирали обороты на рынке, иначе вы бы видели их сейчас как часть основного языка C #.

Утверждения - это не то же самое, что полностью функциональная поддержка условий до / после и инвариант. Хотя он может пытаться эмулировать их, но язык / компилятор с надлежащей поддержкой выполняет анализ «абстрактного синтаксического дерева» и проверяет логические ошибки, которые просто не могут быть подтверждены.

Изменить: я выполнил поиск, и последующее связанное обсуждение может быть полезным: https://stackoverflow.com/questions/4065001/are-there-any-provable-real-world-languages-scala

АЙ
источник
-2

Главным образом причины следующие:

  1. Это доступно только на языках, которые не популярны
  2. Это не нужно, поскольку те же самые вещи могут быть выполнены по-разному в существующих языках программирования любым, кто действительно хочет это сделать
  3. Это трудно понять и использовать - для этого нужны специальные знания, поэтому мало людей это делают
  4. для этого требуется большой объем кода - программисты предпочитают минимизировать количество символов, которые они пишут - если это занимает длинный кусок кода, что-то должно быть не так
  5. преимущества не существует - он просто не может найти достаточно ошибок, чтобы сделать его стоящим
ТР1
источник
1
Ваш ответ недостаточно аргументирован. Вы просто заявляете о своем мнении о том, что нет никаких преимуществ, что для этого требуется большой объем кода, и что это не нужно, поскольку это может быть сделано с существующими языками (ОП специально обращался к этой проблеме!).
Андрес Ф.
Я не думаю об утверждениях и т.д. как о замене. Это не правильный способ сделать это на существующих языках. (Т.е. это еще не было
решено
@ tp1, если бы программисты действительно хотели свести к минимуму набор текста, они бы никогда не попали во что-то столь многословное и красноречивое, как Java. И да, само программирование требует «специальных знаний», «чтобы делать это правильно». Те, кто не обладает такими знаниями, просто не должны иметь права кодировать.
SK-logic
@ Sk-logic: Ну, кажется, половина мира делает OO неправильно, просто потому что они не хотят писать функции пересылки из функций-членов в функции-члены членов данных. Это большая проблема в моем опыте. Это напрямую связано с минимизацией количества символов для записи.
tp1
@ tp1, если бы люди действительно хотели свести к минимуму набор текста, они бы даже не трогали ОО. ООП естественно красноречив, даже в его лучших реализациях, таких как Smalltalk. Я бы не сказал, что это плохое свойство, иногда помогает красноречие.
SK-logic