2022년 12월 28일 글입니다.
ITEM 29. 사용할 때는 너그럽게, 생성할 때는 엄격하게 작성하라
요약 : 매개변수는 너그럽게 받고, 반환값은 엄격하게 반환하라
interface CameraOptions {
center?: LngLat;
zoom?: number;
bearing?: number;
pitch?: number;
}
type LngLat =
{ lng: number; lat: number; } |
{ lon: number; lat: number; } |
[number, number];
type LngLatBounds =
{northeast: LngLat, southwest: LngLat} |
[LngLat, LngLat] |
[number, number, number, number];
declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): CameraOptions;
- 위와 같은 카메라에 대한 타입이 있다. 카메라 설정은 일부 값을 건드리지 않으면서 다른 값을 받을 수 있도록 CameraOptions는 모두 선택적이다.
- 카메라의 경계박스의 뷰포트를 계산하는 viewportForBounds는 bounds 값을 받아 카메라 옵션을 반환한다.
- LngLat, LngLatBounds 모두 union을 이용해 타입을 넓힘으로써 다양한 매개변수를 받고있다. 즉 사용자가 사용하기에 너그럽게 생성했다.
- 너그러움의 결과는 에러로 반환된다. 반환타입의 범위가 너무 넓어서 타입체커가 제대로 예측하지 못한것 같다. camera를 안전한 타입으로 사용하려면 유니온 타입의 각 요소별로 코드를 분기해야 한다.
interface LngLat { lng: number; lat: number; };
type LngLatLike = LngLat | { lon: number; lat: number; } | [number, number];
interface Camera {
center: LngLat;
zoom: number;
bearing: number;
pitch: number;
}
//omit은 ~를 제외하고 부분적으로 받는다는 뜻이다. 즉 camera의 center를 제거한 속성들만 partial로 받겠다는 뜻
interface CameraOptions extends Omit<Partial<Camera>, 'center'> {
center?: LngLatLike;
}
type LngLatBounds =
{northeast: LngLatLike, southwest: LngLatLike} |
[LngLatLike, LngLatLike] |
[number, number, number, number];
declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): Camera;
- 위 코드를 보면 center부분을 분기로 처리하여 Camera의 center를 받아오지 않는다.
CameraOptions에서 center는 LngLatLike로 재정의 된다. LatLngLike로 재정의된 곳엔 LngLat으로 타입이 정의되어있으므로 디스트럭팅으로 {lng,lat}을 받아올 수 있기 때문에 오류가 나지 않는 것 같다.
코드가 중복되지만 이런식으로 명시적 작성을 해줘도 된다는데, 위 코드가 더 멋있는 것 같다.
ITEM 30. 문서에 타입정보를 쓰지 안기
요약 1 : 주석으로 설명하기 보다는, 코드로 한눈에 딱 알아볼 수 있게 작성하기
=> 주석은 나중에 유지보수하기 귀찮기도 하고 자동으로 변경되지도 않는다.
요약 2 : 특정 매개변수를 설명하고 싶다면 JSDoc의 @PARAM 구문을 사용하자
type Color = { r: number; g: number; b: number };
/**매개변수가 있을 때는 특정페이지의 전경색을 반환합니다.. 보다는*/
function getForegroundColor(page?: string): Color {
// COMPRESS
return page === 'login' ? {r: 127, g: 127, b: 127} : {r: 0, g: 0, b: 0};
// END
}
- 이처럼 JSDoc의 param을 이용하는게 더좋다. parameter에 댔을때 관련설명도 보여주는 좋은 기능이다.
- 정보의 모순이 발생할 수도 있기때문에 주석과 변수명에 타입정보를 적는건 피해야 한다.
타입이 명확하지 않을 떈, 변수명에 단위 정보를 포함하는것을 고려해라
(timeMs, temperatureC 등등)
ITEM 31. 타입주변에 null값 배치하기
요약 1 : null값을 잘 걸러줘라, undefined를 포함하게 하지 말아라!
요약 2 : 데이터를 받는 비동기함수의 경우 받을때는 null값을 허용하되 반환할 때는 null값이 아니게 반환하라
요약 3 : null값인 변수와 아닌 변수가 섞이게 하지 말아라, 골치아파진다.
interface UserInfo { name: string }
interface Post { post: string }
declare function fetchUser(userId: string): Promise<UserInfo>;
declare function fetchPostsForUser(userId: string): Promise<Post[]>;
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() {
// ...?
}
}
- 이 코드에서 user와 posts 는 null로 초기화 되어있다. Promise.all이 도는동안 둘다 null 이거나, 둘다 null이 아니거나 둘중 하나만 null 인 4가지 상태가 된다. 그러나 이런 불확실성이 클래스의 모든 메서드에 나쁜 영향을 끼친다고 한다. => 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;
}
}
개선한 코드, init을 호출하면 객체가 반환된다. (js엔 init 내장함수가 없다.)user와 posts가 초기화 된후 UserPosts가 반환된다. 위와 같이 코드를 짜면 null로 초기화 될 이유 없어서 더 좋다고 한다.
'Book Review > Effective Typescript' 카테고리의 다른 글
Day9_Effective Typescript 스터디 (0) | 2023.06.16 |
---|---|
Day8_Effective Typescript 스터디 (item 25~ 26) (0) | 2023.06.16 |
Day7_Typescript Effective 스터디 (item 22~24) (0) | 2023.06.16 |
Day6_Effective Typescript 스터디 (0) | 2023.06.16 |
day5_EffectiveTypescript 스터디 (0) | 2023.06.16 |