2. 변수명과 함수명 짓기

"클린 코드 정독 시리즈 2"

Posted by Dev X on August 3, 2020

관을 열고 대화하는 사람 둘

뭐라고 쓰여있는 건가?
' 좋은 변수명 생각날 때까지 숨 참는다 '라고 적혀있습니다.

이름을 잘 지어야 하는 이유

일단 내가 함수 명을 잘못 지었던 사례를 회상해본다.. (부끄럽지만 변수명을 잘 지어야겠다는 깨달음은 후회 뒤에나 오기 때문에 누군가는 나의 후회를 간접 체험하는 걸로 아까운 시간을 쓰지 않았으면 좋겠다)

단순한 게시판 기능을 구현하고 있었는데 게시글 목록을 조회하는 함수가 필요했다. 나는 매우 자연스럽게 그 함수의 이름을 getListItems로 지었다. 그리고 게시판엔 당연히 게시글 한 개를 조회하는 기능도 있었다. 나는 또 자연스럽게 그 함수 이름을 getListItem으로 지었다.

‘함수명은 함수가 하는 일을 동사형으로 지어야 한다’가 내가 함수 이름을 짓는 규칙이었고 그에 따르면 합리적인 이름이었다.

문제는 내가 코딩을 하면 할수록 getListItemsgetListItem이 난무하며 이게 복수인지 단수인지 눈을 부릅뜨고 쳐다봐야만 했다. 빠른 타자를 위해 IDE 자동완성 기능을 쓰다 보면 관성적으로 화살표를 두 번 누른 것으로 오류를 일으키는 멍청한 실수를 한 적도 있었다.

그 프로젝트엔 getCustomerInfogetCustomerData도 있었다. 전자는 마이페이지용 회원 정보를 불러오는 함수였고 오른쪽은 운영자용 회원 기록을 조회하는 함수였다. 코드를 짰던 나는 그 차이를 명확하게 알았지만 그렇지 않았던 팀원은 노트북을 들고 나를 찾아왔다. 나쁜 이름 짓기 대회에 나가면 1등은 할 수 있을 프로젝트였다..

슬프게도 아직 나는 어디 가서 변수명을 잘 짓는다고 말하진 못한다.. 그래도 클린코드의 이름 짓기 파트에서 내 뼈를 때렸던 지적들을 곱씹으며 이름을 잘지으려고 노력하고 있다. 그리고 어딘가의 나같은 개발자 지망생이 이 글을 읽고 도움을 받았으면 하기에 클린코드의 변수명 짓기 파트를 js 기준 순한 맛으로 정리해본다.

(하지만 이 글을 읽고 만족하기보단 클린코드 책을 읽어보는 걸 추천한다!!!)

1. 의도를 명확히 밝혀라

1
2
3
4
function ckeck(num) {
    let result = num / 1440 >= 1;
    return result;
}

위의 함수는 짧은 덕에 전달받은 인자가 1440보다 크거나 같은지를 확인하는 기능을 한다는 것은 알 수 있다. 그런데… 그래서 왜 그런 일을 하는 건데?라는 질문엔 대답하기 힘들 것이다. 의도에 맞춰 변수명을 수정해보겠다.

1
2
3
4
function ckeck(minutes) {
    let isOverday = minutes / 1440 >= 1;
    return isOverday;
}

이렇게 적는다면 아~ 이 함수는 분을 인자로 받아 하루가 넘는지 확인하는 함수구나~라는 것을 알 수 있다. 한 번 더 맥락을 읽기 쉽게 수정하면

1
2
3
4
5
function ckeck(minutes) {
    let minutesOfDay = 1440;
    let isOverday = minutes % minutesOfDay >= 1;
    return result;
}

아~ 이 이 함수는 분을 인자로 받아 하루가 넘는지 확인하는 함수인데 1440은 24시간을 분으로 나눈 것이구나~까지 알 수 있다.

코드의 함축성은 맥락을 알기 어렵고 코드를 읽는 사람이 중요한 정보를 알고 있다는 걸 가정한다. 짧은 코드는 좋다 하지만 짧은 코드를 쓰기 위해 나만 읽을 수 있는 코드를 짜고 있진 않은지 고민해볼 필요가 있다.

2. 그릇된 정보를 피하자

위에서 나는 게시판 글을 조회하는 함수를 getListItems라고 지었다. 하지만 List는 프로그래머들 사이에선 자료구조의 List로 통용되는 단어이기도 하다. getListItems는 getBoardItems라고 지었다면 오해의 소지를 피할 수 있었을 것이다. Items라는 단어의 뜻도 포괄적이다. getBoardPosts라는 이름은 어떨까? 변수명이 뜻하는 바가 더 명확해졌다.

스코프가 좁은 지역변수를 지을 때는 알파벳을 사용하는 사람도 왕왕 있다.

1
2
3
4
5
6
let a = l;
if (O === l) {
    a = 'O1';
} else {
    l = '0l';
}

이의 이승 같은 코드이다… 물론 IDE는 색으로 정수, 스트링, 변수명을 구분해주고 가독성을 고려한 폰트(D2Coding 만만세)도 많이 나오지만 굳이 이런 문제를 만들 필요가 있을까?

3. 의미 있게 구분하자

불용어라는 말이 있다. 영어로는 stopword라고 하는데 인터넷 검색 시 의미를 갖지 못하는 ‘조사’나 ‘접속사’ 등을 말한다. 변수 명에도 불용어로 통하는 단어들이 있다. DataInfo가 그렇다. DataInfo가 정보라는 의미를 갖고 있다는 것은 알지만 CustomerData와 CustomerInfo가 무엇이 다른지, 그 정확한 개념은 구분되지 않는다. 굳이 Data와 Info를 비교하지 않고 Customer와 CustomerData만 보아도 차이가 명확하지 않다. 다른 예로는 Customer와 CutomerObject 같은 것도 있다.

접두사나 접속사를 ‘절대’ 사용하지 말라는 뜻은 아니다!! 접두사의 의미를 이름의 개념에 알맞게 사용하자는 말이다. 누군가 변수명을 theCustomer와 aCustomer라고 짓는다면 전역 변수가 aCustomer이고 지역변수가 theCustomer겠구나 추측할 수도 있다.

의미 없이 변수명 짓기엔 더 자주 접할 수 있는 사례도 있다.

1
2
3
4
5
6
7
8
9
function char(a1) {
    let a2 = a1[0];

    for (let i = 1; i < a1.length; i++) {
        a2 += '-' + a1[i];
    }

    return a2;
}

나 또한 간단한 알고리즘 공부를 할 때 종종 이런 변수명을 짓곤 했다. a1, a2가 무엇을 의미하는지 아직은 기억하고 있으니까, 이 이름이 당장은 문제를 일으키진 않을 것이다. 하지만 일주일이 지나서 다시 이 코드를 본다면? 그때도 a가 어떤 의미를 가진 변수인지, 이 함수가 무엇을 하는 함수인지 기억하고 있을까? 기억력이 좋은 사람이라면 가능하겠지만 솔직히 나는 기억 못 하겠다. 그래서 아래와 같이 코드를 수정했다.

1
2
3
4
5
6
7
8
9
function connectCharWithDash(originalStr) {
    let newStr = originalStr[0];

    for (let i = 1; i < originalStr.length; i++) {
        newStr += '-' + originalStr[i];
    }

    return newStr;
}

4. 발음하기 쉬운 이름을 짓자

페어 코딩을 하다 보면 자주 마주치게 되는 문제이다. 발음하기 어려운 이름은 토론하기도 어렵다.

간장공장 공장장~을 떠올려보면 이해하기 쉬울 것이다. darkDucksLuck 이라는 변수가 있다면? 눈으로 보기엔 의미가 대강 들어오겠지만 소리 내 말하기는 쉽지 않다. 이름은 커뮤니케이션에 큰 영향을 끼친다.

(+ 책에는 없었던 내용이지만 나는 발음하기에 비슷한 이름도 지양하고 있다. 예를 들면 items와 item은 제대로 발음을 해도 잘못 알아듣기 십상이다. 그래도 이 이름을 사용하려 한다면 items의 s를 발음할 때마다 혀에 힘이 들어가게 될 것이다. items보단 groupOfItem이라는 이름을 쓰려고 노력 중이다.)

5. 검색하기 쉬운 이름을 사용하자

1
2
3
4
5
6
7
8
9
function char(a1) {
    let a2 = a1[0];

    for (let i = 1; i < a1.length; i++) {
        a2 += '-' + a1[i];
    }

    return a2;
}

이건 3번의 ‘의미 있게 구분하자’에서 예시로 들었던 나쁜 코드였다.

1
2
3
4
5
6
7
8
9
function connectCharWithDash(originalStr) {
    let newStr = originalStr[0];

    for (let i = 1; i < originalStr.length; i++) {
        newStr += '-' + originalStr[i];
    }

    return newStr;
}

이 코드를 이렇게 수정하는데 3초가 걸렸다.
ctrl + f로 바꾸고 싶은 변수명을 찾고 새 변수명으로 replace 버튼을 누른 것으로 순식간이었다.

1
2
3
4
5
6
7
8
9
function char(aa) {
    let a = aa[0];

    for (let i = 1; i < aa.length; i++) {
        a += '-' + aa[i];
    }

    return a2;
}

하지만 만약 이름을 이렇게 지었었다면..? a를 검색해도 aa의 변수명에서 멈췄을 것이다.

사실 이 문제는 최신 IDE로 충분히 해결이 가능하다. 내가 사용 중인 VS Code에서는 [ctrl + 우클릭]으로 변수명이 쓰인 곳을 이동할 수 있고 변경을 원하는 변수명을 클릭한 후 [F2]를 누르면 같은 이름의 변수명을 알아서 한 번에 변경해준다.

그래도 모든 사람이 최신 IDE를 사용하진 않을 것이다. 다들 선호하는 IDE가 제 각각이고 세상 어딘가엔 메모장에 코딩하는 사람이 분명 있을 것이다. 혹시 모를 상황을 방지해 검색이 어려운 이름은 지양하는 편이 좋겠다.

6. 내 기억력을 자랑하지 말자

전편(1. 어떤 개발자가 돼야 할까?)에서 내가 지었던 끔찍한 이름 중 적절한 예시가 있다. 바로 CDA이다. CDA는 CustomerDataAdapter의 약자로 서버에서 받아오는 고객 정보 데이터를 프런트에서 쓰기 좋은 양식으로 맞춰주는 함수였다. 긴 이름이 코드를 길게 만드니 짧게 쓰는 게 좋겠다는 취지의 이름이었고, 한 달 뒤에 그 코드를 다시 열어보았을 때 함수 내 constructor를 다 읽고 나서야 그 CDA의 풀 네임을 다시 떠올리게 되었다. 그것도 CDA의 A가 Adapter라는 것만 기억해내고 D가 Data인지 Description인지 아니면 또 다른 무엇인지는 api 명세까지 확인해봐야 확실해졌을 것이다.

우리는 종종 효율을 위해 약어를 사용할 때가 있다. for문의 i가 index라는 것은 모두가 이미 알고 있기 때문에 i로 적어도 문제가 없다. prop이 property라는 것도 str이 string라는 것도 전통적인 사용 예가 있기 때문에 가능한 사례이다. 하지만 나만 이해할 수 있는 약어 이름은 되도록 사용을 지양하자.

7. 클래스 이름

클래스명과 객체명은 명사명사구가 적합하다. 동사는 사용하지 않는다. 좋은 예로는 Customer, WikiPage, Account, AddressParser 등이 있다. 나쁜 예로는 Manager, Processor, Data, Info 등 의미가 불분명한 단어들이 있다. 고객 정보를 관리하는 클래스가 있다면 목적을 정확히 해 CustomerManager라고 이름 짓는 것이 더 좋겠다.

8. 함수 이름

함수명은 동사동사구가 적합하다. 좋은 예로는 postPayment, deletePage, save 등이 있다. 접근자는 get , 변경자는 set, 조건자는 is를 접두사로 붙인다.

JavaScript - 접근자 프로퍼티 (getter, setter)
js의 getter, setter에 대해 참고하면 좋은 글
(MDN에도 getter, setter 함수가 정리되어 있지만 이 글이 가장 이해하기 쉽게 정리된 것 같다)

9. 한 개념에 한 단어를 사용하자

메서드 이름은 독자적이고 일관적이어야 한다. controller, manager, driver 이 세 단어는 제어와 관리 용도의 함수 명에 자주 들어가는 단어이다. controller와 manager 같은 여러 단어를 한 의미로 혼용해 사용하는 것은 코드의 맥락을 이해하는데 혼동을 줄 수 있다. 코드를 읽는 사람은 각각의 함수에서 다른 이름을 사용한 이유를 찾으려 할 것이다. (세 단어가 가진 ‘관리’는 각기 다른 역할의 관리이긴 하다. 역할에 따라 적합한 이름을 사용한 경우는 나쁜 예에서 제외한다.)

반대로 같은 단어를 여러 의미로 사용하는 경우도 있다. add라는 단어 또한 경각심 없이 사용하기 쉽다. 합치는 기능의 함수는 종종 만들게 되는데, add라는 단어가 들어가는 함수가 각기 다른 일을 하는 경우도 있다. 예를 들면 인자로 받은 배열을 합쳐 새로운 배열로 돌려주는 함수 addArrays가 있다. 그리고 인자로 변수와 배열을 받아 배열에 변수를 추가해주는 addPropToArray도 새로 만들었다 하자. 전자의 add와 후자의 add는 의미하는 바가 각기 다르다. 이미 add라는 단어를 ‘합치다’라는 의미로 사용했다면 후자의 addPropToArray를 appendPropToArray로 변경하는 것이 바람직하다.

10. 해법 영역에서 가져온 이름을 사용하자

프로그래머들에게 약속된 의미로 사용되는 단어들이 있다. List는 ArrayList를 떠올리게 하고 Adapter와 Visitor는 디자인 패턴을 떠올리게 한다. 프로그래밍의 기술 개념은 기술 영역에서 가져온 이름을 사용하는 것이 정확한 의미 전달을 돕는다.

기술 영역 안에서 찾을 수 없는 이름이 필요할 때도 있다. 예를 들어 의료 기기용 프로그램을 개발한다고 하자. 기기로 촬영한 데이터를 이미지 파일로 변경해야 한다면, 이때는 문제 영역에서 이름을 가져오는 것이 바람직하다. CT와 MRI를 대체할 이름을 기술 영역에서 찾을 순 없을 것이다.

해법 영역과 문제 영역을 구분하고, 적절한 영역에서 이름을 가져와 쓸 수 있어야 한다.

11. 의미 있는 맥락을 추가하고 불필요한 맥락은 없애자

isApple라는 변수가 있다고 하자. isApple은 이 과일이 사과인지 아닌지를 뜻하는 값일까? 그런데 isApple은 User라는 인스턴트의 속성이었다. 그렇다면 isApple은 Apple사의 기기로 접속한 유저인지를 확인하는 속성 값이었나 보다..

스스로 의미가 분명한 이름도 있지만 맥락 없이는 이해할 수 없는 이름도 있다. 맥락을 추가하는 방법은 여러 가지가 있다. 함수를 쪼개 맥락을 찾기 쉽게 도와주거나, 클래스로 묶어 개념을 전달하거나, 최후의 수단으로 접두어를 추가할 수도 있다.

반대로 불필요한 맥락을 가진 이름도 있다. 이미 isApple이 클래스 안에 있었다면 멀리 가지 않고도 이름의 맥락을 확인 가능하다. 변수명을 isAppleDevice로 수정하는 정도는 괜찮지만 isAppleDeviceUser까지 늘일 필요는 없을 것이다. 일반적으로는 짧은 이름이 긴 이름보다 좋다. 하지만 의미가 이미 충분히 전달된다면 추가적 맥락을 덧붙일 필요는 없다.

코드를 수정할 용기

나쁜 이름을 사용하게 되는 이유는 보통 비슷하다. 시간이 부족하기도 하고, 이미 작성한 코드를 수정하는 것은 어딘가에서 오류를 일으킬 위험을 동반하기 때문이다. 또 은밀하게 함수명을 바꾼다면 함께 코드를 작업한 개발자가 혼란에 빠질지도 모른다.

그렇다고 좋은 이름이 주는 이점을 포기하는 것은 바람직하지 못하다. 단번에 좋은 코드를 짜는 사람은 없다. 좋은 코드는 리팩터링 끝에 찾아온다. 곧장 좋은 이름을 떠올리기 어려울 수도 있고, 추가된 맥락에 따라 이름을 변경해야 할 상황이 생길 수도 있다. 하지만 한번 이름을 수정하고나면 그 수고스러움보단 미래의 내가 느낄 고마움이 더 크게 와닿을 것이다.👍

Bookmark : Curioustore

마지막으로 아무리 머리를 쥐어짜도 적절한 변수명이 생각나지 않을 때 참고할만한 사이트를 소개한다.

Curioustore
사이즈 예시 변수명과 관련된 단어를 검색하면 알고리즘을 통해 조합된 유사 이름들을 좋아요 순으로 정렬해준다.

—— Dev X 2020.08.03


[클린 코드 정독 시리즈]
1. 어떤 개발자가 돼야 할까?
2. 변수명과 함수명 짓기