JS 코드 스타일링 가이드

조 은길·2021년 12월 13일
2
post-thumbnail

오늘 TIL은 " 코드스테이츠 "에서 학습한 내용과 " JS 클린 코드 from Udemy " 강좌를 바탕으로 작성됐다.

들어가기

아직 클린 코드까지 생각할 만한 실력이 아니라고 생각한다. 그렇지만, 매일 코드를 짜면서, 무의식적으로 형성된 악습관들이 훗날 나의 발목을 잡지는 않을지 걱정이 되기 시작했다. 그래서, Udemy의 JS 클린 코드 강의를 수강했고, 도서 "클린 코드" 중에서 이해가 되는 부분들을 내 것으로 만들려고 한다.

코드 스타일링이란?

부트캠프에서 페어 프로그래밍을 할 때, 페어분의 코드를 이해하지 못하는 경우가 종종 있었다. 호이스팅을 남용해서 코드가 스파게티가 된 경우였는데, 지금 생각해봐도 좋은 코드가 아니였다. 아래 코드는 가독성이 좋지 않은 코드의 예시다. 띄어쓰기가 제대로 되지 않은 글을 보는 듯 하다.
( 보기만 해도 끔찍하다... )

지금부터는 조금 더 자신의 코드가 구조적이고 가독성 있게 잘 작성되었는지 신경쓰면서 작성해보도록 하자. 이렇게 가독성 좋은 코드를 작성하는 것을 코드 스타일링이라고 한다.

코드 스타일링은 왜 중요한가?

가독성 좋은 코드를 작성하는 것은 왜 중요할까? 코드 가독성이 좋지 않으면 아래와 같은 문제가 생긴다.

  • 동료가 코드를 쉽게 이해할 수 없기 때문에 소통에 걸리는 시간이 증가함.
  • 코드에 에러가 있는 경우 쉽게 발견을 할 수 없음.

사실, 이런 코드 스타일링은 코드가 에러가 나고 고객에게 보여지는 것 보다는 비교적 큰 문제가 아닌 것같다. 하지만, 이렇게 생각해보면 어떨까?

여러분이 변수 명을 a로 작성하여 선임 개발자가 여러분의 자리로 이동하여 이 변수의 역할에 대해서 묻습니다. 그리고 여러분은 코딩을 잠시 멈추고 선임 개발자에게 이 코드 명에 대해서 설명합니다. 그리고 선임 개발자는 "이렇게 하면 안된다"라는 약간의 훈계를 하고 돌아갑니다. 여러분은 썩 기분이 좋지는 않지만 다시 코딩을 시작합니다.

이미 소통 비용이 발생했다고 볼 수 있다. 개발자에게 10분의 시간은 어쩌면 작은 하나의 기능을 완성하고도 남았을 시간이다. 서로 소통이 되지 않아 추가적인 대화가 필요했던 그 10분 동안, 3시간 동안 머리 싸매고 있던 에러를 해결할 수 있는 좋은 대안을 떠올릴 수도 있을 시간이다. 그래서 통일성 있는 코드 스타일을 기반으로 더 빠르게 리뷰하고 더 적게 고민하는 것이 정말 중요하다. 그 시간에 더욱 더 생산적인 일을 할 수 있으니까...

다만, 코드 스타일링은 개발자들이 일하시는 회사에 따라서 다소 다른 경우가 있다. 그래서, 이번에 정리하는 스타일들은 보편적으로 대부분의 자바스크립트 개발자가 동의하는 내용을 위주로 적고자 노력했다.

Indentation – 들여쓰기

Good:


if (condition) {
  action();
}
// 코드의 로직이 명확하게 종속적으로 나뉘는 코드를 쓸 때, 
종속된 code block는 주인 code block보다 두 칸 들여쓰기 합니다.

Bad:


if (condition) {
action();
}

들여쓰기를 할 때, 탭이 아닌 스페이스를 권장한다.

들여쓰기와 관련된 탭과 스페이스 사이의 논쟁은 프로그래밍 세계에서는 아주 오래된 논쟁이다. 그렇기에 취향의 차이일 뿐 이것이 맞다 틀리다의 문제는 아니지만, 많은 JavaScript 프로젝트에서 대부분의 프로젝트가 2개의 스페이스를 쓰고 있고, 점차 들여쓰기 논쟁의 승리자가 되었다. 다수의 오픈소스 프로젝트가 진행중인 GitHub에서는, Star(일종의 '좋아요')를 받은 프로젝트의 85% 이상의 JavaScript 프로젝트가 스페이스 들여쓰기를 사용하고 있다.

개발자가 절대로 피해야 할 단 한가지는, 바로 스페이스와 탭을 혼용해서 쓰는 것이다!

Good:

if (condition) {
  action();
}
// code block의 마지막 줄을 쓸 때 마지막 줄의 시작은, 
// 시작할 때 줄의 시작과 동일한 곳에서 해주세요. 

Bad:


if (condition) {
  action();
  }
// 종속된 code block의 시작에 맞추면 안됩니다.

Bad:


transmogrify({
   a: {
    b: function(){
    }
}});
// code block이 바뀌고 해당 code block에 맞춰 줄을 바꿀 때, 
// 2칸 들여쓰기 규칙을 지켜야 합니다.
// 속성 a 안에 속성 b가 있는데, 마치 같은 범주에 있는 속성으로 보입니다. 
// 또한 함수와 객체의 구분이 명확하지 않습니다.

Naming – 이름 짓기

변수명은 값의 본질적인 의미를 가지고 있어야 한다.

변수의 이름은 한 단어(Descriptive word)로 표현하는 것이 가장 좋다. 개발 분야(domain)의 핵심을 잘 묘사해주는 단어일수록 좋다. 예를 들어 금융 관련 개발을 하는 경우, 그 산업 분야에서 사용하는 용어를 가능하면 그대로 쓰는 것이 좋다.

Good:

let interestRate = 0.2;
// 이자율임을 쉽게 알 수 있습니다. 
// 어떤 금융상품의 이자율인지 표기할 수 있으면 더 좋겠네요.

Bad:


let numA = 0.2;
// 단지 숫자라는 것 의미 외 다른 의미가 없어 무슨 변수인지 추정해야 합니다.

변수에 할당되는 값의 형식이 아닌, 그 값의 의미가 변수 이름에 반영되어야 한다.

=> 말 자체가 한 번에 와닿지 않는다면, 예시를 보면 쉽게 이해가 된다.

Good:


let animals = ['cat', 'dog', 'fish'];
// 변수가 가지는 의미를 명확하게 표현하고 있습니다.

Bad:

let targetInputs = ['cat', 'dog', 'fish'];
// 변수가 가지는 구조를 표현하고 있는 변수명으로, animals가 더 낫습니다.

Very bad:


let array = ['cat', 'dog', 'fish'];
// targetinputs 보다 더욱 의미가 불명확합니다.

데이터의 모음(collection)이 할당된 변수의 이름은 복수 명사가 좋다.

Good:

let animals = ['cat', 'dog', 'fish'];
// 하나의 동물이 아니라, 
// 여러 마리의 동물이 있는 데이터임이 명시적으로 표현되어 있습니다.

Bad:

let animalList = ['cat', 'dog', 'fish'];
// animals로 표현되는 것이 의미를 조금 더 잘 표현합니다.

Very bad:

let animal = ['cat', 'dog', 'fish'];
// 하나의 동물인지, 여러 마리의 동물 리스트인지 알 수 없습니다.

boolean이 할당된 변수는 is 혹은 are을 붙여서 참 혹은 거짓임을 분명히 표현한다.

많은 개발자들이 이미 boolean이 할당된 변수 이름은 is 혹은 are을 붙여왔다. 왜 그럴까?

Carry is a dog

" 케리는 강아지입니다 "라는 사실을 코드로 가장 쉽게 표현하는 방법이기 때문이다. " 강아지입니다 "가 참(true)인지 거짓(false)인지 명확하게 보여주고 있다.

let isDog = true;
let 강아지입니다 = true;

Good:

let areEqual = true;

Bad:

let pass = true;

함수의 이름은 동사로 시작하는게 좋다.

함수의 기능을 담은 동사를 함수명에 포함하면 역할을 쉽게 파악할 수 있다. 또한, 함수의 입력값과 출력값, 그리고 둘 사이의 변환 과정을 파악하기가 쉽다.

Good:


let countBlocks = function() {
  // 벽돌의 개수를 세는 함수임을 쉽게 알 수 있습니다.
}

Good:


let countWaterBlocksBetweenTowers = function() {
  // 타워 사이의 벽돌의 개수를 세는 함수임을 쉽게 알 수 있습니다.
}

Bad:


let waterBlocks = function() {
  // 변수명으로는 역할이 파악이 되지 않기 때문에, 아래의 주석이 없으면 무슨 역할을 하는지 파악하기 어렵습니다.
  // 이 함수는 벽돌의 개수를 세는 함수입니다. 타워 사이의 벽돌을 세는 것을 의미합니다.
}

변수 할당 값이 Class인 경우에 주로 변수의 첫 글자를 대문자로 사용한다.

class Animal(){
  // ES6 부터 사용 가능한 class가 할당되는 변수는 첫 글자를 대문자로 적습니다.
}
  • new 키워드를 사용한 함수에 한해서 대문자를 쓰기도 한다. class 역할을 하기 때문에!!
function Animal() {
  // class 생성자 함수가 될 함수임으로 대문자로 적습니다.
}

상수는 모두 대문자로 적는다.

=> 이유는 찾지 못했다.

const MAX_ITEMS_IN_QUEUE = 100;

기호 및 구두점(punctuation)

문법적으로 생략 가능한 때에도 중괄호는 생략하지 않는다.

Good:


for (key in object) {
  alert(key);
}
// for문의 작동 범위를 눈으로 확인 가능합니다.

Bad:


for (key in object)
  alert(key);
// for문의 작동 범위를 눈으로 확인할 수 없습니다.

JavaScript의 문자열 표시를 위해서 작은 따옴표를 권장한다.

HTML에서 사용하는 큰 따옴표와 구분하기 위해서 자바스크립트에서 문자열 표기 시 작은 따옴표를 권장한다.

=> HTML에서 class나 ID 등을 덧붙일 때, 큰 따옴표가 사용되니, 그것과 구분해주기 위한 용도이다.

=> C++ 공부하는 사람들에게는 적용하기 어려운 JS만의 코드 스타일링이다.

Good:


let dog = 'dog';
let cat = 'cat';

Acceptable:


let dog = "dog";
let cat = "cat";
// 큰 따옴표도 괜찮습니다. 

Bad:


let dog = 'dog';
let cat = "cat";
// 띄어쓰기와 tab과 같이 혼용은 최악의 코드 스타일입니다.

줄 바꿈이 필요한 문자열을 정의할 때는 `(백틱, backtick) 사용을 권장한다.

Good:


let multilineText = `this is line one
this is line two
this is line three`;
// 엔터가 명확하게 보입니다.

Bad:


let multilineText = 'this is line one' + '\n' + 'this is line two' + '\n' + 'this is line three';
// 엔터가 명확하게 보이지 않습니다.

코드 실행의 가장 작은 단위인 Statement(문)의 끝에 세미콜론을 사용한다.

Good:


alert('hi');

Bad:


alert('hi')

if, for, while문의 끝에는 세미콜론을 사용하지 않아야 한다.

중괄호로 끝나는 Statement는 이미 종료가 암시되어 있기 때문에, 세미콜론을 사용하지 않는다.

Good:


if (condition) {
  response();
}

Bad:


if (condition) {
  response();
};

함수 표현식의 끝에는 세미콜론을 사용한다.

if, for, while 구문의 끝처럼 보일지라도, 코드 끝에 세미콜론을 써야한다.

Good:


let greet = function () {
  alert('hi');
};

Bad:


let greet = function () {
  alert('hi');
}

세미콜론을 쓰지 않아도 된다고 주장하는 사람들도 더러 있으며, ESLint Standard Style에서는 세미콜론을 쓸 필요가 없다고 이야기하고 있다. 하지만 여전히 많은 프로젝트에서는 세미콜론을 선호한다. 다행히도 최근의 자바스크립트는 다른 프로그래밍 언어와 달리 세미콜론을 엄밀하게 적지 않아도 에러가 나는 경우는 거의 없다. 세미콜론 자동 삽입 기능인 ASI(automatic semicolon insertion)이 대부분의 자바스크립트 엔진에서 작동하기 때문이다. 그래도 세미콜론을 왜 작성하는지 알고 작성하지 않는 것과, 그렇지 않은 것에는 이후 디버깅 능력 향상에 큰 차이가 생기게 된다. "이건 오류가 아니다."라고 확신이 늘어야, 무의미한 삽질을 하지 않습니다.

연산자와 키워드

엄격한 비교 연산자를 사용하자

엄격하지 않은 동치 연산(loose equality, ==, !=)는 엄밀하지 않기 때문에 반드시 엄격한 동치 연산(strict equality, ===, !==)을 사용하자.

Good:


if (0 === '') {
  alert('looks like they\'re equal');
}
// 엄격한 동치 연산은 `0`과 `''`을 다르다고 평가합니다.

Bad:


if (0 == '') {
  alert('looks like they\'re equal');
}
// 엄격하지 않은 동치 연산은 `0`과 `''`을 같다고 평가합니다.

3항 연산자(?)는 간결하고 가독성이 좋은 경우만 사용한다.

3항 연산자는 코드를 간결하게 만든다. 그러나 장황한 경우 가독성이 떨어진다.

Good:


if (actual !== expected) {
  console.log('FAILED ' + testName + ': Expected ' + expected + ', but got ' + actual);
} else {
  console.log('passed');
}

Bad:


return (actual === expected) ? 'passed' : 'FAILED ['+ testName + '] Expected "'+expected+'",but got '+'"'+actual+'"';

not 연산자(!)는 바로 앞에 붙여서 사용한다.

Good:


if (!isEqual) {

Bad:


if (! isEqual) {

짧게 쓰기

코드는 뜻이 분명하고 실행 되는 한, 되도록 짧게 쓰자.

Good:


function square(n) {
  return n * n;
}

Not as good:


function square(n) {
  let squaredN = n * n;
  return squaredN;
}
// 변수를 선언하지 않아도 됩니다.

부정의 의미가 명확한 곳에만 NOT 연산자를 사용한다.

Good:


if(equalSizes && equalValues) {
  // 사이즈가 같고, 값이 같은 경우
} else {
}

Bad:


if(!equalSizes || !equalValues) {
  // 사이즈가 같지 않거나 값이 값지 않은 경우
} else {
}
// 이 조건문은 불필요하게 조건에 대해 고민을 해야 합니다.

Boolean으로 평가되는 표현문은 바로 return 하자

Good:


return charSet.size > text.length;

Bad:


if(charSet.size < text.length) {
  return false;
}
return true;
// 비교문은 무조건 true 혹은 false로 평가되기 때문에 장황하게 조건문을 작성하지 않습니다.

코드 문장과 구문 사이 공간

한 번에 더 많은 코드를 읽기 위해서, 줄 바꿈은 최소로 사용해야 한다.

Good:


function square(n) {
  return n * n;
}

function assertEqual(actual, expected, testName) {
  // compare actual and expected
}

Bad:


function square(n) {
  return n * n;
}
/////////////
//////////// 이런 공간들을 최소화해야 된다는 말입니다.
////////////
function assertEqual(actual, expected, testName) {
  // compare actual and expected
}

줄을 바꿔야할 때도 있고, 줄 바꿈을 자제해야 할 때도 있다. 판단 기준은 어떻게 작성해야 이해하기 쉬운가? 에 따라서 유연하게 대처하자.

들여쓰기는 일관성있게, 최소화하여 사용한다.

=> 여기서 핵심은 일관성 있게 사용하자는 것이다.

Good:

alert('I chose to put no visual padding around this string');
// 괄호와 따옴표 사이 공백이 없습니다.

Good:

alert( 'I chose to put visual padding around this string' );
// 괄호와 따옴표 사이 공백이 일관되게 있습니다.

Bad:


alert( 'I only put visual padding on one side of this string');
// 한 쪽에는 공백이 있고, 한 쪽에는 공백이 있습니다.

같은 라인에 값을 보기 위해 하는 들여쓰기는 지양한다.

Good:

let firstItem = getFirst();
let secondItem = getSecond();
let thirteenthItem = getThirteenth();

Bad:


let firstItem      = getFirst();
let secondItem     = getSecond();
let thirteenthItem = getThirteenth();
// 불필요한 들여쓰기가 너무 많아졌습니다.

콤마(,) 사이는 한 칸 띄어쓴다.

Good:

assertEqual(Math.pow(3, 2), 9, 'Math.pow squares properly');

Bad:


assertEqual(Math.pow(3,2),9,'Math.pow squares properly');

연산자 사이는 한 칸 띄어쓴다.

Good:

'Failed [' + testName + ']'...

Good:

if(actual === expected){
  // action
} else {
  // alternate action
}

Bad:


'Failed ['+testName+']'...

Bad:


if(actual===expected){
  // action
}else{
  // alternate action
}

주석

  • 주석은 꼭 필요한 경우에만 작성한다.
  • 주석을 적기 전에, 변수 이름과 구조로 코드의 목적이 명확히 표현되면 주석을 적을 필요가 없다.
  • 주석으로 코드의 작동을 설명하는 것은 지양한다. 코드를 보고 바로 이해할 수 있을 만큼 가독성이 좋게 코드를 작성해야 한다.

나 : 공부 목적으로 주석을 적었는데요, 이것도 하면 안되나요?

  • 교육 엔지니어님 : 주석을 공부 목적으로 활용하는 것은 좋으나, 숙달되어 이해할 수 있는 코드에 주석을 다는 것은 가독성을 떨어뜨립니다. 점차적으로 줄여나갑시다.

camelCase vs. snake_case

JavaScript에서는 변수의 이름을 지정할 때 'Camel Casing'으로 지정한다. 이것은 Ruby 같은 다른 프로그래밍 언어에서 사용하는 'Snake Casing'과 다르다.

Good:

let camelCased = 'Used in javascript';

Bad:


let snake_cased = 'Used in other languages';

JavaScript에서 '뱀 모양'을 사용하는 경우는, 상수 이름을 지을 때입니다.

const MAX_ITEMS_IN_QUEUE = 100;

유명한 코드 스타일 가이드

가장 많이 사용되고 유명한 코드 스타일 가이드를 공유한다. 어떻게 써야할 지 조금 모호하다고 생각할 때 참고하면 좋을 듯하다.


글을 마치며

이렇게 정리해놓고 보니, 클린 코드라는 것은 유지보수성 측면에서 중요하다고 하지만, 내 생각에는 모두가 한 눈에 알아볼 수있기 위한 목적이 더 중요한 것같다. 앞으로는 내 코드를 다른 개발자 분이 쉽게 이해할 수 있을지, 타인의 시선으로 내 코드를 보면서 작성해야겠다.

profile
좋은 길로만 가는 "조은길"입니다😁

0개의 댓글