[240713] TIL

오늘 배운 것

코드 리뷰 받고 배운 것

외부 종속성에 대해

  • 코드 내 dayjs(date 관련 라이브러리) 외부 종속성이 애플리케이션 코드에 많이 들어가는 것 같다.
  • date 관련해서 의도한 대로 컨트롤하려면 strategy pattern을 이용해서 ImmutableDate 클래스를 하나 추가한 뒤 해당 클래스를 dayjs로 구현하고 Immutable에서 필요한 메서드를 만들어 테스트 코드 작성하신 뒤 사용하시는 걸 제안 받음
  • 위와 같이 구성하면 date 관련 라이브러리 변경/같이 사용하게 되어도 외부 종속성이 캡슐화되어 애플리케이션 코드가 오염되지 않는다.

피드백 반영 코드

// immutableDate.ts
import { BaseImmutableDate } from "./baseImmutableDate";
import { DayjsImmutableDate } from "./dayjsImmutableDate";

export class ImmutableDate
  extends DayjsImmutableDate
  implements BaseImmutableDate {}
// dayjsImmutableDate.ts
import dayjs, { ConfigType, Dayjs } from 'dayjs';
import isBetween from 'dayjs/plugin/isBetween';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';

import {
  BaseImmutableDate,
  DateRangeMode,
  UnitType,
} from './baseImmutableDate';

dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(isBetween);

dayjs.tz.setDefault('Asia/Seoul');

export class DayjsImmutableDate implements BaseImmutableDate {
  private readonly date: Dayjs;

  constructor(date?: ConfigType) {
    this.date = this.createDate(date);
  }

  public toISOString(): string {
    return this.date.toISOString();
  }

  public isSame(date?: ConfigType, unitType?: UnitType): boolean {
    return this.date.isSame(date, unitType);
  }

  public isBefore(date?: ConfigType): boolean {
    return this.date.isBefore(date);
  }

  public isAfter(date?: ConfigType): boolean {
    return this.date.isAfter(date);
  }

  public isBetween(
    startDate: ConfigType,
    endDate: ConfigType,
    unitType?: UnitType,
    dateRangeMode?: DateRangeMode,
  ) {
    return this.date.isBetween(startDate, endDate, unitType, dateRangeMode);
  }

  private createDate(date?: ConfigType): Dayjs {
    if (!this.isValidDate(date)) {
      throw new Error('유효하지 않은 날짜 입력입니다.');
    }
    return this.parseDate(date);
  }

  private isValidDate(date?: ConfigType): boolean {
    return dayjs(date).isValid();
  }

  private parseDate(date?: ConfigType): Dayjs {
    if (
      typeof date === 'string' &&
      (date.endsWith('Z') || this.hasTimeZone(date))
    ) {
      return dayjs.utc(date);
    }
    return dayjs.tz(date);
  }

  private hasTimeZone(date: string) {
    const timeZonePattern = /[+-]\d{2}:\d{2}$/;
    return timeZonePattern.test(date);
  }
}

// baseImmutableDate.ts
export type ConfigType = string | number | Date;
export type UnitType =
  | "millisecond"
  | "second"
  | "minute"
  | "hour"
  | "day"
  | "month"
  | "year"
  | "date";

export type DateRangeMode = "()" | "[]" | "[)" | "(]";

export interface BaseImmutableDate {
  toISOString(): string;
  isSame(date?: ConfigType, unitType?: UnitType): boolean;
  isBefore(date: ConfigType): boolean;
  isAfter(date: ConfigType): boolean;
  isBetween(
    startDate: ConfigType,
    endDate: ConfigType,
    unitType?: UnitType,
    dateRangeMode?: DateRangeMode
  ): boolean;
}

Categories:

Updated:

Leave a comment