코드에서 버그 및 오류를 수정하는 것은 시간이 오래 걸리고 때로는 번혹스러운 작업일 수 있습니다. 효과적으로 디버그하는 방법을 알아보는 데 시간이 걸립니다. Visual Studio와 같은 강력한 IDE를 사용하면 작업을 훨씬 쉽게 수행할 수 있습니다. IDE를 사용하면 오류를 수정하고 코드를 더 빠르게 디버그할 수 있으며, 더 적은 버그로 더 나은 코드를 작성할 수 있습니다. 이 문서에서는 "버그 수정" 프로세스에 대한 전체적인 보기를 제공하므로 코드 분석기를 사용해야 하는 시기, 디버거를 사용해야 하는 경우, 예외를 해결하는 방법 및 의도를 위해 코딩하는 방법을 알 수 있습니다. 디버거를 사용해야 한다는 것을 이미 알고 있는 경우 먼저 디버거를 살펴보세요.
이 문서에서는 코딩 세션의 생산성을 높이기 위해 IDE를 사용하는 방법을 알아봅니다. 다음과 같은 여러 작업을 수행합니다.
IDE의 코드 분석기를 사용하여 디버깅을 위한 코드 준비
예외를 해결하는 방법(런타임 오류)
의도를 코딩하여 버그를 최소화하는 방법(어설션 사용)
디버거를 사용하는 경우
이러한 작업을 보여 주려면 앱을 디버그하려고 할 때 발생할 수 있는 가장 일반적인 오류 및 버그 몇 가지 유형을 보여 줍니다. 샘플 코드는 C#이지만 개념 정보는 일반적으로 C++, Visual Basic, JavaScript 및 Visual Studio에서 지원하는 기타 언어(명시된 경우 제외)에 적용됩니다. 스크린샷은 C#에 있습니다.
일부 버그 및 오류가 있는 샘플 앱 만들기
다음 코드에는 Visual Studio IDE를 사용하여 수정할 수 있는 몇 가지 버그가 있습니다. 이 애플리케이션은 일부 작업에서 JSON 데이터를 가져오고, 데이터를 개체로 역직렬화하고, 새 데이터로 간단한 목록을 업데이트하는 간단한 앱입니다.
앱을 만들려면 Visual Studio가 설치되어 있고 .NET 데스크톱 개발 워크로드가 설치되어 있어야 합니다.
Visual Studio를 아직 설치하지 않은 경우 Visual Studio 다운로드 페이지로 이동하여 무료로 설치합니다.
워크로드를 설치해야 하지만 Visual Studio가 이미 있는 경우 도구>도구 및 기능 가져오기를 선택합니다. Visual Studio 설치 관리자가 시작됩니다. .NET 데스크톱 개발 워크로드를 선택한 후 수정을 클릭하세요.
애플리케이션을 만들려면 다음 단계를 수행합니다.
Visual Studio를 엽니다. 시작 창에서 새 프로젝트 만들기를 선택합니다.
검색 상자에 콘솔 을 입력한 다음 .NET용 콘솔 앱 옵션 중 하나를 입력합니다.
다음을 선택합니다.
Console_Parse_JSON 같은 프로젝트 이름을 입력한 다음, 해당하는 경우 다음 또는 만들기를 선택합니다.
권장 대상 프레임워크 또는 .NET 8을 선택한 다음 만들기선택합니다.
.NET용 콘솔 앱 프로젝트 템플릿이 표시되지 않으면 도구>도구 및 기능 가져오기로 이동하여 Visual Studio 설치 관리자를 엽니다. .NET 데스크톱 개발 워크로드를 선택한 후 수정을 클릭하세요.
Visual Studio는 오른쪽 창의 솔루션 탐색기에 표시되는 콘솔 프로젝트를 만듭니다.
프로젝트가 준비되면 프로젝트의 Program.cs 파일의 기본 코드를 다음 샘플 코드로 바꿉다.
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;
}
}
빨간색과 녹색 물결선을 찾아보세요!
샘플 앱을 시작하고 디버거를 실행하기 전에 코드 편집기에서 빨간색 및 녹색 물결선의 코드를 확인합니다. 이는 IDE의 코드 분석기에서 식별된 오류 및 경고를 나타냅니다. 빨간색 물결선은 컴파일 시간 오류이며 코드를 실행하기 전에 수정해야 합니다. 녹색 물결선은 경고입니다. 경고를 수정하지 않고 앱을 실행할 수 있는 경우가 많지만 버그의 원인일 수 있으며 이를 조사하여 시간과 문제를 절약하는 경우가 많습니다. 목록 보기를 선호하는 경우 이러한 경고 및 오류도 오류 목록 창에 표시됩니다.
샘플 앱에는 수정해야 하는 몇 가지 빨간색 밑줄과 확인해야 하는 녹색 밑줄이 표시됩니다. 첫 번째 오류는 다음과 같습니다.
이 오류를 해결하려면 전구 아이콘으로 표시되는 IDE의 다른 기능을 확인할 수 있습니다.
전구를 확인하십시오!
첫 번째 빨간색 물결선은 컴파일 시간 오류를 나타냅니다. 마우스로 가리키면 메시지가 The name `Encoding` does not exist in the current context표시됩니다.
이 오류는 왼쪽 아래에 전구 아이콘을 표시합니다. 드라이버 아이콘
과 함께 전구 아이콘
은 코드를 인라인으로 수정하거나 리팩터링하는 데 도움이 되는 빠른 작업을 나타냅니다. 전구 는 해결해야 하는 문제를 나타냅니다. 스크루드라이버는 해결을 선택할 수 있는 문제를 위한 도구입니다. 왼쪽에서 System.Text를 사용하여 이 오류를 해결하려면 첫 번째 제안된 수정 사항을 사용합니다.
이 항목을 선택하면 Visual Studio에서 using System.Text 파일의 맨 위에 문을 추가하고 빨간색 물결선이 사라집니다. (제안된 수정에 의해 적용된 변경 내용에 대해 잘 모르는 경우 수정 사항을 적용하기 전에 오른쪽에서 변경 내용 미리 보기 링크를 선택합니다.)
위의 오류는 코드에 새 using 문을 추가하여 일반적으로 수정하는 일반적인 오류입니다. 이러한 종류의 오류와 같이 The type or namespace "Name" cannot be found. 이와 유사한 몇 가지 일반적인 오류는 누락된 어셈블리 참조(프로젝트를 마우스 오른쪽 단추로 클릭하고참조> 선택), 철자가 틀린 이름 또는 추가해야 하는 누락된 라이브러리를 나타낼 수 있습니다(C#의 경우 프로젝트를 마우스 오른쪽 단추로 클릭하고 NuGet 패키지 관리 선택).
나머지 오류 및 경고 수정
이 코드에서 살펴볼 몇 가지 주의할 부분이 더 있습니다. 여기서는 일반적인 형식 변환 오류가 표시됩니다. 물결선 위로 마우스를 가져가면, 코드가 문자열을 int로 변환하려고 시도하는데, 이는 명시적 코드를 추가하지 않으면 지원되지 않는 것을 볼 수 있습니다.
코드 분석기는 의도를 추측할 수 없으므로 이번에는 도움이 되는 전구가 없습니다. 이 오류를 해결하려면 코드의 의도를 알아야 합니다. 이 예제에서는 points는 숫자(정수) 값이어야 하며, 이는 points를 totalpoints에 더하려고 하기 때문에 크게 어렵지 않게 알 수 있습니다.
이 오류를 해결하려면 클래스의 멤버를 pointsUser 다음에서 변경합니다.
[DataMember]
internal string points;
이에 대해:
[DataMember]
internal int points;
코드 편집기에서 빨간색 물결선이 사라집니다.
그런 다음, points 데이터 멤버 선언의 녹색 물결선 위로 마우스를 가져다 놓습니다. 코드 분석기는 변수에 값이 할당되지 않음을 알려줍니다.
일반적으로 이 문제는 해결해야 하는 문제를 나타냅니다. 그러나 샘플 앱에서는 실제로 역직렬화 프로세스 중에 변수에 points 데이터를 저장한 다음 해당 값을 데이터 멤버에 totalpoints 추가합니다. 이 예제에서는 코드의 의도를 알고 있으며 경고를 무시해도 됩니다. 그러나 경고를 제거하려면 다음 코드를 바꿀 수 있습니다.
item.totalpoints = users[i].points;
다음으로:
item.points = users[i].points;
item.totalpoints += users[i].points;
녹색 물결선이 사라집니다.
예외 수정
빨간색 물결선을 모두 수정하고, 녹색 물결선을 조사하거나 해결했다면, 디버거를 시작하고 앱을 실행할 준비가 된 것입니다.
F5 키(디버그 > 디버깅 시작) 또는 디버그 도구 모음에서 디버깅 시작 버튼
버튼을 누릅니다.
이 시점에서 샘플 앱은 SerializationException 예외(런타임 오류)를 발생시킵니다. 즉, 앱이 직렬화하려는 데이터를 처리하는 데 문제를 겪고 있습니다. 디버그 모드(디버거 연결됨)에서 앱을 시작했기 때문에 디버거의 예외 도우미는 예외를 throw한 코드로 바로 이동하고 유용한 오류 메시지를 제공합니다.
오류 메시지는 값을 4o 정수로 구문 분석할 수 없음을 지시합니다. 따라서 이 예제에서는 데이터가 잘못되었다는 것을 알 수 있습니다: 4o는 40이어야 합니다. 그러나 실제 시나리오에서 데이터를 제어하지 않는 경우(예: 웹 서비스에서 데이터를 가져오는 경우) 어떻게 해야 할까요? 이 문제를 해결하려면 어떻게 해야 하나요?
예외가 발생하면 다음과 같은 몇 가지 질문을 하고 대답해야 합니다.
이 예외는 해결할 수 있는 버그일 뿐입니까? 또는,
이 예외가 사용자에게 발생할 수 있는 것입니까?
전자인 경우 버그를 수정합니다. (샘플 앱에서는 잘못된 데이터를 수정해야 합니다.) 후자의 경우 블록을 사용하여 코드에서 예외를 try/catch 처리해야 할 수 있습니다(다음 섹션에서 다른 가능한 전략을 살펴봅니다). 샘플 앱에서 다음 코드를 바꿉다.
users = ser.ReadObject(ms) as User[];
이 코드를 사용하여 다음을 수행합니다.
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
}
try/catch 블록에는 어느 정도의 성능 비용이 있으므로, 정말로 필요한 경우에만 사용해야 합니다. 즉, (a) 앱의 릴리스 버전에서 발생할 수 있는 경우와 (b) 메서드에 대한 문서에서 예외를 확인해야 한다고 표시된 경우에만 사용해야 합니다(문서가 완전하다고 가정할 때). 대부분의 경우 예외를 적절하게 처리할 수 있으며 사용자는 예외에 대해 알 필요가 없습니다.
다음은 예외 처리에 대한 몇 가지 중요한 팁입니다.
오류를 노출하거나 처리하기 위해 적절한 조치를 취하지 않는 빈 catch 블록(예:
catch (Exception) {})을 사용하지 마세요. 비어 있거나 정보가 없는 catch 블록은 예외를 숨길 수 있으며 코드를 더 쉽게 디버그하기 어렵게 만들 수 있습니다.try/catch블록을 예외를 발생시키는 특정 함수 주위에 사용하세요(ReadObject, 샘플 앱에서). 더 큰 코드 청크 주위에 사용하는 경우 오류의 위치를 숨깁니다. 예를 들어, 부모 함수try/catch를 호출하는 주위에ReadToObject블록을 사용하지 않거나 그렇지 않으면 예외가 발생한 위치를 정확히 알 수 없습니다.// Don't do this try { User[] users = ReadToObject(data); } catch (SerializationException) { }앱에 포함된 익숙하지 않은 함수, 특히 외부 데이터(예: 웹 요청)와 상호 작용하는 함수의 경우 설명서를 확인하여 함수가 throw할 가능성이 있는 예외를 확인합니다. 이는 적절한 오류 처리 및 앱 디버깅에 중요한 정보일 수 있습니다.
샘플 앱에서 SerializationException 메서드의 GetJsonData를 4o에서 40로 변경하여 수정하세요.
팁 (조언)
copilot있는 경우 예외를 디버깅하는 동안 AI 지원을 받을 수 있습니다. 부조종사에게 물어보기 단추의
단추입니다. 자세한 내용은 Copilot과 함께 디버그 을 참조하세요.
어설션을 사용하여 코드 의도 명확히
디버그 도구 모음에서 다시 시작
단추를 선택합니다 (Ctrl + Shift + F5). 이렇게 하면 더 적은 단계로 앱을 다시 시작합니다. 콘솔 창에 다음과 같은 출력이 표시됩니다.
이 출력에 옳지 않은 것을 볼 수 있습니다. 세 번째 레코드의 이름과성 값은 비어 있습니다.
지금은 함수에서 assert 문을 사용하는, 자주 충분히 사용되지 않는 유용한 코딩 연습에 대해 논의하기에 좋은 시기입니다. 다음 코드를 추가하여 firstname 및 lastname이 null가 아님을 확인하는 런타임 검사를 포함합니다. 메서드에서 다음 코드를 UpdateRecords로 바꾸세요.
if (existingUser == false)
{
User user = new User();
user.firstname = users[i].firstname;
user.lastname = users[i].lastname;
다음으로:
// 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 코드의 의도를 지정할 수 있습니다. 앞의 예제에서는 다음 항목을 지정합니다.
- 이름에 유효한 문자열이 필요합니다.
- 성에 유효한 문자열이 필요합니다.
이러한 방식으로 의도를 지정하면 요구 사항을 적용합니다. 개발 중에 버그를 표시하는 데 사용할 수 있는 간단하고 편리한 방법입니다. (assert 문은 단위 테스트의 주 요소로도 사용됩니다.)
디버그 도구 모음에서 다시 시작
단추를 선택합니다 (Ctrl + Shift + F5).
비고
코드는 assert 디버그 빌드에서만 활성화됩니다.
다시 시작하면 식 assert 이 users[i].firstname != null 대신 false 로 평가되기 때문에 디버거가 true문에서 일시 중지됩니다.
오류 assert는 문제가 있으며 이를 조사해야 함을 알려줍니다.
assert 는 예외가 반드시 표시되지 않는 많은 시나리오를 다룰 수 있습니다. 이 예제에서는 사용자에게 예외가 표시되지 않으며, 레코드 목록에 null 값이 firstname로 추가됩니다. 이 조건으로 인해 나중에 문제가 발생할 수 있으며(예: 콘솔 출력에 표시됨) 디버그하기가 더 어려울 수 있습니다.
비고
null 값에 메서드를 호출하는 경우, NullReferenceException이 생깁니다. 일반적으로 특정 라이브러리 함수에 연결되지 않은 예외인 일반적인 예외에 대한 블록을 사용하지 try/catch 않으려고 합니다. 어떤 객체도 NullReferenceException을(를) 던질 수 있습니다. 확실하지 않은 경우 라이브러리 함수에 대한 설명서를 확인합니다.
디버깅 프로세스 중에는 실제 코드 수정으로 바꿔야 한다는 것을 알 때까지 특정 assert 문을 유지하는 것이 좋습니다. 사용자가 앱의 릴리스 빌드에서 예외가 발생할 수 있다고 가정해 보겠습니다. 이 경우 앱이 치명적인 예외를 발생시키거나 다른 오류가 발생하지 않도록 코드를 리팩터링해야 합니다. 따라서 이 코드를 수정하려면 다음 코드를 바꿉다.
if (existingUser == false)
{
User user = new User();
이 코드를 사용하여 다음을 수행합니다.
if (existingUser == false && users[i].firstname != null && users[i].lastname != null)
{
User user = new User();
이 코드를 사용하면 코드 요구 사항을 충족시키고, firstname 또는 lastname 값이 null인 레코드가 데이터에 추가되지 않도록 합니다.
이 예제에서는 루프 내부에 두 assert 개의 문을 추가했습니다. 일반적으로 assert을(를) 사용할 때, 함수 또는 메서드의 진입점(시작 부분)에 assert 문을 추가하는 것이 가장 좋습니다. 현재 샘플 앱에서 UpdateRecords 메서드를 보고 있습니다. 이 메서드에서는 메서드 인수 중 하나가 null이면 문제가 발생할 수 있으므로, 함수의 진입점에서 assert 문을 사용하여 모두 확인하십시오.
public static void UpdateRecords(List<User> db, User[] users)
{
Debug.Assert(db != null);
Debug.Assert(users != null);
이전 문장의 의도는 기존 데이터(db)를 로드하고 새 데이터(users)를 검색한 후에 모든 항목을 업데이트하는 것입니다.
assert는 true 또는 false로 해결되는 모든 종류의 표현식과 함께 사용할 수 있습니다. 예를 들어 다음과 같은 assert 문을 추가할 수 있습니다.
Debug.Assert(users[0].points > 0);
위의 코드는 다음 의도를 지정하려는 경우에 유용합니다. 사용자의 레코드를 업데이트하려면 0보다 큰 새 포인트 값이 필요합니다.
디버거에서 코드 검사
이제 샘플 앱에 잘못된 중요한 모든 사항을 수정했으므로 다른 중요한 항목으로 이동할 수 있습니다.
디버거의 예외 도우미를 보여 주었지만 디버거는 코드를 단계별로 실행하고 변수를 검사하는 것과 같은 다른 작업을 수행할 수 있는 훨씬 더 강력한 도구입니다. 이러한 더 강력한 기능은 많은 시나리오, 특히 다음 시나리오에서 유용합니다.
코드에서 런타임 버그를 격리하려고 하지만 이전에 설명한 메서드와 도구를 사용하여 이 작업을 수행할 수 없습니다.
코드의 유효성을 검사합니다. 즉, 코드가 실행되는 동안 코드가 예상대로 동작하고 원하는 대로 작동하는지 확인합니다.
실행되는 동안 코드를 보는 것이 유익합니다. 이러한 방식으로 코드에 대해 자세히 알아볼 수 있으며, 명백한 증상이 나타나기 전에 버그를 식별할 수 있는 경우가 많습니다.
디버거의 필수 기능을 사용하는 방법을 알아보려면 절대 초보자를 위한 디버깅을 참조하세요.
성능 문제 해결
다른 종류의 버그에는 앱이 느리게 실행되거나 너무 많은 메모리를 사용하는 비효율적인 코드가 포함됩니다. 일반적으로 성능 최적화는 나중에 앱 개발에서 수행하는 일입니다. 그러나 성능 문제가 초기에 발생할 수 있으며(예: 앱의 일부 부분이 느리게 실행되고 있음을 알 수 있음) 프로파일링 도구를 사용하여 앱을 조기에 테스트해야 할 수 있습니다. CPU 사용량 도구 및 메모리 분석기와 같은 프로파일링 도구에 대한 자세한 내용은 먼저 프로파일링 도구살펴보세요.
관련 콘텐츠
이 문서에서는 코드에서 많은 일반적인 버그를 방지하고 수정하는 방법과 디버거를 사용하는 시기를 알아보았습니다. 다음으로, Visual Studio 디버거를 사용하여 버그를 해결하는 방법에 대해 자세히 알아봅니다.