IT/기초 지식

읽기 쉬운 코드를 쓰기 위한 가이드라인

개발자 두더지 2023. 6. 11. 19:31
728x90

일본의 한 블로그 글을 번역한 포스트입니다. 오역 및 의역, 직역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.

 

 

TypeDoc를 활용하기


 TypeScript용의 도큐멘트를 생성하는 툴이다. 메소드등의 처리를 보완해주므로 처리의 흐름등 여러 가지를 알기 쉽게 해준다.

/**
 * @param a - the first number
 * @param b - the second number
 */
export function sum(a: number, b: number) {
    return a + b;
}

 비슷한 것으로 JavaScript 파일의 정보를 제공하는 JSDoc가 있지만, 데이터형 정보를 {String}과 같이 번거롭게 표기할 필요가 없으므로, 개인적으로는 TypeDoc을 좋아한다.

 

 

간결하지만 이해하기 어려운 코드는 피하기


 코드 행의 수를 줄이기 위해 이것 저것 덫붙인 코드는 반대로 읽이 어렵다. 예를 들면 다음과 같은 경우를 상정할 수 있다.

return order.quantity * order.itemPrice −
Math.max(0, order.quantity − 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1, 100);

 알기 어려운 1행의 코드와 알기 쉬운 20행의 코드가 있다고 했을 때, 다른 엔지니어는 후자의 코드보다 전자의 코드를 이해하기 위해서 상세한 내용을 이해하고 필요한 정보를 습득하는 과정 등의 많은 시간이 필요하게 된다.

따라서 코드가 길어지더라도 로컬 변수(설명 변수)등을 활용하여 이해하기 쉽도록 가독성을 높이자.

const basePrice = order.quantity * order.itemPrice;
const quantityDiscount = Math.max(0, order.quantity − 500) * order.itemPrice * 0.05; const shipping = Math.min(basePrice * 0.1, 100);
return basePrice − quantityDiscount + shipping;

 

 

입력 인수는 변경 전에 복사하기


 함수의 입력 인수를 변경한 경우, 부작용이 발생하여 상정하지 않았던 상태나 버그의 원인이 되는 경우가 있다. 변경하기 전에 스프레드 연산자(Spread syntax)등을 사용하여, 새로운 데이터로서 복사하여 사용하는 것으로, 원래 오브젝트가 변경되는 것을 방지할 수 있다. 

  const toConvertKana = (typeKana: string) => {
    let Kana: string[] = [...typeKana]

    const hira = Kana.filter((type) => {
      return type.match(/^[\u3040-\u309f]+$/)
    })

    return hira
  }

 

 

메소드 체인에 의한 액세스는 최대한 줄이기


메소드 체인이란 "."으로 요소에 액세스하는 방법을 의미한다. 아래의 예와 같이 메소드 체인을 사용하여 꽤 깊은 요소까지 액세스할 수 있게 되면 영향 범위가 넓어, 글로벌 변수와 동일한 성질을 가지게 된다. 

 이 상태에서 만약 버그가 발생했을 경우, 어디에서 버그가 발생했는지 알아내기 위해 호출 장소를 조사하는 범위가 넓어진다는 문제도 발생한다. 되도록 메소드 체인에 의한 액세스는 적게 하는 것이 좋다.

// 메소드 체인의 예
export const armor = (memberId: number, newArmor: number) => {
  if (party.members[memberId].equipments.canChange) {
    party.members[memberId].equipments.armor = newArmor
  }
}

 

 

매직 밸류를 반환값으로 사용하지 않기


 매직 밸류이란 값이 존재하지 않거나 오류가 발생했음을 나타내기 윟 ㅐ함수에서 "-1"을 반환하는 것을 일컫는다. 

    const getAge = (age: number) => {
      if (age === null) {
        return -1 // 연령이 없으면-1를 반환한다.
      }
      return ageCalculation(age)
    }

 기본적으로 매직 밸류가 아닌 null이나 에러를 반환하는 것이 상정하지 않았던 상태가 발생하는 리스크를 피할 수 있게 된다.

 

 

static 메소드를 적용하기


 static 메소드는 오브젝트 지향 프로그래밍에 있어서 클래스의 인스턴스 변수를 생성하지 않고, 클래스에 직접 메소드나 속성에 액세스 할 수 있게된다. 인스턴스를 생성하지 않고, 처리를 공통화할 수 있는 한편, 1개의 클래스가 여러개의 책임을 가지게 되므로 Low Chohesion가 되기 쉬운 특징이 있다.

 따라서 응집도에 영향이 없는 경우, 예를 들어 로그 출력용이나 포맷 변경용으로 static 메소드가 적합하다.

class AmountFormatter {
  static formatAmount(amount: string): string | null {
    if (!amount) return null
    
    const numericAmount = parseFloat(amount.replace(/[^0-9.-]+/g, ''))
    
    const formattedAmount = numericAmount.toLocaleString('ja-JP', {
      style: 'currency',
      currency: 'JPY',
    })
    return formattedAmount
  }
}

 

 

공통화하기


 어디에도 속하지 않고 여러 개의 component에서 사용하는 로직(횡단적 관심사)는 공통화시켜, 한 곳에 정리해두자.

 Date 형태 변환, 로그 출력 처리나 에러 처리, 문서 처리를 하는 유틸리티등, 동일한 처리를 여러 곳에 쓰이게 되는 로직의 경우, 재사용할 수 있는 공통 처리를 구현한 공통의 클래스나 메소드를 작성하자.

 그리고 앞서 설명했듯, 공통 처리용 메소드는 static 으로 구현되는 경우가 많다. 

export class StorageUtil {
  static setItem(key: string, value: any): void {
    sessionStorage.setItem(key, JSON.stringify(value));
  }

  static getItem<T>(key: string): T {
    return JSON.parse(sessionStorage.getItem(key) as string) as T;
  }

  static removeItem(key: string): void {
    sessionStorage.removeItem(key);
  }
}

 

 

묻지 말고, 명령하기(Tell, Don't Ask)


소프트웨어 설계에서 있어서 이러한 격언이 있다. 이것은 정보 은닉에 관련된 설계 원리로, 오브젝트의 내부 상태를 묻거나 그 상태에 따라 호출자가 판단하지 않는다는 것이다. 호출한 쪽은 메소드를 통해서 처리를 명령할 뿐으로, 명령받고 적절한 판단이나 제어를 하는 설계 원칙이다.

 이 설계 원칙이 지켜지지 않는 경우는 다음과 같이 된다.

 설계가 지켜진 경우는 다음과 같다.

 설계 원리를 지키면 아름다운 V자 형태가 된다. 이에 따라 객체의 내부 구조가 은폐되고 캡슐화가 강화되거나 객체 간 의존 관계가 감소되며 독립된 로직을 가짐으로써 코드의 가독성과 부수성을 향상시킬 수 있다.

 

 

컴포넌트의 의존관계에 주의하기


 컴포넌트간의 의존관계는 기본적으로 일방향이므로 역방향으로 의존하지 않도록 한다.

 예를 들어 View > Component > Service > Web-service이라는 의존관계가 있을 경우, 예를 들어 Service > Component의 역뱡향으로 의존관계가 가능하게 되어버리면, 예상치 못한 곳에서 코드의 영향 범위가 확대되어 버린다. 

 

 

외부의 API에 의존하는 처리를 제한하기


 외부 API에 액세스하는 것은 특정 부분의 코드로 제한하자. 예를 들어 API의 사양이 변경이 됐을 때에도 액세스하고 있는 부분만 변경하면 되므로, 보수할 때 편리하다.


참고자료

https://zenn.dev/arsaga/articles/ba9ec8c004511c

728x90