3.10 — Znajdowanie problemów, zanim staną się problemami

Kiedy popełnisz błąd semantyczny, błąd ten może, ale nie musi, zostać natychmiast zauważony po uruchomieniu programu. Problem może czaić się w kodzie niewykryty przez długi czas, zanim nowo wprowadzony kod lub zmienione okoliczności spowodują, że objawi się to jako awaria programu. Im dłużej błąd znajduje się w bazie kodu, zanim zostanie znaleziony, tym większe prawdopodobieństwo, że będzie trudny do znalezienia, a coś, co pierwotnie mogło być łatwe do naprawienia, zamienia się w przygodę z debugowaniem, która pochłania czas i energię.

Co więc możemy z tym zrobić?

Nie popełniaj błędów

No cóż, najlepiej jest nie popełniać błędów w pierwszej kolejności. Oto lista rzeczy, które mogą pomóc uniknąć błędów:

  • Postępuj zgodnie z najlepszymi praktykami.
  • Nie programuj, gdy jesteś zmęczony lub sfrustrowany. Zrób sobie przerwę i wróć później.
  • Zrozum, gdzie najczęściej czyhają pułapki w języku (wszystkie te rzeczy, przed którymi ostrzegamy).
  • Nie pozwól, aby Twoje funkcje były zbyt długie.
  • Jeśli to możliwe, preferuj używanie standardowej biblioteki do pisania własnego kodu.
  • Uważnie komentuj swój kod.
  • Zacznij od prostych rozwiązań, następnie stopniowo zwiększaj złożoność.
  • Unikaj sprytnych/nieoczywistych rozwiązań.
  • Optymalizuj pod kątem czytelności i łatwości konserwacji, a nie wydajności.

Wszyscy wiedzą, że debugowanie jest dwa razy trudniejsze niż pisanie programu. Jeśli więc napiszesz go tak sprytnie, jak tylko potrafisz, jak go kiedykolwiek zdebugujesz?

— Brian Kernighan, „The Elements of Programming Style”, wydanie 2

Refaktoryzacja kodu

W miarę dodawania nowych możliwości do programów („zmiany behawioralne”) zauważysz, że niektóre funkcje wydłużają się. W miarę wydłużania się funkcji stają się one coraz bardziej złożone i trudniejsze do zrozumienia.

Jednym ze sposobów rozwiązania tego problemu jest podzielenie pojedynczej długiej funkcji na wiele krótszych funkcji. Ten proces wprowadzania zmian strukturalnych w kodzie bez zmiany jego zachowania nazywa się refaktoryzacją. Celem refaktoryzacji jest uczynienie programu mniej złożonym poprzez zwiększenie jego organizacji i modułowości.

Jak długo jest zatem za długo dla funkcji? Funkcja zajmująca jeden pionowy ekran kodu jest ogólnie uważana za zbyt długą — jeśli trzeba przewinąć, aby przeczytać całą funkcję, jej zrozumiałość znacznie spada. W idealnym przypadku funkcja powinna mieć mniej niż dziesięć linii. Funkcje krótsze niż pięć linii są jeszcze lepsze.

Pamiętaj, że celem jest tutaj maksymalizacja zrozumienia i łatwości konserwacji, a nie minimalizowanie długości funkcji — porzucenie najlepszych praktyk lub użycie niejasnych technik kodowania w celu zapisania jednej lub dwóch linii nie przynosi żadnej korzyści Twojemu kodowi.

Kluczowa informacja

Wprowadzając zmiany w kodzie, dokonaj zmian w zachowaniu LUB zmianach strukturalnych, a następnie ponownie sprawdź poprawność. Jednoczesne wprowadzanie zmian behawioralnych i strukturalnych zwykle prowadzi do większej liczby błędów, a także błędów, które są trudniejsze do znalezienia.

Wprowadzenie do programowania defensywnego

Błędy mogą być nie tylko spowodowane przez Ciebie (np. nieprawidłowa logika), ale także wystąpić, gdy użytkownicy korzystają z aplikacji w sposób, którego się nie spodziewałeś. Na przykład, jeśli poprosisz użytkownika o podanie liczby całkowitej, a zamiast tego wprowadzi literę, jak zachowa się Twój program w takim przypadku? Chyba, że ​​się tego spodziewałeś i dodałeś obsługę błędów w tym przypadku, prawdopodobnie niezbyt dobrze.

Programowanie defensywne to praktyka, zgodnie z którą programista stara się przewidzieć wszystkie sposoby niewłaściwego wykorzystania oprogramowania przez użytkowników końcowych lub innych programistów (w tym samego programistę) używających kodu. Takie nadużycia często można wykryć, a następnie zaradzić (np. prosząc użytkownika, który wprowadził nieprawidłowe dane, o ponowną próbę).

W przyszłych lekcjach omówimy tematy związane z obsługą błędów.

Szybkie znajdowanie błędów

Ponieważ nie popełnianie błędów jest trudne w dużych programach, następną najlepszą rzeczą jest szybkie wychwytywanie popełnianych błędów.

Najlepszym sposobem na to jest trochę programowania na raz, a następnie przetestuj swój kod i upewnij się, że działa.

Jest jednak kilka innych technik, których możemy użyć.

Wprowadzenie do testowania funkcji

Jednym z powszechnych sposobów wykrywania problemów z programem jest pisanie funkcji testowych w celu „przećwiczenia” napisanego kodu. Oto prymitywna próba, bardziej w celach ilustracyjnych niż cokolwiek innego:

#include <iostream>

int add(int x, int y)
{
	return x + y;
}

void testadd()
{
	std::cout << "This function should print: 2 0 0 -2\n";
	std::cout << add(1, 1) << ' ';
	std::cout << add(-1, 1) << ' ';
	std::cout << add(1, -1) << ' ';
	std::cout << add(-1, -1) << ' ';
}

int main()
{
	testadd();

	return 0;
}

Funkcja testadd() testuje funkcję add(), wywołując ją z różnymi wartościami. Jeśli wszystkie wartości odpowiadają naszym oczekiwaniom, możemy być w miarę pewni, że funkcja działa. Co więcej, możemy zachować tę funkcję i uruchamiać ją za każdym razem, gdy zmienimy funkcję add , aby upewnić się, że przypadkowo jej nie uszkodziliśmy.

Jest to prymitywna forma testowania jednostkowego, czyli metody testowania oprogramowania, za pomocą której testowane są małe jednostki kodu źródłowego w celu ustalenia, czy są poprawne.

Podobnie jak w przypadku struktur rejestrowania, istnieje wiele Struktury testów jednostkowych innych firm, z których można korzystać. Możliwe jest również napisanie własnego, chociaż będziemy potrzebować większej liczby funkcji językowych, aby oddać sprawiedliwość tematowi. Wrócimy do tego na przyszłej lekcji.

Wprowadzenie do ograniczeń

Techniki oparte na ograniczeniach obejmują dodanie dodatkowego kodu (który, w razie potrzeby, można skompilować w wersji bez debugowania), aby sprawdzić, czy nie został naruszony jakiś zestaw założeń lub oczekiwań.

Na przykład, jeśli piszemy funkcję obliczającą silnię liczby, która oczekuje argumentem nieujemnym, funkcja może przed kontynuacją sprawdzić, czy osoba wywołująca przekazała liczbę nieujemną. Jeśli wywołujący przekaże liczbę ujemną, funkcja może natychmiast zgłosić błąd, zamiast generować jakiś nieokreślony wynik, co pomoże zapewnić natychmiastowe wykrycie problemu.

Jedną z powszechnych metod jest użycie makro preprocesoraassert i static_assert, które omawiamy w lekcji 9.6 — Assert i static_assert.

Szybkie rozwiązywanie problemów ogólnych

Programiści mają tendencję do popełniania pewnego rodzaju typowych błędów, a niektóre z tych błędów mogą zostać wykryte przez przeszkolone programy ich szukać. Programy te, ogólnie znane jako narzędzia analizy statycznej (czasami nieformalnie nazywane linters) to programy analizujące kod źródłowy w celu zidentyfikowania określonych problemów semantycznych (w tym kontekście statyczny oznacza, że ​​narzędzia te analizują kod źródłowy bez jego wykonywania). Problemy wykryte przez narzędzia do analizy statycznej mogą, ale nie muszą, być przyczyną konkretnego problemu, który masz, ale mogą pomóc wskazać delikatne obszary kodu lub problemy, które mogą być problematyczne w pewnych okolicznościach.

Masz już do dyspozycji jedno narzędzie do analizy statycznej – Twój kompilator! Oprócz upewnienia się, że program jest poprawny składniowo, większość współczesnych kompilatorów C++ przeprowadza lekką analizę statyczną, aby zidentyfikować niektóre typowe problemy. Na przykład wiele kompilatorów ostrzeże Cię, jeśli spróbujesz użyć zmiennej, która nie została zainicjowana. Jeśli jeszcze tego nie zrobiłeś, zwiększenie poziomu ostrzeżeń i błędów kompilatora (zobacz lekcję 0.11 — Konfigurowanie kompilatora: poziomy ostrzeżeń i błędów) może pomóc je wydobyć.

Istnieje wiele narzędzi do analizy statycznej, a niektóre z nich potrafią zidentyfikować ponad 300 typów błędów programistycznych. W naszych małych programach akademickich użycie narzędzia do analizy statycznej jest opcjonalne, ale jego użycie może pomóc w znalezieniu obszarów, w których kod jest niezgodny z najlepszymi praktykami. W przypadku dużych programów zdecydowanie zaleca się użycie narzędzia do analizy statycznej, ponieważ może ono ujawnić dziesiątki lub setki potencjalnych problemów.

Najlepsza praktyka

Użyj narzędzia do analizy statycznej w swoich programach, aby pomóc znaleźć obszary, w których kod jest niezgodny z najlepszymi praktykami.

W przypadku użytkowników programu Visual Studio

Visual Studio 2019 i nowsze wersje są wyposażone w wbudowane narzędzie do analizy statycznej. Można uzyskać do niego dostęp poprzez Kompiluj > Uruchom analizę kodu na rozwiązaniu (Alt+F11).

Wskazówka

Niektóre powszechnie zalecane narzędzia do analizy statycznej obejmują:

Bezpłatne:

Większość z nich ma rozszerzenia, które umożliwiają ich integrację z Twoim IDE. Na przykład Rozszerzenie Clang Power Tools.

Płatne (może być bezpłatne dla projektów Open Source):

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
75 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze