null 검사를 줄이려면

며칠 전 글에서 자바 초급 개발자를 벗어나려면 null 검사를 잘 해야 한다는 말을 했었는데 그렇다고 소스 코드 한줄 한줄 마다 해야 한다는 의미는 아니었다. 예기치 않은 null이 발생할 만한 곳을 주의해야 한다는 의미다.

일반적인 개발 업무 관행(practice)에서는 다음과 같은 방식으로 null 검사는 사실 많이 건너뛸 수 있다.

  • 메서드 반환 값의 null 여부를 명시하는 설계, 구현
  • 반환 값이 없는 경우를 별도의 값으로 명시하는 설계, 구현
  • 스프링(Spring)과 같은 의존성 주입(Dependency Injection) 프레임웍 사용
  • 웹에서는 JSP에서 JSTL 같은 태그 라이브러리 사용

null 반환값을 명시하는 설계

“컨트랙트 프로그래밍(contract -, 계약 -)” 또는 “컨트랙트에 의한 설계” 개념에서는 소프트웨어 컴포넌트들이 상호간에 주고 받을 관계(인터랙션)를 명확히 규정할 것을 지향한다. 이에 따르면 설계자는 컴포넌트의 인터페이스를 설계할 때 메서드가 null을 반환할 것인지 안할 것인지 명시해야 한다.

따라서 설계 단계부터 null 반환 여부가 명확히 규정돼 있고 실제 구현도 그에 맞게 구현한 컴포넌트라면 그걸 가져다 사용하는 입장에서는 어떤 경우 null 검사를 해야 하는지 알게 되므로 코딩 작업이 명확해진다.

설계 산출물로 명시하기에는 문서 작업의 부담이 크다면 메서드 명칭으로 구분하는 것도 좋다. 예를 들어 기본적으로 모든 서비스 메서드는 null을 반환하지 않는다고 합의하고 메서드 명칭이 N으로 끝나는 경우만 null을 반환할 수 있다고 표준으로 정하는 것이다.

public String getItemName() { ... } // null을 반환하지 않는 것으로 간주
public Date getLastEditDateN() { ... } // null을 반환할 수 있는 것으로 간주

반환 값이 없는 경우를 대체하는 설계

메서드 반환 값이 원하는 값이 아닌 경우 null이 아니라 미리 합의한 다른 값을 반환하는 것도 null 검사를 줄이는 방법이다.

  1. List<Item> list = itemService.findList();
  2.  
  3. if (list != null)
  4.     for (Item item : list) {
  5.         ...
  6.     }

위 예에서 findList 메서드가 찾은 데이터가 없더라도 null을 반환하지 않고 빈 리스트를 반환한다면 3행의 null 검사는 필요 없을 것이다. (실제로 MyBatis라든가 Spring JDBC 등 많은 데이터베이스 처리 라이브러리에서 찾은 결과가 없을 때 null이 아니라 빈 리스트를 반환한다.)

또한 다음처럼 값이 없는 경우를 코드화하는 것도 흔히 볼 수 있다.

String value = itemService.getValue();
if (value.equals("00")) { // 값이 없는 경우는 00을 반환하는 것으로 합의
    ...
} else if (value.equals("01") { // 정상적인 값
    ...
} else if ... {

의존성 주입 프레임웍 사용

스프링 같은 프레임웍의 의존성 주입 기능을 사용한다면 상당 부분의 인스턴스 변수가 컨테이너 구동시부터 채워지므로 null인지 걱정하지 않아도 된다.

@Autowired
private ItemService itemService;

위의 ItemService에 해당하는 클래스를 컨테이너에서 찾을 수 없다면 컨테이너 구동시부터 오류가 발생하므로 반대로 일단 컨테이너가 구동됐다면 실행시(runtime)에는 위 인스턴스 변수가 null이 아닐 것으로 안심하고 사용할 수 있다.

그런데 아래와 같이 임의의 방법으로 인스턴스 변수에 값을 채워 넣는다면

private ItemService itemService; // 인스턴스 변수 선언
...
public 생성자() {
    itemService = getItemService();
}

getItemService() 메서드의 구현에 따라서 itemServicenull이 될 가능성이 조금이라도 있다면 null 검사 후 사용하는 것이 안전하다.

물론 위에서 주입된 인스턴스 변수 itemService의 메서드를 호출하고 그 결과가 null인지 검사하는 것은 별개의 문제다. 이 경우는 앞서 말한 인터페이스 계약(컨트랙트)에서 해결할 문제다.

String itemName = itemService.getItemName(); // itemName은 null인지 아닌지?

JSP에서 JSTL 사용

JSTL의 서버 태그에서는 null과 빈 문자열 ""이 동일하게 값이 없다는 개념으로 취급된다. null일 때 빈 문자열로 바꿔 출력할 필요가 없다.

<c:out value="${myString}"/> <!-- null이어도 빈 문자열로 출력 -->

그래서 위와 같이 해도 웹페이지에 “null”이라고 표시되지 않는다. 필요하다면 c:if 태그에서 null인지 검사할 수도 있겠지만 empty 연산자로 검사하는 것이 보다 일반적이다.

또한 다음과 같이 했을 때 점(.) 왼쪽의 변수가 null이더라도 NullPointerException은 발생하지 않으므로 안심하고 출력 태그를 사용할 수 있다.

<c:out value="${myObj.itemValue}"/> <!-- myObj가 null이어도 오류 없음 -->

맺음말

내가 생각해본 건 여기까지다. 혹시 이 글을 보는 다른 분들이 나름 좋은 방법이 있다면 공유 부탁드린다.

의견 있으시면 냉큼 작성해주세요~