이 글은 ICS MEDIA에 등록된 定番のコード規約とライブラリから学ぶJavaScriptの命名テクニック(初級編)을 번역한 내용입니다
프로그래밍을 할 때 변수나 함수의 네이밍에 고민하십니까?
애써 시간을 들여서 생각한 변수명이나 함수명을 다음에 돌아보면 어떤 것 처리한 것인지 알 수 없을 때도 있고, 다른 개발자가 코딩한 것을 이해하는 데 시간이 걸린 경험은 누구에게나 있을 것입니다.
일반적으로 프로그래머는 코딩하는 시간보다 분석하는 시간이 더 길다고 합니다. 쉽게 이해할 수 없는 네이밍은 소스를 분석하는 시간이 길어진다거나, 버그가 발생하는 원인이 되기도 합니다.
이 글에서는 Google이나 Airbnb와 같은 기업이 채용하고 있는 스타일 가이드나 전 세계에서 쓰여지는 JavaScript 라이브러리인 React와 Vue.js 소스를 조사하면서 찾아낸 알기 쉬운 네이밍 테크닉을 초급편과 상급편으로 나눠 소개하려 합니다.
초급편에서는 실제로 일하면서 자주 봤거나 스스로도 자주 사용하는 등의 바로 사용할 수 있는 테크닉을 모아봤습니다.
우선 이해하기 어려운 네이밍에는 어떤 것들이 있는지 알아보겠습니다.
Node.js 스타일 가이드는 말합니다
한 글자의 변수나 일반적이지 않은 약어는 피해야 합니다.
예를 들면 이러한 소스의 경우 nm
이라는 변수는 name
의 약어인가요? 아니면 number
의 약어인가요?
const result = nm1 + nm2;
result
에는 문자열의 결합 결과인가요? 덧셈의 결과인가요?
간단한 소스라도 일반적이지 않은 약어를 사용하면 어떤 처리를 하는 지 이해하기 어려워집니다.
Airbnb의 스타일가이드에는 네이밍에 대해 다음과 같이 말합니다.
이름만으로 의도를 읽어낼 수 있도록 네이밍
상품 정보를 가져와 세금을 포함한 계산을 하는 프로그래밍을 해 보겠습니다.
const TAX = 0.1;
const item = await fetchItemById(itemId);
const displayPrice = item.price + (item.price * TAX);
fetchItemById()
는 함수명으로 추측하면 서버에서 아이템 정보를 가져와서 리턴하는 함수입니다.
일단 아무런 문제도 없어보이는 소스이지만, 만약 이 fetchItemById()
안에서 이미 계산이 이루어져 있어서 item.price
에 세금을 포함한 가격이 있다고 한다면 어떻게 될까요?
결과적으로 displayPrice
에는 엉뚱한 금액이 전달되어 이 소스가 버그의 원인이 되어버립니다.
이름만으로 예상 가능한 것 이상의 기능이 이루어지면 생각지도 못한 버그를 초래합니다.
Google JavaScript Style Guide에서는 변수는 명사형으로 메소드는 동사형으로 작성하도록 규정하고 있습니다.
슬라이더를 사용한 웹 사이트의 소스를 분석한다고 가정하고 이 함수는 어떤 기능을 하는 지 상상할 수 있습니까?
slider();
이 함수명은 동사가 없기 때문에 슬라이더를 어떻게 처리하는지를 함수명 만으로든 판단할 수 없습니다.
이러한 함수가 여러 개 있는 경우 어디서 무엇을 처리하고 있는 지 알수 없으며 원하는 소스를 찾아내는 대 시간이 오래 걸리게 됩니다.
우선 JavaScript에서 공통적으로 인식하고 있는 규칙을 확인해 봅시다.
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;
공통적인 규칙이 확인되었으니, 이해하기 쉬운 네이밍 테크닉을 살펴보도록 하겠습니다.
동사를 변화시킴으로써 간결하게 객체의 상태를 표현할 수 있습니다.
설명 | 예시 | |
---|---|---|
과거분사 | ~된 상태 ~함 | selected: 선택된 warned: 경고함 |
현재분사 | ~하고 있는 상태 ~중 | waiting: 대기중 pending: 보류 중 |
// 버튼을 선택할 때의 이벤트
numberButtons.forEach((button) => {
button.addEventListener('click', () => {
// 선택된 숫자로 갱신
const selectedNumber = button.innerText;
favoriteNumberText.innerText = selectedNumber;
});
});
"~이 가능한가" 나 "의 상태인가"는 아래와같이 접속사, 접미사를 붙여서 표한할 수 있습니다.
이러한 단어가 붙은 변수나 함수는 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('당신이 좋아하는 숫자는 양수입니다.');
}
커서나 페이징 등 순번이 있는 것(명사)에 대해서 "이전의", "다음의" 등 표현하고 싶을 때에 사용할 수 있습니다.
설명 | 예시 | |
---|---|---|
current | 현재의 | currentValue: 현재 값 currentpage: 현재 페이지 |
previous | 이전의 | previousValue: 이전 값 previousPage: 이전 페이지 |
next | 다음의 | nextValue: 다음 값 nextPage: 다음 페이지 |
const goToPreviousPage = () => {
// 이전 페이지를 이동하기 위한 처리
}
const goToNextPage = () => {
// 다음 페이지를 이동하기 위한 처리
}
before
나 after
는 "~의 앞에서 ~한다", "~의 뒤에서 ~한다"와 같이 액션이나 시간축에 대해 전후를 표현하고 싶을 경우에 사용하는 표현입니다.
설명 | 예시 | |
---|---|---|
before | ~의 앞에 | beforeCreate: 생성하기 전에 insertBefore: ~앞에 삽입한다 |
after | ~의 뒤에 | afterLeave: 떠난 뒤에 insertAfter: ~의 뒤에 삽입한다 |
/**
* 처리를 그만두기 전에 경고
*/
const warnBeforeStop = () => {
window.confirm('정말로 그만두시게요?');
};
뉴스나 블로그 등, 시간 순으로 되어 있는 것을 표현할 때에 편리한 표현입니다. last
나 first
는 배열의 첫 번째나 마지막을 추출할 때등에서도 사용할 수 있습니다.
설명 | 예시 | |
---|---|---|
latest | 최신의 | latestEntry: 최신 엔트리 |
last | 최후의 | ㅣlastestComponent: 마지막 컴포넌트 |
first | 최초의 | firstComponent: 첫 번째 컴포넌트 |
// 배열의 마지막 요소
const last = entryList[entryList.length - 1];
계층 구조로 되어 있는 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로 얻을 경우, 구조에 주목하면 parent
나 children
과 같이 네이밍합니다.
const myself = document.querySelector('.myself');
// 1계층 위의 요소
const parent = myself.closest('.parent');
// 1계층 아래의 요소
const children = myself.querySelectorAll('.child');
배열은 복수형으로 표현할 수 있습니다. ◯◯List
처럼 표현할 수도 있지만 어디까지나 취향의 문제입니다. React나 Vue에서는 복수형으로 표현하는 경우가 많았습니다.
// 화면에서 버튼을 구한다(복수)
const buttons = document.querySelectorAll('.submitButton');
// forEach로 하나씩 추출한 것은 단수형으로
buttons.forEach((button) => {
button.addEventListener('click', () =>{
// 버튼 클릭이벤트 내용
})
})
숫자를 문자열으료 변환할 때 사용하느 toString()
메서드처럼 어떤 것을 변환할 때에는 to◯◯
처럼 네이밍 합니다.
전화번호에 하이픈을 추가하여 변환하는 함수는 toHyphenatedCode
라고 네이밍할 수 있습니다
/**
* 전화번호(11개의 문자열)을 "-"으로 구분된 문자열로 변환합니다.
* @param code {string} 전화번호(11개의 문자열)
*/
const toHyphenatedCode = (code) => {
return code.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
}
init
는 initialize
의 약어로 프로그래밍을 하고 있으면 자주 만나는 표현입니다.
설명 | 예시 | |
---|---|---|
init | 초기화 | initData: 데이터 초기화 |
initial | 초기의 | initialContext: 초기 컨텍스트 |
아래의 SalesPerson
클래스에는 initTask()
라는 메소드가 있으며 이를 호출하면 task
프로퍼티에 초기값이 설정됩니다.
class SalesPerson {
constructor(familyName, givenName) {
this.familyName = familyName;
this.givenName = givenName;
this.initTask();
}
// Task 초기화
initTask() {
this.task = ['자기소개 하기', '상품의 설명 진행']
}
}
초급편에서는 알기 쉬운 네이밍의 중요성이나 네이밍 규칙, 실제로 자주 사용되고 있는 테크닉을 소개하였습니다. 다음 상급편에서는 더욱 복잡하 처리를 하고 싶을 때에 유용한 테크닉을 소개하겠습니다.