방어적 프로그램이 필수일까? - NULL을 허용해서 빠르게 실패해라

Alex·2025년 1월 13일
0

Plaything

목록 보기
74/118
post-thumbnail

방어적 프로그래밍이란?Steam.ofNullable로 Null처리를 해보자

지난 글에서 방어적 프로그래밍에 대한 이야기를 했다.
List가 null로 들어올 때 Stream.ofNullable을 활용해서 null이면 EmptyList로 변환되게 하는 방식이다.

그런데, 이 방식을 적용해보니 이게 정말 베스트한 방식일까? 하는 의문이 들었다.

그 이유는

  • null체크를 너무 많이 한다. 이렇게까지 과하게 할 필요가 없을 거 같다.
  • null체크가 오히려 문제의 원인을 가려버릴 수도 있다.

라는 점들 때문이다.

가령 이렇게 들어오는 images들을 사용할 때마다 null 체크를 하는 게 아무리 성능상 문제가 없다고할지라도 불필요한 과정이 계속 된다는 인상을 준다.

이게 정말 그렇게까지 과하게 방어해야할 로직일까?

어차피 내부적으로 S3에 올리기 전에 한번더 유효성 검증을 한다. file이 null이면 여기서 알아서 예외가 발생해서 요청을 종료한다.

빠른 실패로 원인을 파악해야 한다

사진 등록 API에서는 사진 등록이 필수다. 그래서, 이 사진이 NULL이거나 EmptyList이면 요청을 바로 종료시킨다.

사진 업데이트 API는 어떨까?

새로운 사진 image가 null이거나 emptyList여도 괜찮다.

그런데, 만약에 그럴 의도가 아니었는데 null로 들어왔다면 이 null값은 empty List로 변환돼서
로직을 계속 타고타고 끝난다(예외 x)

방어적인 프로그래밍을 하려다 보니
오히려 디버깅을 하기가 어려워지는 문제가 발생하는 것이다.

만약에 NPE가 발생한다면 제일 처음 NPE가 뜨는 곳에서 "아 여기 문제가 있네"하고 디버깅이 가능했을 것이다.

마틴 파울러의 'FAIL FAST'글을 보면

Some people recommend making your software robust by working around problems automatically.This results in the software “failing slowly.”The program continues working right after an error but fails in strange ways later on.

라는 부분이 있다. 문제를 자동으로 피해가는 방식의 소프트웨어는 결국 "느리게 실패하는" 문제를 낳는다는 것이다. 에러가 있음에도, 코드가 작동하고 나중에 이상한 부분에서 '실패'한다.

A system that fails fast does exactly the opposite: when a problem occurs, it fails immediately and visibly. Failing fast is a nonintuitive technique: “failing immediately and visibly” sounds like it would make your software more
fragile, but it actually makes it more robust. Bugs are easier to find and fix, so fewer go into production.

하지만, 시스템은 이것과 정반대의 방식으로 동작해야 한다. 문제가 발생하면, 그 즉시 실패해야 한다. 이는 시스템을 더욱 연약하게(fragile)만드는 것처럼 보이지만, 오히려 더욱 강하게(robust)만든다. 버그를 찾기 더 쉬워지고, 이러한 버그가 실제 프로덕션에 개입될 여지가 줄어들기 때문이다.

위 코드는 느리게 실패하는 사례이다.

빠르게 실패하려면 아래처럼 바로 예외가 터져야 한다.

만약에, 설정파일을 읽는 메서드가 있을 때 개발자가 실수로 설정파일에 오타를 넣었다고 해보자. maxConnections에 오타를 넣어서 maxConnectionss가 되버린 상황을 가정하자. 그러면, 메서드는 설정파일을 읽지 못할 것이다.

이때 위에서 설정한 디폴트 벨류가 반환된다. 그 이후로 아무런 문제가 없을 것으로 보인다. 그러다 실제 프로그램이 돌아가는 과정에서 이상한 오류들을 계속 만나게 될 것이다. fail fast한 방식이었다면, null이 발생했기에 그 즉시 문제의 원인을 파악할 수 있었을 것이다.

it’s tough to know when to add assertions. One way to tell is to look for comments. Comments often document assumptions about how a piece of code works or how it should be called. When you see those comments, or feel like writing one, think about how you can turn it into an assertion instead.

그렇다면, 유효성 검사를 언제해야할까? 코드를 작성하다 주석을 달아서 "이 코드는 이런 방식으로 동작해야 합니다"라고 설명을 넣고 싶은 부분이 있다면, 그때 유효성 검사를 하는 게 좋다고 한다(진짜 맞는 말 같다)

// NPE 허용 - 문제를 바로 발견할 수 있음
public String toString(Object parameter) {
    return parameter.toString();
}

일반적인 메서드는 위처럼 사용하고

public class Foo {
    private Object instanceVariable;
    
    public Foo(Object instanceVariable) {
        Assert.notNull(instanceVariable);  // 생성자에서는 검증
        this.instanceVariable = instanceVariable;
    }
}

클래스 필드에 넣을 땐 이렇게 검증을 먼저 한다.

이런식으로 Assert.notNull을 쓰거나 명시적으로 null 체킹을 한다음 null이면 예외를 터뜨리는 방식이 좋다고 한다.

결론은 아래와 같다.

  • 일반적인 경우는 NPE가 알아서 터지게 해라
  • 핵심 비즈니스로직, NULL이 발생하면 안되는 곳이면 NULL 체킹을 해서 메시지와 함께 예외를 던져라
profile
답을 찾기 위해서 노력하는 사람

0개의 댓글