반응형
반응형

   github를 사용법을 설명하기전에 sourcetree를 사용한 이유는 명령어를 사용해서 github를 사용할 수도 있다.하지만 그것보다 개발되어 있는 인터페이스 클라이언트들이 많아서 그 중에 무엇을 공부해볼까 생각중에 http://www.slant.co/topics/2089/compare/~sourcetree_vs_git-bash_vs_github-for-windows 에 Git-bash와 github windows, 그리고 sourcetree 의 장단점을 비교한 글이 기재되어 있는데 이것을 보고 sourcetree가 제일 낫다고 생각하여 sourcetree로 github사용법을 작성하였습니다.





1. http://git-scm.com/downloads 에 접속해 git을 설치해줍니다.





2. https://github.com 깃허브에 접속후 가입을 해줍니다.



 





3. 로그인을 한후에 New repository 클릭을 해서, 자신의 repository(저장소)를 설정해 주는 페이지로 이동하게 됩니다.




4.Repository name은 도메인을 설정해주는 부분입니다. 자신의 repository는 저장소를 의미하고 Description은 이 repository를 설명해주는 부분입니다.







5. 빨간색 칸의 주소는 SourceTree와 Github를 연동할 때 사용되는데, 이건 저장소의 도메인 주소입니다.








6. https://www.sourcetreeapp.com (소스트리 : gui 환경에서 git을 사용할 수 있게하는 프로그램) 접속하여 소스트리를 다운받는다. 여기까지면 github를 사용하기 위한 프로그램 설치가 완료 됩니다.



7. SourceTree를 실행하여 주면, 저장소를 만들어주기 위해 왼쪽 상단에 Clone/New를 선택하여 줍니다.




8. Create New Repository탭을 선택하여 Destination Path 항목에 자신이 작업할 디렉토리의 경로를 작성해주고 create 버튼을 클릭하여 repository를 생성해 줍니다.





9. 예시로 작업할 파일에 hellow_world라는 text파일을 생성하여줍니다. 이걸로 github에 push를 해줄것입니다.



10. uncommitted 라고 하나의 로그가 생기는데 이것을 클릭해서 왼쪽상단에 커밋을 눌러주면 이것을 자신의 로컬에 커밋을 하여 버전을 생성하게 됩니다. 하단에는 그 버전의 설명을 작성할 수있습니다.







11. 이제 원격저장소인 github에 연동하기위해 repositort탭에서 repository settings 항목을 클릭하한뒤 Add를 클릭하면 오른쪽 창이 뜨는데 5번에 복사한 주소를 URL/Path에 입력을 한 후에 OK버튼을 눌러줍니다.






12. 이제 hellow_world라는 텍스트를 github에 저장해주기위해 SourceTree 상단탭에서 Push를 클릭한뒤, 현재 branches인 master 항목에 체크를 해준뒤 OK버튼을 클릭해줍니다.




13. 다시 Github 홈페이지의 자신의 저장소로 접속하면 hellow_world가 정상적으로 Push된 것을 확인할 수 있다.




출처: http://hackersstudy.tistory.com/41 [공대인들이 직접쓰는 컴퓨터공부방]

반응형
반응형

RESTful API 에러 처리 팁

2018. 2. 1. 17:29

 개발 중에 에러 스택을 제공하면 생산성 및 디버그에 좋다는 아이디어를 얻어서 저번 프로젝트에 적용해 보았습니다. 대략 컨테이너를 시작할 때 show-error-stack이라는 프로필을 넘기면 오류가 발생시 스택 트레이스를 함께 넘겨주는 형태입니다. 

...... 일반적인 서비스 구조에서는 에러 스택정보를 API 에러 메세지에 포함 시키지 않는 것이 바람직 하다. 그렇지만, 내부 개발중이거나 디버깅 시에는 매우 유용한데, API 서비스를 개발시, 서버의 모드를 production과 dev 모드로 분리해서, 옵션에 따라 dev 모드등으로 기동시, REST API의 에러 응답 메세지에 에러 스택 정보를 포함해서 리턴하도록 하면, 디버깅에 매우 유용하게 사용할 수 있다.......
// 클라이언트에게 넘어가는 JSON 표현 예시 { "errorMessage" : "토큰이 만료되었습니다.", "errorStack" : "service.sp.spring.auth.exception.TokenExpiredException: JWT expired at 2018-01-31T18:14:50Z. Current time: 2018-02-05T18:37:51Z, a difference of 433261020 milliseconds. Allowed clock skew: 120000 milliseconds. at service.sp.spring.auth.JWTPayloadExtractor.extract(JWTPayloadExtractor.java:33) at service.sp.spring.auth.JWTInterceptor.preHandle(JWTInterceptor.java:36) at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:133) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:962) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)....." }
class ApiError{ String errorMessage; String errorStack; } interface ApiErrorBuilder{ ApiError build(Throwable e); } // 개발모드 @Profile("show-error-stack") @Component static class ApiErrorBuilderDev implements ApiErrorBuilder{ public ApiError build(Throwable e) return new ApiError(e.getMessage(), ExceptionUtils.getStackTrace(e)); } // 운영모드 @Profile("!show-error-stack") @Component static class ApiErrorBuilderProd implements ApiErrorBuilder{ public ApiError build(Throwable e) return new ApiError(e.getMessage(), null);// 운영모드에서는 스택에 null }

예제에서는 간단하게 스택 트레이스 정도이지만, exceptionType/customErrorCode/errorData 등을 부가적으로 전송할 수도 있습니다. 또 스택 트레이스는 너무 길으므로 대략 1500자 정도 잘라서 주는 것도 괜찮습니다.

... String simpleStackTrace = ExceptionUtils.getStackTrace(e).substring(0, 1500) + "....... (See log for detail.)"; return new ApiError(e.getMessage, simpleStackTrace); }

마지막으로 아래와 같은 형식도 괜찮은 것 같습니다.

// Json 표현 예시 { "errorMessage" : "유저가 없습니다.", "errorType" : "UserNotFoundException", "developerMessage" : "아이디가 발견되지 않았습니다. 정합성을 체크해주세요.", "customerMessage" : "잠시 후 다시 시도해주세요." }


반응형
반응형

파일 다운로드 창을 하나 만들어서 그 창에 있는 파일 여러개를 버튼 하나로 동시에 다운로드할 수 있는 기능을

만들려고 했다.


하지만 실패.. 구글링에 이것저것 찾아봤는데 http 뭐...response 이런거 통신할때는 파일 하나밖에 전송 안된다고  얼핏 본것같다. (아님말고..)

만약에 그렇게 구현하려면 아마 setTime으로 시간을 조정해서 해야할듯?


아무튼..  잘안되서 포기하고 파일들을 zip파일로 묶어 바로 전송하도록 했다.


참고로 ZipOutputStream은 한글지원이 안된다고 한다..



 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.util.zip.ZipOutputStream;
 
int bufferSize = 1024 * 2;
String ouputName = "test";
            
ZipOutputStream zos = null;
            
try {
                
    if (request.getHeader("User-Agent").indexOf("MSIE 5.5"> -1) {
        response.setHeader("Content-Disposition""filename=" ouputName + ".zip" + ";");
    } else {
        response.setHeader("Content-Disposition""attachment; filename=" + ouputName + ".zip" + ";");
    }
    response.setHeader("Content-Transfer-Encoding""binary");
    
                
    OutputStream os = response.getOutputStream();
    zos = new ZipOutputStream(os); // ZipOutputStream
    zos.setLevel(8); // 압축 레벨 - 최대 압축률은 9, 디폴트 8
    BufferedInputStream bis = null;
                
    
    String[] filePaths = {"filePath1","filePath2","filePath3"};
    String[] fileNames = {"fileName1","fileName2","fileName3"};
    int    i = 0;
    for(String filePath : filePaths){
        File sourceFile = new File(filePath);
                        
        bis = new BufferedInputStream(new FileInputStream(sourceFile));
        ZipEntry zentry = new ZipEntry(fileNames[i]);
        zentry.setTime(sourceFile.lastModified());
        zos.putNextEntry(zentry);
        
        byte[] buffer = new byte[bufferSize];
        int cnt = 0;
        while ((cnt = bis.read(buffer, 0, bufferSize)) != -1) {
            zos.write(buffer, 0, cnt);
        }
        zos.closeEntry();
 
        i++;
    }
               
    zos.close();
    bis.close();
                
                
catch(Exception e){
    e.printStackTrace();
}
 
 
cs



출처 : https://m.blog.naver.com/PostView.nhn?blogId=92211hyeon&logNo=220337059932&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F

반응형
반응형

자바스크립트의 스코프와 클로저

목차

스코프
호이스팅
클로저(Closure)
Reference

Overview

javascript-overview.png
기본적으로 자바스크립트는 ECMAScript 언어 명세를 따르고 있다. 이 명세 8장의 실행코드와 실행컨텍스트 부분에서 스코프에 관한 동작 방식을 확인할 수 있으며, 또 중요한 개념인 1급 객체로서의 함수는 그 특징을 명세의 전반적인 부분에서 나타내고 있다. 그리고, 클로저(Closure)에 대한 정의는 없다. 클로저는 자바스크립트가 채용하고 있는 기술적 기반 혹은 컨셉으로, 자바스크립트는 클로저를 이용하여 스코프적 특징과 일급 객체로서의 함수에 대한 명세를 구현한 것이다.

스코프

김춘수 시인의 "꽃"이라는 시를 보면, 어떤 하나의 몸짓은 이름을 통해 의미를 부여 받고 꽃이 된다.

프로그래밍도 마찬가지로 변수나 함수에 이름을 부여하여 의미를 갖도록 한다. 만약 이름이 없다면, 변수나 함수는 다만 그저 하나의 메모리 주소에 지나지 않는다. 그래서 프로그램은 "이름:값"의 대응표를 만들어 사용한다. 이 대응표의 이름을 가지고 코드를 보다 쉽게 이해하고, 또 이름을 통해 값을 저장하고, 다시 가져와 수정한다.

초기 프로그래밍 언어는 이 대응표를 프로그램 전체에서 하나로 관리했는데, 여기에는 이름 충돌의 문제가 있었다. 그래서 충돌을 피하기 위해, 각 언어마다 "스코프"라는 규칙을 만들어 정의하였다. 그렇게 스코프 규칙은 언어의 명세(Specification)가 되었다.

자바스크립트도 마찬가지로 자신의 스코프 규칙이 있다.

자바스크립트(ES6)는 함수 레벨과 블록 레벨의 렉시컬 스코프규칙을 따른다.

스코프 레벨

자바스크립트는 전통적으로 함수 레벨 스코프를 지원해왔고, 얼마 전까지만 해도 블록 레벨 스코프는 지원하지 않았다. 하지만 가장 최신 명세인 ES6(ECMAScript 6)부터 블록 레벨 스코프를 지원하기 시작했다.

함수 레벨 스코프

자바스크립트에서 var키워드로 선언된 변수나, 함수 선언식으로 만들어진 함수는 함수 레벨 스코프를 갖는다. 즉, 함수 내부 전체에서 유효한 식별자가 된다.

아래 코드는 아무런 문제없이 blue를 출력한다.

function foo() {
    if (true) {
        var color = 'blue';
    }
    console.log(color); // blue
}
foo();

만약 var color가 블록 레벨 스코프였다면, color는 if문이 끝날 때 파괴되고 console.log에서 잘못된 참조로 에러가 발생할 것이다. 그렇지만 color는 함수 레벨의 스코프이기 때문에 foo 함수 내부 어디에서든 에러 발생 없이 참조할 수 있다.

블록 레벨 스코프

ES6의 letconst키워드는 블록 레벨 스코프 변수를 만들어 준다.

function foo() {
    if(true) {
        let color = 'blue';
        console.log(color); // blue
    }
    console.log(color); // ReferenceError: color is not defined
}
foo();

let color를 if블록 내부에서 선언하였다. 때문에 if블록 내부에서 참조할 수 있으며, 그 밖의 영역에서 잘못된 참조로 에러가 발생한다.

var vs letconst

ES6가 표준화되면서, 블록 레벨과 함수 레벨을 모두 지원하게 되었다. "You don't know JS" 시리즈의 저자인 Kyle Simpson은 varletconst가 서로 다르기에 필요한 상황에 알맞게 사용할 줄 알아야 한다고 설명하고 있다.

그렇지만 요즈음 ES6 코드 대부분은 var를 사용하지 않는다. var는 let과 const로 모두 대체가 가능하고, var자체가 함수 레벨의 스코프를 가지기 때문에 블록 레벨 스코프보다 더 많은 혼란을 야기하기 때문이다.

렉시컬 스코프

렉시컬 스코프(Lexical scope)는 보통 동적 스코프(Dynamic scope)와 많이 비교한다.

위키피디아를 보면 동적 스코프와 렉시컬 스코프를 다음과 같이 정의하고 있다.

  • 동적 스코프

    The name resolution depends upon the program state when the name is encountered which is determined by the execution context or calling context.

  • 렉시컬 스코프 (정적 스코프(Static scope) 또는 수사적 스코프(Rhetorical scope))

    The name resolution depends on the location in the source code and the lexical context, which is defined by where the named variable or function is defined.

동적 스코프는 프로그램의 런타임 도중의 실행 컨텍스트나 호출 컨텍스트에 의해 결정되고, 렉시컬 스코프에서는 소스코드가 작성된 그 문맥에서 결정된다. 현대 프로그래밍에서 대부분의 언어들은 렉시컬 스코프 규칙을 따르고 있다.

동적 스코프와 렉시컬 스코프는 자바스크립트와 Perl을 비교하여 확인할 수 있다. 아래는 자바스크립트와 Perl로 같은 코드를 작성하였을 때 나오는 결과이다.

lexical-scope-js.png

자바스크립트는 렉시컬 스코프 규칙을 통해 global, global을 출력하였으며, Perl은 동적 스코프 규칙을 통해 local, global을 출력하였다. (참고로 Perl에서 local대신 my키워드를 사용하면 변수의 유효범위를 제한하여, 자바스크립트와 같은 결과를 얻을 수 있다.)

렉시컬 스코프 규칙을 따르는 자바스크립트의 함수는 호출 스택과 관계없이 각각의 (this를 제외한)대응표를 소스코드 기준으로 정의하고, 런타임에 그 대응표를 변경시키지 않는다. (사실 런타임에 렉시컬 스코프를 수정할 수 있는 방법들(evalwith)이 있지만, 권장하지 않는다.)

중첩 스코프(스코프 체인 또는 스코프 버블)

우리가 말하는 이 자바스크립트의 스코프는 ECMAScript 언어 명세에서 렉시컬 환경(Lexical environment)과 환경 레코드(Environment Record)라는 개념으로 정의되었다.

6.2.5 The Lexical Environment and Environment Record Specification Types

The Lexical Environment and Environment Record types are used to explain the behaviour of name resolution in nested functions and blocks. These types and the operations upon them are defined in 8.1.

간단하게 그림으로 표현해보면 아래와 같은 형태로 볼 수 있다.

execution-context.png

앞에서 설명한 "이름:값의 대응표"가 환경 레코드와 같다고 볼 수 있고, 렉시컬 환경은 이 환경 레코드와 상위 렉시켤 환경(Outer lexical environment)에 대한 참조로 이루어진다.

현재-렉시컬 환경의 대응표(환경 레코드)에서 변수를 찾아보고, 없다면 바깥 렉시컬 환경을 참조하여 찾아보는 식으로 중첩 스코프가 가능해진다. 이 중첩 스코프 탐색은 해당하는 이름을 찾거나 바깥 렉시컬 환경 참조가 null이 될 때 탐색을 멈춘다.

scop-chain.png

참고: ECMA-262 Edition3를 보면 자바스크립트의 스코프적 특징은 Scope chain(=list)과 Activation Object등의 개념으로 설명하였다. 그리고 이 설명들이 전반적으로 널리 알려졌지만, 이 다음 명세인 ECMA262 Edition5부터는 Lexical Environment와 Environment Record의 개념으로 스코프를 설명하고 있다.

호이스팅

전통적인 자바스크립트 스코프의 특징은 다음 두 가지라는 것을 알았다.

  • 렉시컬 스코프
  • 함수 레벨 스코프 (+ 블록 레벨 스코프-ES6)

그럼 아래와 같은 상황에선 어떤 값이 출력될까?

function foo() {
    a = 2;
    var a;
    console.log(a);
}
foo();

2가 정상적(?)으로 출력된다.
그럼 다음은 어떨지 생각해보자.

function foo() {
    console.log(a);
    var a = 2;
}
foo();

이번에는 undefined가 출력된다. 조금 터무니없다고 느낄 수 있지만, 알고보면 그렇게까지 터무니없는 것은 아니다.

자바스크립트 엔진은 코드를 인터프리팅 하기 전에 그 코드를 먼저 컴파일한다. var a = 2;를 하나의 구문으로 생각할 수도 있지만, 자바스크립트는 다음 두 개의 구문으로 분리하여 본다.

  1. var a;
  2. a = 2;

변수 선언(생성) 단계와 초기화 단계를 나누고, 선언 단계에서는 그 선언이 소스코드의 어디에 위치하든 해당 스코프의 컴파일단계에서 처리해버리는 것이다. (언어 스펙상으로 변수는 렉시컬 환경이 인스턴스화되고 초기화될 때 생성된다고 한다.) 때문에 이런 선언단계가 스코프의 꼭대기로 호이스팅("끌어올림")되는 작업이라고 볼 수 있는 것이다.

참고: 블록스코프인 let도 호이스팅이 된다. 그렇지만 선언 전에 참조할 경우 undefined를 반환하지 않고 ReferenceError를 발생시키는 특징이 있다.

Temporal dead zone and errors with let

In ECMAScript 2015, let will hoist the variable to the top of the block. However, referencing the variable in the block before the variable declaration results in a ReferenceError. The variable is in a "temporal dead zone" from the start of the block until the declaration is processed.

클로저(Closure)

자바스크립트에서 (언어 명세에 없는) 클로저에 대한 정의는 꽤 많은 사람들이 가장 궁금해하는 부분이다.

아래는 실제 v8엔진의 클로저 테스트코드와 사람들이 말하는 클로저의 의미들이다.

closure-overview.png

종합해보면 함수가 무언가를 기억하고 그것을 다시 사용한다는 것을 알 수 있지만, 여전히 모호하게 느껴진다. 이 모호함을 없애기 위해 클로저의 탄생부터 알아봐야 할 것 같다.

클로저가 가장 처음 등장한 1964년, Peter J. Landin의 논문The Mechanical Evaluation of Expressions을 보면 클로저를 다음과 같이 정의하고 있다.

closure.png

위 정의를 토대로, 클로저를 현대 프로그래밍에서 다음과 같이 해석하여 정의할 수 있을것 같다.

클로저 =
함수 + 함수를 둘러싼 환경(Lexical environment)

함수를 둘러싼 환경이라는 것이 바로 앞에서 설명했던 렉시컬 스코프이다. 함수를 만들고 그 함수 내부의 코드가 탐색하는 스코프를 함수 생성 당시의 렉시컬 스코프로 고정하면 바로 클로저가 되는 것이다.

이제 이 클로저가 자바스크립트에 어떻게 녹아 들어갔는지 살펴보도록 하자.

자바스크립트의 클로저

  • 자바스크립트에서 클로저는 함수가 생성되는 시점에 생성된다.
    = 함수가 생성될 때 그 함수의 렉시컬 환경을 포섭(closure)하여 실행될 때 이용한다.

따라서 개념적으로 자바스크립트의 모든 함수는 클로저이지만, 실제로 우리는 자바스크립트의 모든 함수를 전부 클로저라고 부르지는 않는다.

다음 예시들을 통해서 클로저를 조금 더 정확하게 파악할 수 있다.

function foo() {
    var color = 'blue';
    function bar() {
        console.log(color);
    }
    bar();
}
foo();

bar함수는 우리가 부르는 클로저일까 아닐까?

일단 bar는 foo안에 속하기 때문에 foo스코프를 외부 스코프(outer lexical environment) 참조로 저장한다. 그리고 bar는 자신의 렉시컬 스코프 체인을 통해 foo의 color를 정확히 참조할 것이다.

그럼 클로저라 볼 수 있지 않을까?

아니다. 우리가 부르는 클로저라고 하기에는 약간 거리가 있다. bar는 foo안에서 정의되고 실행되었을 뿐, foo밖으로 나오지 않았기 때문에 클로저라고 부르지 않는다.

대신, 다음 코드는 우리가 실제로 부르는 클로저를 나타내고 있다.

var color = 'red';
function foo() {
    var color = 'blue'; // 2
    function bar() {
        console.log(color); // 1
    }
    return bar;
}
var baz = foo(); // 3
baz(); // 4
  1. bar는 color를 찾아 출력하는 함수로 정의되었다.
  2. 그리고 bar는 outer environment 참조로 foo의 environment를 저장하였다.
  3. bar를 global의 baz란 이름으로 데려왔다.
  4. global에서 baz(=bar)를 호출했다.
  5. bar는 자신의 스코프에서 color를 찾는다.
  6. 없다. 자신의 outer environment 참조를 찾아간다.
  7. outer environment인 foo의 스코프를 뒤진다. color를 찾았다. 값은 blue이다.
  8. 때문에 당연히 blue가 출력된다.

이게 바로 클로저다. 그냥 단순하게 보면 "이 당연하게 왜?"라고 생각할 수 있지만, 조금 더 자세히 따져보도록 하자.

일단 중요한 부분은 2~4번, 그리고 7번이다. bar는 자신이 생성된 렉시컬 스코프에서 벗어나 global에서 baz라는 이름으로 호출이 되었고, 스코프 탐색은 현재 실행 스택과 관련 없는 foo를 거쳐 갔다. baz를 bar로 초기화할 때는 이미 bar의 outer lexical environment를 foo로 결정한 이후이다. 때문에, bar의 생성과 직접적인 관련이 없는 global에서 아무리 호출하더라도 여전히 foo에서 color를 찾는 것이다. 이런 bar(또는 baz)와 같은 함수를 우리는 클로저라고 부른다.

closure2.png

여기에서 다시 한번 강조하지만 JS의 스코프는 렉시컬 스코프, 즉 이름의 범위는 소스코드가 작성된 그 문맥에서 바로 결정되는 것이다.

추가로, foo의 렉시컬환경 인스턴스는 foo();수행이 끝난 이후 GC가 회수해야 하는데 사실을 그렇지 않다. 앞에 설명했듯 bar는 여전히 바깥 렉시컬 환경인 foo의 렉시컬 환경을 계속 참조하고 있고, 이 bar는 baz가 여전히 참조하고 있기 때문이다.(baz(=bar) -> foo)

gc-closure.png

유명하고 또 유명한 반복문 클로저

function count() {
    var i;
    for (i = 1; i < 10; i += 1) {
        setTimeout(function timer() {
            console.log(i);
        }, i*100);
    }
}
count();

이 코드는 1, 2, 3, ... 9를 0.1초마다 출력하는 것이 목표였는데, 결과로는 10이 9번 출력되었다. 왜일까?

timer는 클로저로 언제 어디서 어떻게 호출되던지 항상 상위 스코프인 count에게 i를 알려달라고 요청할 것이다. 그리고 timer는 0.1초 후 호출된다. 그런데 첫 0.1초가 지날 동안 이미 i는 10이 되었다. 그리고 timer는 0.1초 주기로 호출될 때마다 항상 count에서 i를 찾는다. 결국, timer는 이미 10이 되어버린 i만 출력하게 된다.

그럼 의도대로 1~9까지 차례대로 출력하고 싶으면 어떻게 해야 할까?

  1. 새로운 스코프를 추가하여 반복 시마다 그곳에 각각 따로 값을 저장하는 방식
  2. ES6에서 추가된 블록 스코프를 이용하는 방식

이렇게 두 가지가 있을 것이다.

다음 코드는 원래 의도대로 동작한다.

  1. 새로운 스코프를 추가하여 반복 시마다 그곳에 각각 따로 값을 저장하는 방식

     function count() {
         var i;
         for (i = 1; i < 10; i += 1) {
             (function(countingNumber) {
                 setTimeout(function timer() {
                     console.log(countingNumber);
                 }, i * 100);
             })(i);
         }
     }
     count();
    
  2. ES6에서 추가된 블록 스코프를 이용하는 방식

     function count() {
         'use strict';
         for (let i = 1; i < 10; i += 1) {
             setTimeout(function timer() {
                 console.log(i);
             }, i * 100);
         }
     }
     count();
    

Reference

출처:http://meetup.toast.com/posts/86

반응형

'IT > Javascript|Jquery' 카테고리의 다른 글

[JavaScript] 문자열, 배열 Cookie 저장하기  (0) 2018.03.12
prototype 이란  (0) 2018.03.05
자바스크립트와 이벤트 루프  (0) 2018.02.01
Scope의 이해  (0) 2018.01.25
코드의 의존성을 분리해보기  (0) 2018.01.24
반응형

자바스크립트와 이벤트 루프

목차

ECMAScript에는 이벤트 루프가 없다
단일 호출 스택과 Run-to-Completion
태스크 큐와 이벤트 루프
비동기 API와 try-catch
setTimeout(fn, 0)
프라미스(Promise)와 이벤트 루프
마치며
참고 링크

자바스크립트의 큰 특징 중 하나는 '단일 스레드' 기반의 언어라는 점이다. 스레드가 하나라는 말은 곧, 동시에 하나의 작업만을 처리할 수 있다라는 말이다. 하지만 실제로 자바스크립트가 사용되는 환경을 생각해보면 많은 작업이 동시에 처리되고 있는 걸 볼 수 있다. 예를 들면, 웹브라우저는 애니메이션 효과를 보여주면서 마우스 입력을 받아서 처리하고, Node.js기반의 웹서버에서는 동시에 여러 개의 HTTP 요청을 처리하기도 한다. 어떻게 스레드가 하나인데 이런 일이 가능할까? 질문을 바꿔보면 '자바스크립트는 어떻게 동시성(Concurrency)을 지원하는 걸까'?

이때 등장하는 개념이 바로 '이벤트 루프'이다. Node.js를 소개할 때 '이벤트 루프 기반의 비동기 방식으로 Non-Blocking IO를 지원하고..' 와 같은 문구를 본 적이 있을 것이다. 즉, 자바스크립트는 이벤트 루프를 이용해서 비동기 방식으로 동시성을 지원한다. 동기 방식의(Java 같은) 다른 언어를 사용하다가 Node.js 등을 통해 자바스크립트를 처음 접하게 되는 사람들은 이 '이벤트 루프'의 개념이 익숙하지 않아서 애를 먹는다. 뿐만 아니라 자바스크립트를 오랫동안 사용해서 비동기 방식의 프로그래밍에 익숙한 사람들조차 이벤트 루프가 실제로 어떻게 동작하는지에 대해서는 자세히 모르는 경우가 많다.

좀 지난 동영상이지만 최근에 Help, I’m stuck in an event-loop를 우연히 보게 되었는데, 내가 이벤트 루프에 대해 잘못 이해하고 있는 부분들이 많다는 것을 알게 되었다. 그래서 이번 기회에 이벤트 루프에 대해 좀더 자세히 공부해 보았는데, 정리도 할 겸 중요한 사실 몇 가지를 공유해볼까 한다.

ECMAScript에는 이벤트 루프가 없다

웬만큼 두꺼운 자바스크립트 관련 서적들을 뒤져봐도 이벤트 루프에 대한 설명은 의외로 쉽게 찾아보기가 힘들다. 그 이유는 아마, 실제로 ECMAScript 스펙에 이벤트 루프에 대한 내용이 없기 때문일 것이다. 좀더 구체적으로 표현하면 'ECMAScript 에는 동시성이나 비동기와 관련된 언급이 없다'고 할 수 있겠다(사실 ES6부터는 조금 달라졌지만, 나중에 좀더 설명하겠다). 실제로 V8과 같은 자바스크립트 엔진은 단일 호출 스택(Call Stack)을 사용하며, 요청이 들어올 때마다 해당 요청을 순차적으로 호출 스택에 담아 처리할 뿐이다. 그렇다면 비동기 요청은 어떻게 이루어지며, 동시성에 대한 처리는 누가 하는 걸까? 바로 이 자바스크립트 엔진을 구동하는 환경, 즉 브라우저나 Node.js가 담당한다. 먼저 브라우저 환경을 간단하게 그림으로 표현하면 다음과 같다.

Browser-Event

위 그림에서 볼 수 있듯이 실제로 우리가 비동기 호출을 위해 사용하는 setTimeout이나 XMLHttpRequest와 같은 함수들은 자바스크립트 엔진이 아닌 Web API 영역에 따로 정의되어 있다. 또한 이벤트 루프와 태스크 큐와 같은 장치도 자바스크립트 엔진 외부에 구현되어 있는 것을 볼 수 있다. 다음은 Node.js 환경이다.

NodeJS

(출처: http://stackoverflow.com/questions/10680601/nodejs-event-loop)

이 그림에서도 브라우저의 환경과 비슷한 구조를 볼 수 있다. 잘 알려진 대로 Node.js는 비동기 IO를 지원하기 위해 libuv 라이브러리를 사용하며, 이 libuv가 이벤트 루프를 제공한다. 자바스크립트 엔진은 비동기 작업을 위해 Node.js의 API를 호출하며, 이때 넘겨진 콜백은 libuv의 이벤트 루프를 통해 스케쥴되고 실행된다.

이제 어느 정도 감이 잡힐 것이다. 각각에 대해 좀더 자세히 알아보기 전에 한가지만 확실히 짚고 넘어가자. 자바스크립트가 '단일 스레드' 기반의 언어라는 말은 '자바스크립트 엔진이 단일 호출 스택을 사용한다'는 관점에서만 사실이다. 실제 자바스크립트가 구동되는 환경(브라우저, Node.js등)에서는 주로 여러 개의 스레드가 사용되며, 이러한 구동 환경이 단일 호출 스택을 사용하는 자바 스크립트 엔진과 상호 연동하기 위해 사용하는 장치가 바로 '이벤트 루프'인 것이다.

단일 호출 스택과 Run-to-Completion

이벤트 루프에 대해 좀더 알아보기 전에, 먼저 자바스크립트 언어의 특징을 하나 살펴보자. 자바스크립트의 함수가 실행되는 방식을 보통 "Run to Completion" 이라고 말한다. 이는 하나의 함수가 실행되면 이 함수의 실행이 끝날 때까지는 다른 어떤 작업도 중간에 끼어들지 못한다는 의미이다. 앞서 말했듯이 자바스크립트 엔진은 하나의 호출 스택을 사용하며, 현재 스택에 쌓여있는 모든 함수들이 실행을 마치고 스택에서 제거되기 전까지는 다른 어떠한 함수도 실행될 수 없다. 다음의 예제를 보자.

function delay() {
    for (var i = 0; i < 100000; i++);
}
function foo() {
    delay();
    bar();
    console.log('foo!'); // (3)
}
function bar() {
    delay();
    console.log('bar!'); // (2)
}
function baz() {
    console.log('baz!'); // (4)
}

setTimeout(baz, 10); // (1)
foo();

자바스크립트를 경험해본 사람이라면, 아무리 delay 함수가 10ms 보다 오래 걸린다고 해도 'baz!'가 'foo!' 보다 먼저 콘솔에 찍히는 일은 없을 거라는 것을 알 것이다. 즉, foo 내부에서 bar를 호출하기 전에 10ms이 지났다고 해도 baz가 먼저 호출되지는 않는다는 말이다. 그러므로 위의 예제를 실행하면 콘솔에는 'bar!' -> 'foo!' -> 'baz!'의 순서로 찍히게 된다. 위의 코드가 전역 환경에서 실행된다고 가정하고 코드내 주석으로 숫자가 적힌 각 시점의 호출 스택을 그림으로 그려보면 다음과 같을 것이다.

Call-Stack

(전역 환경에서 실행되는 코드는 한 단위의 코드블록으로써 가상의 익명함수로 감싸져 있다고 생각하는 것이 좋다. 따라서 위의 코드의 첫 줄이 실행될 때에 호출 스택의 맨 아래에 익명 함수가 하나 추가되며, 마지막 라인까지 실행되고 나서야 스택에서 제거된다.)

setTimeout 함수는 브라우저에게 타이머 이벤트를 요청한 후에 바로 스택에서 제거된다. 그 후에 foo 함수가 스택에 추가되고, foo 함수가 내부적으로 실행하는 함수들이 차례로 스택에 추가되었다가 제거된다. 마지막으로 foo 함수가 실행을 마치면서 호출 스택이 비워지게 되고, 그 이후에 baz 함수가 스택에 추가되어 콘솔에 'baz!'가 찍히게 된다.

(결과적으로 baz는 10ms보다 더 늦게 실행되게 될 것이다. 즉, 자바스크립트의 타이머는 정확한 타이밍을 보장해주지 않는데, 이와 관련해서 잘 설명된 John Resig의 글이 있으니 관심 있으신 분들은 클릭!)

태스크 큐와 이벤트 루프

여기서 하나의 궁금증이 생긴다. setTimeout 함수를 통해 넘긴 baz 함수는 어떻게 foo 함수가 끝나자 마자 실행될 수 있을까? 어디서 대기하고 있다가 누구를 통해 실행될까? 바로 이 역할을 하는 것이 태스크 큐와 이벤트 루프이다. 태스크 큐는 말 그대로 콜백 함수들이 대기하는 큐(FIFO) 형태의 배열이라 할 수 있고, 이벤트 루프는 호출 스택이 비워질 때마다 큐에서 콜백 함수를 꺼내와서 실행하는 역할을 해 준다.

앞선 예제를 살펴보자. 코드가 처음 실행되면 이 코드는 '현재 실행중인 태스크'가 된다. 코드를 실행하는 도중 10ms이 지나면 브라우저의 타이머가 baz를 바로 실행하지 않고 태스크 큐에 추가한다. 이벤트 루프는 '현재 실행중인 태스크'가 종료되자 마자 태스크 큐에서 대기중인 첫 번째 태스크를 실행할 것이다. foo가 실행을 마치고 호출 스택이 비워지면 현재 실행중인 태스크는 종료되며, 그 때 이벤트 루프가 태스크 큐에 대기중인 첫 번째 태스크인 baz를 실행해서 호출 스택에 추가한다.

MDN의 이벤트 루프 설명을 보면 왜 '루프'라는 이름이 붙었는지를 아주 간단한 가상코드로 설명하고 있다.

while(queue.waitForMessage()){
  queue.processNextMessage();
}

위 코드의 waitForMessage() 메소드는 현재 실행중인 태스크가 없을 때 다음 태스크가 큐에 추가될 때까지 대기하는 역할을 한다. 이런 식으로 이벤트 루프는 '현재 실행중인 태스크가 없는지'와 '태스크 큐에 태스크가 있는지'를 반복적으로 확인하는 것이다. 간단하게 정리하면 다음과 같을 것이다.

  • 모든 비동기 API들은 작업이 완료되면 콜백 함수를 태스크 큐에 추가한다.
  • 이벤트 루프는 '현재 실행중인 태스크가 없을 때'(주로 호출 스택이 비워졌을 때) 태스크 큐의 첫 번째 태스크를 꺼내와 실행한다.

좀더 명확하게 이해하기 위해 앞의 예제를 조금 바꿔보자.

function delay() {
    for (var i = 0; i < 100000; i++);
}
function foo() {
    delay();
    console.log('foo!');
}
function bar() {
    delay();
    console.log('bar!');
}
function baz() {
    delay();
    console.log('baz!');
}

setTimeout(foo, 10);
setTimeout(bar, 10);
setTimeout(baz, 10);

이 코드를 실행하면 아무런 지연 없이 setTimeout 함수가 세 번 호출된 이후에 실행을 마치고 호출 스택이 비워질 것이다. 그리고 10ms가 지나는 순간 foobarbaz 함수가 순차적으로 태스크 큐에 추가된다. 이벤트 루프는 foo 함수가 태스크 큐에 들어오자 마자, 호출 스택이 비어있으므로 바로 foo를 실행해서 호출 스택에 추가한다. foo 함수의 실행이 끝나고 호출 스택이 비워지면 이벤트 루프가 다시 큐에서 다음 콜백인 bar를 가져와 실행한다. bar의 실행이 끝나면 마찬가지로 큐에 남아있는 baz를 큐에서 가져와 실행한다. 그리고 baz까지 실행이 모두 완료되면 현재 진행중인 태스크도 없고 태스크 큐도 비어있기 때문에, 이벤트 루프는 새로운 태스크가 태스크 큐에 추가될 때까지 대기하게 된다.

(코드는 다르지만 그림으로 표현하면 대략 다음과 같을 것이다)

Event-Loop

(출처: http://www.2ality.com/2014/09/es6-promises-foundations.html)

(글의 서두에 언급했던 영상을 보면 발표자가 직접 만든 인터랙션 환경을 사용해 이 과정을 정말 이해하기 쉽게 잘 설명하고 있다. 안보신 분들은 꼭 확인해 보길 바란다.)

비동기 API와 try-catch

setTimeout 뿐만 아니라 브라우저의 다른 비동기 함수들(addEventListenerXMLHttpRequest… )이나 Node.js의 IO 관련 함수들 등 모든 비동기 방식의 API들은 이벤트 루프를 통해 콜백 함수를 실행한다. 자, 그러면 다음과 아래와 같은 코드가 왜 에러를 잡아낼 수 없는지 이제는 확실히 알 수 있을 것이다.

$('.btn').click(function() { // (A)
    try {
        $.getJSON('/api/members', function (res) { // (B)
            // 에러 발생 코드
        });
    } catch (e) {
        console.log('Error : ' + e.message);
    }
});

위의 코드에서 버튼이 클릭되어 콜백 A가 실행될 때 $.getJSON 함수는 브라우저의 XMLHttpRequest API를 통해 서버로 비동기 요청을 보낸 후에 바로 실행을 마치고 호출 스택에서 제거된다. 이후에 서버에서 응답을 받은 브라우저는 콜백 B를 태스크 큐에 추가 하고 B는 이벤트 루프에 의해 실행되어 호출 스택에 추가된다. 하지만 이때 A는 이미 호출 스택에서 비워진 상태이기 때문에 호출 스택에는 B만 존재할 뿐이다. 즉 B는 A가 실행될 때와는 전혀 다른 독립적인 컨텍스트에서 실행이 되며, 그렇기 A 내부의 try-catch 문에 영향을 받지 않는다.

(마찬가지 이유로 에러가 발생했을 때 브라우저의 개발자 도구에서 호출 스택을 들여다봐도 B만 덩그라니 놓여있는 것을 볼 수 있을 것이다.)

(이런 이유로 Node.js의 비동기 API들은 중첩된 콜백 호출에 대한 에러 처리를 위해 '첫 번째 인수는 에러 콜백 함수' 라는 컨벤션을 따르고 있다)

이를 해결하기 위해서는 콜백 B의 내부에서 try-catch를 실행해야 한다. (물론, 이렇게 해도 네트워크 에러나 서버 에러는 잡을 수 없다. 이를 위해서는 에러 콜백을 따로 제공해야 한다.)

$('.btn').click(function() { // (A)
    $.getJSON('/api/members', function (res) { // (B)
        try {
            // 에러 발생 코드
        } catch (e) {
            console.log('Error : ' + e.message);
        }
    });
});

setTimeout(fn, 0)

프론트엔드 환경의 자바스크립트 코드를 보다 보면 setTimeout(fn, 0)와 같은 코드를 종종 보게 된다. 관용적으로 쓰이는 코드이지만, 사실 처음 보는 사람에게는 직관적으로 이해하기 힘든 코드일 것이다. 0초 이후에 실행을 한다는 건 실제로 그냥 실행하는 것과 다를 게 없으니 말이다. 하지만 실제로 이 코드는 그냥 fn을 실행하는 것과는 상당히 다른 결과를 가져온다. 위의 예제에서도 보았겠지만 setTimeout 함수는 콜백 함수를 바로 실행하지 않고 (호출 스택이 아닌)태스크 큐에 추가한다. 그렇기 때문에 아래의 코드는 콘솔에 B -> A 순서로 출력하게 될 것이다.

setTimeout(function() {
    console.log('A');
}, 0);
console.log('B');

프론트엔드 환경에서는 렌더링 엔진과 관련해서 이런 코드가 특히 요긴하게 쓰일 때가 있다. 브라우저 환경에서는 자바스크립트 엔진뿐만 아니라 다른 여러 가지 프로세스가 함께 구동되고 있다. 렌더링 엔진도 그 중의 일부이며, 이 렌더링 엔진의 태스크는 대부분의 브라우저에서 자바스크립트 엔진과 동일한 단일 태스크 큐를 통해 관리된다. 이로 인해 가끔 예상치 못한 문제가 생길 경우가 있는데, 다음의 코드를 살펴보자.

$('.btn').click(function() {
    showWaitingMessage();
    longTakingProcess();
    hideWaitingMessage();
    showResult();
});

longTakingProcess가 너무 오래 걸리는 작업이기 때문에 그 전에 showWaitingMessage를 호출해서 로딩 메시지('로딩중…'과 같은)를 보여주려고 한다. 하지만 실제로 이 코드를 실행해 보면 화면에 로딩 메시지가 표시되는 일은 없을 것이다. 이유는 showWaitingMessage 함수의 실행이 끝나고 렌더링 엔진이 렌더링 요청을 보내도 해당 요청은 태스크 큐에서 이미 실행중인 태스크가 끝나기를 기다리고 있기 때문이다. 실행중인 태스크가 끝나는 시점은 호출 스택이 비워지는 시점인데, 그 때는 이미 showResult 까지 실행이 끝나 있을 것이고, 결국 렌더링이 진행되는 시점에는 hideWaitingMessgae로 인해 로딩 메시지가 숨겨진 상태일 것이다. 이를 해결하기 위해서 다음처럼 setTimeout를 사용할 수 있다.

$('.btn').click(function() {
    showWaitingMessage();
    setTimeout(function() {
        longTakingProcess();
        hideWaitingMessage();
        showResult();
    }, 0);
});

이 경우에는 longTakingProcess가 바로 실행되지 않고 태스크 큐에 추가될 것이다. 하지만 showWaitingMessage로 인해 태스크 큐에는 렌더링 요청이 먼저 추가되기 때문에 longTakingProcess는 그 다음 순서로 태스크 큐에 추가될 것이다. 이제 이벤트 루프는 태스크 큐에 있는 렌더링 요청을 먼저 처리하게 되고 로딩 메시지가 먼저 화면에 보여지게 된다.

꼭 렌더링 관련이 아니라도, 실행이 너무 오래 걸리는 코드를 setTimeout을 사용하여 적절하게 다른 태스크로 나누어 주면 전체 어플리케이션이 멈추거나 스크립트가 너무 느리다며 경고창이 뜨는 상황을 방지할 수도 있을 것이다.

한가지 짚고 넘어갈 사실은 '0' 이라는 숫자가 실제로 '즉시'를 의미하지 않는다는 점이다. 브라우저는 내부적으로 타이머의 최소단위(Tick)를 정하여 관리하기 때문에 실제로는 그 최소단위만큼 지난 후에 태스크 큐에 추가되게 된다. 그리고 이 최소단위는 브라우저별로 조금씩 다른데, 예를 들어 크롬 브라우저의 경우 최소단위로 4ms 사용하기 때문에 크롬에서 setTimeout(fn, 0)은 setTimeout(fn, 4)와 동일한 의미를 갖게 될 것이다.

이런 문제를 해결하기 위해 setImmediate라는 API가 제안되었지만, 안타깝게도 표준의 반열에 오르지는 못하고 IE10 이상에만 포함되어 있다. 실제로 이 메소드는 setTimeout 와 같은 최소단위 지연이 없이 바로 태스크 큐에 해당 콜백을 추가한다. EsLint로 유명한 N.C.Zakas도 이 메소드가 표준화 되지 않은 것에 대해 비판하는 글을 올린 적이 있다. 비슷한 효과를 위해 postMessage 나 MessageChanel을 사용하기도 하는데, 관련된 내용은 setImmediate의 폴리필을 구현한 라이브러리 페이지에 잘 정리되어 있다.

(Node.js 에는 이런 용도를 위해 nextTick이라는 함수가 있지만 0.9버전 부터는 약간 다른 개념으로 사용된다. 다음 절에서 좀더 설명하겠다.)

프라미스(Promise)와 이벤트 루프

이런 이벤트 루프의 개념은 실제로 HTML 스펙에 정의되어 있다. 문서에서 이벤트 루프, 태스크 큐의 개념에 대해 잘 정의되어 있는 것을 볼 수 있을 것이다. 그런데 문서 중간에 마이크로 태스크(microtask) 라는 생소한 용어가 보인다. 이런… 이제 겨우 이벤트 루프에 대해 이해한 것 같은데 뭔가 상황이 더 복잡해질 것 같은 불길한 예감이 든다. 마음을 가다듬고, 다음 코드를 살펴보자.

setTimeout(function() { // (A)
    console.log('A');
}, 0);
Promise.resolve().then(function() { // (B)
    console.log('B');
}).then(function() { // (C)
    console.log('C');
});

콘솔에 찍히는 순서는 어떻게 될까? 프라미스도 비동기로 실행된다고 할 수 있으니 태스크 큐에 추가돼서 순서대로 A -> B -> C 가 될까? 아니면 프라미스는 setTimeout처럼 최소단위 지연이 없으니 B -> C -> A 일까? 체인 형태로 연속해서 호출된 then() 함수는 어떤 식으로 동작할까? 결론부터 말하자면 정답은 B -> C -> A 인데, 이유는 바로 프라미스가 마이크로 태스크를 사용하기 때문이다. 그럼 마이크로 태스크가 대체 뭘까?

마이크로 태스크는 쉽게 말해 일반 태스크보다 더 높은 우선순위를 갖는 태스크라고 할 수 있다. 즉, 태스크 큐에 대기중인 태스크가 있더라도 마이크로 태스크가 먼저 실행된다. 위의 예제를 통해 좀더 자세히 알아보자. setTimeout() 함수는 콜백 A를 태스크 큐에 추가하고, 프라미스의 then() 메소드는 콜백 B를 태스크 큐가 아닌 별도의 마이크로 태스크 큐에 추가한다. 위의 코드의 실행이 끝나면 태스크 이벤트 루프는 (일반)태스크 큐 대신 마이크로 태스크 큐가 비었는지 먼저 확인하고, 큐에 있는 콜백 B를 실행한다. 콜백 B가 실행되고 나면 두번째 then() 메소드가 콜백 C를 마이크로 태스크 큐에 추가한다. 이벤트 루프는 다시 마이크로 태스크를 확인하고, 큐에 있는 콜백 C를 실행한다. 이후에 마이크로 태스크 큐가 비었음을 확인한 다음 (일반) 태스크 큐에서 콜백 A를 꺼내와 실행한다. (이런 일련의 작업은 HTML 스펙에서 perform a microtask checkpoint 라는 항목에 명시되어 있다.)

잘 와 닿지 않는 분들은 이와 관련해서 인터랙션과 함께 아주 잘 정리된 글이 있으니 꼭 확인해 보길 바란다. 원문 글에서는 브라우저마다 프라미스의 호출 순서가 다른 문제를 지적하고 있는데, 이유는 프라미스가 ECMAScript에 정의되어 있는 반면, 마이크로 태스크는 HTML 스펙이 정의되어 있는데, 둘의 연관관계가 명확하지 않기 때문이다. (ECMAScript에는 ES6부터 프라미스를 위해 잡 큐(Job Queue)라는 항목이 추가되었지만, HTML 스펙의 마이크로 태크스와는 별도의 개념이다.) 하지만 최근에 Living Standard 상태인 HTML 스펙을 보면 자바스크립트의 잡큐를 어떻게 이벤트 루프와 연동하는지에 대한 항목이 포함되어 있다. 또한 현재는 대부분의 브라우저에서 해당 문제가 수정되어 있는 걸 확인할 수 있다.

(프라미스A+ 스펙문서의 Note를 보면 구현 시에 일반(macro) 태스크나 마이크로 태스크 둘 다 사용할 수 있다고 적혀 있다. 실제로 프라미스가 처음 자바스크립트에 도입되는 시점에는 프라미스를 어떤 순서로 실행할 것인가에 대한 논의가 꽤 있었던 것으로 보인다. 하지만 앞서 언급한 것처럼 현재는 프라미스를 마이크로 태스크라고 정의해도 무리가 없을 것 같다.)

휴우. 정리를 하고 다시 봐도 복잡해 보인다. 하지만, 실제로 마이크로 태스크이냐 일반 태스크이냐에 따라 실행되는 타이밍이 달라지기 때문에 둘을 제대로 이해하고 구분해서 사용하는 것은 중요하다. 예를 들어 마이크로 태스크가 계속돼서 실행될 경우 일반 태스크인 UI 렌더링이 지연되는 현상이 발생할 수도 있을 것이다. 관련해서 잘 정리된 스택오버플로우의 답변도 있으니 참고하면 좋을 것 같다.

마무리 하기 전에, 마이크로 태스크를 사용하는 다른 API들도 살짝 살펴보자.

  • MutationObserver는 DOM의 변화를 감지할 수 있게 해 주는 클래스이며, es6-promise와 같은 폴리필에서 마이크로 태스크를 구현하기 위해 사용되기도 한다.
  • 이전 절에서 살짝 언급했던 Node.js의 nextTick은 기존에는 일반 태스크를 이용해 구현되었지만, 0.9 버전부터 마이크로 태스크를 이용하도록 변경되었다.

마치며

이벤트 루프는 실제로 자바스크립트 언어의 명세보다는 구동 환경과 더 관련된 내용이기 때문에 다른 프로세스들(렌더링, IO 등)과 밀접하게 연관되어 있어 잘 정리된 자료를 찾기가 쉽지만은 않다. 또한 Node.js의 libuv는 HTML 스펙을 완벽히 따르지는 않기 때문에 브라우저 환경의 이벤트 루프와 상세 구현이 조금씩 다르다(심지어 브라우저 별로도 구현이 조금씩 다르다). 또한, 최근에는 ES6에 프라미스와 잡 큐라는 항목이 추가되며 마이크로 태스크의 개념과 혼동되며 이해하기가 한층 더 복잡해졌다. 여기서 끝이 아니다. 사실 이 글에서는 브라우저가 '단일 이벤트 루프'를 사용한다고 가정하고 설명했지만, 웹 워커(Web Worker)는 각각이 독립적인 이벤트 루프를 사용하며(Worker Event Loop라는 이름으로 구분되어 있다), 이와 관련된 내용을 추가한다면 더더욱 복잡해질 것이다. (하아…)

하지만 자바스크립트의 비동기적 특성을 잘 활용하기 위해서는 이벤트 루프를 제대로 이해하는 것이 중요하다. 특히 (이 글에서는 다루지 못했지만) 웹 워커나 Node.js의 클러스터를 사용하는 멀티 스레드 환경에서는 이벤트 루프에 대한 탄탄한 이해가 없다면 예상치 못한 버그 앞에 좌절하게 될 지도 모른다. 사실 개인적으로도 계속 스펙문서를 부분 부분 뒤져가며 글을 작성하느라 완벽하게 이해하고 정리하지는 못한 기분이다. 하지만 이 글이 조금이나마 도움이 되었기를 바라며, 여기서 만족하지 말고 관련 링크들을 짬짬이 살펴 보면서 이벤트 루프에 대해 제대로 이해하는 기회가 되었으면 좋겠다.

출처:http://meetup.toast.com/posts/89


반응형

'IT > Javascript|Jquery' 카테고리의 다른 글

prototype 이란  (0) 2018.03.05
자바스크립트의 스코프와 클로저  (0) 2018.02.02
Scope의 이해  (0) 2018.01.25
코드의 의존성을 분리해보기  (0) 2018.01.24
비밀번호검증 스크립트(연속,반복)  (0) 2018.01.22
반응형

로그아웃 동작 원리

모든 URL 요청은 서블릿 요청으로 리졸브 되기 전에 항상 스프링 시큐리티의 전체 필터 체인을 통과 시킵니다.  따라서 j_spring_security_logout에 대한 URL 요청이 시스템에 있는 JSP 페이지와 일치하지 않더라도 이 요청을 처리하기 위해 실제 JSP 나 스프링 MVC 대상 URL을 두지 않아도 됩니다.

j_spring_security_logout 에 대한 URL 요청은 logoutFilter가 가로챕니다. 

 

logout-url 어트리뷰트에 지정한 로그아웃 URL을 감시하고 사용자를 로그아웃 시키는데 이때 다음 과정은 3단계로 나뉘어 집니다.

1. HTTP 세션 무효화(invalidate-session이 true로 설정된 경우)

2. SecurityContext 초기화(사용자를 실제로 로그아웃시키는 부분)

3. logout-success-url에 지정된 URL로의 사용자 리다이렉트  


다음 다이어그램을 보면 로그아웃 과정이 동작하는 원리를 쉽게 이해하실 수 있을겁니다.

 



어트리뷰트 

설명 

invalidate-session 

 true로 설정되면 사용자의 HTTP 세션이 사용자가 로그아웃할 때 무효화된다. 일부 경우(예를들어 사용자의 쇼핑 카트 관리)에는 이를 사용하지 않는것이 좋다 

logout-succesfs-url 

LogoutFilter 가 읽는 URL 

success-handler-ref 

LogoutSuccessHandler 구현체에 대한 빈 레퍼런스 

 

 



출처: http://itmore.tistory.com/entry/스프링-시큐리티-로그아웃 [IT모아]

반응형
반응형

2018년 연봉 실수령액 표

연봉실수령액공제액계국민연금건강보험장기요양고용보험소득세지방소득세
1,000만원771,88361,45032,99022,2501,4504,76000
1,100만원848,22668,44036,74024,7801,6205,30000
1,200만원924,56075,44040,50027,3101,7805,85000
1,300만원1,000,91382,42044,24029,8401,9506,39000
1,400만원1,076,04690,62047,99032,3702,1206,9301,100110
1,500만원1,151,12098,88051,75034,9002,2807,4702,260220
1,600만원1,226,193107,14055,49037,4302,4508,0103,420340
1,700만원1,300,776115,89059,24039,9602,6108,5505,030500
1,800만원1,375,210124,79063,00042,4902,7809,1006,750670
1,900만원1,449,693133,64066,74045,0102,9409,6408,470840
2,000만원1,524,146142,52070,49047,5403,11010,18010,1901,010
2,100만원1,598,590151,41074,25050,0703,27010,72011,9101,190
2,200만원1,673,053160,28077,99052,6003,44011,26013,6301,360
2,300만원1,747,496169,17081,74055,1303,61011,80015,3601,530
2,400만원1,821,940178,06085,50057,6603,77012,35017,0801,700
2,500만원1,896,363186,97089,24060,1903,94012,89018,8301,880
2,600만원1,969,766196,90092,99062,7204,10013,43021,5102,150
2,700만원2,043,170206,83096,75065,2504,27013,97024,1802,410
2,800만원2,116,583216,750100,49067,7804,43014,51026,8602,680
2,900만원2,189,976226,690104,24070,3104,60015,05029,5402,950
3,000만원2,262,310237,690108,00072,8404,77015,60033,1703,310
3,100만원2,331,313252,020111,74075,3604,93016,14039,8703,980
3,200만원2,399,906266,760115,49077,8905,10016,68046,9104,690
3,300만원2,468,410281,590119,25080,4205,26017,22054,0405,400
3,400만원2,536,923296,410122,99082,9505,43017,76061,1706,110
3,500만원2,605,416311,250126,74085,4805,59018,30068,3106,830
3,600만원2,673,910326,090130,50088,0105,76018,85075,4307,540
3,700만원2,742,413340,920134,24090,5405,93019,39082,5708,250
3,800만원2,810,916355,750137,99093,0706,09019,93089,7008,970
3,900만원2,877,890372,110141,75095,6006,26020,47098,2109,820
4,000만원2,943,043390,290145,49098,1306,42021,010108,40010,840
4,100만원3,008,186408,480149,240100,6606,59021,550118,59011,850
4,200만원3,073,320426,680153,000103,1906,75022,100128,77012,870
4,300만원3,138,473444,860156,740105,7106,92022,640138,96013,890
4,400만원3,203,616463,050160,490108,2407,08023,180149,15014,910
4,500만원3,268,750481,250164,250110,7707,25023,720159,33015,930
4,600만원3,333,893499,440167,990113,3007,42024,260169,52016,950
4,700만원3,393,056523,610171,740115,8307,58024,800185,15018,510
4,800만원3,457,150542,850175,500118,3607,75025,350196,27019,620
4,900만원3,521,243562,090179,240120,8907,91025,890207,42020,740
연봉실수령액공제액계국민연금건강보험장기요양고용보험소득세지방소득세
5,000만원3,584,736581,930182,990123,4208,08026,430219,10021,910
5,100만원3,648,230601,770186,750125,9508,24026,970230,79023,070
5,200만원3,711,723621,610190,490128,4808,41027,510242,48024,240
5,300만원3,775,206641,460194,240131,0108,58028,050254,17025,410
5,400만원3,838,680661,320198,000133,5408,74028,600265,86026,580
5,500만원3,902,193681,140201,740136,0608,91029,140277,54027,750
5,600만원3,965,686700,980205,490138,5909,07029,680289,23028,920
5,700만원4,026,410723,590209,250141,1209,24030,220303,42030,340
5,800만원4,089,923743,410212,990143,6509,40030,760315,10031,510
5,900만원4,153,416763,250216,740146,1809,57031,300326,79032,670
6,000만원4,216,880783,120220,500148,7109,74031,850338,48033,840
6,100만원4,280,383802,950224,240151,2409,90032,390350,17035,010
6,200만원4,343,876822,790227,990153,77010,07032,930361,85036,180
6,300만원4,407,360842,640231,750156,30010,23033,470373,54037,350
6,400만원4,470,853862,480235,490158,83010,40034,010385,23038,520
6,500만원4,534,346882,320239,240161,36010,56034,550396,92039,690
6,600만원4,597,810902,190243,000163,89010,73035,100408,61040,860
6,700만원4,661,343921,990246,740166,41010,89035,640420,29042,020
6,800만원4,724,826941,840250,490168,94011,06036,180431,98043,190
6,900만원4,780,930969,070254,250171,47011,23036,720450,37045,030
7,000만원4,836,723996,610257,990174,00011,39037,260469,07046,900
7,100만원4,892,4961,024,170261,740176,53011,56037,800487,77048,770
7,200만원4,920,7701,079,230265,500179,06011,72038,350531,46053,140
7,300만원4,976,3331,107,000269,240181,59011,89038,890550,36055,030
7,400만원5,031,8961,134,770272,990184,12012,05039,430569,26056,920
7,500만원5,087,4401,162,560276,750186,65012,22039,970588,16058,810
7,600만원5,143,0031,190,330280,490189,18012,39040,510607,06060,700
7,700만원5,198,5661,218,100284,240191,71012,55041,050625,96062,590
7,800만원5,254,1001,245,900288,000194,24012,72041,600644,86064,480
7,900만원5,309,6831,273,650291,740196,76012,88042,140663,76066,370
8,000만원5,365,2361,301,430295,490199,29013,05042,680682,66068,260
8,100만원5,420,7901,329,210299,250201,82013,21043,220701,56070,150
8,200만원5,476,3531,356,980302,990204,35013,38043,760720,46072,040
8,300만원5,531,9061,384,760306,740206,88013,55044,300739,36073,930
8,400만원5,587,4501,412,550310,500209,41013,71044,850758,26075,820
8,500만원5,643,0131,440,320314,240211,94013,88045,390777,16077,710
8,600만원5,698,5761,468,090317,990214,47014,04045,930796,06079,600
8,700만원5,754,1201,495,880321,750217,00014,21046,470814,96081,490
8,800만원5,809,6931,523,640325,490219,53014,37047,010833,86083,380
8,900만원5,865,2461,551,420329,240222,06014,54047,550852,76085,270
9,000만원5,920,7801,579,220333,000224,59014,71048,100871,66087,160
9,100만원5,976,3631,606,970336,740227,11014,87048,640890,56089,050
9,200만원6,031,9161,634,750340,490229,64015,04049,180909,46090,940
9,300만원6,087,4701,662,530344,250232,17015,20049,720928,36092,830
9,400만원6,143,0331,690,300347,990234,70015,37050,260947,26094,720
9,500만원6,198,5961,718,070351,740237,23015,53050,800966,16096,610
9,600만원6,254,1301,745,870355,500239,76015,70051,350985,06098,500
9,700만원6,309,7031,773,630359,240242,29015,86051,8901,003,960100,390
9,800만원6,365,2561,801,410362,990244,82016,03052,4301,022,860102,280
9,900만원6,420,8001,829,200366,750247,35016,20052,9701,041,760104,170
연봉실수령액공제액계국민연금건강보험장기요양고용보험소득세지방소득세
1억원6,476,3731,856,960370,490249,88016,36053,5101,060,660106,060
1억100만원6,531,9261,884,740374,240252,41016,53054,0501,079,560107,950
1억200만원6,586,9401,913,060378,000254,94016,69054,6001,098,940109,890
1억300만원6,641,8531,941,480381,740257,46016,86055,1401,118,440111,840
1억400만원6,696,7561,969,910385,490259,99017,02055,6801,137,940113,790
1억500만원6,751,6401,998,360389,250262,52017,19056,2201,157,440115,740
1억600만원6,806,5432,026,790392,990265,05017,36056,7601,176,940117,690
1억700만원6,861,4462,055,220396,740267,58017,52057,3001,196,440119,640
1억800만원6,916,3202,083,680400,500270,11017,69057,8501,215,940121,590
1억900만원6,971,2332,112,100404,240272,64017,85058,3901,235,440123,540
1억1000만원7,026,1262,140,540407,990275,17018,02058,9301,254,940125,490
1억1100만원7,081,0202,168,980411,750277,70018,18059,4701,274,440127,440
1억1200만원7,130,5632,202,770415,490280,23018,35060,0101,298,810129,880
1억1300만원7,175,6262,241,040419,240282,76018,52060,5501,327,250132,720
1억1400만원7,220,6802,279,320423,000285,29018,68061,1001,355,690135,560
1억1500만원7,265,7632,317,570426,740287,81018,85061,6401,384,120138,410
1억1600만원7,310,8362,355,830430,490290,34019,01062,1801,412,560141,250
1억1700만원7,355,8802,394,120434,250292,87019,18062,7201,441,000144,100
1억1800만원7,400,9732,432,360437,990295,40019,34063,2601,469,430146,940
1억1900만원7,446,0362,470,630441,740297,93019,51063,8001,497,870149,780
1억2000만원7,491,0702,508,930445,500300,46019,68064,3501,526,310152,630
1억2100만원7,536,1532,547,180449,240302,99019,84064,8901,554,750155,470
1억2200만원7,462,3762,704,290452,990305,52020,01065,4301,691,220169,120
1억2300만원7,507,2802,742,720456,750308,05020,17065,9701,719,800171,980
1억2400만원7,552,2032,781,130460,490310,58020,34066,5101,748,380174,830
1억2500만원7,597,1062,819,560464,240313,11020,50067,0501,776,970177,690
1억2600만원7,641,9902,858,010468,000315,64020,67067,6001,805,550180,550
1억2700만원7,686,9232,896,410471,740318,16020,83068,1401,834,130183,410
1억2800만원7,731,8162,934,850475,490320,69021,00068,6801,862,720186,270
1억2900만원7,776,7102,973,290479,250323,22021,17069,2201,891,300189,130
1억3000만원7,821,6433,011,690482,990325,75021,33069,7601,919,880191,980
1억3100만원7,866,5363,050,130486,740328,28021,50070,3001,948,470194,840
1억3200만원7,911,4303,088,570490,500330,81021,66070,8501,977,050197,700
1억3300만원7,956,3433,126,990494,240333,34021,83071,3902,005,630200,560
1억3400만원8,001,2463,165,420497,990335,87021,99071,9302,034,220203,420
1억3500만원8,046,1403,203,860501,750338,40022,16072,4702,062,800206,280
1억3600만원8,091,0633,242,270505,490340,93022,33073,0102,091,380209,130
1억3700만원8,135,9663,280,700509,240343,46022,49073,5502,119,970211,990
1억3800만원8,180,8503,319,150513,000345,99022,66074,1002,148,550214,850
1억3900만원8,225,7833,357,550516,740348,51022,82074,6402,177,130217,710
1억4000만원8,270,6763,395,990520,490351,04022,99075,1802,205,720220,570
1억4100만원8,315,5803,434,420524,250353,57023,15075,7202,234,300223,430
1억4200만원8,360,5033,472,830527,990356,10023,32076,2602,262,880226,280
1억4300만원8,405,3963,511,270531,740358,63023,49076,8002,291,470229,140
1억4400만원8,450,2903,549,710535,500361,16023,65077,3502,320,050232,000
1억4500만원8,495,2033,588,130539,240363,69023,82077,8902,348,630234,860
1억4600만원8,540,1063,626,560542,990366,22023,98078,4302,377,220237,720
1억4700만원8,585,0003,665,000546,750368,75024,15078,9702,405,800240,580
1억4800만원8,629,9333,703,400550,490371,28024,31079,5102,434,380243,430
1억4900만원8,674,8263,741,840554,240373,81024,48080,0502,462,970246,290


출처: http://job.cosmosfarm.com/ko/calculator/salary

반응형
반응형

[SPRING,JAVA] 프레임워크에 Ajax사용을 위한 JsonView 설정하기, Spring Framework JsonView Ajax Sample


JSON뷰를 이용하여 AJAX를 사용할 수 있게 설정하는 방법이다


예제는 전자정부 프레임워크로 설정했다


사실 pom.xml과 dispatcher에는 기본적으로 등록되어있었다


다른 스프링기반 프레임웤들은 직접 추가해주시면 된다



pom.xml


1
2
3
4
5
6
7
8
9
10
11
12
<dependency>        
    <groupId>net.sf.json-lib</groupId>        
    <artifactId>json-lib</artifactId>        
    <version>2.4</version>        
    <classifier>jdk15</classifier>    
</dependency>
 
<dependency
    <groupId>org.codehaus.jackson</groupId
    <artifactId>jackson-mapper-asl</artifactId
    <version>1.6.4</version
</dependency>
cs



dispatcher-servlet.xml


1
2
3
4
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" id="viewResolver" p:order="0"/>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" id="jsonView">
    <property name="contentType" value="application/json;charset=UTF-8"/>
</bean>
cs



web.xml


1
2
3
4
5
6
7
8
<servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.ajax</url-pattern>
</servlet-mapping>
cs


Controller.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping("/test.do")
public String test(@ModelAttribute("VO") CommentVO commentVO, ModelMap model) throws Exception {
    return "test/test";
}
 
@RequestMapping("/test.ajax")
public ModelAndView testAjax(@ModelAttribute("VO") CommentVO commentVO, ModelMap model) throws Exception {
 
    ModelAndView modelAndView = new ModelAndView("jsonView",resultMap);
    Map resultMap = new HashMap();
    resultMap.put("result1""1");
    resultMap.put("result2""2");
 
    return modelAndView;
}
cs



test.jsp


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script type="text/javascript">
<!--
 
$.post("${pageContext.request.contextPath}/test.ajax",
    {
        test1: "1111",
        test2: "2222"
    },
    function(data) {
        alert("result: " + data);
    }
);
 
-->
</script>
cs



컨트롤러에서 jsonView 설정을 setView 해주는것을 빼먹어서 삽질했었다

출처 : http://blog.nachal.com/790

반응형
반응형

Eclipse 버그인지, 아니면 뭔가 잘못 설정해서 그런 것이지는 모르겠지만,

(이클립스 버그가 은근히 있어요~ 다른 버전은 모르겠지만, Juno 버전은 많은 사람들이 공통적으로 겪는 꽤 많은 버그가 있는 것으로 알고 있습니다.)



가끔 이미 삭제되어 존재하지도 않은 파일에 Breakpoint 가 걸려있어, 매 번 디버깅할 때마다 무언가 걸려서 'Soure not found' 메시지를 띄워주기도 하고. 또 분명 Breakpoint 를 해제했음에도 불구하고, 계속해서 Breakpoint가 잡히는 경우도 있습니다.



그럼 모든 Breakpoint 를 해제하고(당연히 삭제되어 있는 파일에 존재하는 Breakpoint도 말이지요~), 새롭게 설정하면 좋겠는데요.


도대체 Breakpoint는 어디서 볼 수 있는 것일까요? 설정한 파일마다 모두 직접 찾아봐야 하는 것일까요? 아니면 Breakpoint 를 모두 모아놓은 메뉴가 있는 걸까요?


물론 프로젝트에 현재 설정되어 있는 모든 Breakpoint를 한 눈에 확인할 수 있는 기능을 Eclipse는 제공하고 있습니다.


이클립스의 우측 상단에 보시면 [ JAVA | Debug ] 등 다양한 Perspective 가 표시됩니다. 여기서 Debug Perspective 를 선택합니다. (SVN을 사용하신다면 SVN에 대한 Perspective 도 여기에 표시됩니다.)

만약 Debug Perspective 아이콘과 텍스트가 보이지 않는다면 Open Perspective 아이콘을 클릭하여 Debug Perspective 를 열어주면 됩니다.




Debug Perspecitve 가 빨간 박스에 표시되고 있습니다.

(Debug 바로 우측으로 두번째에 위치한, '네모상자와 작은 더하기' 아이콘 메뉴가 Open Perspective입니다.)


Debug Perspective 모드가 선택되면 디버그와 관련된 다양한 탭이 바로 하단에 뜨는데요, 여기서 Breakpoints 탭을 선택합니다.

해당 탭에는 프로젝트에 설정되어 있는 모든 Breakpoint들을 한 눈에 확인하실 수 있습니다.


만약 Breakpoint 지정은 유지시키되, 실제 기능만을 비활성화 시키려면 특정 Breakpoint의 좌측 체크박스를 체크 해제만 하면 됩니다.

반대로 다시 활성화 시키시려면 체크를 하면 되구요.^^ 간단하지요.



하지만 우리가 원하는 것은 모든 Breakpoint의 해제(삭제)입니다.


Breakpoints 탭의 빈 영역에 마우스 커서를 두고 우측 버튼을 클릭합니다. 컨텍스트 메뉴가 뜨면 메뉴에서 'Remove All'을 선택합니다.


이 기능이 바로 해당 프로젝트에 설정되어 있는 모든 Breakpoints 를 제거하는 일을 합니다. (간단합니다.^^)






정말 삭제할 것인지 다시 한번 묻습니다. 'Yes'를 클릭합니다.


일단 한번 제거된 Breakpoint 들은 다시 복구할 수 없습니다. 그러니 중요 디버깅 작업 중이시라면 관련 Breakpoint들도 모두 삭제되니, 신중히 체크 해제하시기 바랍니다.^^





모든 Breakpoints 들이 사라진것을 확인하실 수 있습니다.





이렇게 Breakpoint 들을 제거하시면 이클립스 버그로 인해 제대로 해제되지 않던 것들. 그리고 실제 파일이 삭제되었음에도 불구하고 삭제된 파일의 Breakpoint 가 존재한다고 인식하여 File not Found 오류 메시지를 보여주던 것들이 모두 해결 됩니다.^^



출처: http://ooz.co.kr/270?category=825610 [이러쿵저러쿵]




꽤 많은 분들이 디버거의 존재 자체를 모르고 있거나 혹은 디버거가 있다는 사실은 알아도 그 효용성에 의문을 제기하곤 합니다. 왜냐하면, 우리에겐 Log 클래스나 혹은 printf같은 훌륭한(?) 디버깅 도구가 있다고 생각하기 때문이죠. 물론 이렇게 필요한 변수를 찍어보면서 어떤 곳에서 버그가 있는지를 알아보는 일이 잘못된 일은 아닙니다만 복잡한 여러 상황이 맞물려 재현되는 버그는 이러한 고전적인(?) 방법을 써서 알아보기가 매우 어렵습니다.

원인을 정확히 그리고 빨리 파악하려면 디버거의 사용법을 숙지하고 사용하는 것이 가장 좋습니다. 대부분의 개발 환경에서 디버거를 제공하는데 다행히 이클립스에서도 쓸만한 디버거를 내장하고 있습니다.

오늘 포스팅에서는 이클립스 디버거 사용법에 대해 다루어 볼까 합니다.

이클립스 디버거 뷰


이클립스는 디버거 뷰를 제공하여 디버거를 사용할 수 있도록 합니다. 디버거 뷰는 어디에서 확인할 수 있을까요? 바로 우측 상단에 Debug 뷰에 들어가면 그곳에서 확인할 수 있습니다.

debugger-view

디버깅의 시작


그렇다면 어떻게 디버깅을 활성화한 상태로 프로그램을 실행할 수 있을까요? 상단 메뉴의 Run에서 프로그램을 실행할 때 Debug를 이용하여 프로그램을 실행하면 디버거가 작동하게 됩니다.

run-debug

브레이크 포인트 설정과 뷰


보통 디버깅을 할 때 가장 먼저 하는 일이 브레이크 포인트를 잡는 일입니다. 브레이크 포인트를 에러가 일어나는 라인이나 혹은 의심이 가는 변수를 추적할 수 있는 라인쯤에 잡아놓고 프로그램을 디버깅하면 해당 라인을 실행할 때 디버거가 작동하게 되고 그곳에서 프로그램을 라인 별로 진행해가며 관찰을 진행할 수 있게 됩니다.

브레이크 포인트 설정은 매우 간단합니다. 편집기 왼쪽에 파란 부분(마커 바)을 더블 클릭하게 되면 파란 원이 생기는데 이 원이 브레이크 포인트입니다. 혹은 오른 클릭하여 Toggle break point를 누르면 됩니다. 설정 후 다시 더블 클릭하게 되면 브레이크 포인트가 사라지게 됩니다.

toggle-breakpoint

또한, 디버그의 브레이크 포인트 뷰에서 지금까지 걸어놓은 모든 브레이크 포인트들의 위치를 확인할 수 있고 활성화/비활성화, 삭제도 할 수 있습니다. 여러 브레이크 포인트가 걸려있을 때에는 이 탭에서 확인하고 관리하는 것이 더 편합니다.

breakpoint-view

또한, 디버깅을 진행하고 있는 도중에도 다른 의심이 가는 라인에 브레이크 포인트를 걸 수 있습니다.

스텝 단위 진행


지정한 브레이크 포인트에 다다르면 동시에 디버거가 작동하게 되고 그 라인부터 스텝 단위의 진행을 할 수 있게 됩니다.

debug-ui

이제 이 뷰의 버튼들을 이용하여 현재 상황을 진행하거나 되돌릴 수 있습니다. 자주 사용하는 버튼의 사용법을 알아보면

  1. Resume : 다음 브레이크 포인트를 만날때까지 진행합니다.
  2. Suspend : 현재 작동하고 있는 쓰레드를 멈춥니다.
  3. Terminate : 프로그램을 종료합니다.
  4. Step Into : 메서드가 존재할 경우 그 안으로 들어가 메서드 진행 상황을 볼 수 있도록 합니다.
  5. Step Over : 다음 라인으로 이동합니다. 메서드가 있어도 그냥 무시하고 다음 라인으로 이동합니다.
  6. Step Return : 현 메서드에서 바로 리턴합니다.
  7. Drop to Frame : 메서드를 처음부터 다시 실행합니다.

등이 있습니다.

실제로 디버깅 화면에서 버튼들을 눌러보면 쉽게 그 쓰임새를 아실 수 있습니다.

변수의 상태 확인을 쉽게 해주는 변수 뷰


디버깅을 진행하는 도중 변수의 값이나 객체의 상태를 알고 싶은 상황이 생기게 됩니다. 현재 의심이 가는 변수 이외에도 이 변수에 영향을 끼칠 다른 변수들이나 객체들의 상황을 실시간으로 검사할 필요가 있을 때 변수 뷰를 이용하면 도움을 얻을 수 있습니다.

variables-view

이곳에서 변수나 객체의 상태를 확인하고 변수의 상황에 대해서 저장할 수 있습니다. 변수나 객체의 상황을 모두 저장해서 클립보드에 붙이고 싶은 일이 생기면 해당 변수를 오른클릭 후 Copy Variables를 선택합니다.

copy-var

편집 창으로 돌아가 변수에서 Command + shift + i를 누르게 되면(혹은 오른 클릭 후 Inspect를 선택) Inspector 창이 뜨게 됩니다. 이 창에서 다시 한번 Command + shift + i를 누르면 해당 변수를 Expression 뷰로 보내게 되고 이곳에서 지속해서 변수의 상태를 관찰할 수 있게 됩니다.

inspector

Expression 뷰 이용


Expression 뷰에서는 변수 이름을 입력하거나 수행해보고 싶은 명령어를 직접 입력하여 그 결과 값을 관찰할 수 있습니다. 결과 값을 관찰할 뿐만 아니라 Expression에 써놓은 변수들은 명시적으로 지우지 않는 이상 계속해서 관찰을 수행하기 때문에 변해가는 상황을 지속해서 관찰할 일이 있는 변수나 명령문을 등록해놓기에 좋습니다.

expression-view

Display 뷰 이용


디스플레이 뷰에서는 현 문맥에서 사용할 수 있는 명령어를 실행하거나 변수의 값을 조작하는 일을 수행하기에 적합한 환경을 제공합니다. Expression에서도 비슷한 기능을 제공하지만, 디스플레이 뷰를 이용하는 것이 더 편합니다. 메모장과 같이 쉽게 쓰고 지울 수 있기 때문입니다.

또한, 원본 코드의 수정 없이 편하게 현재의 맥락을 변화시킬 수 있는 것이 가장 큰 장점이라고 볼 수 있습니다.

필요한 명령어들을 적어놓은 후 실행하고 싶은 부분만 드래그하여 수행하거나 혹은 값을 리턴받을 수 있습니다. 지금은 boolean변수 하나의 값을 바꿔보기도 하고 조건 값에 따라 무언가를 리턴 받도록도 해놓은 상황을 스크린 샷으로 담아보았습니다.

값을 반환받고 싶을 때는 두 번째 버튼을, 단순히 실행만 할 때에는 세 번째 버튼을 누르면 됩니다.

display-get-result두 번째 버튼을 눌러 값을 반환받는 상황입니다.

display-execute단순히 실행만 하려면 세 번째 버튼을 누릅니다.

브레이크 포인트에 조건 걸기


브레이크 포인트에 조건을 거는 것이 굉장히 유용할 때가 있습니다. 특히 반복문안에 들어가 있는 코드들을 디버깅할 때 유용하지요. 반복문의 경우 모든 상황을 검사한다기보다는 특정 조건에서 값이 어떻게 들어가는지를 분석하는 경우가 더 많은데 이러한 상황을 검사하기 위해서 브레이크 포인트에 조건을 걸어야 합니다.

브레이크 포인트를 거는 과정까지는 똑같습니다. 브레이크 포인트를 건 후 그 포인트에서 오른 클릭을 하면 Breakpoint properties 옵션이 있는 것을 확인할 수 있습니다. 이 옵션에서 조건문을 설정하여 디버거의 활성화 조건을 설정할 수 있습니다.

breakpoint-properties

먼저 Conditional을 활성화하여 어떤 조건에서 디버깅 화면으로 전환할지를 쓰면 되는데 이 창에 조건식을 쓰면 됩니다.

breakpoint-condition

또 hit count를 이용하여 조건을 걸 수도 있습니다. hit count에 값을 적용하면 해당 라인에 브레이크 포인트가 hit count만큼 잡힌 이후 디버깅 화면으로 전환하게 됩니다. hit count옵션은 반복문에서 한 100번쯤 이후에 디버깅을 시작하고 싶거나 하는 일이 생길 때 유용하게 쓸 수 있습니다.

breakpoint-hitcount

출처:https://spoqa.github.io/2012/03/05/eclipse-debugger.html


반응형
반응형

이클립스 한글 폰트가 너무 작다면

이클립스 폰트(글꼴) 바꾸기


이클립스(Eclipse)를 처음 설치하게 되면 폰트가 너무 작거나 자신이 선호하는 폰트가 아닐 수 있습니다.

특히나 대부분의 이클립스 버전에서 처음 설치 후, 한글이 포함된 소스파일을 불러오면 한글이 매우 작게 표시됩니다.


아래는 소스코드에 한글 주석을 달아보았습니다. 유독 한글이 작습니다.



이클립스에서 기본 지정된 폰트가 한글에는 잘 안맞는 것 같습니다.

폰트를 변경해 보겠습니다.

이클립스 상단 메뉴에서 [Windows - Preferences]를 선택합니다.




Preferences 다이얼로그 창이 뜨면 좌측 트리메뉴에서 [General - Appearance - Colors and Fonts] 를 선택합니다.



우측 Colors and Fonts 에서 Basic - Text Font 를 선택합니다.



그러면 좌측 버튼들이 일부 활성화 되는데요.

여기에서 [Edit...] 버튼을 클릭합니다.

 


글꼴 설정 다이얼로그 창이 뜨면 폰트를 선택합니다. 

여기서는 제가 선호하는 폰트인 Verdana 를 선택했습니다. 크기는 10으로 설정합니다. 이 크기로 설정하면 제가 원하는 한글 폰트 사이즈로 한글이 표시됩니다. 자신이 사용하기 원하는 폰트를 선택한 후, [확인] 버튼을 클릭합니다.



Preferences 다이얼로그 창으로 빠져나왔다면 [OK] 버튼을 클릭하여 폰트 변경 사항을 저장하고, 창을 닫습니다. Preferences 창을 닫지 않고, 적용사항을 바로 적용시키려면 [Apply] 버튼을 클릭하시면 됩니다.



다시 소스 파일로 돌아와보면! 

짜잔 한글이 더 커졌습니다^^


제가 선호하는 폰트 및 글자 크기를 선택한 것이기 때문에 다른 폰트를 원하시거나 더 큰 사이즈를 원하신다면 글꼴 창에서 이를 변경하시면 됩니다. 지금까지 이클립스 한글 폰트가 작은 경우에 변경하는 방법에 대해 알아보았습니다. 한글 폰트 뿐만 아니라, 이클립스에서 사용되는 다양한 영역의 폰트를 설정할 수 있으니, 자신에게 맞는 폰트를 설정하실 수도 있습니다.



출처: http://ooz.co.kr/405?category=825610 [이러쿵저러쿵]

반응형
반응형
1234567···9

+ Recent posts