본문 바로가기

Development/Coding

유능한 C# 개발자가 착각하는 나쁜 자바스크립트 습관. Part 1

http://enterprisejquery.com/ 에 
How Good C# Habits can Encourage Bad JavaScript Habits 라는 제목으로 올려진 연재글 발.역.

* Java 개발자나 C# 개발자들이 자바스크립트를 코딩할 때 자주 쓰이는 잘못된 방법을 예를 통해 설명하고 옳은 방법(자바스크립트 다운 방법)을 제시해주고 있는 좋은 글이네요.






파트 1


Introduction

그래. 기존의 랭귀지(자바나 씨샵 등)를 공부하던 사람이 클라이언트 사이드 스크립트를 접근하는데 있어 간과하는 부분이 있다고... 님하의 애메모호한 버그들은 자바스크립트를 제대로 배우지 못한 탓이겠지.

야후의 자바스크립트 아키텍트 더글라스 크록포드님의 이렇게 말씀하셨다네.
“…it turns out that if you have absolutely no idea what you’re doing in the language you can still generally make things work.”

그래서! 제이쿼리를 사용하지만 자바스크립트를 배울 필요가 없는 중급 이상의 개발자에게 이글이 도움이 될 거야.

제이쿼리는 자바스크립트 라이브러리야. 이말은 즉 자바스크립트를 사용한다는 말이지. 자 시간을 가지고 자바스크립트를 공부해 보자구.  


1. Having Variables & Functions in Global Scope

씨샵의 경우 전역변수를 제한하고 있지. 이걸 구지 나쁜 자바스크립트 습관이라고 하긴 그렇지만 많은 기존 개발자들이 전역변수나 전역에 걸쳐진 함수를 효과적으로 사용하고 있지 못하고 있어. 

변수를 안쓰면 글로벌스코프를 침해하지 않는다는 것은 알겠어. 그렇다고 변수 안쓸순 없자나.

씨샵에서 하던 것처럼 네임스페이스라는 개념이 자바스크립트에도 있다면 여러 라이브러리들과 브라우저 확장 변수들과 내가 만든 변수들이 충돌할 위험이 줄어들지 않을까?

▶ 나쁜 예

변수와 함수를 선언하는 다음 코드들을 봐봐. 

var iAmGlobal = "Keep these to a minimum";
 
iAmGlobalToo = "Bad";
 
function oldSchoolWay() {
    //Placed in global scope when executed
    iAmGlobalNooo = "Really Bad"; 
 
    console.log( "Bad oldSchoolWay Function" );
}
 
//Bad way to prevent namespace clashing
function namespace_oldSchoolWay() {
    console.log( "Worse oldSchoolWay Function" );
}
 
//Functions 
window.oldSchoolWay();
window.namespace_oldSchoolWay();
 
//Global variables are available off window object
console.log( window.iAmGlobal );
console.log( window.iAmGlobalToo );
console.log( window.iAmGlobalNooo );

▷ 결과

light:24 Bad oldSchoolWay Function
light:29 Worse oldSchoolWay Function
light:37 Keep these to a minimum
light:38 Bad
light:39 Really Bad

결과를 보다시피 iAmGlobal, iAmGlobalToo, iAmGlobalNooo 은 모두 전역변수야. 나는 oldSchoolWay 함수 안에 있는 iAmGlobalNooo 는 아닌줄 알았는데... 이유는 "선언"되지 않았기 때문이래. 이런 애메함을 명확하게 하는 방법은 당연히도 개발자가 직접 선언을 하는 것이겠지!

그리고 좀 멍청하게도 namespace_ 따위를 붙이는 것은 아무 도움이 안된다는거야. 말인 즉, 그래도 전역함수라는 것이지.

자 이 문제를 해결하는 가장 쉬운 방법은 네임스페이스 객체를 생성하고 그 안에 함수나 변수를 넣는 것이지.

- Object Literal (All Public)

오브젝트 리터럴(객체리터럴)은 객체를 생성하는 가장 편리한 방법이지. (미친 자바스크립트 ㅋㅋ) 구조를 보면 딱 JSON 형태 그대로야. 하지만 조금은 다르다네... Ben Alman 님이 이것에 대해 세심하게 글을 올렸어. http://benalman.com/news/2010/03/theres-no-such-thing-as-a-json/

객체리터럴안에 프로퍼티와 메소드를 추가할 수 있어. 당연히. 모든 프로퍼티와 메소드는 퍼블릭하게 접근할 수 있는 상태가 되어 있고.

//Object Literal declaring properties and methods
var skillet = {
    //public property
    ingredient: "Bacon Strips",
     
    //public method
    fry: function() {
        console.log( "Frying " + this.ingredient );
    }
};
console.log( skillet.ingredient ); //Bacon Strips
skillet.fry(); //Frying Bacon Strips
 
//Adding a public property to an Object Literal
skillet.quantity = "12";
console.log( skillet.quantity ); //12
 
//Adding a public method to an Object Literal
skillet.toString = function() {
    console.log( this.quantity + " " + 
                 this.ingredient );
};
skillet.toString(); //12 Bacon Strips?

▷ 결과

light:28 Bacon Strips
light:25 Frying Bacon Strips
light:33 12
light:37 12 Bacon Strips

위 코드에서 skillet 은 객체리터럴로 세팅되어 있어. 처음엔 하나의 프로퍼티와 하나의 메소드로 구성되어 있지만 퍼블릭하게 접근이 가능하기 때문에 프로퍼티나 메소드를 추가할 수 있지. 음... 그저 그런데 좀 있어보이는데?
PHP스쿨에서 이와 관련된 논의가 있었더군.
http://www.phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=52892&page=1

Pros

- 고유한 네임스페이스를 생성하여 글로벌 영역을 침해하지 않도록.
- 객체리터럴을 통해 나중에 변수나 함수를 추가할 수 있어야 한다.
- 모든 프로퍼티와 메소드가 퍼블릭하게 해줘.

Cons

- 닥치고 오브젝트 리터럴을 사용하자.
- 프라이빗 프로퍼티나 메소드는 안된다고...


- 자동실행 익명함수를 사용하는 방법(파트 1)(프라이빗)

자바스크립트은 () 는 함수의 실행을 의미한다는 것을 되세기고!!!
글로벌 스코프를 보호하는 또 다른 방법은 익명함수를 사용하는것이지! 많은 자바스크립트 라이브러리에서 사용하는 방법이기도 하고... 참 쉽죠잉~

//Self-Executing Anonymous Function: Part 1 (All Private)
(function() {
    //private variable
    var ingredient = "Bacon Strips";
 
    //private function
    function fry() {
        console.log( "Frying " + ingredient );
    }
     
    fry();
}());
 
//Variables not accessible
console.log( window.ingredient ); //Undefined
 
//Functions not accessible
try {
    window.fry(); //Throws Exception
} catch( e ) {
    //Object [object DOMWindow] has no method 'fry'
    console.log( e.message ); 
}
 
//Can't add additional functionality

▷ 결과

light:25 Frying Bacon Strips
light:32 undefined
light:39 Object [object DOMWindow] has no method 'fry'

ingredient 변수와 fry 함수가 익명함수에 같혀있고 스스로 실행이 되는 구조를 가지고 있어. 스스로 실행이 되기 때문에 코드가 제 역할을 한다는 것을 다시한번 기억하자고...
그리고 익명함수에 갖힌 변수나 함수는 익명함수가 실행되는 동시에 사라지게 되지. 즉 글로벌 영역에서는 익명함수안의 코드가 접근이 안된다는 것이야. 짱!

Pros

- 외부로부터 코드를 숨기야 한다.
- 일회성 함수를 만들어 실행되는 즉시 사라지게해야해.
- 객체리터럴을 사용하지 않고 해줘.

Cons

- 익명함수를 사용해 코드를 숨길 수 있다.
- 약간 복잡한 기술이 필요하지만 익명함수를 이해하면 껌.


- 공개모듈 패턴을 사용하는 방법 (프라이빗, 퍼블릭)

공개모듈 패턴은 자동실행 익명함수의 개념과 비슷하대. 하지만 실행 결과를 변수에 담아두기 때문에 약간은 다르지. 클로저를 공부했다면 벌써 이해가 되었겠지? 클로저에 대해선 모질라 개발자 사이트의 내용을 참고해두도록 해.
https://developer.mozilla.org/en/JavaScript/Guide/Closures

클로저의 정의는 이렇게 되어 있더라고...

“A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.”

됏고. 코드를 보자!

//Revealing Module Pattern (Public & Private)
var skillet = (function() {
    var pub = {},
        //Private property
        amountOfGrease = "1 Cup";
 
    //Public property    
    pub.ingredient = "Bacon Strips";
 
    //Public method
    pub.fry = function() {
        console.log( "Frying " + pub.ingredient );
    };
 
    //Private method
    function privateWay() {
        //Do something...
    }
 
    //Return just the public parts
    return pub;
}());
 
//Public Properties
console.log( skillet.ingredient ); //Bacon Strips
 
//Public Methods
skillet.fry();
 
//Adding a public property to a Module
skillet.quantity = 12;
console.log( skillet.quantity ); //12
 
//Adding a public method to a Module
skillet.toString = function() {
    console.log( skillet.quantity + " " + 
                 skillet.ingredient + " & " + 
                 amountOfGrease + " of Grease" );
};
 
try {
    //Would have been successful, 
    //but can't access private variable
    skillet.toString();
} catch( e ) {
    console.log( e.message ); //amountOfGrease is not defined
}

▷ 결과

light:42 Bacon Strips
light:29 Frying Bacon Strips
light:49 12
light:63 amountOfGrease is not defined

와우! 복잡하자나...
공개모듈 패턴은 위 코드에서 pub 변수를 return 한다는 것이 핵심이네. 자동실행 익명함수의 확장 형태를 가지고 있으니 꼼꼼히 보자고. 퍼블릭, 프라이빗 억세스를 가능하게하는 패턴이니 좋군!

Pros

- 퍼블릭, 프라이빗 억세스가 가능하도록 쫌.
- 좀 쉽게 이해하게 해줘.
- 객체 리터럴 표현을 사용하지 않고 할 수 있게.

Cons

- 자 새로 생성된 퍼블릭 함수가 객체 내의 프라이빗 프로퍼티에 접근이 불가능하게 되었지?


- 자동실행 익명함수를 사용하는 방법(파트 2)(프라이빗, 퍼블릭)

자동실행 익명함수에서도 프라이빗 영역을 생성할 수 있다네. 그럼 공개모듈 패턴처럼 코딩을 하면 프라이빗한 영역을 생성할 수 있다는 것이지? 음. 게다가 네임스페이스를 쉽게 확장이 가능하다면?
게다가! 지긋한 $ 표시를 글로벌 영역으로부터 보호하는 아래의 방법을 보자고...

//Self-Executing Anonymous Func: Part 2 (Public & Private)
(function( skillet, $, undefined ) {
    //Private Property
    var isHot = true;
 
    //Public Property
    skillet.ingredient = "Bacon Strips";
     
    //Public Method
    skillet.fry = function() {
        var oliveOil;
         
        addItem( "\t\n Butter \n\t" );
        addItem( oliveOil );
        console.log( "Frying " + skillet.ingredient );
    };
     
    //Private Method
    function addItem( item ) {
        if ( item !== undefined ) {
            console.log( "Adding " + $.trim(item) );
        }
    }    
}( window.skillet = window.skillet || {}, jQuery ));
 
//Public Properties
console.log( skillet.ingredient ); //Bacon Strips
 
//Public Methods
skillet.fry(); //Adding Butter & Fraying Bacon Strips
 
//Adding a Public Property
skillet.quantity = "12";
console.log( skillet.quantity ); //12
 
//Adding New Functionality to the Skillet
(function( skillet, $, undefined ) {
    //Private Property
    var amountOfGrease = "1 Cup";
     
    //Public Method
    skillet.toString = function() {
        console.log( skillet.quantity + " " + 
                     skillet.ingredient + " & " + 
                     amountOfGrease + " of Grease" );
        console.log( isHot ? "Hot" : "Cold" );
    };    
}( window.skillet = window.skillet || {}, jQuery ));
 
try {
    //12 Bacon Strips & 1 Cup of Grease
    skillet.toString(); //Throws Exception
} catch( e ) {
    console.log( e.message ); //isHot is not defined
}

▷ 결과

light:44 Bacon Strips
light:38 Adding Butter
light:32 Frying Bacon Strips
light:51 12
light:60 12 Bacon Strips & 1 Cup of Grease
light:71 isHot is not defined

보시다시피 자동실행 익명함수에 파라미터를 추가해서 실행하는 구조야. 첫번째 파라미터는 좀 낯설지만 3 항연산자와 비슷하군... 해석을 하면 skillet 이 전역변수에 있으면 skillet 사용하고 없으면 빈 객체 리터럴을 생성하세요 라는 소리겠지.
아래로 새로운 자동실행 익명함수도 같은 테크닉을 사용하고 있는데 이건 퍼블릭 메소드가 필요할 때 사용할 수 있는 패턴이 되겠군.

두번째 파라미터는 jQuery 군. jQuery 가 $ 로 참조되는 코드가 위와 같다는 것이겠지.

세번째 파라미터는 undefined 으로 되어 있어. 왜 그랫을까... undefined 도 다른 의미로 사용할 수 있다는 것을 보여주기 위해서 그랬다네. 예를 들어 undefined 를 true 로 처리하고 싶을 경우에 undefined 을 파라미터로 전달해서 처리하겠다는 것이지. 미친...

Pros

- 자동실행 익명함수에서 퍼블릭, 프라이빗 둘다 쓰게 해주세요.
- 객체 리터럴 선언 안하고 쓰게.
- undefined 을 다른 의미로 오버라이드 할 수 있나?
- $ 을 다른 라이브러리와 충돌나지 않게 쓰게 해줘.
- "window.namespace = window.namespace || {}" 테크닉을 써봐.
- 훌륭한 다른 라이브러리, 위젯, 플러그인 등에서 쓰이는 패턴을 보여줘.

Cons

- 좀 더 복잡해지지만 이 정도 모르면 답없음. 몰라.

좀더 많은 정보를 원하나 Klaus Komenda 님의 포스트를 살펴봐. 제목도 거창하다. JavaScript Programming Patterns!
http://www.klauskomenda.com/code/javascript-programming-patterns/


☞ 좋은 습관

인정할지 모르지만 자바스크립트에서도 변수를 선언하는 것은 정말 중요한 일이고 전역변수를 만드어내는 것은 신중히 고려해야할 일이라네. 상황에 따라 필요에 따라 위 코딩 패턴을 익히고 사용한다면 좋겠어. 이 패턴이 모든 해결책은 아니지만 목적과 결론을 참고해 좋은 코딩을 해보자고...






2. Not Declaring Arrays & Objects Correctly

자바스크립트는 프로토타입 언어이기 때문에 씨샵처럼 new 연산자를 가지고 있다 할지라도 객체나 배열을 생성할 때 new 연산자를 통해 객체나 배열을 생성하지 마.

▶ 나쁜 예

자바스크립트에서 객체를 생성할 때 보통 아래처럼 했지? (책에서도 그렇게 나오고...)

//Bad way to declare objects and arrays
var person = new Object(), 
    keys = new Array();

new 키워드는 기존 클래식 개발자들을 위해 대충 생색내기용으로 만들어 놓은 연산자라네. 근데 이것 때문에 더 헷갈리게 된 현실. 네이티브한 코딩 방법은 아래와 같다능...

▷ 좋은 예

위 문법 대신 객체와 배열을 생성하는 리터럴을 사용하셈.

//Preferred way to declare objects and arrays
var person = {}, 
    keys = [];

객체 리터럴의 강점을 사용할 수 있기 때문에 적극 추천함.

var person = {
        firstName: "Elijah",
        lastName: "Manor",
        sayFullName: function() {
            console.log( this.firstName + " " + 
                this.lastName );
        }
    }, 
    keys = ["123", "676", "242", "4e3"];

객체 리터럴에 대해서 아래의 글이 참고가 될지도...
http://www.phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=52892&page=1


☞ 좋은 습관

new 연산자 대신 리터럴 문법을 사용하자!




Conclusion

다음 포스트가 기대 됨.