Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Naprawianie usterek i błędów w kodzie może być czasochłonne i czasami frustrujące. Efektywne debugowanie zajmuje trochę czasu. Zaawansowane środowisko IDE, takie jak Visual Studio, może znacznie ułatwić pracę. Środowisko IDE może pomóc w usuwaniu błędów i szybszego debugowania kodu oraz pomaga w pisaniu lepszego kodu z mniejszą liczbą usterek. Ten artykuł zawiera całościowy widok procesu "naprawiania usterek", dzięki czemu możesz wiedzieć, kiedy używać analizatora kodu, kiedy używać debugera, jak naprawić wyjątki i jak kodować intencję. Jeśli wiesz już, że musisz użyć debugera, zobacz Pierwsze spojrzenie na debuger.
Z tego artykułu dowiesz się, jak pracować ze środowiskiem IDE w celu zwiększenia produktywności sesji kodowania. Dotykamy kilku zadań, takich jak:
Przygotowywanie kodu do debugowania przy użyciu analizatora kodu środowiska IDE
Jak rozwiązać wyjątki (błędy czasu wykonywania)
Jak zminimalizować błędy przez kodowanie intencji (przy użyciu asercji)
Kiedy należy używać debugera
Aby zademonstrować te zadania, pokazujemy kilka najczęściej występujących typów błędów i usterek, które mogą wystąpić podczas próby debugowania aplikacji. Mimo że przykładowy kod to C#, informacje koncepcyjne mają zwykle zastosowanie do języków C++, Visual Basic, JavaScript i innych obsługiwanych przez program Visual Studio (z wyjątkiem przypadków, w których zaznaczono). Zrzuty ekranu znajdują się w języku C#.
Tworzenie przykładowej aplikacji z niektórymi usterkami i błędami w niej
Poniższy kod zawiera błędy, które można naprawić przy użyciu środowiska IDE programu Visual Studio. Ta aplikacja to prosta aplikacja, która symuluje pobieranie danych JSON z niektórych operacji, deserializowanie danych do obiektu i aktualizowanie prostej listy przy użyciu nowych danych.
Aby utworzyć aplikację, musisz mieć zainstalowany program Visual Studio oraz pakiet roboczy tworzenie aplikacji komputerowych .NET.
Jeśli program Visual Studio nie został jeszcze zainstalowany, przejdź do strony Visual Studio do pobrania, aby ją zainstalować bezpłatnie.
Jeśli musisz zainstalować obciążenie, ale masz już program Visual Studio, wybierz pozycję Narzędzia>Pobierz narzędzia i funkcje. Zostanie uruchomiony Instalator programu Visual Studio. Wybierz obciążenie programowanie aplikacji .NET na komputerach stacjonarnych, a następnie wybierz Modyfikuj.
Wykonaj następujące kroki, aby utworzyć aplikację:
Otwórz program Visual Studio. W oknie uruchamiania wybierz pozycję Utwórz nowy projekt.
W polu wyszukiwania wprowadź konsolę , a następnie jedną z opcji Aplikacja konsolowa dla platformy .NET.
Wybierz Dalej.
Wprowadź nazwę projektu, taką jak Console_Parse_JSON, a następnie wybierz pozycję Dalej lub Utwórz, jeśli ma to zastosowanie.
Wybierz zalecaną strukturę docelową lub platformę .NET 8, a następnie wybierz pozycję Utwórz.
Jeśli nie widzisz szablonu projektu Aplikacja konsolowa dla platformy .NET, przejdź do pozycji Narzędzia>Pobierz narzędzia i funkcje, co spowoduje otwarcie Instalatora programu Visual Studio. Wybierz obciążenie programowanie aplikacji .NET na komputerach stacjonarnych, a następnie wybierz Modyfikuj.
Program Visual Studio tworzy projekt konsoli, który zostanie wyświetlony w Eksploratorze rozwiązań w okienku po prawej stronie.
Gdy projekt jest gotowy, zastąp domyślny kod w pliku Program.cs projektu następującym przykładowym kodem:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
namespace Console_Parse_JSON
{
class Program
{
static void Main(string[] args)
{
var localDB = LoadRecords();
string data = GetJsonData();
User[] users = ReadToObject(data);
UpdateRecords(localDB, users);
for (int i = 0; i < users.Length; i++)
{
List<User> result = localDB.FindAll(delegate (User u) {
return u.lastname == users[i].lastname;
});
foreach (var item in result)
{
Console.WriteLine($"Matching Record, got name={item.firstname}, lastname={item.lastname}, age={item.totalpoints}");
}
}
Console.ReadKey();
}
// Deserialize a JSON stream to a User object.
public static User[] ReadToObject(string json)
{
User deserializedUser = new User();
User[] users = { };
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json));
DataContractJsonSerializer ser = new DataContractJsonSerializer(users.GetType());
users = ser.ReadObject(ms) as User[];
ms.Close();
return users;
}
// Simulated operation that returns JSON data.
public static string GetJsonData()
{
string str = "[{ \"points\":4o,\"firstname\":\"Fred\",\"lastname\":\"Smith\"},{\"lastName\":\"Jackson\"}]";
return str;
}
public static List<User> LoadRecords()
{
var db = new List<User> { };
User user1 = new User();
user1.firstname = "Joe";
user1.lastname = "Smith";
user1.totalpoints = 41;
db.Add(user1);
User user2 = new User();
user2.firstname = "Pete";
user2.lastname = "Peterson";
user2.totalpoints = 30;
db.Add(user2);
return db;
}
public static void UpdateRecords(List<User> db, User[] users)
{
bool existingUser = false;
for (int i = 0; i < users.Length; i++)
{
foreach (var item in db)
{
if (item.lastname == users[i].lastname && item.firstname == users[i].firstname)
{
existingUser = true;
item.totalpoints += users[i].points;
}
}
if (existingUser == false)
{
User user = new User();
user.firstname = users[i].firstname;
user.lastname = users[i].lastname;
user.totalpoints = users[i].points;
db.Add(user);
}
}
}
}
[DataContract]
internal class User
{
[DataMember]
internal string firstname;
[DataMember]
internal string lastname;
[DataMember]
// internal double points;
internal string points;
[DataMember]
internal int totalpoints;
}
}
Znajdź czerwone i zielone zawijasy!
Zanim spróbujesz uruchomić przykładową aplikację i uruchomić debuger, sprawdź kod w edytorze kodu pod kątem czerwonych i zielonych zygzaków. Reprezentują one błędy i ostrzeżenia zidentyfikowane przez analizator kodu środowiska IDE. Czerwone zygzaki są błędami czasu kompilacji, które należy naprawić przed uruchomieniem kodu. Zielone wężyki to ostrzeżenia. Mimo że często można uruchamiać aplikację bez naprawiania ostrzeżeń, mogą one być źródłem usterek i często oszczędzasz sobie czas i problemy, badając je. Te ostrzeżenia i błędy są również wyświetlane w oknie Lista błędów , jeśli wolisz widok listy.
W przykładowej aplikacji zostanie wyświetlonych kilka czerwonych zygzaków, które należy naprawić, oraz zieloną, którą należy zbadać. Oto pierwszy błąd.
Aby naprawić ten błąd, możesz przyjrzeć się innej funkcji środowiska IDE reprezentowanej przez ikonę żarówki.
Sprawdź żarówkę!
Pierwszy czerwony wywiórz reprezentuje błąd czasu kompilacji. Umieść kursor na nim i zostanie wyświetlony komunikat The name `Encoding` does not exist in the current context.
Zwróć uwagę, że ten błąd pokazuje ikonę żarówki w lewym dolnym rogu. Wraz z ikoną śrubokręta
, ikona żarówki
reprezentuje szybkie akcje, które mogą pomóc naprawić lub refaktoryzować kod w locie. Żarówka reprezentuje problemy, które należy rozwiązać . Śrubokręt jest przeznaczony dla problemów, które można rozwiązać. Użyj pierwszej sugerowanej poprawki, aby rozwiązać ten błąd, klikając using System.Text po lewej stronie.
Po wybraniu tego elementu program Visual Studio dodaje instrukcję using System.Text w górnej części pliku Program.cs , a czerwony wywiórz znika. (Jeśli nie masz pewności co do zmian zastosowanych przez sugerowaną poprawkę, wybierz link Podgląd zmian po prawej stronie przed zastosowaniem poprawki).
Powyższy błąd jest typowym błędem, który zwykle można naprawić, dodając nową using instrukcję do kodu. Istnieje kilka typowych, podobnych błędów, takich jak The type or namespace "Name" cannot be found. Te rodzaje błędów, mogą wskazywać brak odwołania do zestawu (kliknij prawym przyciskiem myszy projekt, wybierz polecenie Dodaj>odwołanie), błędną nazwę lub brakującą bibliotekę, którą należy dodać (dla języka C#, kliknij projekt prawym przyciskiem myszy i wybierz polecenie Zarządzaj pakietami NuGet).
Naprawianie pozostałych błędów i ostrzeżeń
W tym kodzie znajduje się jeszcze kilka podkreśleń do przejrzenia. W tym miejscu zostanie wyświetlony typowy błąd konwersji typu. Po umieszczeniu wskaźnika myszy na przełączniku widać, że kod próbuje przekonwertować ciąg na int, który nie jest obsługiwany, chyba że dodasz jawny kod, aby dokonać konwersji.
Ponieważ analizator kodu nie może odgadnąć intencji, nie ma żarówek, które pomogą Ci w tym czasie. Aby naprawić ten błąd, musisz znać intencję kodu. W tym przykładzie nie jest zbyt trudno zobaczyć, że points powinno być wartością liczbową (całkowitą), ponieważ próbujesz dodać points do totalpoints.
Aby rozwiązać ten błąd, zmień członka klasy points z User w następujący sposób:
[DataMember]
internal string points;
do tego:
[DataMember]
internal int points;
Czerwone faliste linie w edytorze kodu znikają.
Następnie umieść kursor na zielonej falującej linii w deklaracji points elementu członkowskiego danych. Analizator kodu informuje, że zmienna nigdy nie ma przypisanej wartości.
Zazwyczaj reprezentuje to problem, który należy rozwiązać. Jednak w przykładowej aplikacji faktycznie przechowujesz dane w zmiennej points podczas procesu deserializacji, a następnie dodajesz tę wartość do członka danych totalpoints. W tym przykładzie znasz intencję kodu i możesz bezpiecznie zignorować ostrzeżenie. Jeśli jednak chcesz wyeliminować ostrzeżenie, możesz zastąpić następujący kod:
item.totalpoints = users[i].points;
w następujący sposób:
item.points = users[i].points;
item.totalpoints += users[i].points;
Zielony zygzak znika.
Napraw wyjątek
Po naprawieniu wszystkich czerwonych zygzaków i rozwiązaniu problemu — lub przynajmniej zbadaniu — wszystkich zielonych zygzaków, jesteś gotowy do uruchomienia debugera i uruchomienia aplikacji.
Naciśnij F5 (Debugowanie > Rozpocznij debugowanie) lub przycisk Rozpocznij debugowanie na pasku narzędzi Debugowanie.
W tym momencie przykładowa aplikacja zgłasza SerializationException wyjątek (błąd środowiska uruchomieniowego). Oznacza to, że aplikacja dusi dane, które próbuje serializować. Ponieważ aplikacja została uruchomiona w trybie debugowania (dołączony debuger), pomocnik wyjątków debugera przenosi Cię bezpośrednio do kodu, który zgłosił wyjątek i wyświetla pomocny komunikat o błędzie.
Komunikat o błędzie informuje, że nie można przeanalizować wartości 4o jako liczby całkowitej. Dlatego w tym przykładzie wiadomo, że dane są złe: 4o powinno to być 40. Jeśli jednak nie masz kontroli nad danymi w rzeczywistym scenariuszu (załóżmy, że otrzymujesz je z usługi internetowej), co z tym robisz? Jak rozwiązać ten problem?
Po wystąpieniu wyjątku należy zadać (i odpowiedzieć) kilka pytań:
Czy ten wyjątek jest tylko usterką, którą można naprawić? Lub:
Czy ten wyjątek może wystąpić u użytkowników?
Jeśli jest to pierwszy, napraw usterkę. (W przykładowej aplikacji trzeba naprawić błędne dane). Jeśli jest to ta druga opcja, może być konieczne obsłużenie wyjątku w kodzie przy użyciu bloku try/catch (przyjrzymy się innym możliwym strategiom w następnej sekcji). W przykładowej aplikacji zastąp następujący kod:
users = ser.ReadObject(ms) as User[];
za pomocą tego kodu:
try
{
users = ser.ReadObject(ms) as User[];
}
catch (SerializationException)
{
Console.WriteLine("Give user some info or instructions, if necessary");
// Take appropriate action for your app
}
Blok try/catch ma pewien koszt wydajności, więc warto używać ich tylko wtedy, gdy są naprawdę potrzebne, czyli tam, gdzie (a) mogą pojawić się w wersji produkcyjnej aplikacji, i gdzie (b) dokumentacja metody wskazuje, że powinno się sprawdzić wyjątek (przy założeniu, że dokumentacja jest kompletna!). W wielu przypadkach można odpowiednio obsłużyć wyjątek, a użytkownik nigdy nie będzie musiał o tym wiedzieć.
Oto kilka ważnych wskazówek dotyczących obsługi wyjątków:
Unikaj używania pustego bloku catch, takiego jak
catch (Exception) {}, który nie podejmuje odpowiednich działań w celu uwidocznienia lub obsługi błędu. Pusty lub nieinformacyjny blok kodu może ukrywać wyjątki i utrudniać debugowanie zamiast je ułatwiać.try/catchUżyj bloku wokół określonej funkcji, która zgłasza wyjątek (ReadObjectw przykładowej aplikacji). Jeśli używasz go wokół większego fragmentu kodu, ukrywasz lokalizację błędu. Na przykład nie używajtry/catchbloku wokół wywołania funkcji nadrzędnejReadToObject, pokazanej tutaj, lub nie dowiesz się dokładnie, gdzie wystąpił wyjątek.// Don't do this try { User[] users = ReadToObject(data); } catch (SerializationException) { }W przypadku nieznanych funkcji uwzględnionych w aplikacji, zwłaszcza funkcji, które współdziałają z danymi zewnętrznymi (takimi jak żądanie internetowe), zapoznaj się z dokumentacją, aby zobaczyć, jakie wyjątki może zgłaszać funkcja. Może to być krytyczne informacje dotyczące prawidłowej obsługi błędów i debugowania aplikacji.
W przypadku przykładowej aplikacji napraw SerializationException w metodzie GetJsonData poprzez zmianę 4o na 40.
Wskazówka
Jeśli masz Copilot, możesz uzyskać pomoc AI podczas debugowania wyjątków. Po prostu poszukaj przycisku Zapytaj Copilot
. Aby uzyskać więcej informacji, zobacz Debug with Copilot.
Wyjaśnij intencję swojego kodu używając asercji
Wybierz przycisk
Uruchom ponownie aplikację na pasku narzędzi debugowania ( + ). Spowoduje to ponowne uruchomienie aplikacji w mniej krokach. W oknie konsoli są widoczne następujące dane wyjściowe.
Możesz zobaczyć, że coś w tym wyniku nie jest w porządku. Wartości name i lastname dla trzeciego rekordu są puste!
Jest to dobry moment, aby porozmawiać o przydatnej praktyce kodowania, często niedostatecznie wykorzystywanej, która polega na użyciu assert instrukcji w funkcjach. Dodając następujący kod, uwzględniasz sprawdzenie w czasie wykonywania, aby upewnić się, że firstname i lastname nie są null. Zastąp następujący kod w metodzie UpdateRecords :
if (existingUser == false)
{
User user = new User();
user.firstname = users[i].firstname;
user.lastname = users[i].lastname;
w następujący sposób:
// Also, add a using statement for System.Diagnostics at the start of the file.
Debug.Assert(users[i].firstname != null);
Debug.Assert(users[i].lastname != null);
if (existingUser == false)
{
User user = new User();
user.firstname = users[i].firstname;
user.lastname = users[i].lastname;
assert Dodając instrukcje podobne do funkcji podczas procesu programowania, możesz pomóc określić intencję kodu. W poprzednim przykładzie określamy następujące elementy:
- Prawidłowy ciąg jest wymagany dla pierwszego imienia
- Prawidłowy ciąg jest wymagany dla nazwiska
Określając intencję w ten sposób, wymuszasz swoje wymagania. Jest to prosta i przydatna metoda, której można użyć do uwidocznienia usterek podczas opracowywania. Instrukcje assert są również używane jako główny element w testach jednostkowych.
Wybierz przycisk
Uruchom ponownie aplikację na pasku narzędzi debugowania ( + ).
Uwaga / Notatka
Kod assert jest aktywny tylko w kompilacji debug.
Po ponownym uruchomieniu debuger wstrzymuje instrukcję assert , ponieważ wyrażenie users[i].firstname != null zwraca wartość false zamiast true.
Błąd assert informuje o problemie, który należy zbadać.
assert może obejmować wiele scenariuszy, w których niekoniecznie widzisz wyjątek. W tym przykładzie użytkownik nie widzi wyjątku, a wartość null jest dodawana jako firstname na liście rekordów. Ten warunek może powodować problemy później (na przykład w danych wyjściowych konsoli) i może być trudniejsze do debugowania.
Uwaga / Notatka
W scenariuszach, w których wywołujesz metodę na wartości null, NullReferenceException jest wynikiem. Zwykle należy unikać używania try/catch bloku dla wyjątku ogólnego, czyli wyjątku, który nie jest powiązany z określoną funkcją biblioteki. Każdy obiekt może rzucić NullReferenceException. Jeśli nie masz pewności, zapoznaj się z dokumentacją funkcji biblioteki.
Podczas procesu debugowania warto zachować konkretną assert instrukcję, dopóki nie wiesz, że musisz zastąpić ją rzeczywistą poprawką kodu. Załóżmy, że użytkownik może napotkać wyjątek w kompilacji wydania aplikacji. W takim przypadku należy refaktoryzować kod, aby upewnić się, że aplikacja nie zgłasza wyjątku krytycznego ani nie powoduje wystąpienia innego błędu. Aby rozwiązać ten kod, zastąp następujący kod:
if (existingUser == false)
{
User user = new User();
za pomocą tego kodu:
if (existingUser == false && users[i].firstname != null && users[i].lastname != null)
{
User user = new User();
Korzystając z tego kodu, spełniasz swoje wymagania dotyczące kodu i upewniasz się, że rekord z wartością firstname lub lastname równą null nie zostanie dodany do danych.
W tym przykładzie wstawiliśmy dwie instrukcje assert do pętli. Zazwyczaj w przypadku używania metody assert najlepiej dodawać instrukcje assert w punkcie wejścia (początek) funkcji lub metody. Obecnie analizujesz metodę UpdateRecords w przykładowej aplikacji. W tej metodzie wiesz, że występują problemy, jeśli którykolwiek z argumentów metody to null, więc sprawdź je za pomocą assert instrukcji w punkcie wejścia funkcji.
public static void UpdateRecords(List<User> db, User[] users)
{
Debug.Assert(db != null);
Debug.Assert(users != null);
W przypadku powyższych instrukcji intencją jest załadowanie istniejących danych (db) i pobranie nowych danych (users) przed zaktualizowaniem czegokolwiek.
Można użyć assert z dowolnym rodzajem wyrażenia, które rozwiązuje się do true lub false. Na przykład możesz dodać instrukcję podobną assert do tej.
Debug.Assert(users[0].points > 0);
Powyższy kod jest przydatny, jeśli chcesz określić następującą intencję: do zaktualizowania rekordu użytkownika jest wymagana nowa wartość punktu większa niż zero (0).
Sprawdzanie kodu w debugerze
OK, teraz, gdy usunięto wszystkie krytyczne elementy, które są nie tak z przykładową aplikacją, możesz przejść na inne ważne rzeczy!
Pokazaliśmy, jak działa Pomocnik wyjątków w debugerze, ale debuger jest znacznie bardziej zaawansowanym narzędziem, które pozwala również na wykonywanie innych czynności, takich jak przechodzenie przez kod i inspekcja jego zmiennych. Te bardziej zaawansowane możliwości są przydatne w wielu scenariuszach, zwłaszcza w następujących scenariuszach:
Próbujesz wyizolować usterkę środowiska uruchomieniowego w kodzie, ale nie można jej wykonać przy użyciu wcześniej omówionych metod i narzędzi.
Chcesz zweryfikować kod, czyli obserwować go, gdy jest uruchamiany, aby upewnić się, że działa w oczekiwany sposób i robi to, co chcesz.
Jest pouczające obserwowanie kodu, gdy się uruchamia. Możesz dowiedzieć się więcej o kodzie w ten sposób i często identyfikować usterki, zanim manifestują wszelkie oczywiste objawy.
Aby dowiedzieć się, jak używać podstawowych funkcji debugera, zobacz Debugowanie dla bezwzględnych początkujących.
Rozwiązywanie problemów z wydajnością
Usterki innego rodzaju obejmują nieefektywny kod, który powoduje powolne działanie aplikacji lub użycie zbyt dużej ilości pamięci. Ogólnie rzecz biorąc, optymalizacja wydajności jest czymś, co robisz później podczas tworzenia aplikacji. Jednak na wczesnym etapie możesz napotkać problemy z wydajnością (na przykład zobaczysz, że część aplikacji działa wolno) i może być konieczne wcześniejsze przetestowanie aplikacji przy użyciu narzędzi profilowania. Aby uzyskać więcej informacji na temat narzędzi profilowania, takich jak narzędzie do analizy użycia CPU i narzędzie do analizy pamięci, zobacz Pierwsze spojrzenie na narzędzia profilowania.
Treści powiązane
W tym artykule przedstawiono sposób unikania i naprawiania wielu typowych usterek w kodzie oraz sposobu korzystania z debugera. Następnie dowiedz się więcej na temat używania debugera programu Visual Studio do naprawiania usterek.