Callback pattern


 자바스크립에서 함수는 객체다. 함수를 다른 함수의 인자로 넘겨 줄 수 있다. 함수에 다른 함수를 넘겨면 넘겨받은 함수에서 실행 할 것이다. 이때 넘겨 받은 함수를 콜백 함수라고 한다. 아래 코드로 확인해 보자.


[코드1] 콜백 코드 예제



 필자는 이와 같은 패턴을 공통 화면에서 리턴 받을 때 많이 사용하고는 했다. 화면마다 사용하는 공통된 기능을 만들지 않고 콜백 함수를 이용해서 리턴받아야 하는 함수를 파라메터로 넘겨줘서 해당 함수에서 콜백 함수를 호출하면 하나의 기능으로 여러 페이지에서 일관되게 사용할 수 있게 하기 때문이다. 다음 코드에서 보다 자세한 사용 방법에 대해 알아 보자.


[코드2] Callback 함수의 활용


 "코드2"에서와 같이 하나의 로직을 호출 하지만 콜백 함수를 어떻게 세팅해서 넘기는지에 따라서 해당 출력값이 확연하게 다르게 출력이 되고 있는 것을 확인 할 수 있다.


출력 값은 writePlus 콜백 함수를 넘겼을 때는 101 ~ 200까지 출력이 되고 writeMinus 콜백으로 넘겼을 때는 99 ~ 0까지 출력이 되고 있다. 이렇게 콜백을 넘겨 주는 차이로 확연하게 다른 로직이 구현되므로 중요 핵심 로직으로 결과 값을 받아와서 넘겨 받은 콜백에서 원하는 작업을 할 수 있게 하면 확장성있는 함수를 만들 수 있을 것이다.


자신을 정의 하는 함수

(Lazy function definition - Self function definition)


 함수는 동적으로 정의할 수 있고 변수에 할당할 수도 있다. 이전 변수에 새로운 함수를 할당한다면 변수는 새로운 함수를 가리키게 된다. C에서 선언 없이 포인터만 수정하는 것과 같다. 설명만으로는 장황하니 아래 코드를 보도록 하자.


[코드1] 내부에서 함수를 재 정의하는 코드 예제


 이 패턴은 함수 초기화가 필요하고 그 초기화가 한번만 필요한 곳에서 사용할 수 있다. 불 필요한 작업을 반복하지 않아도 된다. 





Javascript 함수 파라메터 생성 패턴



  Javascript platform 환경에서 개발을 하든지, Java, C#, C platform 환경에서 개발을 하든지 지향해야 하는 패턴이라고 할 수 있겠다. 바로 함수에게 넘겨주는 파라메터에 관한 패턴이다. 이 패턴을 왜 지향해야 하는지에 대해서 알아 보자.


 소프트웨어를 개발하는데 있어서 구축시 변경되는 요구사항과 유지보수에서 변경되는 사항때문에 함수는 변경이 될 수 밖에 없는 환경이다. 예를 들어 "코드1"과 같이 함수가 생성이 되었다고 가정하자.


[코드1] 함수 생성 코드 예제


 위와 같이 코드로 생성되어 개발 하다가 변경사항 때문에 파라메터를 더 추가해야 하는 상황에 빠지게 되었다. 그리고 문제는 파라메터 순서를 어떻게 정해야 하는지도 고민에 빠지게 한다. 이런 저런 고민 끝에  "코드2"와 같이 수정하게 되었다. 


[코드2] 변경된 함수 생성 코드 예제


 "코드2"에서 추가되는 파라메터 변수의 중요도 때문에 중간에 끼워 넣게 되었다. 그래서 이 함수를 사용하는 다른 곳에서도 수정이 불가피하게 되었다. 그래서 소스를 찾아 다니면서 수정을 하게 되었고 추가된 파라메터가 필요치 않는 곳에서는 "''" 공백 문자로 바꿔주는 수고까지 하게 되었다. 이런 케이스가 합리적으로 보이는가? 이제 한번 개선시켜 보자. "코드3"을 살펴 보도록 하자.


[코드3] 객체로 파라메터 함수 생성 코드 예제


 "코드3"과 같이 선언하면 다음과 같은 장점이 있다.

  • 매개변수와 순서를 기억할 필요가 없다.
  • 선택적인 매개변수를 안전하게 생략할 수 있다. ( 이름 지원하기 위해서는 해당 함수에서 검증하는 단계에서 에러 처리를 해줘야 가능 하다. )
  • 읽기 쉽고 유지보수가 편하다.
  • 매개 변수를 추가하거나 제거하기가 편한다.
단점은 아래와 같다.
  • 매개변수의 이름을 기억해야 한다.
  • 프로퍼티 이름은 압축되지 않는다. ( 압축 프로그램에서 매개 변수의 이름은 단순한 문자로 치환하여 전체 소스 크기를 줄이는 작업이 안된다. 그렇지만 일부 유틸에서는 프로퍼티의 매개변수도 압축을 해준다. - 구글 Utility - 그렇지만 배포를 해야 하는 상황에서는 적합한 모델이 아니다. 일부 Intellisense가 지원되는 도구에서는 연상되지 않는 프로퍼티명으로 나오게 하기 때문이다. )


 함수를 생성함에 있어서 최소한의 파라메터를 받고 넘겨 줄 수 있는 방안을 고려하여 생성 하도록 하자. 더군다나 라이브러리를 배포할 때는 더욱더 신경 써야 할 것이다. 한번 배포가 되고 나면 수정하는데 비용이 상상이상으로 많이 들기 때문이다. 

초기화 시점의 분기 - 단 한번의 확인
(One check & Memoization Pattern)



 프로그램을 개발하다가 보면 기본 베이스를 깔고 개발해야 하는 부분이 있다. 예를 들면 프로그램은 H/W가 PC이고 운영 체제는 Windows 또는 Linux에서 실행이 된다고 하는 전제 조건을 말이다. 그렇다면 자바스크립트 개발에서는 어떤 전제가 둘 수 있을 까~~


 대체적으로 브라우저의 파편화 때문에 지원여부를 확인해야 하는 사항이 있을 수 있을 것이다. IE에서 지원하는 Active-X기술로 Ajax을 지원하도록 하고 다른 브라우저에서는 다른이름의 내장 객체로 지원하도록 한다. 그리고 이 이외에 다른 사항들을 확인하기 위해서 초기에 확인 작업을 해야 할 것이다. 지원 여부와 상위 단계의 일관적인 프로그래밍 접근 방식을 위해 하위 단계에서 발을 굴려야 하는 것 처럼 말이다. - 백조 처럼 말이다


 자바스크립트에서는 브라우저가 처음 로딩이 될때 이와 같은 작업을 하거나 해당 객체가 처음 실행이 될때 작업할 수 있을 것이다. 문제는 핵심은 전제 조건에 대한 확인을 중복 확인하지 않도록 하는 것이다. 아래 코드에서 잘못된 예와 제안하는 예를 살펴 보자.


[코드1] 중복 확인 하는 코드 예제


 위 코드는 utils에서 브라우저 정보를 체크하는 부분이다. 그렇지만 호출이 될 때마다 체크를 하는 단점을 가지고 있다.  그래서 메모이제이션 패턴을 이용하여 위 코드를 한번의 확인을 재 사용하는 코드로 수정해 보도록 하겠다. 이와 같은 방식의 패턴이 나올 수 있는 전제 조건은 실행 환경에서 초기화가 이뤄진 상태(페이지가 로딩이 되면 초기화가 일어 나게 되어 있다.)에서 전제 조건( 예로 브라우저)이 아무 이유없이 바뀔 수있는 구조가 아니라는 것이다. 그럼으로 한번의 확인된 사항 - 실행 비용이 많이 드는 사항 역시 마찬 가지다 - 을 값을 가지고 있다가 다음 호출에서 저장된 값을 리턴해주는 패턴으로 가도록 하겠다. (Memoization pattern) 아래 코드를 확인해 보자.



[코드1] Memoization pattern으로 중복 확인 제거된 코드 예제


 "코드2"는 같은 Utils 객체에서 BrowserInfo를 체크해서 undefined값이면 브라우저 버전을 체크 하도록 하고 체크된 정보를 가지고 있도록 한다. 그리고 다음에 호출되었을 때 캐시되어 있던 정보를 바로 리턴해 주는 방식으로 구현이 되었다. 이와같이 기본 전제 조건에 대해서 중복 체크하지 않고 한번만 체크할 수 있도록 하여 최적화를 진행할 수 있을 것이다.





객체 생성시 new를 강제할 수 있는 패턴



 자바 스크립트 라이브러리를 사용할 때 new 키워드를 사용하지 않고 객체로 선언하였다고 생각하고 사용할 때가 있다.  이런 오류 때문에 정상적으로 동작하지 않고 디버깅에 많은 시간을 할 애 할 수 있다. 그렇지만 new를 사용하고 객체를 생성하지 않더라도 자체적으로 새로운 객체를 생성해서 반환해 주는 패턴이 있다. 우선 아래 코드를 보고 이해를 돕고자 한다.

<script type="text/javascript">
    //객체로 사용할 함수
    function NewInstance1() {
        this.name = 'name';
        this.age = 0;
    }
 
    //new를 사용해서 객체 생성
    var newObject1 = new NewInstance1();
    console.log(newObject1.name);
 
    //new를 사용하지 않고 객체 생성
    var newObject2 = NewInstance1();
    console.log(newObject2.name);   //에러 발생
</script>

[코드1] 객체 생성 및 사용 예제


 위 코드에서는 일반적인 객체를 선언하고 두번째 코드에서 new를 사용해서 객체의 인스턴스를 생성하여 값을 정상적으로 사용할 수 있었다. 하지만 두번째 객체의 인스턴스 생성시 new를 사용하지 않고 사용하면 에러가 발생하게 된다. Javascript 엔진이 new 키워드를 만들면 안에서 사용하는 this의 범위가 달라지게 하는 특별한 수행 덕분에 발생하는 이유다. 이와 같은 패턴으로 선언되어 있으면 개발자의 실수로 인해 전체 웹 페이지에 영향을 미칠 수 있는 잠재적인 오류가 내재화 되어 있다. 그렇다면 어떻게 하면 실수를 미연에 방지 할 수 있을까. 아래와 같은 패턴으로 실수를 방지 할 수 있다.  아래 코드를 보자.

//객체로 사용할 함수
function NewInstance2() {
    if (!(this instanceof NewInstance2)) {
        return new NewInstance2();
    }
 
    this.name = 'name';
    this.age = 0;
}
 
//new를 사용해서 객체 생성
var newObject3 = new NewInstance2();
console.log(newObject3.name);
 
//new를 사용하지 않고 객체 생성
var newObject4 = NewInstance2();
console.log(newObject4.name);   //에러 발생

[코드2] self 인스턴스 생성 패턴



 위와 같은 코드로 만들어진 객체는 개발자의 실수를 방지할 수 있게 할 수 있을 것이다. 그리고 prototype을 통해 추가된 프로퍼티들을 사용할 수 있는 장점이 있다.



소스 코드 자체에 주석과 직관적인 코딩으로 충분히 파악이 가능할 것으로 예상하므로 별도의 설명을 생략하도록 하겠습니다. 포스트의 내용이 장황한 설명 보다는 주석과 소스코드 자체 만으로도 이해할 수 있도록 하기 위해 노력하였습니다.. 실제 개발에서도 필요한 소스는 단순히 Copy & Paste 만으로도 사용할 수 있습니다. 그리고 주석을 이용해 nDoc이나 별도의 자동 Document 제작 유틸로 API 문서를 만드는 데에도 도움이 되었으면 한다. 
※ DOC에 대한 프로그램 정보 Util link

ing™       


이전시간에 이어서 패턴 개론에 대해서 이어가도록 하겠다.

 

주석

- 모든 변수, 모든 행에 주석을 다는 게 아닌 특이하고 흥미로운 알고르즘이나 기법을 사용한 사용한 경우에 주석을 단다.
  주석은 코드를 읽을 미래의 독자에게 주는 힌트

 

API 문서 작성

아래는 JS Doc를 자동으로 생성해 주는 무료 툴이다. 요구하는 형식에 맞게 주석을 달면 툴에서 자동으로 해당 API 문서를 만들어 낸다.
- JSDoc : http://code.google.com/p/jsdoc-toolkit

- YUIDoc : http://yuilibrary.com/projects/yuidoc

독자를 위한 문서 작성

- API 문서를 작성하라는 것은 초안일 뿐인 첫 번째 코딩에 대해서 문석 작성시 한번 더 살펴 독자 입장에서 살펴 보라는 것이다.

코드 또는 API 문서를 작성할 때 다른 누군가가 읽을 것이라고 생각하라는 것이다. 이 사실 자체만으로도 눈 앞의 문제를 해결하는 좀더 나은 방법을 생각해 보게 된다.

 

동료 리뷰

- 애자일에서 코드 품질을 높이는데 사용하는 방안이다.

자신이 작성한 코드를 옆에 있는 동료에게 검토를 부탁이나 설명을 해주는 것만으로도 가능하다. 이런 방법으로 서로의 경험과 개개인의 접근방식을 배우게 된다는 점에서도 장점을 찾을 수 있다.

 

출시 단계(개발완료 단계)의 압축

- 압축이란 자바스크립트 코드에서 공백, 주석 및 기타 중요하지 않은 부분들을 삭제함으로써 서버에서 브라우저로 전송되는 파일 크기를 감소시키는 공정이다.

압축에 관련된 툴로는 YUI컴프레서, 구글의 클로저 컴파일러, Microsoft Ajax Minifier 가 있다. – 대략 절반 정도의 압축을 할 수 있다.
      - http://yui.github.com/yuicompressor/, http://refresh-sf.com/yui/ online에서 압축 시켜 줌.
      - http://closure-compiler.appspot.com/home online 지원, https://developers.google.com/closure/compiler/?hl=ko
      - http://www.asp.net/ajaxlibrary/AjaxMinDocumentation.ashx, http://aspnet.codeplex.com/releases/view/40584 Download

- 압축 시 지역 변수는 임의의 변수로 치환되어 압축 한다.

 

JSLint 실행

- JSLint에서 제공되는 가이드 라인에서 위배되는 사항을 개발자가 알 수 있도록 알려주는 툴이다.

일반적인 개발 사항에서 좀더 가시적이고 오류가 없도록 도와주는 역할을 하는 도구다.

- Visual Studio JSLint를 컴파일 단계에서 점검해 볼 수 있도록 확장 기능을 제공하기도 한다.

유지보수 가능한 코드 작성

- 읽기 쉽게

- 일관적이게

- 예측 가능하도록

- 문서화( 코드 코멘트, 주석, 개발 문서 )

 

전역 변수 최소화

- vGlobal = ‘hello’;

console.log(vGlobal);      //”hello”

console.log(window.vGlobal);      //”hello”

console.log(window.[“vGlobal”]);      //”hello”

console.log(this.vGlobal);      //”hello”

위에서 “vGlobal”은 다 같은 곳을 바라보고 있다. 그렇지 않다면 'undefined’가 출력이 되었을 것이다.

 

전역 변수의 문제점

- 외부 라이브러리가 변경

- 다른 개발자가 변경

의도치 않게 기준 값이 변경되어 버그가 발생할 수 있다.

 

단일 var 패턴

- 함수 상단에서 var를 이용해 모든 변수를 선언 함

- 강제로 선언 시 함수 초기에 ‘use strict’을 사용하면 html5에서는 변수 선언을 강제 할 수 있다.

 

암묵적인 타입 캐스팅 피하기

- “===” 이나 “!==”을 사용한다.

 

eval() 피하기

- var property = “value”;     //안티패턴
  alert(eval(‘obj.’ + property));

  var property = “value”;     //권장
  alert(obj[property]);

- eval은 보안 문제와도 관련이 있다.
만약 네트워크로 가져온 코드를 실행 하는 케이스가 있을 때 잘못된 코드가 온다면 의도되지 않은 실행이나 개인 정보 유출의 위험성이 존재하게 된다.

 

- 위와 같은 패턴으로 실행되는 케이스가 더 있다.

     1. setTimeout(“execute code”, 1000);     //안티패턴

            -> 권장안 setTimeout(function(){ /* execute code */}, 1000);

           2. new Function() 생성자 – eval을 반드시 사용해야 한다면 new Function() 사용을 고려해 볼 수 있다.
              new Function()으로 실행되는 코드는 지역 함수의 유효범위 안에서 실행 – 자동으로 전역 변수로 만들어 주지는 않는다. (ex. var를 실행하면 eval에서는 전역 변수로 선언되지만 new Function()에서는 지역 변수로 선언된다.)

              var str = ‘var temp = 2; console.log(temp);’;
              new Function(str)();           - new Function()으로 실행 - 추천

              (function() {                     - eval로 실행 – 추천(격리되어 실행되게 한다)

                  eval(temp);

              }());

parseInt()를 통한 숫자 변환

- var month = ‘01’, year = ‘2013’
  month = parseInt(month, 10);
  year = parseInt(year, 10);

위와 같이 기수 매개 변수를 꼭 입력해야 한다.
ECMAScript 3에서는 8진수가 기본이고, ECMAScript 5에서는 변경이 되었다.

위에서 문제가 되는 부분은 시작하는 숫자가 ‘0’으로 시작하는 month 변수가 잘못 해석이 될 수 있다.

 

코딩규칙

- 코딩 규칙을 수립하고 준수하는 목적은 이를 통해 코드의 일관성이 유지되고 예측가능해지며 읽고 이해하기 훨씬 더 쉬워지기 때문이다.
  (코딩 규칙을 정하고자 한다면 조직 안에서 반발이 일어 날 수 있고 예외 적인 부분 때문에 막힐 수도 있지만 규칙을 수립하여 일관되게 준수하는 것 자체가 규칙의 세부 사항보다 훨씬 중요하다.)

   들여 쓰기, 중괄호, 여는 중괄호의 위치, 공백에 대해서 기준을 정하고 규칙을 준수하도록 노력해야 하겠다.

 

명명 규칙

- 코드를 좀더 예측 가능하고 유지보수하기 쉽게 만드는 방법이다. 즉 변수와 함수의 이름을 일관된 방식으로 만드는 것이다.

1. 생성자를 대문자로 하기

   var foo = new Person(); – 함수가 대문자로 시작하면 생성자로 사용할 function인지 파악할 수 있다. – 기본적으로 javascript에서는 function 이름을 소문자로 시작한다.

2. 단어 구분

   javascript에서는 단어 조합시 카멜 표기법(camel case)을 사용한다. 이 표기법의 사용은 
      - 생성자는 단어의 첫 부분을 대문자로 시작하고 나머지는 소문자
         ex. MyConstructor()
      - 함수와 메서드는 단어의 첫 부분은 소문자 이어지는 단어의 첫 문자는 대문자를 사용
         ex. myFunction(){…}
      - 변수명은 단어와 단어 사이에 “_”를 넣는 것을 추천한다. – 함수와 변수의 차이점을 금방 알아 볼 수 있다.
        ex. first_name,  company_name, …

3. 그 밖의 명명 규칙
      - 상수 성격의 변수 ( domain 안에서 값이 변경 되어서는 안 되는 변수 )

        ex. PI = 3.14, MAX_WIDTH = 800;
      - 내부에서 사용하는 비 공개 메서드
        ex. _getName()

+ Recent posts