среда, 28 декабря 2016 г.

Состояние корректности

В программировании часто возникает задача проверки корректности входных данных, которые могут привести к одному из нежелательных последствий, вроде арифметического переполнения, выход за границы массива и других. Особенно остро стоит проблема для задач, связанных с безопасностью, так как через подачу неправильных данных можно добиться непредусмотренного поведения. Явная проверка корректности нередко требует действий, сопоставимых по объёму с основными действиями. Это приводит к частому игнорированию проверок или их некорректному составлению, что также плохо выявляется тестированием, так как каждые по отдельности случаи некорректных данных, как правило, встречаются реже корректных.

Необходим механизм, который позволит сделать проверки синтаксически более легковесными. Для языка защищённого программирования таковым становится явно выведенное состояние корректности.

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

Рассмотрим для примера сложение — так выглядит оно без явных проверок:

sum = a1 + a2;

При переполнении такая программа должна аварийно завершиться. Чтобы проверить возможность переполнения и выполнить сложение только при его отсутствии, можно было бы использовать явные проверки:

if (a2 >= 0) 
    correct = a1 <= max(int) - a2
else
    correct = a1 >= min(int) - a2
end;
if (correct)
    sum = a1 + a2
end

Или, при наличии специальной функции, такого кода:

correct = add(> sum, a1, a2);

Язык предлагает такую запись для выполнения действия и явного получения его корректности:

correct ?= sum = a1 +? a2;

Лексема «?=» служит для обозначения присваивания состояния корректности, а «?» придаёт операции возможность передать это состояние в соответствующую логическую переменную. Без этого знака поведение операции будет обычным несмотря на наличие «?=».

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

Необходимость же введения специальных знаков вместо использования функций, вроде указанной add, объясняется на более сложных примерах. Незащищенный проверками код:

a = b[i] * b[0] + c;

Код с проверками на функциях:

correct = (i >= 0) & (i < len(b)) & mul(> m, b[i], b[0]) & add(> a, m, c);

Новый подход:

correct ?= a = b[?i] *? b[0] +? c;

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

Действия, для которых доступна проверка с помощью «?»:

  1. Арифметика: «+?», «-?», «*?», «/?», «%?»
  2. Арифметика совместно с присваиванием: «+?=», «-?=», «*?=», «/?=», «%?=»
  3. Обращение к элементу массива: array[? i], matrix[? y][? x]
  4. Выделение динамической памяти тоже можно сделать с показателем корректности для избежания лишних проверок на null: new(param)?
  5. Обращение к элементу структуры, заданной указателем: pstr .? item
  6. Вызов функции, которая может возвращать состояние некорректности: fun(param)?
  7. Вызов функции с проверкой корректности параметра: fun(? param)
  8. Вызов рекурсивной функции при исчерпании допустимой глубины вызовов recursive(? deep) fun(param)
  9. Приведение типа: int(? rational)
  10. Приведение динамического типа — переход от основы к расширению (от предка к наследнику): base.(? extended)