여러 언어에서 null 안전성을 주요 마케팅 쟁점으로 내세우면서 null 안전성이 관심을 많이 받고 있습니다.
자바에서도 null을 잘 다루어야 소프트웨어 결함을 줄이고 견고하게 만들 수 있는데 null의 위협에서 코드를 안전하게 지키는 설계 지침을 정리하고 안전하다고 확인하는데 도움이 되는 도구를 소개합니다.
6. JVM 언어 전쟁
2000년대 중반 2010년대 중반
2010년 전후
널 안정성
차기 자바의 자리를 두고 실용 언어들 간
경쟁
실론(Ceylon), 코틀린(Kotlin)
함수형 프로그래밍
멀티코어와 대용량 분산 처리가 인기를
뜰면서 함수형 프로그래밍이 주목을 받음
스칼라(Scala), 클로저(Clojure)
동적 타이핑/스크립팅
RoR과 스타트업 붐으로 생산성이 강조
다언어 프로그래밍(Polyglot) 인기
그루비(Groovy), jRuby, Jython
7. “I call it my billion-dollar mistake. It was the invention of
the null reference in 1965..”
- Tony Hoare
8. null 참조
● “레코드 핸들링": 객체지향의 시초가 된 논문
● 특별한 값이 없음을 나타내려고 null을 도입했고 이
값을 사용하려고 할 때 오류를 내도록 설계
● 두 참조값이 null일 때 두 참조는 동일하다고 판단
● 의미가 모호함: 초기화되지 않음, 정의되지
않음, 값이 없음, null 값
● 모든 참조의 기본 상태(값?)
● 모든 참조는 null 가능
자바의 null 참조
9. 소프트웨어 결함 통계
Sapienz: Multi-objective Automated Testingfor Android Applications 에서
11. 자바 기본 장치
● 단정문(assertion)
● java.util.Objects
● java.util.Optional
12. 자바 기본 장치: 단정문(assertion)
● 부울식인 식1의 거짓이면 AssertionError 발생
● 식2는 AssertionError에 포함될 상세 정보를 만드는 생성식
● 공개 메서드에는 사용하지 말아야 함
● -enableassertions 또는 -ea 옵션으로 활성화
assert 식1 ;
assert 식1 : 식2;
private void setRefreshInterval(int interval)
{
assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE : interval;
……
}
13. 자바 기본 장치: java.util.Objects
자바 8
● isNull(Object obj)
● nonNull(Object obj)
● requireNonNull(T obj)
● requireNonNull(T obj, String message)
● requireNonNull(T obj, Supplier<String> messageSupplier)
자바 9
● requireNonNullElse(T obj, T defaultObj)
● requireNonNullElseGet(T obj, Supplier<? extends T> supplier)
14. 자바 기본 장치: java.util.Optional
Optional - The Mother of All Bikesheds: Stuart Marks
https://www.youtube.com/watch?v=Ej0sss6cq14
1. 절대로 Optional 변수와 반환값에 null을 사용하지 말라
2. Optional에 값이 들어 있다는 걸 확신하지 않는한 Optional.get()을 쓰지 말라
3. Optional.isPresent()이나 Optional.get() 외 API를 가능한 사용하라
4. Optional에서 여러 메서드를 연속해서 호출하고 값을 얻기 위해 Optional을 생성하는 건
권장할만하지 않다
5. Optional로 값을 처리하는 중에 그 안에 중간값을 처리하기 위해 또 다른 Optional이 사용되면 너무
복잡해진다
6. Optional을 필드, 메서드 매개변수, 집합 자료형에 쓰지 말라
7. 집합 자료형(List, Set, Map)을 감싸는 데 Optional을 쓰지 말고 빈 집합을 사용해라.
15. null 잘 쓰는 법
1. API(매개변수, 반환값)에 null을 최대한 쓰지 말아라
2. 사전 조건과 사후 조건을 확인하라: 계약에 의한 설계(design by contract)
3. (상태와 같이) null의 범위를 지역(클래스, 메서드)에 제한하라.
4. 초기화를 명확히 하라
16. null 잘 쓰는 법 1: API에 null을 최대한 쓰지 말아라
● null로 지나치게 유연한 메서드를 만들지 말고 명시적인 메서드를 만들어라
● null을 반환하지 말라
○ 반환 값이 꼭 있어야 한다면 null을 반환하지 말고 예외를 던져라.
○ 빈 반환 값은 빈 컬랙션이나 “Null 객체”를 활용하라
○ 반환 값이 없을 수도 있다면 Optional을 반환하라
● 선택적 매개변수는 null 대신 다형성(메서드 추가 정의; overload)를 사용해서 표현하라
17. null 잘 쓰는 법 1: API에 null을 최대한 쓰지 말아라
Null 객체 (특수 사례 패턴; Special Case Pattern)
● 타입 안전하면서 의미를 표현할 수 있는 동일한 타입의
특수 상황용 객체를 만들어 반환
● 다형성 활용
● 아무 일도 하지 않는 객체; 일종의 더미 객체
● 리스코프 치환 원칙 주의
18. null 잘 쓰는 법 2: 계약에 의한 설계(Design by Contract)
● API 규약을 소비자와 제공자 사이에 지켜야 할 엄격한 계약으로 여기는 설계 방법
● 형식적 규약 외에 사전 조건과 사후 조건과 유지 조건을 포함
● 베르트랑 마이어(Bertrand Meyer) - 에펠( Eiffel) 프로그래밍 언어 제작
● 개방-폐쇄 원칙의 상위 개념
When quality is pursued, productivity follows - K. Fujino
19. null 잘 쓰는 법 2: 계약에 의한 설계(Design by Contract)
계약에 의한 설계 class DICTIONARY [ELEMENT]
feature
put (x: ELEMENT; key: STRING) is
-- Insert x so that it will be retrievable through key.
require
count <= capacity
not key.empty
do
......
ensure
has (x)
item (key) = x
count = old count + 1
end
invariant
0 <= count
count <= capacity
end
20. null 잘 쓰는 법 2: 계약에 의한 설계(Design by Contract)
자바의 계약에 의한 설계
● Interface + Java Doc
● 사전 조건 = 보호절(guard clause)
○ 단정문
○ Objects의 메서드
○ IllegalArgumentException,
NullPointerException,
● 스프링 Assert 클래스
● 구아바 Preconditions 클래스
● valid4j + hamcrest
http://www.valid4j.org/
● AssertJ Preconditions 클래스
● Bean Validation
● Cofoja
https://github.com/nhatminhle/cofoja
21. null 잘 쓰는 법 3: null의 범위를 지역에 제한하라
● 기본 문제 해결 원칙: 큰 문제는 제어 가능한 작은 문제로 나누어 정복하고 다시 통합한다.
● 상태와 비슷하게 null도 지역적으로 제한할 경우 큰 문제가 안된다.
● 클래스와 메서드를 작게 만들어라
● 설계가 잘 된 코드에서는 널의 위험도 약해진다.
OOP to me means only messaging, local retention and protection and hiding of
state-process, and extreme late-binding of all things. - Alan Kay
1 2
3
22. null 잘 쓰는 법 4: 초기화를 명확히 하라
● 초기화 시점과 실행 시점이 겹치지 않아야 한다
● 실행 시점엔 초기화되지 않은 필드가 없어야 한다
● 실행 시점에 null인 필드는 초기화되지 않았다는 의미가 아닌, 값이 없다는 의미여야 한다.
● 객체 필드의 생명주기는 모두 객체의 생명주기와 같아야 한다.
● 지연 초기화(lazy initialization) 필드의 경우 팩토리 메서드로 null 처리를 캡슐화 하라
23. null을 안전하게 다루는 방법 - 요약
● API에 null을 최대한 쓰지 말아라
○ 반환값은 Optional, Null 객체, 빈값, 예외로 처리
○ 매개변수는 명확한 메서드 추가 정의
● 사전 조건과 사후 조건을 확인하라: 계약에 의한 설계(design by contract)
○ 보호절을 통한 사전 조건 확인, 다양한 편의 객체 활용
● (상태와 같이) null의 범위를 지역(클래스, 메서드)에 제한하라.
● 초기화를 명확히 하라
25. “This led me to suggest that the null value is a member
of every type, and a null check is required on every use
of that reference variable, and it may be perhaps a
billion dollar mistake.”
- Tony Hoare
26. null 안전한 언어들
null을 안전하고 쉽게 다루게 해주는 엘비스 연산자
● C#: null 조건 연산자(?.과 ?[])
● 그루비(Groovy) : def name = person?.name
● 코틀린: ?. 과 ?:
● 스위프트: Optional Chaining & 가드(Guard) 문
null이 예외인 언어
● 코틀린: null 가능 타입과 non-null 타입
● 스위프트: Optional
● C# 8.0 (출시 예정)
27. 자바의 엘비스 연산자(?:) 논의
● 자바 문법 개선 프로젝트인 코인(coin)에서 최종 탈락
○ ?:는 삼항 연산자의 축약형로 자바와 맞지 않다: a != null ? a : b -> a ?: c
○ null 안전한 참조(?.)는 null 사용을 부추긴다: 디미터 법칙(Law of Demeter)
○ 진행 중인 타입 어노테이션(JSR 308) 추천
● ?. 대신 Optional 사용 권유
Optional.ofNullable(house)
.map(house -> house.getFloor(0))
.map(floorZero -> floorZero.getWall(WEST))
.map(wallWest -> wallWest.getDoor())
house?.getFloor()?.getWall(WEST)?.getDoor();
28. null 안전성을 도와주는 자바 도구
● JSR 305
○ 중단된 미완성 표준 (JSR 리뷰 2006/8/29~2006/9/11)
○ 정적 분석 (findbug 등)
○ IDE 지원 (Intellij, Eclipse, Spring Framework, Android Studio)
○ Nullaway
● JSR 308
○ CheckerFramework
29. JSR-308 타입 어노테이션
● 선언부가 아닌 타입 지정 위치에 어노테이션 추가 가능
● 어노테이션 프로세싱을 통한 빈약한 자바 타입 시스템을 강화
● 초안 제출 2006/10/17, 최종안 승인 2014/2/18, 자바 8에 추가
● 워싱턴대 마이클 에른스트(Michael Ernst) 교수 주도
● CheckerFramework와 동시에 진행 (버전 0.1.1 2007/6/7 첫 출시)
소스 코드 기본 타입 확인
플러그인
타입 확인
바이트 코드
자바 컴파일러
30. Checker Framework
● null 안전성 확인
@Nullable, @NonNull, @PolyNull
● Map 키, 잠금, 순차 자료형(배열, List 등) 색인값, 정규식, 문자열 형식, 단위 등
다수 확인 기능 제공
● 자작 확인 기능 추가 가능
● 특정 환경이나 IDE 독립적
31. @NonNull과 @Nullable
public class Address
{
public final @NonNull String address1;
public final @Nullable String address2;
public final @NonNull String zipcode;
public final @NonNull String city;
public final @NonNull String country;
private Address(@NonNull String address1, @Nullable String address2, @NonNull String zipcode,
@NonNull String city, @NonNull String country)
{
……
}
public static Address of(@NonNull String address1, @NonNull String zipcode,
@NonNull String city, @NonNull String country)
{
return new Address(address1, null, zipcode, city, country);
}
32. ● 과도한 어노테이션 사용 예방
● 기본 @NonNull
필드, 매개변수, 반환값 등
● 예외적 @Nullable
지역 변수, 타입 캐스트 등
● 패키지, 클래스 수준 정책 설정
@DefaultQualifier
기본 null 정책
public class Address
{
public final String address1;
public final @Nullable String address2;
public final String zipcode;
public final String city;
public final String country;
private Address(String address1, @Nullable String address2,
String zipcode, String city, String country)
{
…...
}
public static Address of(String address1, String zipcode, ……
{
return new Address(address1, null, zipcode, city, country);
}
33. ● @DefaultQualifier
● 패키지(package-info.java)나 클래스 전체의 기본 정책 설정
패키지, 클래스 수준 기본 정책 설정
@DefaultQualifier(value = NonNull.class, locations = TypeUseLocation.LOCAL_VARIABLE)
package dev.fupfin.null_safety.strict;
@DefaultQualifier(value = Nullable.class, locations = TypeUseLocation.FIELD)
class MyClass {
Object nullableField = null;
@NonNull Object nonNullField = new Object();
}
34. ● 자바 8 표준 API
● 자바 11 출시 준비 중
● 임의의 API에 어노테이션 설정 가능
어노테이션 달린 자바 표준 API 제공
private static void password()
{
Console console = System.console();
char[] password = console.readPassword();
……
}
Error:(30, 27) java:
[dereference.of.nullable]
dereference of possibly-null
reference console
35. ● 단순한 정적 타입 확인이 아닌 코드 흐름과 실행 결과를 반영
● 코드로 null 확인을 한 경우 @nonNull로 취급
● 메서드 내부로 제한
자동 타입 개선(Automatic type refinement)
private static void password()
{
Console console = System.console();
char[] password = nonNull(console) ? console.readPassword() : new char[0];
……
}
OK
36. ● List, Set, Map, 배열 등 집합 자료형의 요소도 null 확인
● 기본 @NonNull
● 타입 매개변수에 @Nullable 지정
집합 타입 요소의 null 안정성 지원
private static void printList()
{
List<String> names = Arrays.asList("kim", "choi", null, "park", "hwang");
for(String name: names)
out.println(name);
}
Error:(42, 43) java: [assignment.type.incompatible]
incompatible types in assignment.
found : @Initialized @NonNull List<@Initialized
@Nullable String>......
37. 정리
null은 왜 문제인가?
● 모든 참조 타입에 지정 가능한 값(상태?), 언어가 지원할 문제
null을 안전하게 다루는 방법
● API에 null을 최대한 쓰지 말아라 - 반환값은 Optional, Null 객체, 빈값, 예외로 처리
● 사전 조건과 사후 조건을 확인하라: 계약에 의한 설계(design by contract)
● (상태와 같이) null의 범위를 지역(클래스, 메서드)에 제한하라.
● 초기화를 명확히 하라
null에 안전하다고 보장해주는 도구
● JSR 305 계열과 JSR 308 계열
● 가장 성숙한 자바 타입 확인 확장 기술: Checker Framework
● “(원래) 부족한 자바, 고쳐쓰자”