네이밍, 프로그래머가 가장 힘들어하는 그것

jjanmo·2021년 2월 4일
3
post-thumbnail

위의 설문 조사를 보고 수긍할 수 있나요? 코딩을 시작할 때, 나의 함수, 나의 변수를 위해서 항상, 적어도 몇 분 많게는 몇 시간을 고민해본 적이 있다면 이 조사를 보고 박수치면 소리 지를 수 있을 것이다, '나만 그런게 아니였어!!!' 라고.

아래 글은 Naming cheatsheet 을 읽고 재가공한 글입니다. 자바스크립트 네이밍에 도움이 되는 몇가지 팁에 대해서 설명합니다.

영어를 사용한다.

이건 우리나라 사람이라면 누구나 당연하게 생각할 수 있다. 왜냐하면 코딩을 배우면서, 혹은 공개된 프로젝트를 보거나 진행하면서 일반적으로 한글로된 변수명나 함수명을 본적이 없을 것이기때 문이다. 그런데 실제로 오픈소스로 된 프로젝트를 보다보면 영어인데 모르는 단어를 본적이 있다. 이게 무슨의미인지 찾아보면 영어가 아니라 스페인어 였던 적이 있다. 그 때는 그려려니 했지만, 사실 이건 잘못된 네이밍이라고 볼 수 있다.

English is the dominant language in programming

이상하게 들릴지 모르겠지만, 영어는 프로그래밍 영역에서 주도적인, 독보적인 언어이다. 그렇기 때문에 영어의 syntax에 맞게 사용하는 것이 맞다. 즉, 영어를 잘한다기 보다, 문맥에 맞게 사용하는 방법을 알야할 것이다.

camelCase와 snake_case

카멜케이스와 스네이크케이스는 네이밍을 하는 어떻게 할지를 나타내는 표현법이다. 카멜(camel)은 낙타라는 의미인데, 네이밍이 마치 낙타 등처럼 구불 구불하다고 하여 이름이 붙여졌다. 단어를 붙여쓰되, 붙여진 단어의 첫글자만을 대문자로 적는 방법을 말한다. 스네이크케이스는 언더바를 사용하는 방법인데, 각 단어 사이에 언더바를 넣는 방법이다. 이 역시 언더바가 뱀과 같다고 하여 이렇게 불린다.

자바스크립트에선 일반적으로 카멜케이스를 사용한다. 반면 파이썬은 일반적으로 스네이크케이스를 사용한다. 각각의 언어마다 주로 사용하는 방법이 있기에 내가 공부하는, 사용하는 언어가 어떤 케이스 컨벤션을 사용하는지 코딩하기 전에 알아두는 것이 기본이라 생각한다. 그리고 가장 중요한 것은 어떤 컨벤션을 사용하든 일관성있게 사용해야 한다는 점이다

//bad
function getBoards = () => {
 	//... 
}

const page_count = 10;
const live_exhibitions = getBoards();

S-I-D

A name must be short, intuitive and descriptive

처음에 짧고 직관적이고 설명적인 직관적인데 설명적이게? 라는 말이 굉장히 모순되었다는 생각이 들었다. 각각이 무슨 의미인지 살펴보자.

  • Short : 너무 길지 않고 기억하기 쉬운 정도로 짧아야한다.
  • Intuitive : 자연스럽게 읽히고 일반적으로 사용되어야한다.
  • Descriptive : (함수나 변수가) 무엇을 하는지/무엇을 의미하는지를 효율적으로 설명할 수 있어야 한다.
/* Bad */
const a = 5 // 너무 추상적이다!!
const isPaginatable = a > 10 // Paginatable : 자연스럽지 못한 단어
const shouldPaginatize = a > 10 // Paginatize : 부적절한 동사화

/* Good */
const postCount = 5
const hasPagination = postCount > 10
const shouldPaginate = postCount > 10 

Paginatable, Paginatize은 영어적(?)으로 부적절하고 자연스럽지 못한 동사라고 한다.

const initialState = {
    normalPrice: 0, 
    sellerLiveSellingPrice: 0,
    sellerFeedSellingPrice: 0,
    sellerLiveSupplyPrice: 0,
    feedSupplyPrice: 0,
    liveCommissionRate: 0, 
    feedCommissionRate: 0, 
    sellerLivePrice: 0, 
    sellerFeedPrice: 0,
    sellerLiveCommissionRate: 0,
    sellerFeedCommissionRate: 0,
}

위는 실제로 이번에 라이브 기획전을 만들 때, 사용했던 변수이다. 짧고 직관적이고 설명적인가? 사실 한글을 영어로 만들때, 이와 딱 맞는 단어를 찾는게 쉽지 않기에 보통 직접적인 단어로 번역하여 변수로 사용하는 경우가 많다. 그렇게 하다보면 너무 직접적이라 의미자체가 왜곡되는 경우가 있는 것 같다.

Avoid contractions

축약하지 마라!

처음에 코딩을 할 때, 변수가 너무 길다면 약간 축약해서 사용하였었다. 너무 긴 변수를 좋지못하다는 말을 들었기때문이다. 그런데 그 축약자체가 의미를 퇴색시키기때문에 될 수 있으면 축약을 하지않는 범위에서 사용하는 것을 추천한다. 그리고 우리가 길다고 생각하는 범위가 실제로는 길지 않을 수도 있다는 😅

let isPvC = true;
let curTurn = 'player1'

위 변수는 내가 틱택톡 게임을 만들 때, 사용했던 변수이다. 딱 보고 척 알아볼 수 있을까? 게임이라는 장르를 몰랐다면, PvC라고 하면 뭔지 모를수도 있다. 그렇기 때문에 isPlayerVersusComputer 라고 적어주는 편이 나을 수도 있다. 이렇게 적는게 생각보다 길지는 않다. 또 curTurn 역시 풀네임으로 currentTurn 이라고 적는 것이 좀 더 가독성 있다는 생각이 든다.

Avoid context duplication

문맥을 중복시키지 마라. 이렇게 한국말로 번역하면 좀 이해하기 쉽지않은 것 같다. 코드로 보자.

class MenuItem {
 //bad
  handleMenuItemClick = (event) => { ... }

  //good
  handleClick = (event) => { ... }
}

위 코드를 보면 MenuItem 이라는 문맥(스코프)가 중복된다. 즉 이벤트에 접근하고자 할 때, 각각을 이런식으로 접근한다. menuItem.handleMenuItemClickmenuItem.handleClick 이렇게 적으면 이제 차이를 좀 더 이해할 수 있을 것이다.

const todo = {
    id : 1
  	todo : value, //할 일에 대한 내용 : todo -> contents
  	createdAt : Date.now()
}

ToDoApp을 만들 때 이런 고민을 해 본적이 있을까. 할일에 대한 객체를 만들어서 사용할 때 그 안에 여러가지 속성을 넣을 수 있다. 이 때 할 일의 내용을 나도 자연스럽게 todo로 네이밍을 하여 나중에 그 내용에 접근할 때, todo.todo가 되는 아주 기이한 상황(?)을 경험할 수 있었다. 이런 상황을 피하라는 말 같다 😅

Reflect the expected result

(변수에게) 기대되는 값을 반영하여 네이밍하자. 와닿지않는 말 같다. 다시 코드로 바라보자.

/* Bad */
const isEnabled = itemCount > 3
return <Button disabled={!isEnabled} />

/* Good */
const isDisabled = itemCount <= 3
return <Button disabled={isDisabled} />

enable은 할 수 있는, 허용 하는 의 의미를 갖고 있다. 그렇다면 isEnabled사용 여부에 대한 불리언 값을 의미하는 변수라고 해석할 수 있다. 그런데 이 값을 가지고 disabled에 사용하기 때문에 문맥적으로 바로 와닿지 않을 수 있다. (코드상 잘못된 것은 아니다.) 버튼 태그에서 속성(disabled)이 어떤 경우에 비활성화되는가 에 대해서 묻고 있기 때문에 일관되게 비활성화되는 경우의 네이밍을 만들어주는 것이 더 직관적이라고 할 수 있다. 요약하면, disabled(== the expected result) 로 묻고 있으니 disabled에 해당하는 값을 알려주면 된다는 것이다, 굳이 enabled로 돌려 말하지 말고.

함수 네이밍의 유용한 패턴들

A/HC/LC Pattern

NamePrefix(접두사)Action (A)High context (HC)Low context (LC)
getUsergetUser
getUserMessagesgetUserMessages
handleClickOutsidehandleClickOutside
shouldDisplayMessageshouldDisplayMessage

위 패턴을 우리가 아무런 의심없이 사용하는 부분이라고 생각한다. 우선 이렇게 구조화시킬 수 있는다는 것이 신기했다. 더 놀라운 것은 우리는 이미 이런 구조화된 것을 아무렇지도 않게 사용하고 있다는 점이였다. 그 말은 이러한 구조화가 굉장히 자연스러운 것이고 네이밍이라는 것은 자연스러움을 기저에 깔고 움직이는 패턴이라는 생각이 든다.

이러한 패턴의 중요성은 영어이기 때문에 우리는 덜 느끼는 것이라고 생각한다. 이 글에서 예를 들어주는 것이 있다. shouldUpdateComponent vs shouldComponentUpdate 이다. 각각이 의미하는 것이 무엇인지 생각해보자. 같은 단어의 조합이지만 두 함수는 역할이 달라진다. shouldUpdateComponent는 "곧 컴퍼넌트를 업데이트를 할 거야"라는 함수이다. 그렇기 때문에 이 함수 안에 들어가는 내용은 컴퍼넌트를 어떻게 업데이트 할 것인지에 대한 내용일 것이다. 반면 shouldComponentUpdate는 "컴퍼넌트가 업데이트를 할거야"라는 함수이다. 이 함수의 내용은 컴퍼넌트가 업데이트를 자체적으로 하면 내가 무엇을 할건지에 대한 코드가 들어갈 수 있다. 즉, 이러한 컨텍스트, 패턴으로 의미가 달라지기 때문에 내가 만드는 함수의 의도에 맞게 정확하게 표현하는 것이 중요할 것이다.

Actions

가장 중요한 부분! 함수는 Doing이다, Action이다, 이러한 말은 많이 들어봤을 것이다. 각각의 액션을 어떤 식으로 표현하는지 알아보자. 아마 많이 이러한 방식으로 사용하고 있을 것이다.

get

정보에 접근할 때(정보를 얻을 때), get이라고 표현한다. 일반적으로 getter 라고 한다.

function getBoardCount(){
  return boards.length;
}

set

정보를 수정(변경)할 때, set이라고 표현한다. 일반적으로 setter 라고 한다.

reset

정보를 초기값을 돌리는 경우 reset이라고 표현한다.

const initializeAll = () => { // initializeAll -> resetSelectedData
    setSelectedProducts([]);
    setSelectedImages([]);
 };

위에 처럼 initialize 라는 말을 통해서 초기화를 시키곤 했다. 그런데 reset이라는 단어가 일반적이고 all이라는 단어보다 selectedData라는 말로 좀 더 직관적이고 구체적으로 바꿀수 있을 것 같다.

fetch

비동기적으로 특정 데이터를 요청할 때는 fetch라는 단어로 표현한다.

function fetchPosts(postCount) {
  return fetch('https://api.dev/posts', {...})
}

fetch vs request

remove vs delete

이 두가지는 약간 헷갈린다. remove의 경우엔 특정 영역에서 제거하는 경우를 말하고 delete는 완전히 제거하는 것을 말한다. 코드로 살펴보자.

function removeFilter(filterName, filters) {
  return filters.filter((name) => name !== filterName)
}

위의 코드처럼 필터링을 통해서 나오는 것, 즉 데이터베이스에서 완전히 제거되는 것이 아니라 특정 영역에 보여주기위해서 뭔가를 제거하고 보여주는 기능을 하는 함수를 만들 때, remove를 사용한다.

function deletePost(id) {
  return database.find({ id }).delete()
}

데이터베이스에서(전체 범위에서) 완전히 삭제될 때 delete를 사용한다.

compose

compose는 짓다, 조립하다 라는 의미이다. 즉, 이미 존재하는 것으로 부터 재조합하거나 새로운 정보를 만들 때 compose라는 단어를 사용한다.

function composePageUrl(pageName, pageId) {
  return `${pageName.toLowerCase()}-${pageId}`
}

handle

특정 행동을 핸들링한다는 의미에서 사용한다. 종종 콜백함수에서 사용된다. 이 이름은 이벤트 핸들러를 만들때 주로 사용한다. handleClick, handleSubmit, handleBlur 등으로 각각은 클릭, 서브밋(폼제출), 블러(포커스아웃) 이벤트를 핸들링하는 함수이고 이거 자체가 콜백함수로 사용된다.

function handleLinkClick() {
  console.log('Clicked a link!')
}

link.addEventListener('click', handleLinkClick)

Prefixes

prefiex는 접두사란 의미로서 보통 함수 이름보다는 변수 이름 앞에 많이 사용한다.

is

보통 불리언값으로 많이 사용한다. 특정 상태인지 아닌지를 나타낼때 사용한다.

const isBlue = color === 'blue'; 
const isPresent = true; 

has

보통 불리언값으로 많이 사용한다. 특정 상태나 값을 가지고 있는지(소유하는지) 여부를 나타낼 때 사용한다.

// bad
const areProductsPresent = productsCount > 0; 

// good 
const hasProducts = productsCount > 0;

areProductsPresent 네이밍의 의미는 상품의 존재 여부를 물어보는데, 그 값이 개수를 가지고 표현하기 때문에 몇 개인지 소유 여부를 나타내는 hasProducts 가 더 맞는 표현이다.

should

Reflects a positive conditional statement (usually boolean) coupled with a certain action.

사실 뭐라 설명하는게 어려워서 원문 그대로를 첨부한다.

function shouldUpdateUrl(url, expectedUrl) {
  return url !== expectedUrl
}

min/max

최대/최소를 나타낼 때 사용한다.

prev/next

이전 값/다음 값을 나타낼 때 사용한다.

Singular and Plurals

단수와 복수의 정확히 사용하자.

영어이기 때문에 이 부분을 많이 놓치는 것 같다. 예를 들어서 data라고 하면 단수일까 복수일까? data는 자체가 복수이다. 그렇기 때문에 datas가 아니라 data라고만 써야한다. 또 array, collection, list의 네이밍에는 보통 복수로 사용하는 것이 맞다. 그 구조 안에는 특정 정보의 집합이 들어있을 확률이 높기 때문이다.

사실 이 부분은 영어 문법(?)적인 요소들이기에 사용하는 단어에 대해서 찾아보고 익숙해져야 할 것 같다.

const todo = [ "Study JS", "Study Docker", "Study Python"]; // todo -> todos

사실 네이밍은 답이 없는 것 같다. 지금까지 이야기한 일반론(?)을 토대로 실무 환경, 협업하는 팀원들과의 커뮤니케이션을 통한 합의로서 만들어가는 것이 진정한 네이밍이 아닐까 싶다. 그리고 가장 필요한 것은 내가 사용하는 변수 이름, 함수 이름이 다른 사람이 봤을 때, 이해하기 쉬운지에 대한 스스로의 피드백이라고 생각한다. 더불어 이러한 습관을 지속적을 갖추면서 코딩을 하는 것! 덤으로 영어적인 syntax를 이해하면서 작성하는 것! 음... 말하다보니 너무 많군 🤯

그렇다면 '좋은 네이밍을 하는 습관을 갖자'로 마무리하겠다. 오늘도 Happy Coding 🚀

부족한 글 읽어주셔서 감사합니다. 언제나 내용에 대한 피드백은 환영입니다. 앞으로 조금이라도 도움이 될 수 있는 글을 쓰기 위해서 노력하겠습니다.

profile
눈길을 걸어갈 때 어지럽게 걷지 말기를.

2개의 댓글

comment-user-thumbnail
2021년 2월 5일

좋은 글 잘 보고 갑니다.

1개의 답글