ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • NullReferenceException, Null 참조 오류 등 Null 예외 처리에 대한 고찰과 지침서
    닷넷/C# 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();
    }

     

     

    반응형

    댓글

Designed by Tistory.