자바스크립트, 유지보수 가능한 코드의 작성

5 minute read

패턴

패턴은 일반적인 문제에 대한 해결책!

  • 더 나은 코드를 작성하는데 도움
  • 팀 커뮤니티 케이션에 도움(코드의 구조를 일일이 설명하지 않아도 됨)

패턴 종류

  • 디자인 패턴 : 특정 언어에 종속되지 않은 패턴들. singleton, factory, decorator, observer
  • 코딩 패턴 : 자바스크립트 특유의 패턴(함수의 활용, 자바스크립트의 독특한 기능과 관련된 패턴)
  • 안티 패턴 : 오류를 더 많이 일으킬 수 있는 잘못된 접근 방법

유지보수 가능한 코드의 작성

처음에 코드를 작성하는데 드는 시간보다 코드를 유지보수 하는 시간이 더 크다.

유지보수의 비용

  • 문제를 다시 학습하고 이해하는 데 걸리는 시간
  • 이 문제를 해결하는 코드를 이해하는 데 걸리는 시간

애플리케이션은 완성되는 과정에서 재검토되고 수정과 변형을 거치게 되므로 유지보수가 가능한 코드를 작성하는것이 애플리케이션의 성공의 핵심이 된다.

유지보수 가능한 코드의 특징

  • 읽기 쉽다.
  • 일관적이다.
  • 예측 가능하다.
  • 한 사람이 작성한 것처럼 보인다.
  • 문서화 되어 있다.

유지보수 가능한 코드의 작성은 소프트웨어 프로젝트의 성공 뿐 아니라 개발자와 주변 사람들의 행복과 정신건강에도 중요한 주제이다.

전역변수를 최소화

전역변수는 기존에 정의된 전역변수를 동일한 이름으로 덮어 쓸 수 있기 때문에 문제가 발생할 소지가 크다.

g_hello = "hello"; // 안티패턴
console.log(g_hello); // hello
console.log(window.g_hello); // hello
console.log(window["g_hello"]); // hello
console.log(this.g_hello); // hello
function sum(x, y) {
    // 안티패턴: 암묵적 전역
    result = x + y;
    return result;
}
function sum(x, y) {
    // 모범적 관행 : 언제나 var를 사용하여 변수 선언
    var result = x + y;
    return result;
}
function foo() {
    // 안티 패턴: a는 지역변수이지만 b는 전역변수
    var a = b = 0;
}

단일 var 패턴

함수 상단에서 var 선언을 한번만 쓰는 패턴

  • 함수에서 필요로 하는 모든 지역변수를 한군데서 찾을 수 있다.
  • Hoisting(변수가 끌어올려 지는 것)으로 발생할 수 있는 로직상의 오류를 막을 수 있다.
function func() {
    // 단일 var 패턴
    var a = 1,
        b = 2,
        sum = a + b,
        myobject = {};
}
function func() {
    // 안티패턴: hoisting의 문제점
    console.log(a);  // undefind
    var a = 1;       
    console.log(a);  // 1
}

for 루프

반복문을 사용할 때 array의 length에 반복적으로 접근하는 문제

// 최적화되지 않은 루프
for (var i = 0; i < someArray.length; i++) {
    //...
}
// 반복문을 최적화 하기 위하여 length를 캐시
for (var i = 0; max = someArray.length; i < max; i++){
    //...
}
// 단일 var 패턴을 다르는 length 캐시
function looper() {
    var i = 0,
        max,
        myarray = [];
    // ...

    for (i = 0, max = myarray.length; i < max; i++) {
        // ...
    }
}

for-in 루프

프로퍼티 순회시 프로토타입 체인으로 상속되는 프로퍼티들을 걸러내기위해 hasOwnProperty() 메서드를 사용하라.

// 객체
var man = {
    hands: 2,
    legs: 2,
    heads: 1
};

// 코드 어딘가에서 모든 객체에 매서드 하나가 추가되었다.
Object.prototype.clone = function() {};
// 안티패턴:
// hasOwnProperty()를 확인하지 않은 for-in 루프
for (var i in man) {
    console.log(i, " : ", man[i]);
}

/*
콘솔 출력
hands  :  2
legs  :  2
heads  :  1
clone  :  ƒ () {}
*/
// 프로토타입 프로퍼티를 걸러낸 코드
for (var i in man) {
    if(man.hasOwnProperty(i)) {
        console.log(i, " : ", man[i]);
    }
}

/*
콘솔 출력
hands  :  2
legs  :  2
heads  :  1
*/

Object.prototype에서 hasOwnProperty()를 호출하는 것도 또 하나의 패턴이다.

for (var i in man) {
    if (Object.prototype.hasOwnProperty.call(man, i)) {
        console.log(i, " : ", man[i]);
    }
}

/*
콘솔 출력
hands  :  2
legs  :  2
heads  :  1
*/

내장 생성자 프로토타입 확장하기 / 확장하지 않기

프로토타입을 확장하는 것은 혼동을 일으킬 수 있기 때문에 일단 프로토타입을 확장하지 않는 것이 최선이다. 만약 프로토타입을 확장하려 한다면 다음과 같은 조건을 모두 만족시켜야 한다.

  1. 해당 기능이 ECMAScript의 향후 버전이나 자바스크립트 구현에서 일관되게 내장 메서드로 구현될 예정이다.
  2. 이 프로퍼티 또는 메서드가 이미 존재하는지, 이미 코드 어딘가에 구현되어 있거나, 지원 브라우저 중 일부 자바스크립트 엔진에 내장되어 있는지 확인한다.
  3. 이 변경 사항을 명확히 문서화하고 팀내에서 공유한다.

프로토타입을 확장하는 패턴

if (typeof Object.prototype.myMethod !== "function") {
    Object.prototype.myMethod = function () {
        //...
    };
}

switch 패턴

switch문의 가독성과 견고성을 향상시킬 수 있는 패턴

var inspect_me = 0,
    result = '';

switch (inspect_me) {
case 0:
    result = "zero";
    break;
case 1:
    result = "one";
    break;
default:
    result = "unknown";
}
  • 각 case문을 switch문에 찾추어 정렬한다.
  • 각 case문 안에서 코드를 들여쓰기한다.
  • 각 case문은 명확하게 break;로 종료한다.
  • break문을 생략하여 통과(fall-through)시키지 않는다. fall-through를 시키는게 최선이라면 반드시 기록을 남겨라.
  • 상응하는 case문이 하나도 없을 때도 정상적인 결과가 나올 수 있도록 default;문을 꼭 써라.

암묵적 타입캐스팅 피하기

암묵적 타입캐스팅으로 인한 혼동을 막기 위해서, 항상 표현식의 값과 타입을 모두 확인하는 ===와 !== 연산자를 사용하라.

var zero = 0;
if (zero === fase) {
    // 실행되지 않음
}

// 안티패턴
if (zero == false) {
    // 실행됨
}

eval() 사용 피하기

eval() 함수는 임의의 문자열을 받아 자바스크립트 코드로 실행한다.

  • 코드가 런타임에 결정되는게 아니라면 eval()을 쓸 필요가 없다.
  • 코드가 런타임에 결정되더라도 eval()을 사용하지않고 다른 방법으로 프로그래밍을 해야한다.
  • ex) 동적으로 생성되는 프로퍼티에 접근하는 코드는 eval()을 사용하지 않고 대괄호 표기법을 사용할 수 있다.
var obj = {};
var prop = "hands";

// 안티패턴
eval("obj." + prop + " = 2");

// 권장안
obj[prop] = 2;

eval()을 반드시 사용해야 한다면 var로 선언된 변수가 지역변수가 되게 하기 위해 new Function()을 사용하거나 eval()의 호출을 함수안에서 사용하라.

// a는 전역변수가 됨.
eval("var a = 1");

// b는 지역변수가 됨.
new Function("var b = 2");

// c는 지역변수가 됨.
(function () {
    eval("var c = 3");
}());

parseInt()를 통한 숫자 변환

parseInt()는 두번째 매개변수로 기수를 받는다. 이 기수를 생략하는 경우가 많은데, 일관성 없고 예측을 벗아나는 결과를 피하려면 항상 기수 매개변수를 지정해 주어야한다.

var month = "06",
    year = "09";

parseInt(month, 10);
parseInt(year, 10);

코딩 규칙

  • 들여쓰기 : (JSLint의 기본값은 4)
  • 중괄호 : 중괄호는 생략할 수 있을 때도 항상 써라.
  • 중괄호의 여는 위치 : 여는 중괄호는 선행하는 명령문과 동일한 행에 두자.
  • 공백 : 코드 단위를 분리할 때 빈행을 사용한다.
// 안티패턴
var d= 0.
    a =b+1;
if (a &&b&&c) {
d=a %c;
    a+= d;
}

// 권장안
var d = 0,
    a = b + 1;
if (a && b && c) {
    d = a % c;
    a += c;
}

명명 규칙

  • 생성자로 사용할 함수: 첫 글자가 대문자인 카멜표기법
  • 함수: 첫글자가 소문자인 소문자 카멜표기법
  • 변수: 소문자를 쓰고 및줄로 단어를 분리
  • 변경 불가한 변수: 대문자를 쓰고 및줄로 단어를 분리
  • 비공개 메서드나 프로퍼티: 변수명 앞에 _를 붙힘

주석 작성

문제에 깊이 몰입했을 때는 코드가 어떤일을 하는지 명백히 보이는것 같지만, 시간이 지나 다시 코드를 보면 코드의 동작이 기억나지 않는다.

  • 주석과 함수명 또는 프로퍼티명만 읽고도 코드가 어떤 일을 하는지 이해할 수 있어야 한다.
  • 주석은 코드를 읽을 미래의 독자에게 주는 힌트이다.

동료의 리뷰

동료의 리뷰를 거치면 좀더 명확한 코드를 작성하는데 도움이 된다.

장점

  • 동료의 리뷰를 통해 결과적으로 코드의 품질이 개선된다.
  • 코드 작성자와 리뷰어 모두 지식을 교환 및 공유하게 된다.
  • 서로의 경험과 개개인의 접근방식을 배우게 된다.

참고 도서
제목: 자바스크립트 코딩 기법과 핵심 패턴(JavaScript Patterns)
지은이: 스토얀 스테파노(stoyan stefanov) 지음
옮긴이: 김준기, 변유진 옮김
출판사: 인사이트