Podczas debugowania programu w większości przypadków zdecydowaną większość czasu spędzasz na próbach ustalenia, gdzie faktycznie występuje błąd. Po znalezieniu problemu pozostałe kroki (naprawienie problemu i sprawdzenie, czy problem został rozwiązany) są często trywialne w porównaniu.
W tej lekcji zaczniemy odkrywać, jak znaleźć błędy.
Wyszukiwanie problemów poprzez inspekcję kodu
Załóżmy, że zauważyłeś problem i chcesz wyśledzić jego przyczynę. W wielu przypadkach (szczególnie w mniejszych programach) możemy w przybliżeniu określić, gdzie prawdopodobnie występuje problem, w oparciu o charakter błędu i strukturę programu.
Rozważ następujący fragment programu:
int main()
{
getNames(); // ask user to enter a bunch of names
sortNames(); // sort them in alphabetical order
printNames(); // print the sorted list of names
return 0;
}Jeśli spodziewałeś się, że ten program wydrukuje nazwy w kolejności alfabetycznej, ale zamiast tego wydrukował je w odwrotnej kolejności, problem prawdopodobnie leży w funkcję sortNames . W przypadkach, w których możesz zawęzić problem do konkretnej funkcji, być może uda Ci się go wykryć, po prostu patrząc na kod.
Jednak w miarę jak programy stają się coraz bardziej złożone, znajdowanie problemów poprzez kontrolę kodu staje się również coraz trudniejsze.
Po pierwsze, trzeba przyjrzeć się znacznie większej liczbie kodu. Patrzenie na każdą linię kodu w programie liczącym tysiące linii może zająć naprawdę dużo czasu (nie wspominając, że jest niesamowicie nudne). Po drugie, sam kod jest zwykle bardziej złożony i zawiera więcej miejsc, w których może coś pójść nie tak. Po trzecie, zachowanie kodu może nie dać wielu wskazówek, gdzie coś idzie nie tak. Jeśli napisałeś program wyświetlający rekomendacje dotyczące zapasów, a w rzeczywistości nie wyświetlał on żadnych informacji, prawdopodobnie nie miałbyś większego pojęcia, od czego zacząć szukać problemu.
Wreszcie, błędy mogą być spowodowane przyjęciem złych założeń. Prawie niemożliwe jest wizualne wykrycie błędu spowodowanego złym założeniem, ponieważ prawdopodobnie przyjmiesz to samo błędne założenie podczas sprawdzania kodu i nie zauważysz błędu. Jeśli więc mamy problem, którego nie możemy wykryć poprzez inspekcję kodu, jak go znaleźć?
Wyszukiwanie problemów poprzez uruchomienie programu
Na szczęście, jeśli nie możemy znaleźć problemu poprzez inspekcję kodu, możemy skorzystać z innego sposobu: możemy obserwować zachowanie programu podczas jego działania i na tej podstawie spróbować zdiagnozować problem. Podejście to można uogólnić w następujący sposób:
- Wymyśl, jak odtworzyć problem
- Uruchom program i zbierz informacje, aby zawęzić miejsce występowania problemu
- Powtarzaj poprzedni krok, aż znajdziesz problem
W pozostałej części tego rozdziału omówimy techniki ułatwiające to podejście.
Odtwarzanie problemu problem
Pierwszym i najważniejszym krokiem w znalezieniu problemu jest możliwość odtworzenia problemu. Powielanie problemu oznacza sprawianie, że problem pojawia się w spójny sposób. Powód jest prosty: niezwykle trudno jest znaleźć problem, jeśli nie można go zaobserwować.
Wróćmy do naszej analogii z dozownikiem lodu — powiedzmy, że pewnego dnia Twój przyjaciel powie Ci, że Twój dozownik lodu nie działa. Idziesz, patrzysz i wszystko działa dobrze. Jak byś zdiagnozował problem? Byłoby to bardzo trudne. Jeśli jednak rzeczywiście zauważyłeś, że dozownik lodu nie działa, możesz zacząć diagnozować, dlaczego nie działa on znacznie skuteczniej.
Jeśli problem z oprogramowaniem jest rażący (np. program zawiesza się w tym samym miejscu przy każdym uruchomieniu), odtworzenie problemu może być trywialne. Czasami jednak odtworzenie problemu może być znacznie trudniejsze. Problem może wystąpić tylko na niektórych komputerach lub w określonych okolicznościach (np. gdy użytkownik wprowadzi określone dane). W takich przypadkach pomocne może być wygenerowanie zestawu etapów odtwarzania. Etapy odtwarzania to lista jasnych i precyzyjnych kroków, które można wykonać, aby spowodować ponowne wystąpienie problemu z dużą przewidywalnością. Celem jest spowodowanie jak najczęstszego powtarzania się problemu, abyśmy mogli wielokrotnie uruchamiać nasz program i szukać wskazówek w celu ustalenia, co jest przyczyną problemu. Jeśli problem można odtworzyć w 100% przypadków, jest to idealne rozwiązanie, ale powtarzalność poniżej 100% może być w porządku. Problem, który występuje tylko w 50% przypadków, oznacza po prostu, że jego zdiagnozowanie zajmie dwa razy więcej czasu, ponieważ w połowie przypadków program nie wykryje problemu i tym samym nie wniesie żadnych przydatnych informacji diagnostycznych.
Koncentrowanie się na problemach
Gdy uda nam się w rozsądny sposób odtworzyć problem, następnym krokiem jest ustalenie, gdzie w kodzie leży problem. W zależności od charakteru problemu może to być łatwe lub trudne. Dla przykładu załóżmy, że nie mamy zielonego pojęcia, gdzie właściwie leży problem. Jak to znajdziemy?
Przyda nam się tutaj analogia. Zagrajmy w grę hi-lo. Poproszę Cię o odgadnięcie liczby od 1 do 10. Przy każdym Twoim przypuszczeniu powiem Ci, czy jest ono za wysokie, za niskie czy prawidłowe. Przykład tej gry może wyglądać następująco:
You: 5 Me: Too low You: 8 Me: Too high You: 6 Me: Too low You: 7 Me: Correct
W powyższej grze nie musisz zgadywać każdej liczby, aby znaleźć liczbę, o której myślę. Poprzez proces zgadywania i uwzględnianie informacji, których dowiadujesz się z każdego zgadywania, możesz „domyślić się” właściwej liczby za pomocą zaledwie kilku zgadnięć (jeśli zastosujesz optymalną strategię, zawsze możesz znaleźć liczbę, o której myślę, w 4 lub mniejszej liczbie zgadnięć).
Możemy zastosować podobny proces do debugowania programów. W najgorszym przypadku możemy nie mieć pojęcia, gdzie znajduje się błąd. Wiemy jednak, że problem musi leżeć gdzieś w kodzie wykonywanym pomiędzy początkiem programu a momentem, w którym program wykazuje pierwszy nieprawidłowy symptom, jaki możemy zaobserwować. To przynajmniej wyklucza te części programu, które wykonują się po pierwszym zauważalnym objawie. Ale to wciąż potencjalnie pozostawia dużo kodu do omówienia. Aby zdiagnozować problem, dokonamy kilku świadomych przypuszczeń na temat jego lokalizacji, mając na celu szybkie zlokalizowanie problemu.
Często cokolwiek, co spowodowało, że zauważyliśmy problem, daje nam wstępne przypuszczenie, które jest bliskie rzeczywistemu problemowi. Na przykład, jeśli program nie zapisuje danych do pliku wtedy, gdy powinien, problem prawdopodobnie leży gdzieś w kodzie obsługującym zapis do pliku (duh!). Następnie możemy zastosować strategię podobną do hi-lo, aby spróbować wyizolować, gdzie faktycznie występuje problem.
Na przykład:
- Jeśli w którymś momencie naszego programu możemy udowodnić, że problem jeszcze nie wystąpił, jest to analogiczne do otrzymania „zbyt niskiego” wyniku hi-lo — wiemy, że problem musi znajdować się gdzieś w dalszej części programu. Na przykład, jeśli nasz program ulega awarii za każdym razem w tym samym miejscu i możemy udowodnić, że program nie uległ awarii w określonym momencie wykonywania programu, to awaria musi nastąpić w dalszej części kodu.
- Jeśli w którymś momencie naszego programu zaobserwujemy nieprawidłowe zachowanie związane z problemem, jest to analogiczne do otrzymania „zbyt wysokiego” wyniku hi-lo i wiemy, że problem musi znajdować się gdzieś wcześniej w programie. Załóżmy na przykład, że program wypisuje wartość jakiejś zmiennej x. Spodziewałeś się, że wydrukuje wartość 2, ale zamiast tego wydrukowała 8 . Zmienna x musi mieć błędną wartość. Jeśli w którymś momencie wykonywania naszego programu zobaczymy, że zmienna x ma już wartość 8, to wiemy, że problem musiał wystąpić wcześniej.
Analogia hi-lo nie jest idealna — czasami możemy też usunąć z rozważań całe sekcje naszego kodu, nie uzyskując żadnej informacji, czy rzeczywisty problem wystąpił wcześniej, czy później. punkt.
W następnej lekcji pokażemy przykłady wszystkich trzech przypadków.
W końcu, mając wystarczającą liczbę domysłów i dobrą technikę, będziemy mogli znaleźć dokładną linię powodującą problem! Jeśli przyjęliśmy jakieś złe założenia, pomoże nam to odkryć gdzie. Kiedy wykluczysz wszystko inne, jedyną rzeczą, która pozostanie, musi być przyczyna problemu. To tylko kwestia zrozumienia dlaczego.
To, jaką strategię zgadywania chcesz zastosować, zależy od Ciebie — najlepsza zależy od rodzaju błędu, więc prawdopodobnie będziesz chciał wypróbować wiele różnych podejść, aby zawęzić problem. Gdy zdobędziesz doświadczenie w rozwiązywaniu problemów, Twoja intuicja będzie Ci pomagać.
Jak zatem „zgadywać”? Można to zrobić na wiele sposobów. W następnym rozdziale zaczniemy od kilku prostych podejść, a następnie będziemy na nich opierać się i omawiać inne w przyszłych rozdziałach.

