Niezawodność Oprogramowania, R00-2, 1


0x08 graphic

Wstęp

Czytając swego czasu książkę D. Knutha o popularnym wówczas edytorze TEX jego autorstwa, natknąłem się w przedmowie na takie oto stwierdzenie, które mocno mnie zastanowiło:

Jestem przekonany, iż ostatni błąd w TEX został wykryty i usunięty 27 listopada 1985 roku. Obiecuję nagrodę w wysokości 20,48 $ pierwszej osobie, której udałoby się znaleźć jeszcze jakiś błąd — kwota ta stanowi dwukrotność nagrody poprzednio przyrzeczonej i będzie podwajana co roku; niech będzie to wyrazem mojego zaufania do własnych programów.

Nie warto dociekać, skąd taka, a nie inna (20,48 czy też 40,96) kwota nagrody; zdumiewająca jest natomiast siła zaufania, jakim Knuth darzy tworzone przez siebie programy. Jak myślisz, ilu znanych Ci programistów zdobyłoby się na podobną deklarację? Zauważ — wiara w to, iż żaden błąd nie zostanie wykryty w TEX w ciągu kolejnych kilku lat graniczy niemal z pewnością, iż edytor ten nie zawiera już żadnych błędów.

Może zabrzmi to mało optymistycznie, ale dla wielu programistów zaufanie takie nie jest bynajmniej sprawą wartą zachodu. Spychają oni całą (lub prawie całą) odpowiedzialność za błędy na zespoły testujące oprogramowanie — ich zdaniem testerzy nie mają nic innego do roboty, podczas gdy zasadniczym zadaniem programistów jest tworzenie oprogramowania. Odpowiedzialni menedżerowie nie mają dużych szans na zmianę takiego sposobu myślenia — programista zmuszony do testowania swych własnych wytworów świadom jest faktu, iż i tak trafią one niedługo do zespołu testującego.

Tymczasem to właśnie programista może w znacznym stopniu przyczynić się do tego, iż wykrywanie błędów i walka z nimi staną się zadaniami łatwiejszymi i bardziej skutecznymi — tę właśnie tezę staram się udowodnić w niniejszej książce poprzez ilustrowanie swych wywodów konkretnymi przykładami. Wszyscy popełniamy błędy i programiści nie są w tym względzie wyjątkiem, szkopuł jednak w tym, by — świadomi własnej niedoskonałości — przyjęli oni taki styl pracy, w którym popełniane przez nich pomyłki łatwe były do wykrycia i poprawienia. Należy za wszelką cenę unikać sytuacji, w której pomyłka, głęboko ukryta — niczym bomba z opóźnionym zapłonem — eksploduje przed oczami niczego nie spodziewającego się użytkownika programu.

Dwa najważniejsze pytania

Nie sposób na ogół udowodnić, iż dany program jest całkowicie bezbłędny, za to każdy znaleziony w nim błąd jest dowodem czegoś wręcz przeciwnego. Programiści zadają sobie wówczas dwa, niezmienne od dziesięcioleci, zasadnicze pytania:

Oczywiście banalna odpowiedź mogłaby brzmieć „lepsze przetestowanie” (we własnym zakresie, jeszcze przed przekazaniem programu zespołowi testującemu), chodzi tu jednak o coś diametralnie innego — o wypracowanie pewnych szczególnych technik programowania, ułatwiających wczesne wykrywanie całych klas rozmaitych błędów lub przynajmniej czyniących łatwiejszymi poszukiwania ich przyczyny. Może wówczas upowszechni się to, co pewnie wielu programistów skwituje uśmiechem niedowierzania — przed przekazaniem programu zespołowi testującemu programista sam przekonany będzie o jego (programu) bezbłędności.

Niektóre ze wskazówek i zaleceń zawartych w treści niniejszej książki sprzeciwiają się wielu powszechnie przyjętym praktykom programowania i mogą prowokować do stwierdzeń w rodzaju „nikt tak nie pisze” lub „wszyscy łamią tę regułę”. Warto wówczas zastanowić się nad przyczyną — jeżeli „nikt tak nie pisze”, to dlaczego? Czy przypadkiem stare nawyki nie okazują się silniejsze od racjonalności? Wszak coś, co 30 lat temu idealne było dla FORTRAN-u, niekoniecznie musi być najlepszym pomysłem na gruncie programowania strukturalnego czy obiektowego. Nieprzypadkowo mówię tu o „wskazówkach” i „zaleceniach”, bowiem niniejsza książka nie pretenduje do miana kodeksu reguł programowania. Niektórzy programiści skłonni są przestrzegać rozmaitych zaleceń — w rodzaju unikania instrukcji goto — z konsekwencją godną dekalogu, co nie wydaje się do końca uzasadnione. To prawda, iż niczym nieskrępowane faszerowanie kodu programu instrukcjami goto może zamienić go w talerz spaghetti (ruszysz coś z jednej strony i natychmiast coś rusza się z drugiej), ponadto instrukcje skoku generalnie utrudniają kompilatorom dokonywanie optymalizacji, istnieją jednakże przypadki, w których rezygnacja z ogólnie przyjętych zasad na rzecz prymitywnego goto poprawia zarówno czytelność, jak i efektywność programu; niewolnicze trzymanie się „reguł” może całą sprawę jedynie pogorszyć.

Nazewnictwo

Nazwy zmiennych, funkcji itp. stosowane w niniejszej książce stanowią przykład tzw. „konwencji węgierskiej” opracowanej we wczesnych latach 70. przez Charlesa Simonyiego. Nazewnictwo wynikające z tej konwencji charakteryzuje się dużym stopniem komunikatywności, istotnej zwłaszcza przy weryfikowaniu dużych programów.

Zgodnie z konwencją węgierską część nazwy zmiennej stanowi zapis jej typu. Nic w tym szczególnie odkrywczego, wszak większość programów stosuje oznaczenie c lub ch dla zmiennych znakowych, b dla zmiennych bajtowych, i — dla całkowitych itp.; notacja węgierska rozszerza tę ideę na wszystkie rodzaje danych w programie, na przykład:

char ch; /* znajomy typ znakowy */

byte b; /* bajt, czyli unsigned char */

flag f; /* flaga, przyjmująca wartości "prawda" albo "fałsz" */

symbol sym; /* dowolna struktura symboliczna */

Konwencja ta nie definiuje przy tym konkretnych skrótów na oznaczenie konkretnych kategorii i typów danych — wymaga jednakże, by przyjęte w tym względzie ustalenia stosowane były konsekwentnie w całych programie.

Użyteczność konwencji węgierskiej uwidacznia się szczególnie w odniesieniu do typów wskaźnikowych, szczególnie wskaźników „wyższych rzędów”. Otóż nazwa zmiennej typu wskaźnikowego rozpoczynać się musi od litery p, po której następuje skrót charakterystyczny dla wskazywanego typu bazowego — w odniesieniu do prezentowanego przed chwilą przykładu wygląda to następująco:

char *pch /* wskaźnik do znaku */

byte *pb /* wskaźnik do bajtu */

flag *pf /* wskaźnik do flagi */

symbol *psym /* wskaźnik do struktury */

Wskaźniki „wyższych rzędów”, czyli po prostu „wskaźniki do wskaźników” stosują tę regułę rekurencyjnie, na przykład:

char **ppch /* wskaźnik do wskaźnika do znaku */

Każda kolejna litera p na początku nazwy wynika z kolejnego stopnia „zagnieżdżenia” wskazywania.

Nawet jeżeli konstruowane w ten sposób nazwy trudne są do wymówienia, są one jednak wystarczające do tego, by w krótkiej nazwie zmiennej zawrzeć wystarczające informacje na temat jej typu. Nie tylko ułatwia to studiowanie programów, lecz umożliwia proste rozróżnienie pomiędzy zmiennymi różnych typów. Przykładowo parametrami funkcji bibliotecznej strcpy są dwa wskaźniki do znaku, zatem jeden z możliwych jej prototypów mógłby wyglądać następująco:

char *strcpy(char *pchTo, char *pchFrom); /* prototyp */

Ponieważ jednak obszary wskazywane przez wspomniane parametry nie są dowolnymi obszarami znakowymi, lecz łańcuchami z zerowymi ogranicznikami, sama zaś notacja węgierska przyczyniać się powinna do lepszego zrozumienia kodu, bardziej komunikatywny wydaje się tu zapis:

char *strcpy(char *strTo, char *strFrom); /* prototyp */

Nie inaczej ma się rzecz z nazwami funkcji i tablic — z jednym wszakże wyjątkiem: otóż zgodnie z oryginalną notacją węgierską nazwa funkcji powinna rozpoczynać się od wielkiej litery, lecz dla spójności prezentowanego kodu postanowiłem odstąpić od tej zasady i zastosować charakterystyczne dla typów przedrostki nazw funkcji na równi ze zmiennymi prostymi. Tak więc gdyby zapisać w notacji węgierskiej prototypy funkcji (przykładowo) malloc i realloc, mogłyby one wyglądać tak:

void *pvNewBlock(size_t size); /* prototyp */

void *pvResizeBlock(void *pv, size_t sizeNew); /* prototyp */

Powróćmy jeszcze do nazw „zagnieżdżonych” wskaźników:

*ppb = pbNew;

Chociaż na pierwszy rzut oka może to wyglądać dziwnie, zauważ, iż „gwiazdki” mogą znosić się z sąsiadującymi literami p — stosując tę zasadę do powyższej instrukcji przypisania otrzymamy:

pb = pbNew;

czyli zgodną co do typów instrukcję przypisania.

W analogiczny sposób redukować się mogą (z literami p) operatory & i ->. Spójrz na poniższe instrukcje:

pb = &b;

b = psym->bLength;

W wyniku wspomnianej redukcji otrzymamy:

b = b;

b = sym.bLength;

a więc przypisania bajtów do bajtów.

I jeszcze jedno. Trudno co prawda podać taką definicję „błędu w programie”, która zadowoliłaby wszystkich Czytelników niniejszej książki, na jedną rzecz chciałbym wszakże zwrócić uwagę: należy odróżniać błędy wprowadzane (bezwiednie) przez programistę podczas tworzenia kodu od błędów pozostających w tym kodzie wówczas, gdy programista uzna go za (przypuszczalnie) bezbłędny — treść niniejszej książki koncentruje się na tej drugiej kategorii. Innymi słowy — trudno spodziewać się, by programiści produkowali bezbłędny kod każdorazowo, gdy tylko usiądą do klawiatury, lecz powinni dążyć do usunięcia wszystkich popełnionych przez siebie błędów przed scaleniem swego kodu z „głównymi” źródłami.

XXXXXXXXXX

Stosowana w tej książce konwencja nazewnicza jest tak naprawdę tylko namiastką propozycji Ch. Simonyiego. Na podstawie reguł dotychczas przedstawionych nie sposób na przykład rozstrzygnąć, czy pch[] jest wskaźnikiem do tablicy znakowej, czy też tablicą wskaźników do znaków. Pełna specyfikacja konwencji węgierskiej jednoznacznie rozstrzyga takie i podobne wątpliwości; jeżeli jesteś zainteresowany jej szczegółami, zajrzyj do pracy doktorskiej Ch. Simonyiego dostępnej pod adresem:

www.parc.xerox.com/publications/pubs-hst.html (rok 1976).

Wśród programistów notacja węgierska nie cieszy się jednakowym poważaniem: jedni uważają ją za największy wynalazek od czasów programowania strukturalnego, inni — wręcz wyśmiewają.

D.Knuth: „TEX: The Program”.

14 Niezawodność oprogramowania

Wstęp 13

14 D:\Roboczy\Niezawodność oprogramowania\9 po skladzie 1\r00-2.doc

D:\Roboczy\Niezawodność oprogramowania\9 po skladzie 1\r00-2.doc 13



Wyszukiwarka

Podobne podstrony:
Niezawodność Oprogramowania, R00-1, 1
Niezawodność Oprogramowania, R07, 1
Niezawodność Oprogramowania, R08, 1
Niezawodność Oprogramowania, rdodA, 1
Niezawodność Oprogramowania, rdodC, 1
Niezawodność Oprogramowania, rdodB, 1
Niezawodnosc oprogramowania nieopr
Metody testowania kodu oprogramowania Badanie niezawodności oprogramowania(1)
Niezawodność Oprogramowania, uwagi po, w r2 trzeba wykonac 4 rysunki
Niezawodność Oprogramowania, R01, 1
Niezawodność Oprogramowania, R09, 1
Niezawodnosc oprogramowania nieopr
Niezawodnosc oprogramowania 2

więcej podobnych podstron