[번역]고전적인 코드 규약과 라이브러리에서 배우는 JavaScript 네이밍 테크닉(초급편)

doakuma·2022년 10월 30일
0

CodingConvention

목록 보기
3/4
post-thumbnail

이 글은 ICS MEDIA에 등록된 定番のコード規約とライブラリから学ぶJavaScriptの命名テクニック(初級編)을 번역한 내용입니다

프로그래밍을 할 때 변수나 함수의 네이밍에 고민하십니까?

애써 시간을 들여서 생각한 변수명이나 함수명을 다음에 돌아보면 어떤 것 처리한 것인지 알 수 없을 때도 있고, 다른 개발자가 코딩한 것을 이해하는 데 시간이 걸린 경험은 누구에게나 있을 것입니다.

일반적으로 프로그래머는 코딩하는 시간보다 분석하는 시간이 더 길다고 합니다. 쉽게 이해할 수 없는 네이밍은 소스를 분석하는 시간이 길어진다거나, 버그가 발생하는 원인이 되기도 합니다.

이 글에서는 Google이나 Airbnb와 같은 기업이 채용하고 있는 스타일 가이드나 전 세계에서 쓰여지는 JavaScript 라이브러리인 React와 Vue.js 소스를 조사하면서 찾아낸 알기 쉬운 네이밍 테크닉을 초급편과 상급편으로 나눠 소개하려 합니다.

초급편에서는 실제로 일하면서 자주 봤거나 스스로도 자주 사용하는 등의 바로 사용할 수 있는 테크닉을 모아봤습니다.

이해하기 어려운 네이밍

우선 이해하기 어려운 네이밍에는 어떤 것들이 있는지 알아보겠습니다.

1. 약어로 네이밍

Node.js 스타일 가이드는 말합니다

한 글자의 변수나 일반적이지 않은 약어는 피해야 합니다.

예를 들면 이러한 소스의 경우 nm이라는 변수는 name의 약어인가요? 아니면 number의 약어인가요?

const result = nm1 + nm2;

result에는 문자열의 결합 결과인가요? 덧셈의 결과인가요?
간단한 소스라도 일반적이지 않은 약어를 사용하면 어떤 처리를 하는 지 이해하기 어려워집니다.

2. 이름으로부터 읽어내는 것 이상의 의도가 숨어있다.

Airbnb의 스타일가이드에는 네이밍에 대해 다음과 같이 말합니다.

이름만으로 의도를 읽어낼 수 있도록 네이밍

상품 정보를 가져와 세금을 포함한 계산을 하는 프로그래밍을 해 보겠습니다.

const TAX = 0.1;
const item = await fetchItemById(itemId);
const displayPrice = item.price + (item.price * TAX);

fetchItemById()는 함수명으로 추측하면 서버에서 아이템 정보를 가져와서 리턴하는 함수입니다.

일단 아무런 문제도 없어보이는 소스이지만, 만약 이 fetchItemById() 안에서 이미 계산이 이루어져 있어서 item.price에 세금을 포함한 가격이 있다고 한다면 어떻게 될까요?

결과적으로 displayPrice에는 엉뚱한 금액이 전달되어 이 소스가 버그의 원인이 되어버립니다.

이름만으로 예상 가능한 것 이상의 기능이 이루어지면 생각지도 못한 버그를 초래합니다.

3. 어떤 것을 처리하는 지 알 수 없다

Google JavaScript Style Guide에서는 변수는 명사형으로 메소드는 동사형으로 작성하도록 규정하고 있습니다.

슬라이더를 사용한 웹 사이트의 소스를 분석한다고 가정하고 이 함수는 어떤 기능을 하는 지 상상할 수 있습니까?

slider();

이 함수명은 동사가 없기 때문에 슬라이더를 어떻게 처리하는지를 함수명 만으로든 판단할 수 없습니다.

이러한 함수가 여러 개 있는 경우 어디서 무엇을 처리하고 있는 지 알수 없으며 원하는 소스를 찾아내는 대 시간이 오래 걸리게 됩니다.


이해하기 쉬운 네이밍을 위하여

우선 JavaScript에서 공통적으로 인식하고 있는 규칙을 확인해 봅시다.

참고: Node.js Style Guice

변수, 함수, 클래스 구문의 메소드, 프로퍼티명

camelCase로 작성합니다. camelCase는 familyName과 같이 첫 글자가 소문자이며 그 뒤에 붙는 단어의 첫 글자는 대문자로 작성하는 기법입니다.

// 변수명은 camelCase
const familyName = 'Odinson';

// 함수명은 camelCase
const multiplyNumbers = (number1, number2) => {
  return number1 * number2;
}

클래스 명

PascalCase로 작성합니다. PascalCase는 SalesPerson과 같이 첫 글자가 대문자이며 그 뒤에 붙는 단어의 첫글자를 대문자로 작성하는 기법입니다.

// 클래스명은 PascalCase
class SalesPerson {
  constructor(familyName, givenName) {
    this.familyName = familyName;
    this.givenName = givenName;
  }
  
  // 클래스 메소드명은 camelCase
  getFullName() {
    // 클래스 프로퍼티명(familyName이나 gienName)은 camelCase
    return `${this.familyName}${this.givenName}`;
}

상수명

상수는 대문자로 SNAKE_CASE로 작성합니다. SNAKE_CASE는 LIMIT_PER_REQUEST처럼 단어와 단어 를 underscore로 연결하는 기법입니다.

  • 여기서 언급된 상수는 단순히 const로 선언한 변수가 아닌 하드코딩을 피하기 위해 프로그램 내에서 반복적으로 사용되는 값을 지정하기 위해 선언된 특별한 의미를 갖는 값을 말합니다.
// 상수명은 SNAKE_CASE
const LIMIT_PER_REQUEST = 100;

공통적인 규칙이 확인되었으니, 이해하기 쉬운 네이밍 테크닉을 살펴보도록 하겠습니다.


1. 동사의 변화로 상태를 알려준다

동사를 변화시킴으로써 간결하게 객체의 상태를 표현할 수 있습니다.

설명예시
과거분사~된 상태
~함
selected: 선택된
warned: 경고함
현재분사~하고 있는 상태
~중
waiting: 대기중
pending: 보류 중
// 버튼을 선택할 때의 이벤트
numberButtons.forEach((button) => {
  button.addEventListener('click', () => {
    // 선택된 숫자로 갱신
    const selectedNumber = button.innerText;
    favoriteNumberText.innerText = selectedNumber;
  });
});

2. 가능성을 표현

"~이 가능한가" 나 "의 상태인가"는 아래와같이 접속사, 접미사를 붙여서 표한할 수 있습니다.

이러한 단어가 붙은 변수나 함수는 true이거나 false가 반환되는 것입니다.

설명예시
is~인가isValid: 유효한가
isWhiteSpace: 빈 문자인가
can~이 가능한가canMove: 움직일 수 있는가
should~해야 하는가shouldCache: 캐싱해야만 하는가
has~을 가지고 있는가hasOwnProperty: ~라는 프로퍼티를 가지고 있는가
exists~이 존재하는가pathExists: 패스가 존재하는가

아래과 같은 밸리데이션 함수 네이밍 등에 편리한 테크닉입니다.

/**
 * 인수로 전달받은 숫자가 음수인지 아닌지를 반환
 */
const isNegative = (number) => {
  return number < 0;
};

const favoriteNumber = 7;
if (isNegative(favoriteNumber)) {
  console.log('당신이 좋아하는 숫자는 음수입니다.');
} else {
  console.log('당신이 좋아하는 숫자는 양수입니다.');
}

3. 현재의 / 이전의 / 다음의

커서나 페이징 등 순번이 있는 것(명사)에 대해서 "이전의", "다음의" 등 표현하고 싶을 때에 사용할 수 있습니다.

설명예시
current현재의currentValue: 현재 값
currentpage: 현재 페이지
previous이전의previousValue: 이전 값
previousPage: 이전 페이지
next다음의nextValue: 다음 값
nextPage: 다음 페이지
const goToPreviousPage = () => {
  // 이전 페이지를 이동하기 위한 처리
}

const goToNextPage = () => {
  // 다음 페이지를 이동하기 위한 처리
}

4. ~의 앞에 / ~의 뒤에

beforeafter는 "~의 앞에서 ~한다", "~의 뒤에서 ~한다"와 같이 액션이나 시간축에 대해 전후를 표현하고 싶을 경우에 사용하는 표현입니다.

설명예시
before~의 앞에beforeCreate: 생성하기 전에
insertBefore: ~앞에 삽입한다
after~의 뒤에afterLeave: 떠난 뒤에
insertAfter: ~의 뒤에 삽입한다
/**
 * 처리를 그만두기 전에 경고
 */ 
const warnBeforeStop = () => {
  window.confirm('정말로 그만두시게요?');
};

5. 최신의 / 최후의 / 최초의

뉴스나 블로그 등, 시간 순으로 되어 있는 것을 표현할 때에 편리한 표현입니다. lastfirst는 배열의 첫 번째나 마지막을 추출할 때등에서도 사용할 수 있습니다.

설명예시
latest최신의latestEntry: 최신 엔트리
last최후의ㅣlastestComponent: 마지막 컴포넌트
first최초의firstComponent: 첫 번째 컴포넌트
// 배열의 마지막 요소
const last = entryList[entryList.length - 1];

6. 관계 표현

계층 구조로 되어 있는 HTML이나 컨텐츠의 관계를 표현하고 싶을때 사용할 수 있는 표현입니다.

설명예시
parent부모의
1단계 위의
parentData: 부모의 데이터
child자식의
1단계 아래의
childValue: 자식의 값
sibling형재의
같은 단계의
nextSibling: 다음 형제 요소
ancestor선조의
2단계 이상 위의
isAncestor: 선조인지
descendant자손의
2단계 이상 아래의
isDescendant: 자손인지
related관계 있는relatedTarget: 관계 있는 대상
<div class="parent">부모 요소
  <div class="myself">자신
    <div class="child">자식</div>
    <div class="child">자식</div>
  </div>
</div>

위와 같은 구조의 HTML을 JavaScript로 얻을 경우, 구조에 주목하면 parentchildren과 같이 네이밍합니다.

const myself = document.querySelector('.myself');

// 1계층 위의 요소
const parent = myself.closest('.parent');

// 1계층 아래의 요소
const children = myself.querySelectorAll('.child');

7. 복수형으로 배열 표현

배열은 복수형으로 표현할 수 있습니다. ◯◯List처럼 표현할 수도 있지만 어디까지나 취향의 문제입니다. React나 Vue에서는 복수형으로 표현하는 경우가 많았습니다.

// 화면에서 버튼을 구한다(복수)
const buttons = document.querySelectorAll('.submitButton');

// forEach로 하나씩 추출한 것은 단수형으로
buttons.forEach((button) => {
  button.addEventListener('click', () =>{
    // 버튼 클릭이벤트 내용
  })
})

8. 다른 형태로 변형

숫자를 문자열으료 변환할 때 사용하느 toString() 메서드처럼 어떤 것을 변환할 때에는 to◯◯처럼 네이밍 합니다.

전화번호에 하이픈을 추가하여 변환하는 함수는 toHyphenatedCode라고 네이밍할 수 있습니다

/**
 * 전화번호(11개의 문자열)을 "-"으로 구분된 문자열로 변환합니다.
 * @param code {string} 전화번호(11개의 문자열)
 */
const toHyphenatedCode = (code) => {
  return code.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
}

9. 초기화, 초기값 세팅

initinitialize의 약어로 프로그래밍을 하고 있으면 자주 만나는 표현입니다.

설명예시
init초기화initData: 데이터 초기화
initial초기의initialContext: 초기 컨텍스트

아래의 SalesPerson클래스에는 initTask()라는 메소드가 있으며 이를 호출하면 task프로퍼티에 초기값이 설정됩니다.

class SalesPerson {
  constructor(familyName, givenName) {
    this.familyName = familyName;
    this.givenName = givenName;
    this.initTask();
  }
  
  // Task 초기화
  initTask() {
    this.task = ['자기소개 하기', '상품의 설명 진행']
  }
}

맺음말

초급편에서는 알기 쉬운 네이밍의 중요성이나 네이밍 규칙, 실제로 자주 사용되고 있는 테크닉을 소개하였습니다. 다음 상급편에서는 더욱 복잡하 처리를 하고 싶을 때에 유용한 테크닉을 소개하겠습니다.

profile
늦깎이 프론트 개발자

0개의 댓글