`Kryterium jakości oprogramowania



Pobieranie 104.3 Kb.
Data01.05.2016
Rozmiar104.3 Kb.
`Kryterium jakości oprogramowania

1. Extendibility – łatwość adaptacji systemu informatycznego do zmian w jego specyfikacji:

wymagań użytkownika, rozumienia tych wymagań przez wykonawcę, algorytmów,

reprezentacji danych i technik implementacyjnych.

2. Reusability – własność fragmentów oprogramowania ułatwiająca ich wykorzystanie do

budowy różnych systemów informatycznych.

3. Compatibility – łatwość łączenia fragmentów oprogramowania w większą całość.

Wymagają następujących własności oprogramowania:

• Stabilności architektury programów. • Kohezji modułów programowych.

• Ograniczonej do minimum liczby powiązań międzymodułowych.

• Jawności powiązań międzymodułowych.

• Jednoczesnej otwartości i zamkniętości modułów programowych.

Paradygmaty programowania : • Programowanie proceduralne

Problem jest modelowany w procesie dekompozycji funkcyjnej jako zbiór algorytmów.

Program jest implementowany jako zbiór procedur.

• Programowanie obiektowe Problem jest modelowany jako zbiór abstrakcyjnych typów danych. Program jest budowany jako zbiór klas implementujących operacje abstrakcyjnych typów dany.

Podstawowe pojęcia modelu obiektowego

Obiekt jest elementarną jednostką programową charakteryzująca się strukturą, stanem

i zachowaniem. Obiekt integruje zbiór logicznie powiązanych danych ze zbiorem

przetwarzających je operacji. Operacje związane z obiektem umożliwiają dostęp do jego

stanu. Strukturalne własności obiektów są nazywane cechami. Wyróżnia się dwa rodzaje

cech: atrybuty przechowujące stan obiektu i powiązania między obiektami. Operacje

skojarzone z obiektem są implementowane przez metody będące specyficznymi

procedurami występującymi jedynie w kontekście danego obiektu.

komunikat Komunikaty służą do uaktywniania procedur związanych z obiektem. Oprócz

nazwy procedury komunikat może zawierać parametry wejściowe wywołania metody.

Po wykonaniu procedury obiekt może zwrócić odpowiedź do miejsca wysłania komunikatu.

Zbiór wszystkich komunikatów rozumianych przez obiekt stanowi interfejs obiektu.

klasa jest specyfikacją i implementacją definiowanego przez programistę abstrakcyjnego

typu danych. Obiekty są wystąpieniami klas. Klasy definiują stałe cechy obiektów:

strukturę danych i zbiór metod.



Podstawowe własności języków obiektowych :

Wsparcie dla definicji abstrakcyjnych typów danych : możliwość definiowania przez

usera własnych typów danych, które mogą być stosowane tak samo jak typy wbudowane

Hermetyczność – ukrywanie informacji

zwiększenie autonomii obiektów przez ukrycie ich implementacji przed użytkownikiem obiektu

Klasy jako moduły klasy są podstawowymi (jedynymi) modułami programu

Przesyłanie komunikatów przesyłanie komunikatów jest podstawowym mechanizmem

uaktywniania przetwarzania danych

Dziedziczenie klasy mogą być tworzone poprzez ich dziedziczenie z innych klas.

Polimorfizm możliwość podstawiania pod zmienną określonego typu wystąpień różnych klas

Dynamiczne wiązanie szukanie metod w obiekcie realizowane w trakcie działania programu

Specyfikacja klas :

• Każda klasa powinna być starannie wydzieloną abstrakcją pochodzącą z pojęć dziedziny

danego problemu (klasa encyjna) lub jego rozwiązania (klasy graniczne, klasy sterujące).

Powinna ona obejmować elementy strukturalne i behawioralne.

• Klasa powinna obejmować mały i dobrze określony zbiór zobowiązań. Rozważ możliwości:

podziału klas o zbyt dużej liczbie zobowiązań, scalenia klas o zbyt małej liczbie zobowiązań

lub przeniesienia zobowiązań między klasami.

• Klasa powinna być spójna i luźno związana z innymi klasami; w tym celu przeanalizuj

diagramy współpracy klas.

• Definicja klasy powinna wyraźnie oddzielać specyfikację od implementacji.



Proceduralny paradygmat programowania

Problem jest modelowany i implementowany jako zbiór algorytmów (funkcji)



Obiektowy paradygmat programowania

Problem jest modelowany i implementowany jako zbiór klas



Powiązania między obiektami

Obiekty świata rzeczywistego są powiązane różnymi semantycznymi związkami.

Semantyka powiązań: • zależność • niezależność • wyłączność • współdzielenie

Związki te powinny być reprezentowane za pomocą pojęć języków obiektowych.

Atrybutami obiektu złożonego są inne obiekty lub referencje na inne obiekty.

Obiekty złożone w C++ : Zależny i wyłączny obiekt składowy

class Silnik { ... }; class Samochód { protected: ...

Silnik silnik; public:

Samochód(char*, int);... };

Samochód::Samochód(char *typ, int pojemność): silnik(typ, pojemność)

{ ... }


Obiekty złożone w C++ : Niezależny i współdzielony obiekt składowy

class Osoba { protected:

Osoba *małżonek;

public: Osoba(char*, Osoba*); ... };

Osoba::Osoba(char *nazwisko, Osoba *kandydat)

{ małżonek = kandydat; ... }



Obiekty złożone – Eiffel : Referencje na obiekty

class Osoba feature

Nazwisko: String Imię: String wiek: Integer Małżonek: Osoba

Rozwinięcie pod-obiektu w ciele obiektu złożonego

class Samochód feature

Producent: String Model: String silnik: expanded Silnik

Cechy dobrego projektu :

Każdy z modułów powinien komunikować się jedynie zminimalnym zbiorem innych modułów.

•mniej powiązań oznacza większą autonomię modułów: mniej możliwości propagacji zmian;

• mniej powiązań oznacza większą uniwersalność modułów;



Minimalizacja rozmiaru interfejsów : COMMON | init | x, y, z -- Fortran

EXTERN float x25, j23; // C, C++ • im mniejszy interfejs tym mniej możliwości propagacji zmian;

• im mniejszy interfejs tym mniej potencjalnie propagowanych błędów.

Dwa komunikujące się moduły powinny wymieniać minimalnie wystarczające ilości informacji.

Hermetyczność klas : ukrywa przed ich użytkownikami wybrane cechy klas. Dzięki temu

nawet jeśli klient modułu zna jego wewnętrzną implementację, to nie może tej wiedzy

wykorzystać podczas odwoływania się do niego. Dzięki temu zmiany odnoszące się do implementacji, a nie funkcjonalności modułu nie są propagowane do innych modułów. Ograniczenie interfejsów modułów do ich własności funkcjonalnych zwiększa autonomię i uniwersalność modułów.

Stosowanie hermetyczności : Część publiczna: • specyfikacja funkcjonalności

Część ukryta: • reprezentacja • implementacja funkcji

Poprawne rozwiązanie polega na upublicznieniu funkcjonalności klasy (co klasa robi) i ukryciu implementacji (jak to robi). Projekt każdego modułu powinien rozróżniać cechy implementacyjne modułu, które będą ukryte przed innymi modułami systemu od cech funkcjonalnych, które mogą być udostępniane innym modułom.

Hermetyczność w C++ : Nowe rodzaje zasięgu zmiennych i funkcji. Zasięg elementów klasy jest jawnie specyfikowany za pomocą jednego z trzech kwalifikatorów zasięgu:

private – dostęp do zmiennych (atrybutów obiektu) i metod o zasięgu private mają metody wszystkich wystąpień danej klasy; jest to zasięg domyślny;

protected – dostęp mają metody wszystkich wystąpień danej klasy i klas potomnych;

public – dostęp mają metody wszystkich klas oraz wszystkie funkcje programu.



Przyjaciele klasy Uzupełnieniem funkcjonalności podstawowego mechanizmu ukrywania informacji w C++ są przyjaciele klasy (ang. friend). Przyjacielem klasy może być funkcja, wybrana metoda innej klasy, lub wszystkie metody innej klasy.

Hermetyczność w SmallTalk-80 Podejście fundamentalne – statyczne • Wszystkie metody są publiczne • Wszystkie atrybuty są prywatne • Brak składni dla odwołania się do atrybutów innego

obiektu – jedynym sposobem odwołania się do obiektu jest przesłanie do niego komunikatu



Hermetyczność w języku Eiffel Eksport cech: • do wszystkich klas – cechy publiczne (domyślne)

• do wybranych klas • do pustego zbioru klas – pełna hermetyzacja.

class Punkt

feature {ANY} -- publiczne

x: REAL is do Result := internal_x end

y: REAL is do Result := internal_y end

feature {NONE} – prywatne internal_x, internal_y: REAL

feature {Figura} -- publiczne dla "Figura" azymut: REAL do ... end



Uniwersalność modułów programowych

• Brak uniwersalnych modułów – złożone programy klienta

• Moduły uniwersalne – brak kontroli typu • Rzutowanie (C++, Java) – błędy

Klasy generyczne Elastyczność <-> bezpieczeństwo

bezpieczeństwo: języki statycznie typowane

class SortedCollectionOfInteger { protected:

unsigned rozmiar; int tab[ ]; public:

SortedCollectionOfInteger( );

add(int el); };

class SortedCollectionOfFloat {...}; class SortedCollectionOfString {...};



elastyczność: języki dynamiczne typowane

napis  String new: ’Boguś jest OK'. liczba  3.1415. tablica  #(1, 'dwa', $3).

kolekcja  SortedCollection new.

kolekcja add: napis. kolekcja add: liczba. kolekcja add: tablica.



Klasy generyczne Parametryzacja typów

= class IntList

class List [T] = class FloatList

= class PersonList



dziedziczenie (klasy abstrakcyjne)  Parametryzacja typów (klasy generyczne)

Klasa generyczna jest klasą, która akceptuje sparametryzowane typy danych.

class STACK[T] feature -- T - parametr formalny

push(element: T) is do

storage(position):=element;

position:=position + 1; end;

item: T is do

Result:= storage(position); end; end;

Tworzenie wystąpień klas generycznych

sp: STACK[POINT]; -- POINT parametr aktualny

sf: STACK[FIGURE]; slp: STACK[LIST[POINT]]; ssp: STACK[STACK[POINT]];

p: POINT; f: FIGURE; sp.push(p); sf.push(f); ssp.push(sp); p:=sp.item; f:=sf.item;



Kontrola typów

si: STACK[INTEGER]; sp: STACK[POINT]; sf: STACK[FIGURE]; i: INTEGER; p: POINT;

f: FIGURE; w: WIELOKĄT; c: CZWOROKĄT;

sp.push(p); -- OK. sf.push(f); -- OK. si.push(i); -- OK. sp.push(i); -- błąd typu

i:=sf.item; -- błąd typu Generyczność klas, a dziedziczenie :

sf.put(w); -- OK; polimorfizm sf.put(c); -- OK; polimorfizm f:=sf.item; -- OK; polimorfizm

c:=sf.item; -- błąd typu Wielokrotna parametryzacja typu :

Klasy generyczne mogą mieć wiele parametrów

class DICTIONARY [G, H] feature

add(key: G, value: H) is .... end;

dict: DICTIONARY[INTEGER, STRING];

Ograniczona generyczność klas

Czy implementacja klas generycznych jest poprawna dla dowolnego typu parametru?

class VECTOR[T] feature

infix "+" (other: VECTOR[T]):VECTOR is

local i: INTEGER; do:

from i:=1 until i

Result.put(item(i)+other.item(i),i); i:=i + 1; end; end; end;

vf1: VECTOR[FIGURE]; -- błąd typu

vf2: VECTOR[FIGURE]; -- interfejs klasy figura

vf3: VECTOR[FIGURE]; -- nie zawiera komunikatu +

vf3:=vf1+vf2; -- ???

Ograniczona generyczność klas Wartości parametrów aktualnych klasy generycznej są

ograniczone do wskazanej klasy i jej pochodnych.

class BIN_TREE[T->COMPARABLE] feature

insert (element: T) is do



if element < current_node then end; end;

class ELEMENTS inherit COMPARABLE end;

class AVL feature

tree: BIN_TREE[ELEMENTS]; -- OK end;



Wzorce klas – C++ Wzorzec klasy opisuje grupę potencjalnych klas-wzorca. Definicja wzorca:

template <class T> class Tablica { protected:

unsigned rozmiar; T *tp; public:

Tablica (unsigned roz = 12);

T &operator[](unsigned indeks)

{return *(tp + indeks);} ~Tablica ( ) { delete tp; }



template <class T> Tablica<T>:: Tablica ((unsigned roz) {

tp = new T[rozmiar = roz];

for (int i=0; i

Kompilator nie posiada informacji wystarczających dla

utworzenia kodu dla wzorca klas. Jest to wynikiem specyficznych

rozwiązań zastosowanych w języku C++.

Definicja wystąpień klasy-wzorca : są rozwijane przez kompilator do postaci źródłowej (jedna wersja dla każdej wartości parametru aktualnego typu !!!). Prowadzi to do nadmiernego

rozrastania kodu wynikowego.

Tablica<char> ct(25); ==> Tablica<char>::Tablica (unsigned roz) {

tp = new char[rozmiar = roz];

for (int i=0; i

*(tp+i) = 0; }

Tablica<double> dt(1024); ==> Tablica<double>::Tablica (unsigned roz) {

tp = new double [rozmiar = roz];

for (int i=0; i

*(tp+i) = 0; }

ct[0] = 'a'; ==> char& operator[](unsigned indeks) {

return *(tp + indeks);}

dt[0] = 1034905.79; ==> double& operator[](unsigned indeks) {

return *(tp + indeks);}



Implementacja klasy-wzorca Język C++ nie wspiera ograniczonej gener. klas. Definicja wzorca:

template class Tablica { protected: unsigned rozmiar; T *tp; public:

.... Tablica& sortuj( ); };

template Tablica& Tablica::sortuj () {

for (int i=0; i

for (int j=rozmiar-1; i < j; j--)

if ( tp[j] < tp[j-1] ) { T pom = tp[j]; tp[j] = tp[j-1]; tp[j-1] = pom; } }

Wymaga dostarczenia implementacji wybranych metod dla niektórych wystąpień wzorca klas:

Tablica& Tablica::sortuj () {

for (int i=0; i

for (int j=rozmiar-1; i < j; j--)

if ( strcmp(tp[j], tp[j-1] )) < 0 { T pom = tp[j]; tp[j] = tp[j-1]; tp[j-1] = pom; } }



Inne parametry wzorca-klas template class Tablica { protected:

X tp[rozmiar]; public:

Tablica ( ); X& operator[](unsigned indeks) {return tp[indeks];} };

Tablica t1; // są to definicje dwóch

Tablica t2; // różnych klas

Tablica t3; Tablica t4;

t2 = t3; // błąd typu t2 = t4; // OK t1 = t2; // błąd typu Wzorce funkcji Definicja wzorca funkcji:

Template x25 max(x25 a, x25 b)

{ return a > b ? a : b; }

Definicja funkcji -wzorca:

int a, b; char c, d; int max1 = max (a, b); // max (int, int);

char max2 = max (c, d); // max (char, char);

int max3 = max (a, c); // błąd typu



Rozróżnianie przeciążonych funkcji wzorca:

int a, b; short c, d; int max1 = max (a, b); // max (int, int);

short max2 = max (c, d); // max (short, short);

int max3 = max ( int(c), int(d) ); // max (int,int);



Konstrukcja systemów odpornych na błędy Dwa podejścia do obsługi błędów:

- Unikanie degradacji systemu. Zadania, w których pojawiły się błędy, powinny być jak najszybszej wykryte i przerwane w celu uniknięcia propagacji błędów do innych powiązanych zadań. Zakończenie zadania powinno być poprzedzone odtworzeniem poprawnego stanu środowiska sprzętowo-programowego.

- Zapewnienie ciągłości działania systemu. Stosowane są 2 podstawowe techniki wspierające ciągłość działania oprogramowania: programowania nwersyjnego i bloków odtwarzania poprawności. W pierwszym podejściu zadania systemu realizowane są równolegle przez alternatywne moduły oprogramowania. Po zakończeniu ich działania moduł weryfikatora wybiera poprawne rozwiązania. W drugim podejściu dopiero niepoprawne działanie jakiegoś modułu powoduje wycofanie wyników jego działania i uruchomienie alternatywnego modułu.

Błędy w działaniu systemu informatycznego Podstawowe definicje :

Celem obsługi wyjątków jest niedopuszczenie by wyjątki powodowały fiasko działania programów.



Definicja sukcesu i fiaska Wywołanie metody zakończy się sukcesem, jeżeli w wyniku jej działania osiągnięty zostanie stan końcowy spełniający warunki końcowe metody i niezmienniki

klasy. W przeciwnym wypadku wywołanie metody zakończy się fiaskiem.



Definicja wyjątku : Wyjątek jest zdarzeniem występującym w trakcie działania programu, który może spowodować, że wywołanie metody zakończy się fiaskiem.

Zależność między fiaskiem, a wyjątkiem Fiaska i wyjątki tworzą przeplatający się ciąg przyczynowo-skutkowy. Fiasko wykonania metody, a wyjątek Fiasko wywołania metody powoduje wystąpienie wyjątku w metodzie wywołującej. Definicja wystąpienia fiaska

Wywołanie metody zakończy się fiaskiem wtedy jeżeli w trakcie jej działania wystąpi wyjątek Własności języka programowania wspierające obsługę wyjątków :



1. Możliwość rozdzielenia w strukturze programów przepływu sterowania dla normalnej pracy programu i dla obsługi wyjątków.

2. Mechanizm zgłaszania wyjątków: jawnie przez programistów lub niejawnie przez środowisko sprzętowo-programowe (np. dla przypadków dzielenia przez zero, odwołania do ujemnych indeksów tablicy, itp.). Obydwa rodzaje wyjątków powinny być obsługiwane w jednorodny sposób.

3. Mechanizm dla przekazywania sterowania do procedur obsługi błędów w przypadku zgłoszenia wyjątku. Powinny być zdefiniowane reguły przypisujące każdemu wyjątkowi określoną procedurę obsługi wyjątku.

4. Mechanizm umożliwiający zdefiniowanie zachowania programu po zakończeniu obsługi wyjątku na jeden z dwóch sposobów: podjęcie akcji dla zakończenia pracy systemu, lub ponowne wykonanie przerwanych operacji.

5. Powiązanie mechanizmu obsługi wyjątków z asercjami klas. i nie zostanie on obsłużony.

Model obsługi wyjątków w C++ Przekazywanie sterowania i informacji do procedury obsługi

wyjątków


1. Definicja wyjątku • typ prosty – ograniczona liczba wyjątków • typ wyliczeniowy – ograniczona taksonomia wyjątków • klasa Język C++ nie wspiera obsługi wyjątków systemowych

2. Zgłoszenie wyjątku throw wyrażenie definiujące wyjątek - parametr aktualny;

3. Wyłapanie wyjątku try { ... } catch(wyjątek - parametr formalny)

4. Obsługa wyjątku

Funkcje związane z obsługą wyjątków

1. Nieoczekiwane wyjątki Funkcja unexpected() jest wywoływana jeśli funkcja z wyspecyfikowaną listą wyjątków zgłasza wyjątek spoza tej listy. Funkcja unexpected() domyślnie wywołuje funkcję

terminate(). Programista może samodzielnie zdefiniować znaczenie funkcji unexpected() za pomocą funkcji set_unexpected()



2. Nie wyłapane wyjątki Funkcja terminate() jest wywoływana kiedy:

• mechanizm obsługi wyjątków nie może znaleźć procedury obsługi dla zgłoszonego wyjątku;

• przed przejściem do obsługi wyjątku nastąpi uszkodzenie stosu;

• kiedy destruktor wywołany przed przejściem do obsługi wyjątku próbuje zgłosić wyjątek.

Funkcja terminate () domyślnie wywołuje funkcję abort(). Programista może samodzielnie zdefiniować znaczenie funkcji terminate () za pomocą funkcji set_ terminate().

Formalna specyfikacja semantyki ADT Semantyki języków programowania

Stosowane semantyki: • algebraiczna • aksjomatyczna • denotacyjna • operacyjna



Wpływ technik formalnych na koszt realizacji projektu:

Przy zastosowaniu technik formalnych koszt specyfikacji rośnie, a projektu, implementacji i testowania maleje.



Abstrakcyjne typy danych Specyfikacja ADT powinna spełniać następujące wymagania:

• powinna być dokładna i jednoznaczna (sformalizowana);

• powinna być kompletna, lub co najmniej wystarczająco kompletna dla każdego z przypadków;

• nie powinna być nadmiarowa (niezależna od reprezentacji);

• powinna opisywać semantykę typu danych.

Abstrakcyjny typ danych jest typem danych opisanym jedynie poprzez własności danych i operacji wykonywanych na tych danych (a nie przez reprezentację danych i implementację operacji).



Formalizacja specyfikacji ADT Semantyki algebraiczne

Formalny zapis pełnej specyfikacji abstrakcyjnego typu danych z zastosowaniem semantyk algebraicznych, obejmuje pięć elementów:

• nazwę ADT z opcjonalną listą parametrów generycznych; • stosowane dziedzin wartości;

• składnię operacji dostępnych dla ADT w postaci sygnatur operacji; • dziedzinę operacji;

• semantykę ADT w postaci aksjomatów;

Specyfikacja ADT Nazwa typu: STACK [G] gdzie G - jest formalnym parametrem generycznym

Specyfikacja składni ADT Funkcje:


• put: STACK[G]×G STACK[G] - modifier

• remove: STACK[G] ~STACK[G] - modifier

• item: STACK[G] ~ G - accessor

• empty: STACK[G] BOOLEAN - accessor

• new: STACK[G] - modifier






Specyfikacja ADT jest pośrednim opisem wystąpień ADT:

• opisuje funkcjonalność wystąpień ADT - co z nimi można zrobić, a nie to jakie są;

• również funkcje są opisane w sposób pośredni – jakie są ich własności, a nie jak działają.

Dzięki temu zachowują one własność otwartości.



Funkcje o ograniczonej dziedzinie

remove: STACK[G] ~STACK[G] –

operacja nie jest stosowalna dla wszystkich wystąpień typu STACK

put: STACK[G]×G ・STACK[G] -

Funkcje takie wymagają określenia dziedziny operacji, czyli warunków początkowych funkcji:

Warunki początkowe

• remove(s: STACK[G]) require not empty(s) • item(s: STACK[G]) require not empty(s)



Specyfikacja aksjomatów Aksjomaty w jednoznaczny sposób definiują semantykę ADT.

Są to wyrażenia logiczne, które muszą być spełnione dla każdego wystąpienia ADT.

Dopiero wprowadzenie aksjomatów umożliwia rozróżnienie ADT o takiej samej składni (takim samym interfejsie funkcji), np. Stos od Kolejki FIFO (put, remove, item, empty)

Aksjomaty dla ADT Stos:

• item(put(s,x)) = x • remove(put(s,x)) = s • empty(new) • not empty(put(s,x))



Stos

Kolejka FIFO

+ put (Item)

+ remove ()

+ item() : Item

+ empty : boolean



+ put (Item)

+ remove ()

+ item() : Item

+ empty : boolean



Pełna specyfikacja ADT : Specyfikacja ADT STACK

ADT NAME • STACK [G] DOMAINS • Boolean

FUNCTIONS • put: STACK[G]×G ・STACK[G] • remove: STACK[G] STACK[G]

• item: STACK[G] ・G • empty: STACK[G] ・BOOLEAN • new: ・STACK[G]



AXIOMS • item(put(s,x)) = x • remove(put(s,x)) = s • empty(new) • not empty(put(s,x))

PRECONDITIONS

• remove(s: STACK[G]) require not empty(s) • item(s: STACK[G]) require not empty(s)



Kompletność definicji ADT Poprawność wyrażeń zbudowanych za pomocą funkcji

ADT: put(new,x) empty(remove(put(put(new,x1),x2))) item(new)



Definicja poprawności wyrażeń Niech f(x1, …, xn) będzie poprawnym składniowo wyrażeniem,

odwołującym się do jednej lub więcej funkcji jakiegoś ADT. Wyrażenie to będzie semantycznie poprawne wtedy i tylko wtedy, gdy wszystkie wartości xi są (rekurencyjnie) poprawne poprzez spełnienie warunków początkowych funkcji.



Zapytania - wyrażenia, których wartościami nie jest typ ADT:

item(put(new,x)) empty(remove(put(put(new,x1),x2))) item(new)



Kompletność specyfikacji ADT:Formalne dowodzenie kompletności i poprawności specyfikacji

Definicja kompletności specyfikacji ADT Specyfikacja ADT typu T jest kompletna wtedy i tylko

wtedy, gdy zdefiniowany zbiór aksjomatów pozwala dla dowolnego poprawnego składniowo wyrażenia e: • stwierdzić poprawność wyrażenia; • jeżeli wyrażenie e jest poprawne i jest zapytaniem, wyznaczyć wartość wyrażenia.



Definicja spójności ADT Specyfikacja ADT typu spójna wtedy i tylko wtedy, gdy dla dowolnego poprawnego składniowo zapytania e, aksjomaty ADT pozwalają na wyznaczenie co najwyżej jednej wartości e. Formalne dowodzenie kompletności i poprawności specyfikacji.

Niezawodność budowanego i rozwijanego oprogramowania

Właściwa architektura systemu - modułowość, prostota architektury i rozszerzalność wspierają tworzenie niezawodnego oprogramowania

Czytelny program źródłowy - wspierający czytanie i modyfikowania oprogramowania

Systemowe zarządzanie pamięcią operacyjną – procedury automat. odśmiecania pamięci

Statyczne typowanie danych – unikanie błędów typu w trakcie działania programów

Czy to wystarcza? Jak zdefiniować poprawność programów?



Poprawność oprogramowania Poprawność oprogramowania jest pojęciem względnym.

Określa ona zgodność oprogramowania z jego specyfikacją (asercje).



Asercje Język specyfikacji semantyki oprogramowania Warunki początkowe i końcowe związane metodami klasy opisują kontrakt miedzy klasą (modułem) i jej klientami. Kontrakt ten wiąże klasę tak długo, jak wywołania metod klasy spełniają warunki początkowe. Wtedy klasa powinna zagwarantować, że jej stan końcowy i parametry wyjściowe są zgodne warunkami końcowymi.

• Niespełnienie warunków początkowych oznacza błąd po stronie klienta klasy.

• Niespełnienie warunków końcowych oznacza błąd po stronie dostawcy klasy.

Asercje są wyrażeniami logicznymi opisującymi semantykę,są wykorzystywane do definiowania:

• warunków początkowych – określających poprawne wartości parametrów wejściowych metody i stanu obiektu, niezbędnych dla poprawnego działania metody;

• warunków końcowych – określających poprawne wartości parametrów wyjściowych metody i stanu obiektu gwarantowanych po zakończeniu działania metody;

• niezmienników klas – określających dopuszczalne stany wystąpień klasy przez cały czas ich życia. Specyfikacja poprawności oprogramowania Przykład:

Warunek początkowy: { x >= 0 } Program: x := x + 5 Warunek końcowy: { x >= 5 }



Formuła poprawności oprogramowania: { V } S { P } Jeżeli warunek początkowy V (hipoteza) jest spełniony bezpośrednio przed wykonaniem programu S, wtedy warunek końcowy P (teza) będzie spełniony po wykonaniu programu S. Użyteczność formuły poprawności

Użyteczność formuły poprawności jest zależna od jej siły. Siła formuły poprawności programowania jest odwrotnie proporcjonalna do siły warunku początkowego i wprost proporcjonalna do siły warunku końcowego. 1. { False } S { … } Warunek początkowy False jest najsilniejszą możliwą asercją. Warunek ten nigdy nie jest spełniony, niezależnie

od stanu początkowego. Każde wywołanie S będzie niepoprawne. W związku z tym, każdy program jest poprawny z powyższą specyfikacją. { False } null { … } { False } for i =1 to 100 do y := y + y i ; end; { … } 2. { … } S { True } Warunek końcowy True jest najsłabszą możliwą

asercją. Każde pomyślne zakończenie programu S jest poprawne niezależnie od jego wyniku.



3. { x >= 9 } x := x + 5 { x = x + 5 } Powyższy warunek końcowy jest przykładem bardzo

silnej asercji. Dla danej wartości początkowej x istnieje tylko jedno poprawne rozwiązanie.



Asercje w języku Eiffel

require -- warunki początkowe ensure -- warunki końcowe

not_empty: not empty not_full: not full

do one_fewer_item: count = old count – 1

end

Niezmienniki klas Warunki początkowe i końcowe są cechami poszczególnych metod klasy. Niezmienniki klas są cechami całej klasy, to znaczy muszą być spełnione przez wszystkie

metody klasy.



class STACK [G]

invariant

count_non_negative: count >= 0

count_bounded: count <= capacity

consistent_with_array_size: capacity = array.size

empty_if_no_elements: empty = (count = 0)

item_at_top: (count>0) implies (array(count) = item) end

Dodatkowe instrukcje asercji : check pozwala na kontrolę poprawności klasy w trakcie działania metod klasy. Dodatkowe operatory logiczne:

x and then y - jeżeli x jest równe FALSE to x, w przeciwnym wypadku y

x or else y - jeżeli x jest równe TRUE to x, w przeciwnym wypadku y

x implies y . not x or else y



Poprawność klasy gdzie:

DefaultC – jest asercją opisującą domyślne wartości atrybutów klasy C;

prer – jest zbiorem warunków początkowych metody klasy C; jeżeli dla metody r nie zdefiniowano warunków początkowych prer = True;

bodyr – jest implementacją metody r klasy C;

postr – jest zbiorem warunków końcowych metody r klasy C; jeżeli dla metody r nie zdefiniowano warunków końcowych postr = True; • INV – jest niezmiennikiem klasy.

Definicja poprawności klasy Definicja klasy C jest poprawna ze względu na zbiór asercji wtedy i tylko wtedy, gdy: • dla każdego konstruktora p klasy C i dla poprawnego zbioru argumentów każdego konstruktora xp:

{DefaultC and prep(xp)} Bodyp {postp(xp) and INV};

• dla każdej publicznej metody r klasy C i dla poprawnego zbioru argumentów xr dla każdej z tych metod: { prer (xr) and INV } Bodyr {postr (xr) and INV}.



Związek zachodzący między specyfikacją semantyki ADT, a asercjami klasy:

• Warunki początkowe funkcji ADT przekładają się na asercje opisujące warunki początkowe odpowiednich metod klasy.

• Aksjomaty opisujące pojedyncze funkcje ADT przekładają się na asercje opisujące warunki końcowe metod klasy.

• Aksjomaty opisujące kilka funkcji ADT przekładają się na asercje opisujące niezmienniki klasy.

• Definicja klasy może być dodatkowo rozszerzona o asercje związane z fizyczną reprezentacją klasy.

Korzyści ze stosowania asercji

• Narzędzie i skojarzona z nim metodyka, których celem jest tworzenie poprawnego oprogramowania.

• Wsparcie dla pełnego i jednoznacznego dokumentowania oprogramowania. Specyfikacja semantyki klas jest elementem definicji klas.

• Narzędzie wspomagające testowanie oprogramowania, usuwanie błędów oraz zapewnienie jakości oprogramowania (monitorowanie asercji w trakcie działania oprogramowania).

• Baza dla definiowania mechanizmu obsługi wyjątków (oprogramowanie odpornego na błędy).

Dziedziczenie niezmienników klas

• Przez indukcję klasa dziedziczy niezmienniki po wszystkich pośrednich klasach przodkach.

• Jeżeli w klasie nie zdefiniowano własnych niezmienników, domyślnie przyjmuje się, że posiada ona niezmiennik True. Reguła dziedziczenia niezmienników Niezmienniki wszystkich klas rodzicielskich są dziedziczone przez klasę potomną. Dziedziczone niezmienniki są one dodane do własnych niezmienników klasy za pomocą operatora logicznego and then.

Redefinicja warunków początkowych i końcowych metod

Dziedziczenie nie może naruszyć kontraktu między klientem, a dostawcą. Proces tworzenia klasy potomnej może być traktowany jako skorzystanie przez wykonawcę z usług podwykonawcy.



Reguła redefinicji warunków początkowych i końcowych Redefinicja metody w klasie potomnej może zmienić warunki początkowe metody na równe lub słabsze od warunków metody klasy rodzicielskiej,a warunki końcowe na równe lub silniejsze od warunków metody klasy rodzicielskiej.

Rozszerzenie języka

programowania

Jak zapewnić systemową weryfikację poprawności redefiniowanych

warunków początkowych i końcowych.

• Moduł sztucznej inteligencji weryfikujący poprawne zależności

między redefiniowanymi warunkami.

• Logiczne składanie warunków: suma logiczna warunków

początkowych i iloczyn logiczny warunków końcowych.

Zmodyfikowana reguła redefinicji warunków początkowych

i końcowych metod

Podczas redefinicji warunków początkowych lub końcowych

odziedziczonej metody nie wolno stosować standardowej

składni require i ensure. Zamiast tego należy:

• użyć klauzuli require else dla wyznaczenia sumy logicznej oryginalnego i zmodyfikowanego warunku początkowego;

• użyć klauzuli ensure then dla wyznaczenia iloczynu logicznego oryginalnego i zmodyfikowanego warunku końcowego. Brak powyższych klauzul oznacza odziedziczenie oryginalnych warunków początkowych i końcowych.



Asercje w Javie Asercja jest instrukcją języka służącą do testowania założeń

programisty, co do stanu programu w określonym momencie jego działania. Każda asercja zawiera wyrażenie logiczne, które w poprawnym stanie programu powinno być prawdziwe. Niespełnienie asercji jest zgłaszane jako specjalny wyjątek. Składnia:

assert wyrażenie_logiczne; lub

assert wyrażenie_logiczne: wyrażenie;

Drugi argument (wyrażenie) umożliwia przekazanie

dodatkowych informacji do procedury obsługi błędu.



Typowe zastosowania asercji Niezmienniki wewnętrzne – kontrola wewnętrznej

poprawności programów • Niezmienniki przepływu sterowania – kontrola poprawności



Warunki początkowe, końcowe i niezmienniki klas – programowanie przez kontrakt.


©absta.pl 2016
wyślij wiadomość

    Strona główna