А всё же Дейкстра - голова, ему палец в рот не клади

3 август, 2009 - 09:15Андрей Зубинский

В инженерии всегда работало и работает правило перехода от простого к сложному. Например, освоение прикладной теории систем автоматического управления начинается с исторически значимых примеров - например, с регулятора паровой машины Уатта. И продолжается более развитыми устройствами и теоретическим аппаратом. Это, пожалуй, самый правильный подход. хотя бы потому, что основной механизм жизненного цикла артефактов, порождённых инженерной деятельностью - эволюция (на самом деле есть даже такой раздел инженерии - эволюция технических систем).

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

Эдгар Дейкстра - из времён структурного программирования (которые, кстати, в самых массовых вычислительных системах - встраиваемых, - до сих пор не закончились, и структурное программирование не собирается сдавать своих позиций). Соответственно, его взгляды и суждения, вырванные из контекста, в программировании требуют пояснений (в инженерии не требуют, потому что в ней принято изучать контекст).

Итак, на Дейкстру нападают за его неприятие оператора принудительно прерывания исполнения цикла, часто называемого break. Дескать, в реальных языках break - это очень удобно и без него никак, потому Дейкстра ничего не смыслит в практическом программировании.

На самом же деле Дейкстра был против использования break из-за сопровождающего этот оператор снижения "понимаемости" кода и пригодности к его неавтоматизированной верификации "в голове".

Структурированную программу - продукт структурного программирования, - можно представить некоторой последовательностью фрагментов, каждый из который де-факто что-то "передаёт" на вход последующего. Внутри фрагментов могут быть упрятаны разные конструкции, которых, по сути, не так уж и много (для любопытных - ключевые слова "диаграммы Насси-Шнейдермана").

Представим себе некоторый фрагмент кода:

while ( q ) do Body

И представим себе, что есть некоторый инвариант (выражение, связывающее значения разных переменных) p, для которого доподлинно известно, что р остаётся истинным при правильной работе кода фрагмента. Что означает - если фрагмент кода "отработал" правильно, после выполнения цикла инвариант p должен быть истинным, а условие q - ложным. В этом случае мы можем считать тело цикла Body атомарной конструкцией.

А теперь представим себе, что мы можем прервать выполнение цикла оператором break. Можем теперь мы считать Body атомарной? Конечно, нет. Нам придётся "влезать" в детали реализации тела цикла. Соответственно, снижать уровень абстрактности кода, повышать сложность его понимания и, как следствие, - или снижать его надёжность при фиксированных затратах на его производство, или повышать затраты.

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

К сожалению, об анализе программ на основе инвариантов после структурного программирования как-то совсем немного говорили. Хотя и в объектно-ориентированном программировании от структурного никуда не деться - внутри методов классов, по крайней мере, ООП не предлагает ничего существенно нового. Да и не может предложить в рамках императивной модели.