- полнота
- непротиворечивость
- замкнутость
Эти характеристики нужны при проведении валидации спецификации. Это сообщение посвящено тому, как проверять спецификацию на замкнутость.
Собственно, надо проверить
- возможность создания целевого типа
- возможность обнаружения изменений, которые выполняют функции-генераторы;
- возможность создания ситуаций, в которых функции-обсерверы ведут себя по-разному.
Сначала на примере спецификации с единственным целевым типом. Надо проверить, что есть константа целевого типа или функция-генератор, среди входных параметров которой нет целевого типа. Если спецификация содержит несколько целевых типов, то для каждого целевого типа должна существовать цепочка из констант и функций-генераторов, для которой выполняются все следующие свойства:
- цепочка начинается с константы;
- цепочка заканчивается функцией, возвращающей этот целевой тип;
- каждая следующая функция в цепочка в качестве своего входного параметра принимает целевой тип, возвращаемый предыдущей функцией.
Пример незамкнутой спецификации:
scheme SCHEME = class type E, S value add : E >< S -> S, remove : E >< S -~-> S end
Ее можно исправить, например, добавив константу (empty) :
scheme SCHEME = class type E, S value empty : S, add : E >< S -> S, remove : E >< S -~-> S end
Проверка возможности обнаружения изменений, выполняемых генераторами
Каждая функция-генератор выполняет некоторую модификацию целевого типа. Функций-обсерверов должно быть достаточно, чтобы "увидеть" выполненные модификации.
Что надо делать:
Пример незамкнутой спецификации:
Ее можно исправить, например, добавив обсервер has :
Проверка возможности создания ситуаций, в которых обсерверы ведут себя по-разному
Если функция-генератор является частичной функцией (т.е не является тотальной функцией), то надо выписать все условия, гарантирующие ее выполнение (т.е. предусловие). Предусловие - это обычно конъюнкция некоторых более простых условий. Каждое условие представляется в виде функции-обсервера, возвращающего булевское значение. Т.е. добавив в систему функцию-генератор, у нас появится еще и некоторое количество функций-обсерверов. В этом случае для каждой функции-обсервера должен быть генератор, от которого этот обсервер возвращает true на каких-то данных, и должен быть генератор, от которого этот обсервер возвратит false на каких-то данных. Подобную проверку нужно делать и для обсерверов, возвращающих другие типы данных. Поскольку обычно в типах данных достаточно много значений, то эти значения делят на классы (эквивалентности) и требуют, чтобы в спецификации было описано свойство обсервера возвращать значения из каждого класса.
Что надо делать:
Что надо делать:
- сформулировать свойства (функциональные особенности), которые изменяются после выполнения функции-генератора;
- проверить, что это изменение отражено в аксиомах;
- если не отражено, то добавить функции-обсерверы, показывающие эти свойства, и записать недостающие аксиомы.
Пример незамкнутой спецификации:
scheme SCHEME = class type E, S value empty : S, add : E >< S -> S, remove : E >< S -~-> S end
Ее можно исправить, например, добавив обсервер has :
scheme SCHEME = class type E, S value empty : S, add : E >< S -> S, remove : E >< S -~-> S, has : E >< S -> Bool axiom all e : E :- ~has(e, empty), all e1, e2 : E, s : S :- has(e2, add(e1, s)) is e1 = e2 \/ has(e2, s) end
Проверка возможности создания ситуаций, в которых обсерверы ведут себя по-разному
Если функция-генератор является частичной функцией (т.е не является тотальной функцией), то надо выписать все условия, гарантирующие ее выполнение (т.е. предусловие). Предусловие - это обычно конъюнкция некоторых более простых условий. Каждое условие представляется в виде функции-обсервера, возвращающего булевское значение. Т.е. добавив в систему функцию-генератор, у нас появится еще и некоторое количество функций-обсерверов. В этом случае для каждой функции-обсервера должен быть генератор, от которого этот обсервер возвращает true на каких-то данных, и должен быть генератор, от которого этот обсервер возвратит false на каких-то данных. Подобную проверку нужно делать и для обсерверов, возвращающих другие типы данных. Поскольку обычно в типах данных достаточно много значений, то эти значения делят на классы (эквивалентности) и требуют, чтобы в спецификации было описано свойство обсервера возвращать значения из каждого класса.
Что надо делать:
- для каждого обсервера разделить значения в возвращаемом им типе на классы эквивалентности (для типа Bool один класс содержит true, другой класс - false; для типа Nat это может быть класс, состоящий только из нуля, и класс, состоящий из всех положительных чисел);
- проверить, что для каждого класса каждого обсервера есть аксиома с цепочкой генераторов, завершающаяся этим обсервером, в котором значение обсервера принадлежит каждому этому классу;
- если это не так,
- сформулировать поведение системы, при котором значение обсервера попадает в нужный класс;
- обозначить это поведение в виде цепочки генераторов;
- объявить сигнатуры этих генераторов;
- объявить необходимые аксиомы с этими генераторами.
scheme SCHEME = class type Vertex, Edge = Vertex >< Vertex, Graph value empty : Graph, add : Edge >< Graph -~-> Graph, has : Edge >< Graph -> Bool axiom all e : Edge :- ~has(e, empty), all e1, e2 : Edge, g : Graph :- has(e2, add(e1, g)) is e1 = e2 \/ has(e2, g) pre has(e1, g) end
add обладает предусловием, что добавляемое ребро не должно быть уже добавлено (все ребра в этом графе уникальны). Но это предусловие неполное, т.к. чтобы добавить ребро, надо быть уверенным в существовании вершин ребра (в противном случае мы можем "ошибиться" при вводе edge и добавить ребро, не проверив его, в результате получим "кривой" граф). Итак, нам нужно еще предусловие, поэтому добавляем обсервер:
Вершины можно разделить на два класса эквивалентности: те, что уже есть в графе, и тех, которых еще нет. Других свойств вершины, кроме вхождения в граф, нам сейчас не видно. Раз вхождение в граф, значит нужен генератор для изменения вхождения, т.е. добавление вершины. Добавляем соответствующий генератор и аксиомы:
scheme SCHEME = class type Vertex, Edge = Vertex >< Vertex, Graph value empty : Graph, add : Edge >< Graph -~-> Graph, has : Edge >< Graph -> Bool, hasVertex : Vertex >< Graph -> Bool axiom all e : Edge :- ~has(e, empty), all e1, e2 : Edge, g : Graph :- has(e2, add(e1, g)) is e1 = e2 \/ has(e2, g) pre ~has(e1, g) end
Вершины можно разделить на два класса эквивалентности: те, что уже есть в графе, и тех, которых еще нет. Других свойств вершины, кроме вхождения в граф, нам сейчас не видно. Раз вхождение в граф, значит нужен генератор для изменения вхождения, т.е. добавление вершины. Добавляем соответствующий генератор и аксиомы:
scheme SCHEME = class type Vertex, Edge = Vertex >< Vertex, Graph value empty : Graph, add : Edge >< Graph -~-> Graph, has : Edge >< Graph -> Bool, hasVertex : Vertex >< Graph -> Bool, addVertex : Vertex >< Graph -~-> Graph, v1 : Edge -> Vertex v1((v,_)) is v, v2 : Edge -> Vertex v2((_,v)) is v axiom all e : Edge :- ~has(e, empty), all e1, e2 : Edge, g : Graph :- has(e2, add(e1, g)) is e1 = e2 \/ has(e2, g) pre ~has(e1, g) /\ hasVertex(v1(e), g) /\ hasVertex(v2(e), g), all e: Edge, v: Vertex, g : Graph :- has(e, addVertex(v, g)) is has(e, g) pre ~hasVertex(v, g), all v : Vertex :- ~hasVertex(v, empty), all v1, v2 : Vertex, g : Graph :- hasVertex(v1, addVertex(v2, g)) is v1 = v2 \/ hasVertex(v1, g) pre ~hasVertex(v2, g), all v: Vertex, e: Edge, g : Graph :- hasVertex(v, add(e, g)) is hasVertex(v, g) pre ~has(e, g) /\ hasVertex(v1(e), g) /\ hasVertex(v2(e), g) end
Комментариев нет:
Отправить комментарий