Pink Spider/Null Safety란 무엇인가? 개념부터 실전 예시까지

Created Mon, 13 Jul 2026 11:00:00 +0900 Modified Tue, 14 Jul 2026 10:40:24 +0900

Null Safety란 무엇인가? 개념부터 실전 예시까지

1. Null Safety의 정의

**Null Safety(널 안전성)**란 프로그램이 실행되는 도중에 null(혹은 nil, None 등 언어별 명칭은 다르지만 “값이 없음"을 의미하는 특수 값)을 참조함으로써 발생하는 오류를 **컴파일 타임(코드 작성 시점)**에 미리 방지하도록 설계된 언어적 기능 또는 프로그래밍 원칙을 말합니다.

가장 대표적인 문제가 바로 NullPointerException(NPE) 입니다. 자바의 창시자 중 한 명인 토니 호어(Tony Hoare)는 1965년 Null 참조를 발명한 것을 두고 스스로 “10억 달러짜리 실수(Billion Dollar Mistake)” 라고 표현할 정도로, null로 인한 버그는 소프트웨어 개발 역사에서 가장 흔하고 비용이 큰 오류 중 하나였습니다.

Null Safety를 지원하는 언어는 다음과 같은 질문에 컴파일러가 대신 답해줍니다.

“이 변수는 null이 될 수 있는가? 그렇다면 사용하기 전에 null 체크를 했는가?”

이 체크를 개발자가 실수로 빠뜨리면, 프로그램은 컴파일조차 되지 않습니다. 즉, 런타임에 터질 폭탄을 컴파일 타임에 미리 제거하는 것이 핵심 아이디어입니다.


2. Null Safety가 없을 때 생기는 문제

Null Safety가 없는 언어(예: Java의 초기 버전, JavaScript)에서는 아래와 같은 코드가 컴파일 자체는 문제없이 통과됩니다.

// Java (Null Safety 미지원 시절 스타일)
String name = getUserName(); // null을 반환할 수도 있음
int length = name.length();  // 런타임에 NullPointerException 발생 가능

getUserName()null을 반환하는 상황이 실제로 발생하기 전까지는, 이 버그는 테스트에서도, 코드 리뷰에서도 발견되지 않을 수 있습니다. 결국 운영 서버에서 장애가 나고 나서야 발견되는 경우가 많습니다.


3. Null Safety를 지원하는 언어들의 접근 방식

3-1. Kotlin: 타입 시스템에 Null 가능성을 포함

Kotlin은 타입 자체를 Nullable 타입Non-nullable 타입으로 구분합니다.

var name: String = "Alice"   // Non-nullable: null 대입 불가
name = null                  // 컴파일 에러!

var nickname: String? = null // Nullable: '?'를 붙여야 null 허용
nickname = "Al"              // 정상

Nullable 타입은 사용하기 전에 반드시 null 체크를 강제합니다.

val nickname: String? = getNickname()

// 방법 1: 안전 호출 연산자(?.)
val length = nickname?.length   // nickname이 null이면 length도 null

// 방법 2: 엘비스 연산자(?:)로 기본값 지정
val safeLength = nickname?.length ?: 0

// 방법 3: 명시적 null 체크 (스마트 캐스트)
if (nickname != null) {
    println(nickname.length) // 이 블록 안에서는 자동으로 non-null로 취급
}

3-2. Swift: Optional 타입

Swift는 값이 있을 수도, 없을 수도 있음을 Optional이라는 타입으로 명시적으로 표현합니다.

var name: String = "Alice"     // Non-optional
var nickname: String? = nil     // Optional

// 옵셔널 바인딩(if let)으로 안전하게 값 꺼내기
if let unwrapped = nickname {
    print("닉네임: \(unwrapped)")
} else {
    print("닉네임 없음")
}

// guard let으로 조기 반환 패턴
func greet(_ nickname: String?) {
    guard let name = nickname else {
        print("이름이 없습니다")
        return
    }
    print("안녕, \(name)!")
}

// nil 병합 연산자(??)
let displayName = nickname ?? "익명"

3-3. TypeScript: strictNullChecks

TypeScript는 tsconfig.json에서 strictNullChecks 옵션을 켜면 Null Safety를 활성화할 수 있습니다.

function getLength(name: string | null): number {
  if (name === null) {
    return 0;
  }
  return name.length; // 이 시점에서 name은 string으로 좁혀짐(narrowing)
}

// 옵셔널 체이닝과 널 병합 연산자
const user = { profile: { nickname: null } };
const nickname = user.profile?.nickname ?? "익명";

3-4. Java: Optional 클래스 (언어 차원의 강제는 아님)

Java는 언어 자체에 Null Safety가 내장되어 있지 않지만, Java 8부터 Optional<T>을 통해 “값이 없을 수 있음"을 명시적으로 드러내는 관례를 제공합니다.

Optional<String> nickname = Optional.ofNullable(getNickname());

// 값이 있으면 처리, 없으면 기본값
String result = nickname.orElse("익명");

// 값이 있을 때만 특정 동작 수행
nickname.ifPresent(n -> System.out.println("닉네임: " + n));

// 함수형 스타일 체이닝
int length = nickname.map(String::length).orElse(0);

단, Optional은 강제성이 없어서 여전히 Optional.get()을 아무 체크 없이 호출하면 NoSuchElementException이 발생할 수 있습니다. 즉, 관례일 뿐 컴파일러가 강제하는 진짜 Null Safety는 아닙니다.

3-5. Rust: Option 열거형

Rust는 애초에 null 자체가 언어에 존재하지 않고, Option<T> 열거형으로 값의 유무를 표현합니다.

let nickname: Option<String> = None;

match nickname {
    Some(n) => println!("닉네임: {}", n),
    None => println!("닉네임 없음"),
}

// unwrap_or로 기본값 지정
let display_name = nickname.unwrap_or("익명".to_string());

Rust는 Option을 처리하지 않고 그냥 사용하려고 하면 컴파일 자체가 되지 않기 때문에, 가장 엄격한 형태의 Null Safety를 제공한다고 평가받습니다.


4. 언어별 비교 요약

언어 Null Safety 지원 방식 강제 수준
Kotlin Nullable(?) / Non-nullable 타입 구분 컴파일러가 강제
Swift Optional 타입 컴파일러가 강제
TypeScript strictNullChecks 옵션 설정 시 컴파일러가 강제
Rust Option<T> 열거형, null 없음 컴파일러가 강제 (가장 엄격)
Java Optional<T> (관례) 강제 아님, 개발자 재량
JavaScript 없음 (undefined/null 혼재) 강제 없음

5. Null Safety를 도입하면 좋은 점

  1. 런타임 오류 감소: NPE 같은 오류를 배포 전, 컴파일 시점에 잡아낼 수 있습니다.
  2. 코드의 의도가 명확해짐: 어떤 값이 “없을 수도 있다"는 사실이 타입에 드러나므로, 코드를 읽는 사람이 별도 문서 없이도 null 가능성을 파악할 수 있습니다.
  3. 방어적 코드 감소: 불필요하게 모든 곳에 if (x != null)을 남발하지 않고, 진짜 null이 가능한 지점에서만 체크하면 됩니다.
  4. 리팩토링 안정성: 함수 시그니처가 바뀌어 null 가능성이 추가/제거되면, 컴파일러가 관련된 모든 호출부의 수정 필요 지점을 알려줍니다.

6. 마무리

Null Safety는 단순한 문법적 편의 기능이 아니라, “값이 없을 수 있다"는 사실을 타입 시스템 차원에서 다루도록 강제함으로써 소프트웨어의 신뢰성을 높이는 설계 철학입니다. Kotlin, Swift, Rust처럼 처음부터 Null Safety를 언어 차원에서 강제하는 언어들이 늘어나고 있는 것도, 그만큼 null로 인한 버그가 실무에서 얼마나 큰 비용을 초래했는지를 보여주는 방증이라 할 수 있습니다.

새 프로젝트를 시작한다면, 가능하면 Null Safety를 기본으로 지원하거나 강제할 수 있는 언어/설정을 선택하는 것이 장기적으로 유지보수 비용을 크게 줄여줄 수 있습니다.