strictNullChecks 설정을 활성화 시키면 오류를 회피하기위해 null check하는 예외 처리 구문이 코드 전반에 추가 되어야 한다고 생각 할 수 있다. 그러나, 값이 전부 null이거나 전부 null이 아닌 경우로 분명하게 구별될 수만 있다면 코드 작성이 훨씬 쉬워진다고 한다. 이와 관련하여 타입에 null을 추가하는 방식을 사용했을 때 어떻게 편리해 지는지 알아보도록 하자.
다음은 숫자들의 최소값과 최대값을 게산하는 extent 함수 예제이다.
|
// 이 함수의 문제
// nums가 깡통 배열이면 [undefined, undefined] 배열이 반환된다. 객체에 undefined 가 포함되는 것을 최대한 기피하는 것이좋다고 한다.
// 배열에 0이 들어 잇으면 의도하지 않게 앞쪽 if(!min) 조건에 성립하게 된다.
function extent(nums: number[]){
let min, max;
for (const num of nums){
if(!min) {
min = num;
max = num;
}
else {
min = Math.min(min, num);
max = Math.max(max, num); // (오류) max가 null인 상태에 대해서는?
}
}
return [min, max];
}
// 수정
function extentFix(nums: number[]) {
let result: [number, number] | null = null; // 타입 주변에 null 값 배치하기.
for(const num of nums) {
if(!result){
result = [num, num];
} else {
result = [Math.min(num, result[0]), Math.max(num, result[1])];
}
}
return result;
}
|
null과 null이 아닌 값을 혼재하여 사용하면 클래스에서도 문제가 발생한다.
예를 들어 사용자와 그 사용자의 포럼 게시글을 나타내는 클래스가 있다고 가정해보자.
|
// init 함수 내 프로미스가 완료되지 않은 상태에서 user와 Post 상태는 null이게 된다.
// 이 시점에서 user와 posts는 총 4가지 상태를 가질 수 있다.
// 둘다 null이거나 모두 null이 아니거나, 둘중 하나만 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 fetchPostUser(userId)
]);
}
getUser(){
///... ?
return this.user;
}
}
|
위와 같은 클래스 설계는 분명히 문제가 있으므로 아래의 예제와 같이 개선할 수 있다.
|
class UserPostsFix {
user: UserInfo;
posts: Post[];
constructor(user: UserInfo, posts: Post[]){
this.user = user;
this.posts = posts;
}
static async init(userId: string) : Promise<UserPostsFix> {
const [user, posts] = await Promise.all([
fetchUser(userId),
fetchPostsForUser(userId)
]);
return new UserPostsFix(user, posts);
}
getUserName() {
return this.user.name;
}
}
|
* 한 값의 null 여부가 다른 값의 null 여부에 암시적으로 관련되도록 설계하는 것은 피해야한다.
* API 작성 시에는 반환 타입을 큰 객체로 만들고 반환 타입 전체가 null이거나 null이 아니게 만들어야 한다.
* 클래스를 만들 때는 필요한 모든 값이 준비되었을 때 생성하여 null이 존재하지 않도록 하는 것이 좋다.
'IT' 카테고리의 다른 글
| Effective Typescript - 33 - string 타입보다 더 구체적인 타입 사용하기 (0) | 2022.01.23 |
|---|---|
| Effective Typescript - 32 - 유니온의 인터페이스보다는 인터페이스의 유니온을 사용하기 (0) | 2022.01.23 |
| Effective Typescript - 30 - 문서에 타입 정보를 쓰지 않기 (0) | 2022.01.23 |
| Effective Typescript - 29 - 사용할 때는 너그럽게, 생성할 때는 엄격하게 (0) | 2022.01.23 |
| Effective Typescript - 28 - 유효한 상태만 표현하는 타입을 지향하기 (0) | 2022.01.23 |