코드 냄새 | 원시적 집착

29564 단어 codequalityrefactorit
안녕하세요, 오늘 다시 글을 쓰는데 이번에는 Primitive Obsession라는 매우 흔한 코드 냄새가 어떻게 나는지 소개하려고 합니다. 명확하지 않습니까? 축소된 예를 살펴보겠습니다.

class User {
  #locale: string;
  #age: number;
  #email: string;

  #SPANISH_LANGUAGE: string = "es";
  #UNDERAGE_UNTIL_AGE: number = 18;
  #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  constructor(locale: string, age: number, email: string) {
    this.#locale = locale;
    this.#age = age;
    this.#email = email;

    if (!this.isValidEmail()) throw new Error("Invalid email format");
  };

  understandSpanish(): boolean {
    const language = this.#locale.substring(0, 2);

    return language === this.#SPANISH_LANGUAGE;
  };

  isOlderAge(): boolean {
    return this.#age >= this.#UNDERAGE_UNTIL_AGE;
  };

  isValidEmail(): boolean {
    return this.#EMAIL_REGEX.test(this.#email);
  };
}



const user = new User("es", 18, "[email protected]");

user.understandSpanish(); // true
user.isOlderAge(); // true
user.isValidEmail(); // true


그렇게 나쁘지는 않지?

이 예는 작기 때문에 약간 오해의 소지가 있을 수 있지만 User 클래스의 코드가 커지기 시작하면 클래스 내에서 추상화할 수 있는 몇 가지 논리가 여전히 있음을 더 명확하게 보기 시작할 것입니다. 우리 수업이 훨씬 좋아 보인다고


우리의 가장 친한 친구: 가치 객체



값 개체는 단순히 기본 유형의 모델링입니다. 예를 살펴보겠습니다.

class Email {
  #email: string;
  #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  constructor(email: string) {
    this.#email = email;

    if (!this.isValid()) throw new Error("Invalid email format");
  };

  isValid(): boolean {
    return this.#EMAIL_REGEX.test(this.#email);
  };

  value(): string {
    return this.#email;
  };
}

new Email("[email protected]").value(); // "[email protected]"
new Email("susogmail.com").value(); // Error: Invalid email format


보시다시피, 우리는 기본string 유형의 추상화를 생성하고 있으며, 그 안에 수신한 이메일이 유효한지 확인하는 논리를 추가하고 있습니다. 신청

VO는 어떤 이점을 제공합니까?


  • 불변성
  • 검증의 견고성 향상
  • 더 큰 의미 체계, 클래스 서명의 가독성 향상
  • 논리 자석
  • IDE/편집기 자동 완성 지원
  • API를 단순화합니다
  • .
  • 어떤 클래스에도 결합되지 않으므로 응용 프로그램의 다양한 부분에서 재사용할 수 있습니다
  • .



    리팩토링 시간



    초기 상태:

    class User {
      #locale: string;
      #age: number;
      #email: string;
    
      #SPANISH_LANGUAGE: string = "es";
      #UNDERAGE_UNTIL_AGE: number = 18;
      #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    
      constructor(locale: string, age: number, email: string) {
        this.#locale = locale;
        this.#age = age;
        this.#email = email;
    
        if (!this.isValidEmail()) throw new Error("Invalid email format");
      };
    
      understandSpanish(): boolean {
        const language = this.#locale.substring(0, 2);
    
        return language === this.#SPANISH_LANGUAGE;
      };
    
      isOlderAge(): boolean {
        return this.#age >= this.#UNDERAGE_UNTIL_AGE;
      };
    
      isValidEmail(): boolean {
        return this.#EMAIL_REGEX.test(this.#email);
      };
    }
    


    VO 클래스 코드를 User , LocaleAge의 세 가지 값 개체로 분할합니다.
    Email
    class Locale {
      #locale: string;
      #SPANISH_LANGUAGE: string = "es";
    
      constructor(locale: string) {
        this.#locale = locale;
      };
    
      understandSpanish(): boolean {
        const language = this.#locale.substring(0, 2);
    
        return language === this.#SPANISH_LANGUAGE;
      };
    
      value(): string {
        return this.#locale;
      };
    }
    

    Locale
    class Age {
      #age: number;
      #UNDERAGE_UNTIL_AGE: number = 18;
    
      constructor(age: number) {
        this.#age = age;
      };
    
      isOlderAge(): boolean {
        return this.#age >= this.#UNDERAGE_UNTIL_AGE;
      };
    
      value(): number {
        return this.#age;
      };
    }
    

    Age
    class Email {
      #email: string;
      #EMAIL_REGEX: RegExp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    
      constructor(email: string) {
        this.#email = email;
    
        if (!this.isValid()) throw new Error("Invalid email format");
      };
    
      isValid(): boolean {
        return this.#EMAIL_REGEX.test(this.#email);
      };
    
      value(): string {
        return this.#email;
      };
    }
    


    마지막으로 Email 클래스는 다음과 같이 유지됩니다.

    class User {
      #locale: Locale;
      #age: Age;
      #email: Email;
    
      constructor(locale: Locale, age: Age, email: Email) {
        this.#locale = locale;
        this.#age = age;
        this.#email = email;
      };
    
      understandSpanish(): boolean {
        return this.#locale.understandSpanish();
      };
    
      isOlderAge(): boolean {
        return this.#age.isOlderAge();
      };
    }
    


    이러한 방식으로 볼 수 있듯이 사용자 클래스가 유효성 검사를 수행할 책임이 없는 방식으로 각 기능을 해당 기능User에 캡슐화하므로 시간이 지남에 따라 더 읽기 쉽고 유지 관리 가능한 코드를 얻을 수 있습니다.

    새 리팩터링된value object 클래스 사용:

    // with valid email
    const user1 = new User(
      new Locale("es"), 
      new Age(18),
      new Email("[email protected]")
    );
    
    user1.understandSpanish(); // true
    
    
    // with invalid email
    const user2 = new User(
      new Locale("es"), 
      new Age(18),
      new Email("susogmail.com")
    );
    
    user2.understandSpanish(); // Error: Invalid email format
    



    경고!



    모든 것이 장점은 아닙니다. 아래에서 이러한 추상화를 적용할 때 고려해야 하는 몇 가지 단점을 볼 수 있습니다.
  • 경우에 따라 특히 많은 유지 관리가 필요하지 않은 소규모 프로젝트에서 조기 최적화가 발생할 수 있습니다
  • .
  • 프로젝트의 크기가 상당한 경우 클래스가 많을 수 있습니다
  • .



    읽어주셔서 감사합니다 😊

    좋은 웹페이지 즐겨찾기