13
WSTĘP
Czytając swego czasu książkę D. Knutha o popularnym wówczas edytorze TEX 1
jego autorstwa, natknąłem się w przedmowie na takie oto stwierdzenie, które moc-no 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 przyrze-czonej 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 zda-niem 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.
1 D.Knuth: „ TEX: The Program”.
C:\WINDOWS\Pulpit\Szymon\Niezawodność oprogramowania\r00-2.doc
13
NIEZAWODNOŚĆ OPROGRAMOWANIA
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. Na-leż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 spodzie-wają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:
♦ Co mogłem — jako programista — uczynić, by ten błąd wykryty został auto-matycznie?
♦ W jaki sposób mógłbym ustrzec się przed popełnianiem takich błędów?
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ól-nych 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 sprze-ciwiają się wielu powszechnie przyjętym praktykom programowania i mogą pro-wokować do stwierdzeń w rodzaju „nikt tak nie pisze” lub „wszyscy łamią tę re-gułę”. Warto wówczas zastanowić się nad przyczyną — jeżeli „nikt tak nie pisze”, to dlaczego? Czy przypadkiem stare nawyki nie okazują się silniejsze od racjonal-noś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”, bo-wiem niniejsza książka nie pretenduje do miana kodeksu reguł programowania. Niektórzy programiści skłonni są przestrzegać rozmaitych zaleceń — w rodzaju uni-kania 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ć.
14
C:\WINDOWS\Pulpit\Szymon\Niezawodność oprogramowania\r00-2.doc
15
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 na-stę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, C:\WINDOWS\Pulpit\Szymon\Niezawodność oprogramowania\r00-2.doc
15
NIEZAWODNOŚĆ OPROGRAMOWANIA
sama zaś notacja węgierska przyczyniać się powinna do lepszego zrozumienia ko-du, 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 wy-jątkiem: otóż zgodnie z oryginalną notacją węgierską nazwa funkcji powinna rozpoczynać się od wielkiej litery, lecz dla spójności prezentowanego kodu postano-wił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 (bez-wiednie) 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 po-pełnionych przez siebie błędów przed scaleniem swego kodu z „głównymi” źró-
dłami.
16
C:\WINDOWS\Pulpit\Szymon\Niezawodność oprogramowania\r00-2.doc
17
XXXXXXXXXX
Stosowana w tej książce konwencja nazewnicza jest tak naprawdę tylko na-miastką propozycji Ch. Simonyiego. Na podstawie reguł dotychczas przedstawio-nych 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 do-stę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ża-
niem: jedni uważają ją za największy wynalazek od czasów programowania strukturalnego, inni — wręcz wyśmiewają.
C:\WINDOWS\Pulpit\Szymon\Niezawodność oprogramowania\r00-2.doc
17
NIEZAWODNOŚĆ OPROGRAMOWANIA
18
C:\WINDOWS\Pulpit\Szymon\Niezawodność oprogramowania\r00-2.doc