[클린코드 TS] 함수

moonee·2021년 7월 29일
0

클린코드

목록 보기
2/5

본 게시글은 책 클린코드와 클린코드 for 타입스크립트 을 정리한 내용입니다

1. 작게 만들기

  • if / else 문 , while 문 등에 들어가는 블록은 한 줄 이어야 한다.
  • 중첩 구조가 생길만큼 함수가 커져서는 안된다.


2. 한가지만 하기

  • 지정된 함수 이름 아래에서 추상화 수준이 하나인 단계만 수행한다면, 그 함수는 한 가지 작업만 한다고 볼 수 있다.
  • 단순히 다른 표현이 아니라, 의미있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하고 있는 것이다.
  • 한가지 작업만 하는 함수는 자연스럽게 섹션으로 나누기 어렵다.


3. 함수 당 추상화 수준은 하나로

  • 함수가 한가지 일만 하려면, 함수 내 모든 문장의 추상화 수준이 동일해야 한다.
  • 하나의 함수 내에 추상화 수준을 섞으면 특정 표현이 근본 개념인지 세부사항인지 구분하기 어려워진다.
  • 근본 개념과 세부사항을 뒤섞기 시작하면 깨어진 창문처럼 사람들이 함수에 세부사항을 점점 더 추가한다.

Bad

function parseCode(code: string) {
  const REGEXES = [
    /* ... */
  ]
  const statements = code.split(' ')
  const tokens = []

  REGEXES.forEach(regex => {
    statements.forEach(statement => {
      // ...
    })
  })

  const ast = []
  tokens.forEach(token => {
    // lex...
  })

  ast.forEach(node => {
    // parse...
  })
}

Good

const REGEXES = [
  /* ... */
]

function parseCode(code: string) {
  const tokens = tokenize(code)
  const syntaxTree = parse(tokens)

  syntaxTree.forEach(node => {
    // parse...
  })
}

function tokenize(code: string): Token[] {
  const statements = code.split(' ')
  const tokens: Token[] = []

  REGEXES.forEach(regex => {
    statements.forEach(statement => {
      tokens.push(/* ... */)
    })
  })

  return tokens
}

function parse(tokens: Token[]): SyntaxTree {
  const syntaxTree: SyntaxTree[] = []
  tokens.forEach(token => {
    syntaxTree.push(/* ... */)
  })

  return syntaxTree
}


4. 조건문

조건문 캡슐화 하기

Bad

if (subscription.isTrial || account.balance > 0) {
  // ...
}

Good

function canActivateService(subscription: Subscription, account: Account) {
  return subscription.isTrial || account.balance > 0
}

if (canActivateService(subscription, account)) {
  // ...
}


부정조건문 사용하지 않기

Bad

function isEmailNotUsed(email: string): boolean {
  // ...
}

if (isEmailNotUsed(email)) {
  // ...
}

Good

function isEmailUsed(email): boolean {
  // ...
}

if (!isEmailUsed(node)) {
  // ...
}


조건문 피하기

  • if 블록 혹은 switch case1가지 이상이 있다면 해당 함수는 1개 이상의 일을 하고 있는 것이다.
  • 따라서 함수 내에서 블록은 무조건 1개여야한다.

Bad

class Airplane {
  private type: string
  // ...

  getCruisingAltitude() {
    switch (this.type) {
      case '777':
        return this.getMaxAltitude() - this.getPassengerCount()
      case 'Air Force One':
        return this.getMaxAltitude()
      case 'Cessna':
        return this.getMaxAltitude() - this.getFuelExpenditure()
      default:
        throw new Error('Unknown airplane type.')
    }
  }

  private getMaxAltitude(): number {
    // ...
  }
}

Good

abstract class Airplane {
  protected getMaxAltitude(): number {
    // shared logic with subclasses ...
  }

  // ...
}

class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getPassengerCount()
  }
}

class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude()
  }
}

class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getFuelExpenditure()
  }
}


5. 함수 인수

  • 인수는 2개 이하가 적당하다.

  • 인수가 3개 이상이 될 경우, 테스트 할 때 3개의 다른 인수를 넣어줘야하므로 매우 복잡해지고, 해당 함수 호출 시 함수가 어떤 일을 하는지 명확히 판단하기 어렵다.

  • 여러개의 인수를 사용해야한다면, 객체 리터럴을 사용하도록 한다.

  • 객체 리터럴을 사용 할 때 구조분해 구문을 사용하면 아래와 같은 이점이 있다.

    • 함수 시그니쳐(인수의 타입, 반환값의 타입 등)를 볼 때, 어떤 속성이 사용되는지 즉시 알 수 있다.
    • 명명된 인수처럼 보이도록 사용 가능하다.
    • 구조 분해는 함수로 전달된 매개변수 객체의 특정한 원시 값을 복제하여 부수 효과를 방지한다.
    • 타입스크립트는 사용하지 않은 속성에 대해서 경고를 주며, 구조 분해를 사용하면 경고를 받지 않을 수 있다.
  • 함수 인수로 플래그(flag) 사용하지 않기

    • 플래그를 사용한다는 것은 해당 함수가 한가지 이상의 일을 처리하고 있다는 것이다.
    • boolean 변수로 그 함수가 실행된다면 무조건 쪼개라.


6. 명령과 조회 분리하기

Bad

function set(attribute: String, value: String){
	//....
};

if(set("username","bob")) ...

Good

if (attributeExist('username')) {
  setAttribute('username', 'bob')
}


7. 오류 처리도 한 가지 작업이다.

  • 함수는 한 가지 작업만 해야한다고 했다. 오류처리도 한 가지 작업에 속한다. 그러므로 오류를 처리하는 함수는 오류만 처리해야 마땅하다.


8. Object.assign 혹은 구조분해로 객체 만들기

Bad

type MenuConfig = {
  title?: string
  body?: string
  buttonText?: string
  cancellable?: boolean
}

function createMenu(config: MenuConfig) {
  config.title = config.title || 'Foo'
  config.body = config.body || 'Bar'
  config.buttonText = config.buttonText || 'Baz'
  config.cancellable =
    config.cancellable !== undefined ? config.cancellable : true

  // ...
}

createMenu({ body: 'Bar' })

Good - Object Assign

type MenuConfig = {
  title?: string
  body?: string
  buttonText?: string
  cancellable?: boolean
}

function createMenu(config: MenuConfig) {
  const menuConfig = Object.assign(
    {
      title: 'Foo',
      body: 'Bar',
      buttonText: 'Baz',
      cancellable: true,
    },
    config
  )

  // ...
}

createMenu({ body: 'Bar' })

Good- 구조분해

function createMenu({
  title = 'Foo',
  body = 'Bar',
  buttonText = 'Baz',
  cancellable = true,
}: MenuConfig) {
  // ...
}

createMenu({ body: 'Bar' })


9. 부수효과 피하기

  • 함수는 값을 가져와서 → 다른 값을 반환하는 로직을 지켜야한다.
  • 이 이외의 다른 것을 할 경우, 부수효과가 일어날 수 있다.
  • 불변성을 지키는 것도 부수효과를 피하는 방법 중 하나이다.

Bad

let name = 'Robert C. Martin'

function toBase64() {
  name = btoa(name)
}

toBase64()

console.log(name)
// 'Robert C. Martin'이 출력되는 것을 예상했지만
// 'Um9iZXJ0IEMuIE1hcnRpbg=='가 출력됨

Good

const name = 'Robert C. Martin'

function toBase64(text: string): string {
  return btoa(text)
}

const encodedName = toBase64(name)
console.log(name)


10. 타입체킹 피하기

  • 타입스크립트의 기능을 최대한 활용하기 위해 항상 변수의 타입, 매개변수, 반환값의 타입을 지정하도록 한다.


참고

profile
기록

0개의 댓글