NullReferenceException, Null 참조 오류 등 Null 예외 처리에 대한 고찰과 지침서
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();
}