Clean Code

Clean Code "Function"

오스타 2022. 4. 1. 21:52

Clean Code "Function"

Clean Code 저서를 읽은 후 주관적인 내용을 많이 포함하고 있는 글이기 때문에 틀린 내용이나 생각이 다른 부분이 있다면 제안해주세요 :)

 

 

한 가지만 하는 함수를 만들어라

사실 한 가지 기능만 하는 함수를 정의하는 것은 꽤나 모호하다

한 가지 기능이란 것은 추상화 수준에 따라 다르게 정의될 수 있기 때문이다

추상화 수준을 설명하기 위해 하나의 예제를 만들어보았다

 

예제 코드 시나리오 - 간단한 두 수 계산기 만들기

  1. 유저는 '계산하기' 버튼을 누른다
  2. '계산하기' 버튼을 누르면 입력된 두 가지 수, 연산자를 DOM에서 가져온다
  3. 입력된 연산자, 피연산자가 유효한지 판별한다
    • 두 수가 모두 입력되었는가?
    • 연산자는 선택되었는가?
    • 연산할 수 없는 숫자가 제공되지는 않았는가? (ex. 0으로 나누는 연산 등)
  4. 유효한 경우 연산 결과를 DOM에 주입한다

 

저서에서는 추상화 수준을 판별할 때 내려가기 규칙을 활용한다. 한 함수에서 세부 작업을 더 분리할 경우 추상화 수준이 한 단계 낮은 함수가 나온다는 것이다

추상화 수준을 따져보고자 위 예제 시나리오를 도식화해보았다

그리 복잡하지 않은 시나리오이기 때문에 추상화 수준 단계가 깊게 나뉘어지지는 않았다

위 도식을 살펴보면 '유효할 경우 연산 결과를 DOM에 주입한다' 기능을 수행하는 함수를 만들 수 있을 것이다. 하지만 어느 관점에서 바라본다면

  1. 연산 결과를 도출한다
  2. 연산 결과를 DOM에 주입한다

이렇게 두 가지 작업을 수행한다고 볼 수 있다

하지만 이 함수를 두 가지 작업이라고 판단하고 기능을 더 분리하게 된다면 계산기 함수의 추상화 수준이 깨지게 된다. 전의 함수들은 추상화 수준을 높게 유지하고 있지만 연산 결과 주입 부분에서 갑자기 추상화 수준이 낮아지는 것이다.

 

if (isValidCalculationForm(operand1, operand2, operator)) {
  insertCalculResultIntoDOM(Number(operand1), Number(operand2), operator.value)
}

위 코드에서는 '연산자, 피연산자가 유효한지 판별한다' 기능은 isValidCalculationForm 함수로 추상화되어 있으며 '유효할 경우 연산 결과를 DOM에 주입한다' 기능은 insertCalculResultIntoDOM 함수로 추상화되어 있다. 본 코드는 함수 내부가 모두 높은 추상화 단계로 통일된 모습을 보여주고 있다

또한 위 3줄의 코드는 함수명만 보더라도 어떤 작업을 진행하는 것인지 가독성있게 읽힌다. 이것은 추상화 수준을 맞춰서 코드가 구성된 덕분일 것이다

함수명이 너무 길다고 느껴질 수도 있을 것이다. 하지만 우리는 변수명, 함수명은 짧은 것보다 긴 것이 훨씬 가독성이 좋다는 것을 명심해야 한다. 함수명에 서술적인 네이밍을 하는 것에 익숙해지자

 

if (isValidCalculationForm(operand1, operand2, operator)) {
  let calculateResult
​
  switch (operator) {
    case '+':
      calculateResult = operand1 + operand2
      break
    case '-':
      calculateResult = operand1 - operand2
      break
    case '*':
      calculateResult = operand1 * operand2
      break
    default:
      calculateResult = operand1 / operand2
  }
​
  document.querySelector('#calcul-result').innerHTML = calculateResult
}

위 코드는 유효성 판단의 경우 높은 추상화를 유지하고 있지만 연산 진행과 DOM 주입 코드는 낮은 추상화 수준으로 세부사항 로직을 포함하고 있다. 물론 예시로 들기 충분한 코드는 아닐 수 있지만 추상화 수준을 섞으면 함수에 불필요한 세부사항을 더 추가하게 되는 상황이 지속될 것이다

또 추상화 수준을 맞춘 코드와는 달리 코드를 읽었을 때 위에서 아래로 이야기처럼 읽히지는 않는다. 추상화를 맞춘 코드보다 가독성이 떨어지는 것을 알 수 있는 부분이다

[ 시나리오 구현 코드 ]

 

함수 인수

함수 인수는 되도록 적은 갯수를 사용하는 것을 추천하고 있다

함수 인수가 늘어날수록, 개발자는 각 인수들의 용도가 무엇인지 파악하는데 시간이 소요되고 다수의 인수를 사용하게 될 시 인수 사이의 순서 또한 숙지해야 한다

 

플래그 인수

인수 중에서도 플래그 인수는 특히 사용을 자제하고 있다

플래그 인수는 함수 내에서 여러 작업을 처리한다고 공표하는 셈일 것이다. 다시 강조하지만 함수 하나 당 단일 기능을 수행하는 것을 추천한다

 

Javascript "RORO" 패턴

인수를 최대한 적게 사용하는 것이 가장 이상적이긴 하지만 무조건 인수를 사용하지 않을 수는 없는 노릇이다

또 경우에 따라서는 3개 이상의 인수를 사용해야 하는 함수를 만들어야 하곤 한다. 이런 상황에서 JS, TS는 별도로 named parameter와 같은 문법을 제공하고 있지 않기 때문에 함수 호출에 있어 인수가 개발자를 헷갈리게 하는 요소로 작용되곤 한다

function displayMyInfo(name: string, secondAuthStatus: boolean, inActiveStatus: boolean) {
  console.log(`Name: ${name}`)
  console.log(`Secondary Auth Status: ${secondAuthStatus}`)
  console.log(`InActive Status: ${inActiveStatus}`)
}
​
displayMyInfo('ostar', true, true)

위 코드에서 'displayMyInfo' 함수를 호출하는 경우 두 번째, 세 번째 인수가 모두 boolean 값을 가지기 때문에 인수를 판별하기 어려워진다

Javscript에서는 destructuring 기능을 활용해 이러한 문제점을 해결하는 효과적인 패턴을 만들 수 있다. 그 이름이 바로 RORO(Receive an Object, Return an Object) 패턴이다. 이름은 생소할지 몰라도 우리는 생각보다 흔히 이 패턴을 사용하고 있을 것이다

interface MyInfo {
  name: string
  secondAuthStatus: boolean
  inActiveStatus: boolean
}
​
function displayMyInfo({ name, secondAuthStatus, inActiveStatus }: MyInfo) {
  console.log(`Name: ${name}`)
  console.log(`Secondary Auth Status: ${secondAuthStatus}`)
  console.log(`InActive Status: ${inActiveStatus}`)
}
​
displayMyInfo({ name: 'ostar', secondAuthStatus: true, inActiveStatus: true })

패턴은 매우 간단하다. 전달할 인수를 하나의 객체로 묶어서 정의하기 때문에 함수 호출 시에도 각 인수들이 무엇을 가리키는지 명시적으로 표시되는 named parameter 효과를 낼 수 있는 것이다

참조) "모던 자바스크립트의 엘레강스한 패턴 - RORO"

 

 

예외 처리

try/catch 블록 뽑아내기

export const login = async (payload) => {
  try {
    const response = await axios.post("/api/user/login", payload, {
      headers: {
        "Content-type": "application/json",
        Accept: "application/json",
      },
    });
    return response.data;
  } catch (e) {
    console.error(e);
  }
};

저서 본문에서는 에러 코드를 반환하는 방식의 코드보다는 예외처리를 위한 try/catch 블록 코드 사용을 권유하고 있다 (그것이 코드 분리에 깔끔하기 때문에)

하지만 try/catch 블록 또한 코드 중간에 섞여있다면 코드 구조 인식에 혼란을 주기 쉽다. 따라서 별도 함수로 뽑아내는 것을 추천한다

나의 경우 API 비동기 요청 코드에 try/catch 블록을 많이 사용했지 않나 생각한다. 특히 API 호출의 경우 대표적인 부수 효과 코드에 포함되기 때문에 try/catch 블록을 분리하는 동시에 React 부수효과를 시각적으로 분리하는 효과도 얻어낼 수 있으리라 예상한다

 

 

LIST

'Clean Code' 카테고리의 다른 글

Clean Code "오류 처리"  (0) 2022.04.24
Clean Code "주석"  (0) 2022.04.10
Clean Code "의미있는 네이밍"  (0) 2022.03.22