아이템 31. 타입 주변에 null 값 배치하기 #
값이 전부 null, null이 아닌 경우로 분명히 구분한다면 값이 섞여 있을 때보다 다루기 쉽다.
if
혹은!(단언)
을 통해 쉽게 타입을 좁힐 수 있다.
추가로
undefined
를 포함하는 객체는 다루기 어렵고 절대 권장하지 않는다.
타입에 null 을 추가하는 방식으로 이러한 경우를 모델링할 수 있다.
function extent(nums: number[]) {
let result: [number, number] | null = null;
for (const num of nums) {
if(!result) {
result = [num, num];
} else {
result = [Math.min(num, results[0]), Math.min(num, results[1])]
}
}
return result;
}
// 반환 타입(return result)이 [number, number] | null 이 되어서 사용하기가 더 수월해진다.
// (= undefined 를 명시적으로 없애는 것이 중요하다고 말하는 것 같다.)
null, null이 아닌 값을 섞어서 사용하면 안좋다. #
class UserPosts {
user: UserInfo | null;
posts: Post[] | null;
constructor() {
this.user = null;
this.posts = null;
}
async init(userId: string) {
return Promise.all([
async () => this.user = await fetchUser(userId),
async () => this.posts = await fetchPostsForUser(userId),
]);
}
getUserName() {
// ...?
}
}
두 번의 네트워크 요청(fetchUser, fetchPostsForUser) 동안 user, posts 속성은 null 값이다.
특정 시점에는 아래와 같은 경우의 수를 가질 수 있다. (두 변수에 대해서만 계산해도 4가지이다.)
- user :
null
, posts :null
- user :
not null
, posts :not null
- user :
null
, posts :not null
- user :
not null
, posts :null
속성 값의 불확실성(null, not null)이 클래스의, 메서드의 모든 부분에 나쁜 영향을 미친다.
= null 체크가 난무하고, 버그를 양산한다.
아래와 같이 개선해볼 수 있다.
class UserPosts {
user: UserInfo;
posts: Post[];
constructor(user: UserInfo, posts: Post[]) {
this.user = user;
this.posts = posts;
}
static async init(userId: string): Promise<UserPosts> {
const [user, posts] = await Promise.all([
fetchUser(userId),
fetchPostsForUser(userId)
]);
return new UserPosts(user, posts);
}
getUserName() {
return this.user.name;
}
}
UserPosts
클래스는 이제 null 인 경우의 수를 고려하지 않아도 된다.
= 이제 클래스, 메서드를 작성하기 쉬워졌다.
요약 & 정리 #
- 한 값의 null 여부가 다른 값의 null 여부에 암시적으로 관련되도록 설계하며 안된다.
- 즉, null 값들이 관계가 있어서 위의 예시처럼 n^2 와 같은 경우의 수가 나오지 않도록 한다.
- API 작성 시 ‘반환 타입’을 (전체가) null 이거나, (전체가) null 이 아니게 만들어야 한다.
- 클래스를 만들 때는 필요한 모든 값이 준비되었을 때 생성하여 null 이 존재하지 않도록 한다.
strictNullChecks
를 설정하자.
아래 내용은 기억하자.
" 클래스를 만들 때는 필요한 모든 값이 준비되었을 때 생성하여 null 이 존재하지 않도록 한다. “