A
Lista kontrolna kodowania
Aby krótko przypomnieć najważniejsze zagadnienia niniejszej książki, sporządziłem listę kontrolną zawierającą najważniejsze zagadnienia dotyczące projektu, implementacji, opcji testowych, testowania i poprawiania błędów. Zrezygnowałem przy tym z przypominania rzeczy oczywistych, jak odpowiednie ustawienie opcji kompilatora, utrzymywanie testowej wersji programu (obok jego wersji handlowej), konieczność usuwania pojawiających się błędów na bieżąco itd. Warto przynajmniej pobieżnie przeczytać ją co jakiś czas, szczególnie przed przystąpieniem do rozbudowy lub modyfikacji istniejącego kodu.
Projekt
Gdy wybiera się określoną koncepcję projektową, nie należy kierować się wyłącznie kryterium efektywności lub rozmiaru kodu. Należy także uwzględnić ryzyko związane z implementowaniem i konserwacją kodu oraz upewnić się, iż kod ten istotnie realizuje założone cele projektowe. Należy wówczas zadać sobie następujące pytania:
Czy projekt uwzględnia wszelkie możliwe sposoby zachowania się programu, czy też istnieją w nim elementy losowe lub niezdefiniowane? Czy projekt zawiera elementy nadmiernej elastyczności lub czyni nieuzasadnione założenia?
Czy jakiekolwiek dane przekazywane są za pośrednictwem statycznych lub globalnych buforów? Czy jakakolwiek funkcja uzależnia swe poprawne działanie od szczegółów implementacyjnych innych funkcji? Czy każda funkcja wykonuje tylko jedno, ściśle określone zadanie?
Czy projekt określa sposób postępowania z danymi o specjalnej postaci? Czy kod dokonujący tej specjalnej obsługi jest odizolowany od reszty kodu?
Czy każdy z parametrów — i ewentualny wynik — funkcji reprezentuje dobrze określoną informację, czy też łączy użyteczne dane z informacjami diagnostycznymi?
Czy sposób zdefiniowania każdej z funkcji właściwie sugeruje poprawny sposób jej użycia?
Czy w projekcie istnieją funkcje, które mogą zwracać informację o błędzie zamiast oczekiwanego wyniku? Czy jest możliwe takie ich przedefiniowanie, by zawsze kończyły się bezbłędnie? Pamiętaj, iż każdy przypadek sygnalizacji błędu musi być obsłużony przez funkcję wywołującą.
I najważniejsze: czy możliwe jest automatyczne zweryfikowanie projektu za pomocą testowania jednostek? Jeżeli nie, należy wybrać inny projekt.
Implementacja
Po zaimplementowaniu projektu należy upewnić się, iż implementacja jest wystarczająco solidna i odporna na błędy.
Czy implementacja realizuje projekt sensu stricto, czy tylko jego przybliżenie? Nawet drobne uchybienia pod tym względem niosą ze sobą poważne ryzyko błędów. Przypomnij sobie zachowanie funkcji IntToStr w sytuacji, gdy konwertowana liczba jest najmniejszą liczbą ujemną.
Czy przy tworzeniu kodu poczyniono jakieś nieuzasadnione założenia? Czy użyto nieprzenośnych typów danych? Czy implementacja w jakikolwiek sposób zależna jest od konkretnych cech sprzętu, użytego kompilatora, czy systemu operacyjnego?
Czy obliczane wyrażenia mogą powodować nadmiar lub niedomiar?
Czy używane są jakiekolwiek ryzykowne idiomy języka C? Czy kod zawiera zagnieżdżone operatory ?:? Czy istnieją w kodzie wyrażenia łączące operatory z różnych grup?
Czy kod napisany jest w sposób zrozumiały dla programisty o przeciętnych kwalifikacjach?
Czy każda z czynności zawartych w projekcie wykonywana jest w ściśle określonym miejscu kodu, czy też istnieje kilka fragmentów wykonujących (w różny sposób) tę samą operację? Czy występują fragmenty dokonujące obsługi „przypadków szczególnych” i czy można je wyeliminować dzięki użyciu innego algorytmu? Czy istnieją instrukcje if, które można wyeliminować?
Czy wywoływana jest jakakolwiek funkcja, której wykonywanie może nie zakończyć się pomyślnie? Czy można zmienić projekt w taki sposób, by taki przypadek wyeliminować i uniknąć kłopotliwej obsługi błędu?
Czy występują w kodzie odwołania do nieprzydzielonych obszarów pamięci, w szczególności — pamięci zwolnionej? Czy implementacja nie narusza prywatnych danych należących do innych aplikacji?
Czy implementacja funkcji określa ograniczony rozmiar buforów wejściowych i wyjściowych? (Funkcja wywołująca mogła ograniczyć ten rozmiar do wartości niezbędnej z punktu widzenia wywołania.)
Elementy testowe w aplikacji
Wyposażenie kodu aplikacji w asercje (i inny kod diagnostyczny) może przyczynić się do zmniejszenia czasu niezbędnego do znalezienia i poprawienia błędu.
Czy funkcja zawiera asercje weryfikujące poprawność jej parametrów? Jeżeli nie jest możliwe zweryfikowanie określonego parametru, z powodu niedostatecznej informacji o nim, czy istnieją inne, dodatkowe środki kontrolujące integralność funkcji?
Czy przyjęte w implementacji założenia weryfikowane są za pomocą stosownych asercji? Czy kontrolowane są przypadki nadużywania mechanizmów specyficznych dla określonego środowiska?
Programowanie defensywne przyczynia się do łatwiejszego wykrywania wewnętrznych błędów aplikacji — czy w wersji testowej programu istnieją związane z tym asercje?
Czy każda z asercji jest samodokumentująca? Jeżeli nie, należy opatrzyć ją stosownymi komentarzami. W przeciwnym razie programiści, nie rozumiejąc zadania spełnianego przez asercję, uznają ją za zbędną i usuną z kodu.
Czy przydział i zwalnianie pamięci w wersji testowej połączone jest z wypełnianiem przydzielonych i zwalnianych bloków odpowiednim wzorcem? Wyeliminowanie losowej zawartości pamięci przyczynia się do powtarzalności błędów wynikających z niewłaściwego zarządzania nią.
Czy procedury organizacyjne zwalniające bloki pamięci niszczą jednocześnie ich zawartość?
Czy wyniki produkowane przez użyty algorytm dają się zweryfikować za pomocą innego algorytmu?
Czy w programie istnieją dane, których poprawność można by zweryfikować już przy starcie? Czy program zawiera tablice przeglądowe i czy ich zawartość można w jakiś sposób zweryfikować?
Testowanie
Jest sprawą niezmiernie ważną, by programiści gruntownie testowali tworzony przez siebie kod, nawet jeżeli miałoby to powodować opóźnienia w harmonogramie.
Czy kod kompiluje się bez ostrzeżeń? Jeżeli używasz programu lint lub innego narzędzia diagnostycznego, czy przeprowadziłeś wszystkie dostępne testy? Czy przetestowane zostały wszystkie jednostki kodu?
Czy wykonanie kodu prześledzone zostało za pomocą pracy krokowej? Czy zweryfikowana została poprawność przepływu danych?
Czy kod został poddany adiustacji lub reformatowaniu? Czy został później przetestowany ponownie?
Czy dla nowo stworzonego kodu opracowano testy weryfikujące poprawną współpracę jednostek?
Poprawianie błędów
Gdy przystępuje się do wykrycia przyczyny błędu raportowanego przez testerów, należy każdorazowo rozważyć następujące kwestie:
Czy raportowany błąd istotnie występuje? Jeżeli nie, należy pamiętać, iż błędy nie znikają samoczynnie, a jedynie przestają się objawiać w sposób powtarzalny wskutek zmian poczynionych w kodzie. Być może błąd został poprawiony przez innego programistę. Podczas analizy kodu źródłowego, należy zawsze używać tej jego wersji, z której korzystają testerzy.
Czy dana okoliczność jest przyczyną błędu, czy tylko jego objawem?
W jaki sposób mogłem uniknąć popełnienia tego błędu? W jaki sposób mogę go uniknąć w przyszłości?
W jaki sposób mógłbym doprowadzić do automatycznego wykrycia tego błędu? Jakie zmiany w testowej wersji produktu należałoby wprowadzić w związku z tym?
182 Niezawodność oprogramowania
Lista kontrolna kodowania 181
182 D:\Roboczy\Niezawodność oprogramowania\9 po skladzie 1\rdodA.doc
D:\Roboczy\Niezawodność oprogramowania\9 po skladzie 1\rdodA.doc 181