(트러블 슈팅)중요 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! NullPointerException 예방하기

참고 : http://www.jpstory.net/2014/02/avoid-nullpointerexception/
<습관 들이기>

객체가 언제든 null일 수 있다는 판단하에 접근하면 도움이 될 것이다.

1. equals 메소드 사용시


문자열 비교시 non-null String 기준으로 비교합시다. equals 메소드는 symmetric하므로
a.equals(b) 와 b.equals(a) 가 동일하다.
그렇다면  null 일 수 있는 객체에서 equals메소드 호출은 피하는게 낫다

public void doSomething() {
  //name이 null일 경우, NPE발생
   if(name.equals("BAD")){
    //do Something
  }
}

public void doSomething(){
   //name이 null이어도 NPE 발생 안함
   if("BAD".equals(name)){
     //do something
    }
}


다만 equals를 통과한 null 객체가 if문 안에서 메소드 호출등의 이유로 다시 사용된다면 동일한 NPE가 발생할 수 있습니다. 이럴경우는 미리 null 체크를 하는게 좋다.


2. toString()보다는 valueOf()를 사용할 것

1번 처럼 null 일 수 있는 객체에서 메소드 호출은 NPE발생 위험이 있겠죠?
static으로 제공되는 valueOf()를 사용하면 null를 Parameter로 넘겨도 null return 할 뿐 NPE는 발생하지 않는다.

(valueOf()는 String, Boxed Primitives 클래스(Integer, Double 등) 에서 static 메소드로 제공 됨)

BigDecimal bd = getPrice();
System.out.println( String.valueOf(bd));  //NPE 발생 안함
System.out.println( bd.toString()); //NPE 발생


3. 메소드에서 null return 하지 않기.


간단한 방법은 메소드 구현시 의미없는 null을 return 하지 않도록 처리한다.
가장 간단한 방법은 메소드 구현시 의미없는 null을 return 하지 않도록 처리한다.
null이 아닌 빈 문자열 또는 빈 콜렉션을 return 하도록 한다.

public List<User> getUsers() {
  //........
   Result result = executeNamedQuery(GET_ALL_USERS);

   if(!result.isEmpty()){
       return result;
   }
 return Collections.EMPTY_LIST;
}

4. null Parameter로 넘기지 말것.


null을 Parameter로 넘길 경우는 받는 쪽에서도 어찌보면 불필요한 null 체크가 필요하다.
null자체에 어떤 특별한 의미가 없다면 주지도 받지도 말자.



5. 불필요한 autoboxing, unautoboxing 피하기  & Object 보다는 기본형 사용하기


Boxed Primitives 클래스와 기본형(primitive) 타입간에 자동으로 autoboxing, unboxing을 해주는데요. 자동으로 기본형 변환을 해주기 때문에 헷갈리기가 쉽다. (그냥 기본값이 들어가겠거니.....) 가능하면 null reference를 가질 수 있는 객체가 아닌 자바 기본형(Primitive)을 이용하는 것도 방법이다.

Person ram = new Person("ram");
// getPhone()  에서 return 된 결과가 null 일 경우 NPE 발생
int phone = ram.getPhone();



private static Integer count;

//NPE 발생
if( count <= 0 ){
 S.O.P("Count is not started yet");
}



6. Chaining 메소드 호출 자제하기


String city = getPerson(id) . getAddress() . getCity();

중간에 return 받은 값이 null 일 경우, NPE가 발생하여 Stack Trace에서도 해당 line 위치만 출력되기 때문에 어디서 에러가 발생했는지 디벙기 하기도 어렵다.



Library 이용하기

Apache Commons lang, Google Guava(예전 Goolge Collections) 등 null safe한 method를 이용하는 방법입니다. 자세한 사용은 Library의 JavaDoc을 읽어보길 권합니다.
1. Apache Commons의 StringUtils
String의 null 체크를 간단히 할 때 많이 사용하는 클래스입니다. StringUtils.isNotEmpty(), isBlank(), isNumeric(), isWhiteSpace() 등이 있죠.
// StringUtils methods are null safe, they don't throw NullPointerException
System.out.println(StringUtils.isEmpty(null));
System.out.println(StringUtils.isBlank(null));
System.out.println(StringUtils.isNumeric(null));
System.out.println(StringUtils.isAllUpperCase(null));

Output:
true
true
false
false
2. Guava의 Optional 클래스 이용하기
Optional는 nullable한 T를 non-null 값으로 대체시키기 위한 방법인데요. (말이 어렵죠?)
Optional 객체는 non-null인 T reference를 포함하거나 아무것도 포함하고 있지 않습니다.
한마디로 Optional 객체는 명시적으로 null 값을 갖지 않는다는거죠.
NullObject Pattern을 일반화시켰다고나 할까요? 바로 이 점을 이용해서 NPE를 예방합니다.
– absent : 아무 것도 포함하고 있지 않은 상태
– present : non-null 값을 갖은 상태
1) Optional 객체의 생성 (static 메소드)
Optional.of(T)
– T로 받은 non-null 값을 포함하는 Optional 객체 반환, T가 null 일 경우 NPE 발생
Optional.absent()
– 아무것도 포함하고 있지 않는 absent Optional 객체 반환
Optional.fromNullable(T)
– T로 받은 값이 non-null일 경우 present로, null일 경우 absent로 처리한 Optional 객체 반환
2) Optional 객체를 다루기 위한 메소드
boolean isPresent()
– Optional 객체가 non-null 인스턴스를 포함할 경우 true 반환
T get()
– Optional 객체가 present 일 경우 포함하고 있는 인스턴스를 반환, absent일 경우 IllegalStateException 발생
T or(T)
– Optional 객체의 present 값을 반환. 만일 값이 없을 경우 명시한 T를 반환 (기본값)
T orNull()
– Optional 객체의 present 값을 반환, 만일 값이 없을 경우 null을 반환. fromNullable의 역임
Set asSet()
– Optional 객체에서 포함하고 있는 인스턴스의 변경 불가능한 싱글톤 Set을 반환. 만일 인스턴스가 없다면 변경불가능한 Empty set을 반환
3) 간단 예제
Optional<Integer> possible = Optional.of(5);
System.out.println(possible.isPresent());
System.out.println(possible.get());

Output:
true
5
4) 응용 예제
Integer a = 10;
Integer b = null;

Optional present = Optional.fromNullable(a);
Optional absent = Optional.fromNullable(b);

System.out.println("a is present : " + present.isPresent());
System.out.println("b is present : " + absent.isPresent());
System.out.println("a value : " + present.or(0));
System.out.println("b value : " + absent.or(0));

Output:
a is present : true
b is present : false
a value : 10
b value : 0
http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Optional.html
https://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained#Optional
요건 Wrapper 메소드 형식으로 Optional 객체를 반환하게끔 사용하면 좋을 것 같네요.

Nullness Annotation 활용하기

메소드의 return값 또는 파라미터의 null 허용여부를 Annotation을 이용하여 지정합니다. 코딩 습관 들이기 3, 4번 항목을 제약하기 위해 사용하는데요.
IDE에서 지원하는 @Nullable, @NotNull 등의 Annotation을 사용하면 코드에서 NPE 발생 가능여부를 미리 경고해줍니다. 또 해당 Annotation 제약사항을 위반했을 경우 컴파일시 NPE가 아닌 IllegalArgumentException 또는 IllegalStateException이 발생됩니다. Argument로 NotNull이어야 한다고 정했는데 Null이 들어왔다면 의미상 NPE보다는 잘못된 Argument가 맞겠죠?
현재 ItelliJ, Eclipse IDE 및 Find Bugs에서는 서로 다른 Annotation 라이브러리를 사용하는데요. JCP에 Software Defect Detection하기 위한 Annotation이 JSR-305로 요청 중이나 현재 dormant(중단) 상태네요. 언제 JDK에 포함될런지는 모르겠네요 :'(
상세 사용법은 아래를 참고하세요.
4. IntelliJ 예제
– 프로젝트에 /REDIST/annotations.jar 추가
– Project Settings > Inspections > Probable bugs 체크 > Constant conditions & exception 체크 > Sueggest @Nullable annotation …. 체크
@Nullable
private String getDefinition() {
   // ...
}

public void doSomething() {
   if (getDefinition().equals("DEFINITION")) {
      // ...
   }

   // ...
}
@Nullable인 getDefinition()는 NPE가 발생할 수 있다.’고 IntelliJ에서 경고합니다.
private User findUser(@NotNull final String id) {
   // ...
}

public void doSomething() {
   // try invoking with null parameter
   User user = findUser(null);

   // ...
}
@NotNull 사용한 곳에서 null 값을 전달하고 있다고 IntelliJ에서 경고합니다.

기타

1. DB 테이블 컬럼 생성시 default값 설정하기
사용자로부터 입력 받은 값 중 null을 허용하더라도 default값을 설정했다면,
추후 해당 테이블에서 값을 불러와 사용할 경우 null을 예방할 수 있겠죠.
2. Null Object Pattern, Factory Pattern 사용하기
3. Spring MVC에서 지원되는 Bean Validation에서 @NotNull 사용하기 (JSR-303)

결론

1. 개발자가 코딩시 주의하여 NPE를 발생시키는 코딩 습관은 자제한다.
2. 간단한 Apache Commons나 Guava Library의 null safe 관련 클래스를 학습한 후 적용한다.
3. IDE에서 지원하는 Annotation 등을 이용해서 NPE 발생 부분은 미리 캐치하여 예방한다.




댓글

이 블로그의 인기 게시물

(ElasticSearch) 결과에서 순서 정렬

(네트워크)폴링방식 vs 롱 폴링방식

(18장) WebSocekt과 STOMP를 사용하여 메시징하기