piasecki,podstawy programowania, Wychwytywanie błędnych zachowań programu

Wychwytywanie błędnych zachowań programu - asercje , wyjątki.


Pojęcia :

serwer - klasa udostępniająca jakąś usługę

klient - korzystający z usług serwera ( klasa, aplikacja, człowiek).

bład - fizyczny błąd występujący w oprogramowaniu ( error)

błędne wykonanie ( zachowanie) - objaw występowania błędu ( fault ).


Rodzaje błędów :

syntaktyczne - składniowe

logiczne

błędne zachowania klienta, sytuacje awaryjne w otoczeniu serwera.


Sposoby sygnalizacji błędnych zachowań :

wydruki kontrolne

kody powrotu z metod

zgłaszanie wyjątków

asercje


Wyjątki :


Wyjątki - obiekty służące do przekazywania informacji diagnostycznej .


Zgłaszanie wyjątku :

void f ( )

{ .....

if ( o = = null) throw new NullPointerException( " Nie przekazano obiektu ");

....

}


Postać instrukcji throw :

throw new TypWyjatku(" opcjonalny komunikat diagnostyczny")

lub :

TypWyjatku e = new TypWyjatku("komunikat");

throw e;


Hierarchia klas ziązanych z obsługą wyjątków :

Throwable

--- NulPointerException

--- IndexOutOfBoundsException

--- inne niekontrolowane wyjątki


Wyjątki klasy error sygnalizują poważne błędy systemowe, zazwyczaj nie są przechwytywane i nie powinno się wymuszać ich obsługi przez klienta.

Wyjątki klasy RuntimeException sygnalizują błędy programisty, czasami ich obsługa ma sens i wtedy je przechwytujemy, ale wymuszanie ich obsługi nie jest wskazane.

Wyjątki dziedziczące po klasie Exception sygnalizują błędne zachowanie otoczenia serwera na które serwer powinien być przygotowany, zazwyczaj ich obsługa jest możliwa i najczęściej jest wymuszana przez serwer.


Do wymuszania obsługi wyjątku służy klauzula throws.


void f ( ) throws NullPointerException

{ .....

if ( o = = null) throw new NullPointerException( " Nie przekazano obiektu ");

....

}


Jeśli mamy sensowną możliwość reakcji (np. poprawa wartości argumentu, doprecyzowanie informacji diagnostycznej itp. ) na wystąpienie wyjątku powinniśmy go przechwycić i obsłużyć - służą do tego instrukcje try ... catch.


void f ( )

{ ...

try {

// instrukcje mogące wygenerować wyjątek

} catch(Exception e) {

// obsługa wyjątku

}

...

// pozostałe instrukcje - pomijane w przypadku wystąpienia wyjątku

}


Jeśli instrukcje bloku try mogą wygenerować więcej rodzajów wyjątków to możemy użyć kilku instrukcji catch. Ponieważ sprawdzane są kolejno więc ustawiamy je od od najbardziej szczegółowego wyjątku do najbardziej ogólnego.


void f ( )

{ ...

try {

// utwórz strumień plikowy , odczytaj i zamknij go

} catch(FileNotFoundException e) {

// nie odnaleziono pliku o podanej nazwie

}

catch(IOException e ) {

// obsługa pozostałych błędów wejścia wyjścia

}

...

// pozostałe instrukcje - pomijane w przypadku wystąpienia wyjątku

}


Jeśli pewne instrukcje muszą być wykonane niezależnie od tego czy wystąpił wyjątek czy nie do bloków try catch dodajemy klauzulę finally.



void f ( )

{ ...

try {

// utwórz strumień plikowy i odczytaj go

} catch(FileNotFoundException e) {

// nie odnaleziono pliku o podanej nazwie

}

catch(IOException e ) {

// obsługa pozostałych błędów wejścia wyjścia

}

finally {

// instrukcje wykonywane zawsze np. zamknięcie pliku ( jeśli został otwarty )

}


...

// pozostałe instrukcje - pomijane w przypadku wystąpienia wyjątku

}



Jeśli nie wiemy co zrobić w przypadku wystąpienia wyjątku kontrolowanego nie powinniśmy przechwytywać go ( i dawać pustą obsługę, lub powielać systemową informację diagnostyczną - jak to się często robi ), ale przekazać go "wyżej" - do klienta ( może on będzie wiedział co z tym zrobić).


void f ( ) throws IOException

{

// instrukcje mogące wygenerować wyjątek IOException

}


Uwaga: możemy również zgłaszać nowe wyjątki w bloku catch.


Asercje :


Asercja jest to warunek logiczny sprawdzający czy stan programu ( wartości zmiennych ) jest zgodny z przyjętymi założeniami (ułatwia wykrywanie błędów logicznych programu). Stosowana przede wszystkim na etapie uruchamiania programu. Domyślnie java wyłącza sprawdzanie asercji. Włączenie wymaga ustawienia odpowiednich opcji kompilatora i loadera :


javac -source 1.4 MyClass.java

java -ea MyClass


W środowisku BlueJ trzeba ustawić opcję -ea ( i -eas jeśli chcemy objąć asercjami klasy systemowe ) dla maszyny wirtualnej :

czyli w pliku bluej.defs należy do wiersza bluej.vm.args dopisać -ea ( i ewentualnie usunąć znak komentarza '#' ), wiersz po zmianie :


bluej.vm.args=-server -Xincgc -ea -esa



Postać instrukcji assert :


assert warunek [ : wyrażenie]

jeśli warunek nie jest spełniony generowany jest wyjątek AssertionError z dodatkową informacją ( jeśli jest) przekazaną przez opcjonalne wyrażenie.


Asercje stosyjemy do sprawdzania :

    1. inwariantów wewnętrznych

    2. inwariantów przepływu sterowania

    3. warunków wstępnych (pre) i końcowych ( post) dla metod

    4. inwariantów klasowych


Nie powinno się stosować asercji :


np. // Broken! - action is contained in assertion

assert names.remove(null);

należy zrealizować tak :

// Fixed - action precedes assertion

boolean nullsRemoved = names.remove(null);

assert nullsRemoved; // Runs whether or not asserts are enabled


Ad. a


if (i % 3 == 0) {

...

} else if (i % 3 == 1) {

...

} else {

assert i % 3 == 2 : i;

...

}

Pozornie niemożliwa sytuacja.


Podobnie :

switch(suit) {

case Suit.CLUBS:

...

break;


case Suit.DIAMONDS:

...

break;


case Suit.HEARTS:

...

break;


case Suit.SPADES:

...

}

Przyjęliśmy założenie, że kolor karty może mieć tylko jedną z czterech wartości. Dla sprawdzenia tego możemy dodać :

default:

assert false : suit;

Inne ( nawet lepsze ) rozwiązanie to :

default:

throw new AssertionError(suit);



Ad. b


Zasada : wstawiaj asercje wszędzie tam gdzie nigdy nie powinno się dojść ( assert false).


Ad. c

Warunki wstępne ( poprawność argumentów wywołania metod ) sprawdzamy tylko dla metod prywatnych.


Warunki końcowe - sprawdzają czy efekt działania metody ( wynik i zmiana pól ) spełnia założenia.


Ad. d

Warunki jakie muszą być spełnione przez wartości pól aby obiekt był uznany za poprawny ( patrz np. Data). Powinniśmy sprawdzać w każdej metodzie modyfikującej pola.



Rekurencja.

Tym razem tylko przykłady, wprowadzenie mam w pamięci operacyjnej.


Wersja trochę niedydaktyczna - tak na prawdę jest to przykład programowania strukturalnego , a nie obiektowego.


import java.io.*;

public class Kalkulator

{StreamTokenizer wej=new StreamTokenizer(new BufferedReader(new InputStreamReader

(System.in)));


void oblicz() throws IOException

{ // ustawiamy analizator tak aby nowa linia (EOL) '/' i '-' były traktowane jako tokeny

// pod pojęciem liczby rozumiemy liczbę bez znaku

wej.eolIsSignificant(true);

wej.ordinaryChar('/');

wej.ordinaryChar('-');

System.out.println(" Wprowadz wyrażenie ");

wej.nextToken();

// każda metoda startuje z wczytanym piewrszym tokenem

System.out.println(" = " + wyrazenie());

}


double wyrazenie()throws IOException

{ double suma=0;

int oper=wej.ttype;

if(oper!='+' && oper!='-') // liczba lub wyrazenie w ( ) - czyli skladnik

{suma=skladnik(); oper=wej.ttype;}

while(oper=='+' || oper=='-')

{ wej.nextToken();

switch(oper)

{ case '+': suma+=skladnik(); break;

case '-': suma-=skladnik();

}

oper=wej.ttype;

}

return suma;

}


double skladnik() throws IOException

{double iloczyn=czynnik();

int oper=wej.ttype;

while(oper=='*' ||oper=='/')

{ wej.nextToken();

switch(oper)

{ case '*': iloczyn*=czynnik(); break;

case '/': iloczyn/=czynnik();

}

oper=wej.ttype;

}

return iloczyn;

}



double czynnik()throws IOException

{ double wart;

if(wej.ttype==wej.TT_NUMBER) wart=wej.nval;

// jeśli nie liczba to musi być wyrażenie w nawiasach

else {wej.nextToken(); wart=wyrazenie();}

wej.nextToken();

return wart;

}

}



Tym razem wersja wykorzystująca klasy wewnętrzne. Moim zdaniemwprowadzone nieco na siłę. Dodana jest również częściowa kontrola poprawności zapisu wyrażenia.


import java.io.*;


public class Kalkulator

{StreamTokenizer wej= new StreamTokenizer(new BufferedReader(new InputStreamReader

(System.in)));


void oblicz() throws IOException

{ // ustawiamy analizator tak aby nowa linia (EOL) '/' i '-' były traktowane jako tokeny

// pod pojęciem liczby rozumiemy liczbę bez znaku

wej.eolIsSignificant(true);

wej.ordinaryChar('/');

wej.ordinaryChar('-');

System.out.println(" Wprowadz wyrażenie ");

wej.nextToken();

// każda metoda startuje z wczytanym piewrszym tokenem

System.out.println(" = " + (new Wyrazenie()).dajWart());

}


class Wyrazenie

{ double lewy=0.0, // pierwszy argument i jednocześnie wynik

prawy=0.0; // drugi argument operatora dodawania

Wyrazenie() throws IOException

{ wyrazenie();}


double dajWart()

{ return lewy;}

void wyrazenie()throws IOException,RuntimeException

{ int oper=wej.ttype;

if(oper!='+' && oper!='-') // liczba lub wyrazenie w () - czyli skladnik

if(oper==wej.TT_NUMBER ||oper=='(') {lewy=(new Skladnik()).dajWart(); oper=wej.ttype;}

else throw new RuntimeException("Oczekiwany ( lub liczba ");

while(oper=='+' || oper=='-')

{ wej.nextToken();

prawy=(new Skladnik()).dajWart();

switch(oper)

{ case '+': lewy+=prawy ; break;

case '-': lewy-=prawy; break;

default : assert false : "nieoczekiwany znak "+(char) oper;

}

oper=wej.ttype;

}

}

}


class Skladnik

{ double lewy=0.0,

prawy=0.0;

Skladnik() throws IOException

{skladnik();}

double dajWart()

{return lewy;}

void skladnik() throws IOException

{ lewy=(new Czynnik()).dajWart();

int oper=wej.ttype;

while(oper=='*' ||oper=='/')

{ wej.nextToken();

prawy=(new Czynnik()).dajWart();

switch(oper)

{ case '*': lewy*=prawy; break;

case '/': lewy/=prawy; break;

default : assert false : "Nieoczekiwany znak "+ (char)oper;

}

oper=wej.ttype;

}

}

}


class Czynnik

{ double wart;

Czynnik() throws IOException

{czynnik();}

double dajWart()

{return wart;}

void czynnik()throws IOException, RuntimeException

{ if(wej.ttype==wej.TT_NUMBER) wart=wej.nval;

// jeśli nie liczba to musi być wyrażenie w nawiasach

else if(wej.ttype=='(')

{wej.nextToken(); wart=(new Wyrazenie()).dajWart();

if(wej.ttype!=')')

throw new RuntimeException("Oczekiwany ) "); }

else throw new RuntimeException("Oczekiwany ( ");

wej.nextToken();

}

}

}




Wyszukiwarka

Podobne podstrony:
piasecki,podstawy programowania, Kolekcje w Javie
piasecki,podstawy programowania, Typy wyliczeniowe w Javie
piasecki,podstawy programowania, Tablice w Javie
piasecki,podstawy programowania, Definicja klasy, tworzenie obiektów
piasecki,podstawy programowania, Podstawowe elementy języka java
piasecki,podstawy programowania, Hierarchia klas, dziedziczenie, polimorfizm
piasecki,podstawy programowania, Wejście wyjście w javie
piasecki,podstawy programowania, budowa programu w języku java
piasecki,podstawy programowania, Klasy i metody abstrakcyjne, interfejsy
piasecki,podstawy programowania, Instrukcje jezyka Java
Woynarowska Edukacja zdrowotna w nowej podstawie programowej
Nowa podstawa programowa WF (1)
1 Podstawy programowania dialogowego
nowa podstawa programowa sp
11-nkb~1, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, l2
2-eukl~1, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, l2
Zmiany w podstawie programowej w zakresie edukcji matematycznej, Wczesna edukacja, Materiały do prac
1-algo~1, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, l2
c-zadania-w3, wisisz, wydzial informatyki, studia zaoczne inzynierskie, podstawy programowania, kol

więcej podobnych podstron