14

네이밍에 대하여

이 글은 TkDodoOn naming things 포스트를 번역한 글입니다.

컴퓨터 과학에서 어려운 것은 오직 두 가지입니다: 캐시 무효화와 네이밍

Phil Karlton

이 두 가지 중에서 네이밍이야말로 가장 어려운 것이라 믿습니다. 왜냐하면 네이밍은 단순히 어려울 뿐만이 아니라 매우 주관적인 영억이기 때문입니다. 좋은 변수명이라고 생각하는 것이 다른 사람에게는 그렇지 않을 수도 있고 사소한 네이밍 문제에 대해 과도하게 신경을 쓰다가 더 크고 복잡한 문제를 놓칠 수 있기 때문에 때로는 이에 대한 논쟁이 무의미하기도 합니다. 좋은 변수명은 수치화시킬 수도 없기 때문에 팀이 커지게 될수록 일관성 있는 변수명을 유지하는 것이란 쉽지 않습니다.

왜 중요한가요?

누구라도 컴퓨터가 이해할 수 있는 코드는 작성할 수 있습니다. 훌륭한 프로그래머는 사람이 이해할 수 있는 코드를 작성합니다.

Martin Fowler, 2008

변수명은 프로그램을 이해하기에 있어 매우 중요한 부분입니다. 코드를 읽은 누구라도 어떤 것이 무엇을 의미하는지 알 수 있도록 상수, 함수, 파라미터에 변수명을 부여합니다.

컴퓨터는 실제로 이에 대해 신경 쓰지 않습니다. 사실, 프론트엔드 프로그래밍에서 많은 코드들은 브라우저에게 전달되기 전에 압축됩니다.

컴퓨터는 단순히 코드를 해석할 뿐 이해할 필요가 없습니다. 그러나 우리 같은 사람은 버그를 고치고 새로운 기능을 추가해야 할 필요가 있기에 코드에서 어떤 일이 벌어지고 있는지 이해하는 것은 가장 중요한 부분입니다.

그러면 어떤 것을 할 수 있을까요?

일관성 있는 코드 베이스를 지향하는 것이 이상적이라면 모든 팀들이 그들에게 맞는 네이밍 체계에 대해 고안하고 그것을 가능한 잘 시행해야 한다고 생각합니다. 개발자가 코드를 작성하는 것보다 더 많은 시간을 읽는데 사용하는 것이 잘 알려져 있으므로 항상 가독성과 유지 보수성을 위해 최적화하는 것이 좋습니다.

개인적으로 좋아하는 규칙 몇 가지를 알려드리겠습니다. 아마 이해가 되지 않을 수도 있는 주관적인 규칙도 있고 객관적을 봤을 때도 옳아 보이는 규칙도 있습니다.

줄여서쓰지 마세요

줄여서 쓰는 것은 흔하게 사용되는 것이더라도 (거의 항상) 선호하지 않습니다. 비개발자에게 PR(pull request)이라는 용어를 사용한다면 그들은 대부분 그것이 public relations(대인 관계)를 의미한다고 생각할 것입니다. 트위터는 작성할 수 있는 글자가 제한되어 있기 때문에 많은 사람들이 줄임말을 사용하게 만들었고 이는 가독성을 매우 안 좋게 만듭니다. 심지어 때로는 사람들이 줄임말에 오타를 작성해서 이해하는 것이 거의 불가능한 말을 만들어 냅니다.

그렇기에 저는 다음과 같이 가정하는 마인드셋을 가지고 있습니다:

줄임말이 사용될 때마다 적어도 팀에서 그것을 이해하지 못 하는 사람이 한 명은 있다.

TkDodo, 2020

최근에 OTOH(on the other hand - 반면에)가 무엇을 의미하는지 배웠습니다. 다른 사람이 무엇을 모르는지 당신은 모르기 때문에 가독성을 위해서 가능한 줄임말은 피해주세요.

스코프가 작을 경우라면 괜찮습니다.

posts.map((p) => p.id)

하지만 개인적으로는 p 대신에 post를 사용한다고 해서 어떤 해로운 점도 없다고 생각하기에 이것조차 하지 않습니다.

줄임말을 어쩔 수 없이 사용해야 하는 상황이 있기 때문에 가능한 한 피하려고 합니다. 예를 들면, 대부분의 회사는 사업과 관련된 줄임말들이 있으며 리액트 같은 외부 라이브러리를 사용할 경우 props라는 줄임말을 어쩔 수 없이 사용해야 합니다.

eslint-plugin-unicorn 처럼 이에 대해 알려주는 룰이 있기는 하지만 이 문제에 관해서는 관습과 상식을 따르는 것이 더 쉽다고 생각합니다.

인라인

사용되는 곳에 바로 작성하는 인라인 함수들은 몇 가지 장점이 있습니다. 첫 번째, 네이밍을 할 필요가 없습니다. 네이밍은 어렵기에 이는 매우 좋은 장점입니다.

두 번째, 변수명을 부여하는 것이 어렵고 가독성을 높여주지도 않을 때 함수를 인라인 합니다. 좋은 변수명이 떠오르지 않는다면 인라인을 사용하는 것도 좋습니다.

마지막, TypeScript를 사용한다면 타입이 자동으로 추론되며 많은 복잡도를 줄여줍니다.

type Post = { id: string; title: string }
const getPostId = (post: Post) => post.id
// extracted
posts.map(getPostId)
// inlined
posts.map((post) => post.id)

이런 상황에서는 함수를 인라인 하는 것을 권장합니다:

  • 자주 사용되지 않을 때
  • 작을 때
  • 의미 있는 변수명이 생각나지 않을 때

이는 리액트에서 이벤트 핸들러를 작성할 때 매우 효과적이고 성는에 부정적인 영향을 끼치지도 않습니다.

<button onClick={() => login({ username, password })}>Login</button>

의미하는 것을 네이밍하기

export const TWENTY = 20

초심자는 종종 마법의 숫자(예시의 20)를 사용하지 말라는 말을 듣곤 합니다. (모든 것은 반복하지 말아야 한다며) 만약 코드에서 마법의 숫자를 사용해야 한다면 상수로 추출하는 것을 잊지 마세요. 한곳만 수정하면 모든 곳이 수정될 수 있도록 진실의 원천은 단 하나여야 합니다.

위 상수의 문제점은 무엇을 의미하는지가 아닌 무엇인지를 네이밍 했다는 것입니다. TWENTY가 다른 의미를 가지고 있을 수 있기 때문에 다양한 함수에서 사용할 수 없습니다.

const calculateTaxes = (amount) => amount * percentage(TWENTY)
const sessionTimeout = minutes(TWENTY)

반복을 하지 않기 위해 같은 상수를 사용했는데도 그렇게 좋아 보이진 않습니다.

인라인 하는 것과 마찬가지로 표현하고자 하는 것이 두 번 이상 필요할 때 상수로 추출하세요. 비슷해 보인다고 해서 추상화를 하면 안 됩니다. - 실제로 나타내고자 하는 것이 같은 것이어야 합니다.

이렇게 표현하는 것이 더 좋은 변수명이 될 것이라고 생각합니다.

const SALES_TAX = 20
const calculateTaxes = (amount) => amount * percentage(SALES_TAX)

이제서야 20이라는 숫자가 무엇을 의미하는지 알 수 있습니다.

이번에는 다른 예시로 다음과 같은 상태 값을 네이밍 해야 한다고 가정해 봅시다.

const ??? = status === 'fetching' && !data
...
??? && <LoadingSpinner />

어떻게 네이밍을 하시겠어요?

a) showLoading

b) isFetchingAndHasNoData

c) isInitializing

현재 쓰임에 맞게 네이밍하기

a)처럼 로딩 스피너를 보여줄 것이므로 상태 값을 showLoading, renderLoader, 또는 hasLoadingSpinner로 네이밍 하는 방법입니다.

그러나 언젠가는 OverlayButton 같이 다른 무언가 보여주고 싶을지도 모르고 그렇게 된다면 이 변수명은 더 이상 유효하지 않습니다. 물론 모든 것을 미래를 예측하고 할 수는 없지만 약간의 변화가 생겨도 변수명에 타격이 없다는 것은 좋은 변수명이라는 증거입니다.

게다가 JSX를 읽으면 언제 스피너가 보이는지 알 방법이 없습니다. 코드는 이렇게 말하고 있기 때문이죠:

로딩 스피너를 보여줘야만 한다면 로딩 스피너를 보여줍니다.

구현 사항을 보지 않고는 무슨 상태 값인지 이해할 수 없기 때문에 이는 나쁜 변수명이라고 생각합니다.

구현사항에 맞게 네이밍하기

b)는 단언컨대 가장 나쁜 변수명 후보라고 할 수 있습니다. 이 상태 값의 구현 사항을 바꿀 때마다 변수명을 변경시켜야 하며 이는 이전의 twenty 예시와 매우 유사합니다.

isInitializing

이것이 제가 생각하기에 가장 좋은 변수명입니다. 무엇이 발생하는지 가장 잘 묘사하고 있으며 이때 무엇이든 렌더링 할 수 있습니다. 이 변수명은 쓰임과 강하게 결합되어 있지 않기 때문에 네이밍을 변경하지 않고도 initializing이 의미하는 것을 변경할 수 있습니다.

리액트 이벤트 핸들러

위와 같은 것이 리액트 이벤트 핸들러를 만들 때도 적용됩니다.

const handleClick = () => {
login(
{ userName, password },
{
onSuccess: (url) => routes.push(url),
onError: () => showToast('Could not login'),
}
)
}
<button onClick={handleClick}>Login</button>

저 역시도 이렇게 몇 년간을 써왔지만 위에서 언급했던 것과 같은 문제를 가지고 있습니다: handleClick이 무엇이 발생할지에 대해 어떠한 정보도 알려주지 않으며 클릭 이벤트와 강하게 결합되어 있습니다.

만약 UX를 변경하기 위해 폼이 제출되었을 때 로그인하기를 원한다면 이벤트 핸들러의 변수명을 handleSubmit과 같이 변경해야 할 것입니다.

이벤트 핸들러를 loginUser 또는 performLogin처럼 네이밍 하는 것이 작은 차이지만 훨씬 더 정확하고 잘 함수를 설명해 줍니다.

핵심 내용

두 가지 중요 요점은 다음과 같습니다:

  • 구현 사항이 변경되면 변수명이 바뀌어야 하는가? (a)
  • 쓰임이 변경되면 변수명이 바뀌어야 하는가? (b)

만약 그렇다면 매우 좋은 변수명은 아닐 것입니다.