자바스크립트, 유지보수 가능한 코드의 작성
패턴
패턴은 일반적인 문제에 대한 해결책!
- 더 나은 코드를 작성하는데 도움
- 팀 커뮤니티 케이션에 도움(코드의 구조를 일일이 설명하지 않아도 됨)
패턴 종류
- 디자인 패턴 : 특정 언어에 종속되지 않은 패턴들. 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
*/
내장 생성자 프로토타입 확장하기 / 확장하지 않기
프로토타입을 확장하는 것은 혼동을 일으킬 수 있기 때문에 일단 프로토타입을 확장하지 않는 것이 최선이다. 만약 프로토타입을 확장하려 한다면 다음과 같은 조건을 모두 만족시켜야 한다.
- 해당 기능이 ECMAScript의 향후 버전이나 자바스크립트 구현에서 일관되게 내장 메서드로 구현될 예정이다.
- 이 프로퍼티 또는 메서드가 이미 존재하는지, 이미 코드 어딘가에 구현되어 있거나, 지원 브라우저 중 일부 자바스크립트 엔진에 내장되어 있는지 확인한다.
- 이 변경 사항을 명확히 문서화하고 팀내에서 공유한다.
프로토타입을 확장하는 패턴
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) 지음
옮긴이: 김준기, 변유진 옮김
출판사: 인사이트