JavaScript: 기분좋은 소스코드

JavaScript는 웹 기반의 스크립트 언어로 매우 동적인 특성을 가지고 있습니다. 그래서 JavaScript를 잘 모르고 사용하거나 다른 언어에서 사용하던 방법과 비슷하게 사용하여도 프로그램이 수행되는 경우가 많습니다. 이러한 점은 JavaScript를 매우 쉬운 언어라고 생각하기 쉽고 이름에서 오는 오해 때문에 Java와 비슷하게 생각할 수도 있습니다. 하지만 언어 내부의 동작 매커니즘은 Java는 물론이고 그 외의 다른 언어와도 다른 점이 많습니다.

또한 JavaScript는 언어 자체의 좋은 점도 많지만, 미성숙한 언어라고도 볼 수 있는 안 좋은 점도 있습니다. 그 대표적인 예가 프로그래밍 모델이 전역변수에 기초하고 있다는 점입니다. 그렇지만 JavaScript는 애플리케이션 플랫폼 중 많이 사용되는 웹 기반의 모든 브라우저에서 사용할 수 있는, 그리고 이미 많이 사용되고 있는 언어입니다. 그렇기 때문에 프로그램을 돌아가게끔 만드는 것도 중요하지만 언어를 잘 이해하고 좋은 코드를 작성하는 것 또한 매우 중요하다고 생각합니다.

1. 좋은 코드란

그렇다면 좋은 코드란 무엇을 말하는 걸까요? 좋은 코드라는 것을 평가하는 기준에는 크게 성능과 유지보수 측면이 있을 것 같습니다. 유지보스 측면에는 가독성, 확장성, 프로그램 수정/보완이 쉬운 구조 등이 있습니다.

이 글에서는 이러한 측면들 중 가독성과 성능 그리고 잠재적인 버그를 유발할 수 있는 3가지 측면에 대해서 알아보고자 합니다.

모든 소프트웨어는 개발 완료 후 지속적으로 수정/보완을 하여 유지보수를 하게 됩니다. 이 유지보수를 쉽게 만드는 요소 중 하나가 가독성입니다. 소프트웨어는 대게 개발자와 유지보수자가 다르기 때문에 다른 사람이 내 코드를 읽고 수정할 것이라는 것을 감안하여 가독성을 좋게 만들어야 합니다. 가독성이란 코드가 단순하고 명확하여 읽기 쉽고 이해가 빠르도록 만드는 것입니다. 코드를 단순하고 명확하게 만들면 다른 사람이 코드를 읽으며 왜 이 코드를 이렇게 만들었을까? 라는 고민을 하게 만드는 시간을 줄여주고 원래의 의도를 잘못 파악할 가능성 또한 줄어들어 변경이나 오류 수정을 쉽고 올바르게 할 수 있습니다.

요즘은 PC의 성능이 많이 좋아져서 웬만한 성능차이는 체감이 되지 않을 정도로 성능의 중요성이 많이 줄었습니다. 하지만 성능이 중요한 애플리케이션에서는 중요할 것이며 일반적인 어플리케이션에도 성능을 적지 않게 저하시키는 요소들은 피해야 할 것입니다.

JavaScript는 언어의 안 좋은 부분들이 있습니다. 당장은 프로그램이 잘 돌아가는 것 같지만 상황에 따라 오류가 발생하거나 버그가 생기는 요소들이 있습니다.
이런 3가지 측면에 대해서 어떻게 하면 JavaScript의 좋은 코드를 만들 수 있을까에 대해서 적었습니다.

2. 좋은 코드를 만들기 위한 코딩 규칙

좋은 코드를 만드는 요소 중에는 코드의 문법이나 구조가 아닌 코딩의 스타일이나 규칙 같은 단순한 요소들도 있습니다. 이 단순한 요소들이 별거 아닌 것 같지만 코드의 가독성을 높입니다.

2.1  명명규칙

명명규칙을 명확히 정하는 것은 다른 언어에서도 좋은 일이지만 함수도 변수이고 상수나 접근제한자를 제공하지 않으며, 전역변수에 기반 하는 등 JavaScript의 언어적 특성 때문에 더 빛을 발하게 됩니다.
명명규칙을 꼭 아래처럼 사용하라는 것은 아닙니다. 팀 내에서 정의된 명명규칙이 있다면 그것을 따르면 되고, 사람들이 많이 쓰는 규칙을 쓴다면 조금 더 좋겠죠. 중요한 것은 명명규칙을 다르게 하여 구분하기 쉽게 만드는 것입니다.

2.1.1 함수 : ′Lower camel case′ 표기법

′Lower camel case′ 표기법은 보통 Java의 메서드에 사용하는 규칙으로 첫 글자는 소문자, 이어지는 각 단어의 첫 글자는 대문자로 표기하는 방법입니다. getName() 과 같이 표기하는 것으로 JavaScript에서도 흔히 사용되는 일반적인 방법입니다.

2.1.2 변수 : 함수와 다른 규칙 사용

JavaScript는 함수도 변수에 담을 수 있습니다. 그렇기 때문에 함수로 사용될 변수와 데이터를 담을 용도로 사용 될 변수를 구분하기 위한 규칙을 사용하는 것도 좋습니다. 예를 들면 first_name과 같이 모두 소문자를 사용하고 각 단어 사이에 ′_′를 넣어 표기하는 것도 한 방법입니다.

2.1.3 상수, 전역변수 : 대문자 표기

JavaScript에서는 상수를 정의할 수 있는 방법이 없습니다. 그렇기 때문에 이 변수는 상수로 정의되었고 값을 바꾸면 안 된다는 것을 명시하기 위해 다른 규칙과 구분 짓는 것이 좋습니다. 표기법은 FULL_NAME과 같이 모두 대문자로 표기하고 단어마다 ′_′를 넣어 표기하는 방법이 있습니다.

시스템에 안 좋은 영향을 끼치는 전역변수 또한 규칙을 달리하면 전역변수가 어떤 것인지, 어디에 쓰이는지 좀 더 쉽게 알 수 있을 것입니다.

2.1.4 생성자 함수 : ′Upper camel case′

JavaScript에는 new 연산자를 이용한 생성자 함수가 있습니다. 하지만 new 연산자를 실수로 빼먹게 되도 일반 함수 호출로 작동은 하지만 this가 가리키는 곳이 달라지는 등의 기대한 것과 다르게 작동할 수 있기 때문에 오류를 찾기가 어렵습니다. 그렇기 때문에 생성자 함수 목적으로 만들어진 함수는 함수 명을 구분 짓는 것이 좋습니다. Java의 클래스 표기에 사용하는 ′Upper camel case′ 표기법이 있습니다. ′Lower camel case′와 비슷하지만 첫 글자도 대문자로 나타내는 것으로 SportsCar와 같이 나타냅니다.

2.1.5 private 메서드 : ′_′로 단어 구분

Java에서는 접근제한자를 private으로 선언하면 private 필드 또는 메서드를 쉽게 만들 수 있지만 JavaScript는 그렇지 않습니다. 물론 JavaScript에서도 클로저를 이용한 private 프로퍼티, 메서드를 구현하는 방법이 있지만 때에 따라서는 표기법을 달리 하여 private 메서드라는 것을 명시해주는 방법도 있습니다. 이렇게 해도 메서드에 접근하는 것은 가능하지만 이 메서드는 공개된 API가 아니기 때문에 직접적으로 호출하는 목적이 아니며 직접 호출 할 경우 정상적인 동작을 보장하지 않는다는 것을 경고하는 것입니다. 표기법으로는 메서드(함수)명 앞에 ′­_′를 넣어 표기합니다.

2.2  공백

너무 빽뺵한 코드는 읽기가 쉽지 않아 읽는데 시간과 노력을 늘어날 수 있습니다. 때문에 적절한 공백의 사용만으로도 코드의 가독성을 높일 수 있고 다른 개발자 또는 유지보수 팀에서 모니터에 눈을 들이대가며 읽어야 되는 수고를 덜어줄 것입니다.
영어에서는 공백을 단어와 단어 사이, 마침표, 그리고 쉼표 뒤에 사용합니다. 코드에서도 마찬가지입니다. 표현식을 열거할 때 쉼표 다음, 명령문 끝과 모든 연사자와 피연산자 양쪽에 공백을 넣으면 코드가 조금 더 읽기 쉽습니다.

[표 1] 공백을 사용하면 좋은 곳

2.3  증감연산자

++, --와 같은 증감연산자를 사용하면 코드가 간결하게 보이고 직관적으로 보입니다. 하지만 이것이 한 구문에서 다른 연산자와 함께 사용되거나 여러 번 사용되었다면 어떨까요?

``` var a = 1, b = 2, c;

// 증감연산자 사용
c = a++ + ++b;

</td>
<td style="background-color:#fff; border:none;">

var a = 1,
b = 2,
c;
b += 1;
c = a + b;
a += 1;

</td>
</tr>
</tbody>
</table>

*[소스1] 증감연산자*

위의 예제의 경우 왼쪽과 오른쪽 중 어느 코드가 이해가 더 쉽고 빠르게 됩니까? 증감연산자를 1개만 사용할 때는 직관적이고 이해가 쉽지만 위의 예제보다도 더 복잡하게 사용되는 경우에는 오히려 코드가 복잡해질 수 있습니다.
 
##3. 가독성과 성능을 위해 권장하지 않는 안티 패턴

###3.1  switch case 문에서 break 생략

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

var number = getNumber();
switch (number) {
case 0 :
console.log("This is Zero");
break;
case 1 :
// 안티패턴
console.log("This is One");
case 2 :
console.log("This is Two");
break;
default :
console.log("Out of Range!!");
}

</td>
<td style="background-color:#fff; border:none;">

var number = getNumber();
switch (number) {
case 0:
console.log("This is Zero");
break;
case 1:
console.log("This is One");
break;
case 2:
console.log("This is Two");
break;
default:
console.log("Out of Scope!!");
}

</td>
</tr>
</tbody>
</table>

*[소스2] switch case 문 break 생략*

switch 문에서 case 절에는 break를 사용하지 않으면 다음 case절까지 계속 실행하게 됩니다. 보통은 각각의 case 절 마다 break를 사용하는데 이것을 의도적으로 빼는 경우 좋지 않습니다. 이 코드를 만든 사람은 왜 이렇게 만들었는지 사연을 알지만 유지보수 팀에서는 이게 실수로 빠진 오류인 건지 의도적으로 뺀 건지 알기 위해 관련 된 로직을 다 뒤져가며 자세하게 살펴봐야 할 수도 있기 때문 입니다. 이러한 case 절에 break 문을 항상 사용하는 방침은 의도하지 않은 break 생략 오류를 찾기 좀 더 쉽습니다. 만약 break 문을 쓰지 않고 다음 case 절 까지 실행하는 형태로 사용해야 한다면 왜 이렇게 된 건지, 흐름이 어떻게 흘러가는지 주석으로 자세하게 설명 해 놓는 것은 선택이 아닌 필수일 것입니다.
 
3.2  객체, 배열의 생성자 함수

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

// 모두 안티패턴
var arr = new Array();
var obj = new Object();

</td>
<td style="background-color:#fff; border:none;">

//
var arr = [];
var obj = {};

</td>
</tr>
</tbody>
</table>

*[소스3] 객체, 배열의 생성자 함수와 리터럴 표기법*

JavaScript에서 객체란 클래스로부터 생성하는 것이 아닌 그저 변형 가능한 해시에 불과합니다. 때문에 굳이 길고 복잡한 new 연산자를 사용하여 생성자 함수로 객체와 배열을 생성할 필요가 없습니다.

또한 몇 가지 문제점은 객체 생성자 함수 new Object()는 인자를 받을 수 있는데, 인자의 타입에 따라 동작을 다르게 합니다. 때문에 런타임에 생성되는 동적인 값이 생성자에 인자로 전달될 경우 예기치 않은 결과가 반환 될 수 있습니다.

배열 생성자 함수 new Array()도 비슷한 문제로 인자를 받을 수 있는데 생성자에 숫자 하나를 전달할 경우 이 값은 배열의 첫 번째 원소가 되는 것이 아니고 배열의 길이를 지정합니다. JavaScript에서의 배열은 Java나 C와 같은 진짜 배열이 아니기 때문에 대부분 이런 식으로 배열의 길이를 지정해 줄 필요가 없습니다. 이것도 의외의 동작 방식이지만, 인자에 정수가 아닌 소수를 가지는 수를 전달할 경우에는 배열의 길이로 유효한 값이 아니기 때문에 에러가 발생 하기까지 합니다.
 
###3.3  원시 데이터타입의 랩퍼 객체

JavaScript에는 데이터타입 랩퍼들이 있습니다. 이 랩퍼 객체들에는 toFixed(), substring(), length와 같은 유용한 프로퍼티, 메서드들이 있습니다. 하지만 JavaScript에서는 원시 데이터타입 그대로 써도 랩퍼 객체의 메서드를 활용할 수 있습니다. 메서드를 호출하는 순간 내부적으로 원시 데이터 타입 값이 객체로 임시 변환되어 객체처럼 동작하게 됩니다.

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

// 모두 안티패턴
var bool = new Boolean(true);
var str = new String("Hello");
var no = new Number(1);

</td>
<td style="background-color:#fff; border:none;">

//
var bool = true;
var str = "Hello";
var no = 1;

</td>
</tr>
</tbody>
</table>

*[소스4] 원시 데이터타입 랩퍼 객체*

다만, 원시 데이터 타입은 객체가 아니기 때문에 프로퍼티를 추가하여 확장할 수 없습니다. 그렇기 때문에 이런 특이한 상황이 아닌 일반적인 경우에는 생성자 함수를 사용 할 필요가 없습니다.
 
###3.4  비트연산자

JavaScript에는 Java와 같은 비트연산자 들이 있습니다. Java에서 비트 연산자는 정수에 대해서 동작합니다. 그런데 JavaScript에는 정수형은 없고 단지 부동 소수점 숫자형 만이 존재합니다. 그래서 JavaScript의 비트 연산자는 대상이 되는 숫자를 일단 정수형으로 변환한 다음 비트 연산을 수행하고 다시 원래의 타입으로 되돌립니다. 대부분의 언어에서 비트 연산자는 하드웨어에 친근하고 속도도 매우 빠르지만, JavaScript의 비트 연산자는 하드웨어와 전혀 친근하지 않고 속도가 느립니다.

###3.5  for 구문의 length

var domList = document.getElementsbyTagName("td");
// 안티패턴
for (var i = 0; i < domList.length; i += 1) {
...
}


var domList = document.getElementsbyTagName("td"),
length = domList.length;
for (var i = 0; i < length; i += 1) {
...
}

 
*[소스5] for 구문 length 캐싱*

for 구문 안에서는 보통 배열이나 HTMLCollection 등 배열과 비슷한 객체를 탐색하며 처리합니다. 그런데 배열이라면 별로 상관이 없으나 예제처럼 HTMLCollection 대상으로 for 구문을 수행할 때 length를 캐싱 하지 않고 바로 사용하는 것은 성능에 문제가 있습니다. 캐싱을 할 경우 사파리에서 3에서 2배, IE7에서 190배에 이르기까지 모든 브라우저에서 속도가 향상 된다고 합니다. 이것은 HTML 페이지에 대한 실시간 질의라는 점에서 문제가 됩니다. 즉 HTMLCollection 의 length 속성에 접근할 때마다 실제 DOM에 요청하는 것과 같으며, DOM 접근은 일반적으로 비용이 큰 작업입니다.

그래서 실제로 얼마나 차이가 나는지 간단하게 테스트 해봤습니다. 아래는 테스트 내용입니다.

var domList = document.getElementsByTagName("td"),
firstTime,
secondTime,
thirdTime,
domLength;

firstTime = new Date();

for (var i = 0; i < domList.length; i += 1) {
domList[i].textContent = 1;
}

secondTime = new Date();

domLenth = domList.length;
for (var i = 0; i < domLenth; i += 1) {
domList[i].textContent = 2;
}

thirdTime = new Date();

console.log(firstTime);
console.log(secondTime);
console.log(thirdTime);

console.log((secondTime - firstTime) + "ms");
console.log((thirdTime - secondTime) + "ms");


*[소스6] for 구문 length 캐싱 테스트*

HTMLCollection의 length를 캐싱 할 경우 IE10에서는 약 25%, Chrome에서는 약 65% 시간이 감소 했습니다.

<figure>
    <img src="/content/images/2021/01/---1------------.png" alt="">
    <figcaption>[그림1] 테스트 수행시간 비교</figcaption>
</figure>

HTML 페이지의 td 태그는 라인(tr) 별 120개, 55라인으로 총 6600개로 테스트 했습니다. td 태그의 숫자가 조금 많긴 하지만 실무에서는 얼마든지 이보다 많을 수 있을 것이고, 혹은 클라이언트의 PC 성능이 안 좋을 경우 탐색시간의 절대 감소치는 더욱 커질 것입니다.

HTMLCollection이 아닌 배열에서도 테스트를 해봤지만 위에서 말한 바와 같이 배열에서는 시간차가 거의 없어서 오차범위 이내였습니다.
 
##4  오류를 발생 시킬 수 있는 안티 패턴

###4.1  항등연산자에서의 암묵적 타입캐스팅

JavaScript는 변수를 비교할 때 암묵적으로 타입캐스팅을 실행합니다. 때문에 의도하지 않았던 결과를 반환하기도 하는데 이로 인한 혼동을 막기 위해서, 동등 비교 연산자로 항상 값과 타입까지 체크하여 비교하는 `===`와 `!==` (완전항등연산자)를 사용하는 것이 좋습니다.

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

// 모두 안티패턴
"" == "0" // false
0 == "" // true
0 == "0" // true

</td>
<td style="background-color:#fff; border:none;">

//
"" === "0" // false
0 === "" // false
0 === "0" // false

</td>
</tr>
</tbody>
</table>
 
*[소스7] 항등연산자*

`==`으로 충분할 때에도 `===`를 쓰는 것은 불필요한 일이라고 생각하는 사람들도 있습니다. 저 또한 불과 몇 달 전까지만 해도 `==` 와 `===`를 타입체크가 필요한지 여부에 따라 나누어서 사용했습니다. 하지만 완전항등연산자를 사용함으로써 코드의 일관성을 지키고 다른 사람이 코드를 읽으며 `==`가 의도된 것인지 아니면 누락된 것인지 고민 하는 것을 없애줍니다.

###4.2  암묵적 전역변수

JavaScript의 가장 나쁜 점 중 하나는 언어가 전역변수에 기반하고 있다는 것입니다. 전역변수는 프로그램이 점점 커짐에 따라 다루기가 까다롭고 복잡해지며, 프로그램의 모든 부분에서 변경될 수 있기 때문에 오류를 유발 시킵니다. 또한 JavaScript는 전역변수가 필요한 경우에만 사용할 수 없기 때문에 문제가 됩니다.

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

function func() {
// 안티패턴
var a = b = 0; // a는 지역변수, b는 전역변수
}

</td>
<td style="background-color:#fff; border:none;">

function func() {
var a, b; // a, b 모두 지역변수
a = b = 0;
}

</td>
</tr>
</tbody>
</table>
 
*[소스8] 암묵적 전역변수*

JavaScript는 유효범위 안에서 var를 사용하여 변수를 생성하면 지역변수로 생성 되고, 전역범위에 선언을 하거나 var를 사용하지 않고 선언하거나 선언되지 않은 변수를 사용하게 되면 전역변수로 생성합니다. 그렇기 때문에 변수를 선언하지 않고 사용하거나 var를 빼먹는 것을 주의해야 합니다.
 

###4.3  부동소수점 연산

JavaScript에서 소수점 숫자의 계산을 할 때는 주의 깊게 사용해야 합니다. 이진 부동 소수점 연산의 문제인데 소수 부분을 제대로 처리하지 못합니다.

var booleanA,
booleanB;

booleanA = (0.1 + 0.2 === 0.3); // 0.1 + 0.2는 0.30000000000000004, false
booleanB = ((0.1 * 10 + 0.2 * 10) / 10 === 0.3); // true


*[소스9] 부동소수점 연산*

위의 예제와 같이 0.1과 0.2를 더하면 0.3이 아닌 값이 나옵니다. 이를 정확하게 계산하기 위해서는 소수점 숫자를 정수 형태가 되도록 값을 곱한 후 계산 한 뒤에 다시 나눕니다.
 
###4.4  함수선언문

// 안티패턴
// 전역함수
function hello() {
console.log("Hello");
}
function goodBye() {
console.log("Good Bye");
}

function hoistFunc() {
console.log(typeof hello); // “function”
console.log(typeof boodBye); // “undefined”

hello();   // “local Hello”
goodBye(); // TypeError: undefined is not  a function
// 함수 선언문
function hello() {
    console.log("local Hello");
}
// 함수 표현식
var goodBye = function () {
    console.log("local Good Bye");
}

}

hoistFunc();

 
*[소스10] 함수선언문과 함수 표현식*

JavaScript에서 함수를 정의하는 방법은 크게 함수 선언문과 함수 표현식이 있습니다. 위의 예제에서 hoistFunc() 내의 goodBye가 함수 표현식이고 나머지는 모두 함수 선언문입니다. 이 둘에는 호이스팅(Hoisting, 끌어올려짐)에서 차이점이 있는데 JavaScript에서 함수는 호이스팅 대상입니다. 그렇기 때문에 함수 선언문으로 작성 된 hoistFunc() 함수 내 hello() 지역 함수는 hoistFunc() 함수가 실행될 때 함수의 유효범위 맨 위로 올라갑니다. 반면 함수 표현식으로 작성 된 goodBye는 변수 선언만 호이스팅 되고 변수에 함수를 대입하는 함수 선언부는 코드상의 위치 그대로 있습니다.

실제로 코드가 실행되는 순서는 아래와 같습니다. 전역함수 부분은 생략 하였습니다.

// 전역함수
...

function hoistFunc() {
//
function hello() {
console.log("local Hello");
}
var goodBye;

console.log(typeof hello);    // "function"
console.log(typeof goodBye);  // "undefined"

hello();   // "local Hello"
goodBye(); // TypeError: undefined is not a function

goodBye = function () {
    console.log("local Good Bye");
};

}

hoistFunc();

 
*[소스11] 함수 선언문 호이스팅 후*

그럼 함수가 호이스팅이 되고, 되지 않는 것에는 근본적으로 어떤 차이점이 발생할까요? 우리는 대다수의 프로그래밍에서 함수를 먼저 선언하고 호출한다는 것에 익숙해져 있습니다. 하지만 함수 선언문으로 정의된 함수는 우리가 함수 정의를 위치시키는 곳에서 동작하지 않고 우리의 생각과는 다른 순서로 동작할 수 있습니다. hoistFunc() 함수 내에서 hello() 함수를 선언한 후 호출한 다음에 함수의 내용을 바꿨지만 함수명은 같은 함수를 재정의하고 다른 목적으로 호출 했다고 생각해 봅시다. 이것은 우리의 의도와는 다르게 두 개의 서로 다른 함수 정의가 모두 호이스팅 되어서 두 번의 함수 호출 모두 두 번째 hello()가 동작하게 될 것입니다.

또한 함수 표현식은 함수 값을 가진 변수라는 것을 명확히 나타냅니다. 이는 JavaScript라는 언어의 특징인 함수도 값이라는 것을 잘 나타내고 있습니다.

###4.5  for, for-in 구문

JavaScript에는 일반적인 for 구문 말고도 for-in 구문이 있습니다. Java의 for each 구문과 비슷한 것으로 객체에 있는 모든 속성을 인덱스 없이 탐색할 때 사용합니다. 하지만 for 구문과 다르게 for-in 구문은 프로토타입에 있는 속성 까지도 포함되고 순서가 보장되지 않습니다. 그렇기 때문에 배열에서는 for 구문을 사용하고 객체에만 for-in 구문을 사용하는 것이 좋습니다. 또한 for-in 구문을 사용할 때는 원하지 않는 프로토타입의 속성을 필터링 하기 위해 hasOwnProperty() 메서드를 사용해야 합니다.
 
###4.6  중괄호

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

// 안티패턴
if (car)
car.start();
car.end();

</td>
<td style="background-color:#fff; border:none;">

if (car) {
car.start();
}
car.end();

</td>
</tr>
</tbody>
</table>
 
*[소스12] 중괄호 시작 위치*

if문 등의 중괄호를 사용하는 구문에서 내용이 단 한 줄이라도 중괄호를 생략하지 않는 것이 좋습니다. 이것은 비단 JavaScript에서만이 아니라 모든 언어에서 마찬가지일 것입니다. 위의 예제에서 car.end() 부분이 없다고 하더라도 나중에 다른 사람에 의해서 추가될 수 있고 추가하는 사람이 car.start()에서 한 라인을 띄우고 car.end()를 넣는다는 보장은 없습니다. 이렇게 if문의 내용과 바깥영역의 라인이 붙어있으면 마치 둘 다 if문 안에 있는 것으로 보일 수 있습니다.

또 다른 문제로 중괄호의 위치는 버그를 발생시킬 수 있습니다. 아래의 예제를 보겠습니다.

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

// 안티패턴
return // return undefined
{
lastName : "홍",
firstName : "길동"
};

</td>
<td style="background-color:#fff; border:none;">

//

return {
lastName : "홍",
firstName : "길동"
};

</td>
</tr>
</tbody>
</table>

*[소스13] 중괄호 시작 위치의 버그*

위의 예제는 객체를 리턴 하고 있고 코드만 봐서는 별 문제가 없어 보입니다. 하지만 JavaScript의 특성 때문에 문제가 발생합니다. JavaScript는 자동으로 세미콜론을 삽입하여 잘못된 프로그램을 교정하려는 메커니즘이 있습니다. 세미콜론을 쓰지 않고 행을 종료하면 알아서 세미콜론을 추가해 주는 것이죠. 그래서 가끔 세미콜론을 빼먹어도 문제없이 작동하는 것을 보셨을 겁니다.

하지만 이러한 점 때문에 예상치 못한 결과가 발생합니다. 코드 작성자가 원했던 것은 lastName과 firstName을 프로퍼티로 가지고 있는 객체를 리턴 하는 것 이었지만, 중괄호의 위치 때문에 return 뒤에 세미콜론이 자동 삽입 되고 결과로 undefined가 return 되는 것이죠. 물론 밑의 코드는 수행되지 않고 함수가 종료되게 됩니다. 중괄호의 위치가 개인에 따라 스타일이 다를 수 있지만 이러한 점 때문에 JavaScript에서는 항상 여는 중괄호를 선행 명령문과 같은 라인에 두는 것이 좋습니다.
 
###4.7  parseInt()

parseInt()는 문자열을 정수로 변환해 주는 함수입니다. 첫 번째 문자열부터 시작하여 정수가 아닌 문자열이 나올 때까지 정수로 변환하여 리턴 하게 됩니다. 하지만 이 함수는 주의해서 사용해야 할 부분이 있습니다.

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

// 안티패턴
parseInt("08"); // 0

</td>
<td style="background-color:#fff; border:none;">

//
parseInt("08", 10); // 8

</td>
</tr>
</tbody>
</table>
 
*[소스14] parseInt()의 기수 인수*

parseInt()는 첫 번째 문자열이 0일 경우 8진수로 간주하게 됩니다. 그런데 8은 8진수의 범위 안에 들어가지 않으므로 변환하지 않고 8진수 0을 리턴 하게 되는 것입니다. 이러한 이유 때문에 parseInt()를 사용할 때는 항상 두 번째 인수인 기수를 입력해 주는 것이 좋습니다.
 
###4.8  eval()

eval() 함수는 문자열을 JavaScript 컴파일러에 넘긴 후 그 결과를 실행시킵니다. 하지만 eval() 함수는 여러 이유 때문에 좋지 않은 함수입니다. 먼저 코드가 읽기 매우 어렵고 문자열을 컴파일러에 넘겨 수행하므로 속도가 느려질 수 있습니다. 제일 안 좋은 점은 매개변수로 넘어온 텍스트에 너무 많은 권한을 허용하기 때문에 프로그램의 보안을 위협할 수 있습니다. 심지어 ′eval is evil′ 이라고 표현하는 분도 있더군요.
아래의 예제는 객체의 프로퍼티가 동적으로 생성되는 경우입니다.

<table border="0">
<col width="50%" />
<col width="50%" />
<tbody>
<tr>
<td style="background-color:#fff; border:none;">

var obj = {},
property = "name";
// 안티패턴
eval("obj." + property + " = '홍길동';");
console.log(obj.name);

</td>
<td style="background-color:#fff; border:none;">

var obj = {},
property = "name";
obj[property] = "홍길동";
console.log(obj.name);
console.log(obj[property]);

</td>
</tr>
</tbody>
</table>

*[소스15] eval()*

함수 생성자 new Function()이나 setTimeout(), setInterval() 함수도 문자열을 인수로 넘길 경우 eval()과 비슷하게 동작하므로 안 좋습니다. 반드시 eval()을 사용해야 한다면 즉시실행 함수로 감싸거나 Function 함수 생성자를 사용하여 전역 혹은 유효범위 내의 변수들을 덜 오염 시키는 방법도 있습니다.
 
##5. JSLint

JavaScript는 정적 컴파일을 하지 않는 인터프리터 언어이기 때문에 사소한 타이핑 실수를 알아채지 못할 때도 있습니다. 이럴 때 JSLint를 사용하면 유용합니다. JSLint는 더글라스 크락포드(Douglas Crockford)가 개발한 JavaScript 품질 검사 도구로 코드를 검사하고 잠재적인 문제들에 대해 경고해줍니다.

JSLint는 웹의 http://www.jslint.com/에서 사용할 수 있는데 웹페이지에 재미있는 문구가 있습니다.

<figure>
    <img src="/content/images/2021/01/jshint.png" alt="">
    <figcaption>[그림2] JSLint 경고</figcaption>
</figure>

작성한 코드를 넣고 실행해 보면 의외로 많은 경고들이 뜨는 것을 보실 수 있을 겁니다. 그 중에는 이런 것들까지 엄격하게 검사할 필요가 있을까 하는 항목도 있을 수 있지만 많은 부분들이 코드의 품질을 높이는데 도움이 될 것이며 본 글의 많은 내용들이 JSLint에 포함되어 있습니다.

웹에서 JSLint를 복사 붙여넣기로 사용하기 불편하다면 이클립스 플러그인을 이용하여 JSLint를 바로 사용 할 수도 있습니다. jslint4java라는 이름으로 제공되고 있습니다. 그리고 JSLint에 대해서 찾다 보니 JSHint라는 것도 있습니다. 마찬가지로 코드 검사 도구인데 Anton Kovalyov라는 분이 만들었으며 JSLint가 현실에 맞지 않게 너무 엄격하다는 점에서 등장하게 되었다고 합니다.

JSLint나 JSHint를 사용함으로써 코드의 품질을 높이고 잠재적인 에러를 방지할 수 있는 면이 있지만 너무 맹신하면 안되고, 이것만이 정답은 아니며 툴의 활용을 넘어서 얽매인다면 좋지 못하겠다는 생각도 해봅니다.
 
##6. 마무리

여기까지 JavaScript에서 좋은 코드를 작성하는 여러 가지 방법에 대해서 살펴 봤습니다. 글에서 말한 방법들 중에는 개인 취향에 따라 다를 수 있는 부분도 있고 꼭 이 방법을 사용하라고 말씀 드리고 싶은 부분도 있습니다. 글의 내용 외에도 많은 방법들이 있을 것이며 특히 코드의 구조적인 문제나 디자인 패턴 영역에 대해서는 내용도 방대하고 제가 이해하고 있는 수준이 낮다고 생각하여 언급하지 않았습니다. 위의 방법들 조차도 아직 무심코 안 좋은 것을 사용하거나, 좋은 것을 사용하지 않을 때도 있습니다.

저의 부족한 글을 봐주셔서 감사 드리며 코드를 작성하고 몇 개월 혹은 몇 년 뒤에 다시 봐도 참 좋은 코드라고 뿌듯해하며 볼 수 있는 저의 모습을 상상하며 글을 마칩니다.
감사합니다.
 
##참고 도서 및 사이트

1. 이재욱, Javascript: 함수(function) 다시 보기, 넥스트리 홈블로그, 2014
2. Douglas Crockford 저 / 김명신 역, "더글라스 크락포드의 자바스크립트 핵심 가이드", 한빛미디어, 2008
3. 데이비드 플래너건 저 / 구경택·박경욱·변치훈·이의호 공역, "자바스크립트 완벽 가이드", 인사이트, 2013
4. 스토얀 스테파노프 저 / 김준기·변유진 공역, "자바스크립트 코딩 기법과 핵심 패턴", 인사이트, 2011
5. JSLint, The JavaScript Code Quality Tool, www.jslint.com
6. JSHint, a JavaScript Code Quality Tool, www.jshint.com