닷넷/C#

NullReferenceException, Null 참조 오류 등 Null 예외 처리에 대한 고찰과 지침서

FreeBear 2024. 7. 12. 11:26
반응형

 

NullReferenceException 에러가 발생 할 때마다 아, 다음에는 좀 더 꼼꼼하게 확인해야지 해도 시간이 지나면 같은 문제를 겪고는 한다.

그래서 매 테스트 때마다 하나 씩 확인하고, 잊지 않기 위해 이렇게 지침서를 작성한다.

 

Visual Studio2022에서의 NullReferenceException 오류 메시지 팝업 화면

 

그리고 문득 이런 고찰을 하게 된다.

왜 프로그램은 Null인 것을 참조하면 그냥 넘어가지 않고, 기어이 오류 메시지를 뱉어내는 걸까?

왜 Null 참조 오류를 무시 하지 못 하게 하는 걸까?

 

○ 그 이유

Null 객체에 접근하려고 하면 메모리 접근 오류가 발생하여 프로그램이 비정상적으로 종료되거나 프로그램의 성격에 따라서 데이터 손실, 보안 문제 등을 일으킬 수 있다. 이러한 문제는 심각한 버그를 야기한다. 이를 방지하고 예방하기 위해 개발자에게 문제를 인지시키는 방법이자, 오류 메시지를 출력하고 그 이후의 동작을 중단시킨다.

만약, 이 오류가 메시지 조차 뜨지 않고 무시가 된 채로 프로그램이 정상적으로 실행이 된다면 Null 참조가 일어나는 곳에서 데이터 누수가 일어난다거나 생각치 못 한 문제들이 연쇄적으로 발생하면서 프로그램을 사용하는 사용자들뿐만 아니라 해당 프로그램을 통해 이익을 얻는 사람들에게 불행이 닥쳐올 것이고, 이는 비용 손실 또한 일어날 수 밖에 없다. 

데이터베이스를 배울 때 무결성의 중요함을 배운다. 이는 데이터를 다루는 프로그램과 그 프로그램을 만드는 개발자 또한 무관한 영역이 아니다.

Null 참조는 무결성을 해치는 심각한 버그이므로, 이 버그가 나타났을 때 프로그램에서 오류를 뱉어내어 개발자가 이를 절대 무시할 수 없게 만들어진 시스템은 개발자 입장에서는 정말 감사한 일이다.

이 버그를 단순히 try catch로 오류를 무시로 일관하는, 조치를 취하는 누를 범하지 말자.

문제를 인지하고 근본적인 것에 다가감으로써 적절한 조치를 취하도록 하자.

 

using System;

class Program
{
    static void Main()
    {
        MyClass obj = null;

        // Null 참조 오류를 무시하고 프로그램 계속 실행
        try
        {
            obj.Method();
        }
        catch
        {
            // 오류를 무시하고 넘어감
        }

        Console.WriteLine("Program continues execution.");
    }
}

class MyClass
{
    public void Method()
    {
        Console.WriteLine("Method called");
    }
}

이 경우, 프로그램은 Null 참조 오류를 무시하고 계속 실행되지만, 실제로는 예기치 않은 동작이 발생할 수 있다.

 

using System;

class Program
{
    static void Main()
    {
        MyClass obj = null;

        // Null 참조 오류를 처리하고 프로그램 중단
        try
        {
            obj.Method();
        }
        catch (NullReferenceException ex)
        {
            Console.WriteLine($"NullReferenceException caught: {ex.Message}");
            // 필요한 경우 프로그램 중단
            Environment.Exit(1);
        }

        Console.WriteLine("This line will not be executed if an exception occurs.");
    }
}

class MyClass
{
    public void Method()
    {
        Console.WriteLine("Method called");
    }
}

이 경우, 프로그램은 Null 참조 오류를 감지하고 적절한 메시지를 출력하며 필요 시 프로그램을 중단하여 이후의 예기치 않은 동작을 방지한다.

 

● 요약

Null 참조 오류를 무시하지 않고 오류 메시지를 출력하는 것은 프로그램의 안정성과 안전성을 보장하기 위한 중요한 방법이다. 이를 통해 개발자는 문제를 신속하게 파악하고 해결할 수 있으며, 프로그램의 예측 가능한 동작을 유지할 수 있다.

 

 

○ Null 참조 오류 해결 방법

● Null 참조 오류를 해결하기 위한 선행 학습, 발생 조건 알기

1. 객체가 null일 때 인스턴스 멤버에 접근

MyClass obj = null;
obj.Method(); // NullReferenceException 발생

2. 컬렉션이 null일 때 접근

List<int> list = null;
int count = list.Count; // NullReferenceException 발생

3. 배열 요소가 null일 때 접근

MyClass[] array = new MyClass[10];
MyClass item = array[0];
item.Method(); // NullReferenceException 발생

4. 메서드 반환 값이 null일 때 접근

MyClass obj = GetMyClass();
obj.Method(); // GetMyClass가 null을 반환하면 NullReferenceException 발생

 

● 해결 방법

1. null 확인: 모든 객체 접근 전에 null인지 확인

if (obj != null)
{
    obj.Method();
}

2. Null-conditional 연산자 "?.": 객체가 null인 경우 null을 반환하고, 그렇지 않으면 멤버에 접근

obj?.Method();

3. Null-coalescing 연산자 "??": 객체가 null일 경우 기본 값을 제공

MyClass obj = GetMyClass() ?? new MyClass();

4. Null-coalescing 할당 연산자 "??=": 객체가 null인 경우 기본 값을 할당

obj ??= new MyClass();

5. nullable 타입과 null 병합 연산자 사용: nullable 타입을 사용할 때 기본 값을 설정

int? number = null;
int value = number ?? 0;

6. Optional Chaining 사용: "?." 연산자를 사용하여 객체가 null인지 확인한 후 null이 아니면 멤버에 접근하는 기능

public class Person
{
    public string Name { get; set; }
    public Person? Parent { get; set; }
}

var child = new Person { Name = "Child" };
var parent = child.Parent?.Name ?? "Unknown";

7. try-catch 블록 사용: 예외가 발생할 수 있는 코드 블록을 감싸서 처리

try
{
    obj.Method();
}
catch (NullReferenceException ex)
{
    // 예외 처리 코드
    Console.WriteLine("NullReferenceException caught");
}

 

 

추가적으로 null 참조 문제를 방지하기 위해 확인하거나 테스트할 수 있는 몇 가지 사항과 방법이 더 있다.

○ 추가적인 확인 및 테스트 방법

1. Nullable Reference Types 사용: C# 8.0부터는 Nullable Reference Types를 사용하여 컴파일 타임에 null 참조 문제를 방지할 수 있다.

#nullable enable
public class MyClass
{
    public string? NullableProperty { get; set; }
    public string NonNullableProperty { get; set; }

    public MyClass(string nonNullableProperty)
    {
        NonNullableProperty = nonNullableProperty;
    }
}

 

2. Data Annotations 및 Code Contracts 사용: Data Annotations와 Code Contracts를 사용하여 메서드와 속성의 유효성을 검사할 수 있다.

using System.ComponentModel.DataAnnotations;

public class MyClass
{
    [Required]
    public string NonNullableProperty { get; set; }
}

 

3. Unit Tests 작성: Unit Test를 작성하여 null 참조 예외가 발생하지 않도록 테스트할 수 있다.

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void TestMethod_NullCheck()
    {
        MyClass obj = null;

        Assert.ThrowsException<NullReferenceException>(() => obj.Method());
    }
}

 

4. Defensive Programming: 가능한 모든 경로에서 null이 발생하지 않도록 방어적인 코딩을 수행한다.

public void Process(MyClass obj)
{
    if (obj == null)
    {
        throw new ArgumentNullException(nameof(obj));
    }

    obj.Method();
}

 

5. Guard Clauses 사용: 메서드 시작 부분에서 인수의 유효성을 검증하는 Guard Clauses를 사용한다.

public void Process(MyClass obj)
{
    _ = obj ?? throw new ArgumentNullException(nameof(obj));
    obj.Method();
}

 

 

반응형