책 읽기

이것저것

  • 책의 내용을 시작하기 전에 이것저것 설명이 나오는데 이 중에서 웹 퍼블리셔라는 용어가 나온다.
    • Q. 저는 웹 퍼플리셔로 일하고 있는데 프런트 엔드 개발자로 커리어를 전향하고 싶어요. 이 책이 도움이 될까요?
    • 프런트 엔드 개발자는 뭔 줄 알겠는데, 웹 퍼플리셔라는 용어를 처음 들어봐서 정리.
    • 웹 퍼블리셔에 대한 설명은 웹 퍼블리셔의 역할 - 프론트 앤드 개발자와 차이점를 방문해서 확인해 보면 쉽게 그림으로 되어 있다.
    • 간단히 정리하면, 예전에 디자이너가 그림을 그리고, 개발자가 코딩을 해야 할 때 중간에 HTML화(?) 시키는 영역이 발전했다고 보면 된다고 한다.
    • 최초에는 HTML화 시키는 일만 했겠지만, 지금은 웹 접근성등을 지켜야 하기 때문에 더 많은 고려를 해야 하는 영역이 되었다고 한다.
    • 하지만 웹퍼블리셔를 돌아보다라는 글을 확인해 보면, 웹 퍼블리셔가 프론트엔드 개발자와 비슷한 개념으로 혼용되고 있는듯 하기도 하다.
    • 뭐.. 암튼 그럼. ㅋ.

읽으면서 정리하기

Vue.js 필수 기술 살펴보기

Vue.js 소개

Vue.js란 무엇인가?

Vue.js의 특징

  • 20페이지 아래쪽 화면 요소를 꾸미는 HTML, CSS 코드와 데이터베이스에서 데이터를 가져와 제어하는 Java 코드가 한 파일에 섞이면서 가독성이 현저하게 떨어졌습니다. 라는 말
    • 이 문장에서 Java는 꼭, Java만들 뜻하는게 아니라, 서버사이드의 코드를 뜻하는것이라고 생각하는게 맞는것 같다. Java나 PHP 등 웹 서버쪽 코드 라고 하는게 더 좋을듯 하다.

개발 환결 설정 및 첫 번째 프로젝트

뷰 학습을 위한 개발 환경 설정하기

Hello Vue.js! 프로젝트 만들기

  • 37 페이지 중간, 뷰 개발자 도구쪽에서 아래와 같이 '파일 URL에 대한 엑세스 허용' 체크 박스에 체크합니다. 라는게 있는데, 요즘에 크롬이 업데이트 되어서 화면이 좀 다르다.
  • 아래의 그림을 참고하여 켜 주면 된다.
  • 옵션을 키고나면 아래와 같이 Toolbar에 Vue 아이콘이 활성화 되고, 개발자 도구 영역에 Vue 라는게 생긴다. 그리고 Vue의 데이터 상태 같은 것도 확인할 수 있다.

화면을 개발하기 위한 필수 단위 - 인스턴스 & 컴포넌트

뷰 인스턴스

  • 46 페이지 마지막 단락 부분에 만약 여기에 값을 변경하는 로직을 넣더라도 화면이 다시 그려지지는 않습니다. 라는 말
    • 이 말을 이해하는데 오래 걸렸다.
    • 여기서 설명하는 부분은 beforeUpdate 이벤트(?) 이다.
    • 원래 일반적인 동작에서는 값을 변경하면 화면을 새로 그려야 한다.
    • 이 시점은 아직 새로 그리기 직전이니, 원래 그릴려고 하는 값을 강제로 변경하는 코드를 넣는다고 하더라도, 바뀐 값을 이용해 1번만 그린다. 즉, 원래 값을 강제로 바꾸었다고 해도, 다시 그릴려고 시도하지는 않는다. 는 의미로 받아들이면 될 듯 하다.
    • 책을 읽을때도 좀 이해하기 어려웠는데, 이걸 글로 표현하려고 하니... 어렵네? ㅋ
    • 음... 좀 이상하다. 실제로 beforeUpdate에서 message 값을 변경해 보면, 무한루프 에러가 발생한다. ;; 음.. 아직 뭔 말인지 이해가 잘 되지 않는다. ;;;
    • 여기에서 설명하는 그려지지 않습니다.라는 말은, 가상 DOM에 node를 추가하는것인지, Real DOM에 node를 추가하는것인지 모르겠다. ;;; 암튼 그려준다라는 말의 용어가 좀 더 명확해야 할 듯 하다. 뒤의 내용을 차츰 읽어 가면서, 추후 이 내용에 대한 업데이트를 하겠다.

뷰 컴포넌트

뷰 컴포넌트 통신

상용 웹 앱을 개발하기 위한 필수 기술들 - 라우터 & HTTP 통신

뷰 라우터

뷰 HTTP 통신

화면을 개발하기 위한 기본 지식과 팁 - 템플릿 & 프로젝트 구성

뷰 템플릿

  • 102 페이지 둘째, 복잡한 연산은 인스턴스 안에서 처리하고 화면에는 간단한 연산 결과만 표시해야 합니다. 라는 부분.
    • 간단한 연산 결과만 표시하는것을 권장합니다.라고 읽는게 좋다.
    • 안되는게 아니고, 권장하는것이기 때문에 결과만 표시해야 합니다.라는 말이 정확하지 않다.
    • 물론 그 다음 페이지인 103페이지 두번째 단락에서는 뷰에서 이러한 방식을 권하는 이유는 이라고 설명이 적혀 있긴 하다.

뷰 프로젝트 구성 방법

Vue.js 실전 투입!

실전 애플리케이션 만들기

할 일 관리 앱 살펴보기

프로젝트 생성하고 구조 확인하기

컴포넌트 생성하고 등록하기

  • 137 페이지의 중간 부분에 보면, components를 등록하는 부분이 있다.
    • 그런데 그것만 코드를 넣으면 에러가 난다.
    • 그 페이지 제일 마지막에 있는 import 관련 코드도 같이 적어야 한다.
    • 이 책의 설명이 좋긴 한데, 한 개의 코드 덩어리에서 설명해야 하는 부분을, 두개 이상의 덩어리로 나눠서 설명하는 경우가 있다. 이런 부분이 책을 좀 읽기 어렵게 만드는 경향이 있다.

컴포넌트 내용 구현하기

  • 151 페이지의 아래쪽 예제에서 <li v-for='todoItem in todoItems'>{{ todoItem }}</li> 부분
    • vue 2.2.0 이상에서는 v-bind:key필수로 있어야 한다.
    • 그러므로 vue 2.2.0 이상을 쓰면 아래와 같이 코드를 바꿔야 한다.
    • <li v-for='todoItem in todoItems' v-bind:key='todoItem'>{{ todoItem }}</li>

기존 애플리케이션 구조의 문제점 해결하기

더 나은 사용자 경험을 위한 기능 추가하기

  • 페이지 171의 중간에 있는 :key 속성은 v-for 디렉티브를 사용할 때 지정하는 게 좋습니다. 부분
    • 이미 바로 위인 151 페이지의 내용에 대한 설명에 적어 두었다. vue 2.2.0 이상에서는 :key속성은 필수이다.

Vue.js 고급 개발자 되기

뷰 중.고급 레벨로 올라가기 위한 지식

뷰 개발을 위한 웹팩

뷰 개발을 위한 ES6

뷰 CLI에서 사용하는 NPM

지금 당장 실무에서 써 먹는 Vue.js

뷰와 제이쿼리를 같이 사용해도 되나요?

개발 기간이 너무 짧은데 기존 레거시 고드에 어떻게 뷰를 바로 적용하죠?

뷰에 UI 라이브러리와 차트를 어떻게 연동할까요?

뷰로 프로그레시브 웹 앱을 개발하려면 어떻게 시작해야 하죠?

Vue.js

공부하기...

  • Vue.js 를 공부 해 보려고 책을 구매 했는데.. 아직 도착 안했다.
  • 그럴줄 알고, 온라인에 한글로 되어 있는 Vue.js 가이드 문서를 출력했다.
  • 링크 : https://kr.vuejs.org/v2/guide/installation.html

읽기

설치방법

시작하기

시작하기

  • <script src="https://cdn.jsdelivr.net/npm/vue"></script>를 보면, 주소의 끝이 vue로만 끝나고 'js' 확장자가 붙지 않는데, 실제로 다운로드 받아 보면 javascript 파일이 다운로드 된다.
    • 심지어 response header에서도 javascript가 전달 된다고 정보가 온다.
  • 영어 문서를 보면, 아래의 내용이 더 있다. 한국어 문서에는 빠져 있는 내용이다. 개발할때는 아래 코드를 쓰는것이 도움이 될 거란다.

    <!-- development version, includes helpful console warnings -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    

선언적 렌더링

  • 텍스트 보간 이외에도 다음과 같은 엘리먼트 속성을 바인딩 할 수 있습니다.를 보면 보간이라는게 나온다.

    • 여기서 나오는 보간은 영어로는 interpolation이다. 텍스트 끼워넣어 교체하기 이외에도 다음과 같은 엘리먼트 속성을 바인딩 할 수 있습니다. 정도로 읽으면 될 거다.

컴포넌트를 사용한 작성방법

  • v-bind:key="item.id" 부분이 있다.

    • 그 직전의 코드는 v-bind:todo="item"인데, 이 코드의 뜻은 item의 값(속성)을 Component안에서 todo라는 이름으로 사용할 것이라는거다.
    • 그렇다면 v-bind:key="item.id"라는 코드는 item.id를 Component안에서 key라는 이름으로 사용할 것이라고 예상이 된다. 하지만 실제로 key를 출력해 보면 제대로 출력 되지 않는다.
    • v-bind:key는 특별한 의미를 가지고 있기 때문에 원하는대로 처리 되지 않는 듯 하다.
    • 자세한건 뒤에 내용을 보면서 추후 업데이트 하겠다.

Vue 인스턴스

속성과 메소드

  • 유념할 점은, data에 있는 속성들은 인스턴스가 생성될때 존재한 것들만 반응형이라는것입니다. 라는 부분
    • 말그대로 Vue 객체를 생성하는 시점에 mapping 된 sturcture에 존재하는 속성들만 연동된다는 말이다.
    • 추후에 data에 속성을 추가해도, 그 값은 연동되지 않는다.
    • 근데, 그 다음 내용을 보면 $data라는 특별한 속성에 대해서 이야기가 나온다.
    • 생각해 보면 Vue 컴포넌트 내부에서 $data의 값을 이용해 연동 시킨다고 생각할 수도 있다. 하지만 주의해야 한다.
    • Vue 객체를 생성할때 mapping된 structure가 $data로 매핑되는것이다. 그렇기 때문에 vm.$data.b = 3와 같이 값을 넣는다고 해도, 추후에 컴포넌트는 만든 이후에 설정된 속성이므로 "연동"되지 않는다.
    • 그러므로 책에서 설명하듯이, 나중에 사용하려고 하는 속성들은 미리 data 의 속성으로 추가해 두어야 한다.
    • 만약 뒤늦게 반응형 속성을 추가하고 싶다면 '객체 변경 감지에 관한 주의사항' 부분을 읽어 보면 Vue.set(...)를 사용할 수 있다는것을 알려 준다.

인스턴스 라이프사이클 훅

  • options 속성이나 콜백에 ... 와 같은 화살표 함수 사용을 지양하기 바랍니다. 부분
    • 화살표를 이용한 함수사용시 this 에 대해서 다르게 동작할 수 있다.
    • 일반적인 function() 정의 안에서 this를 사용하면, 해당 function이 호출되는 Context(부모객체?)의 this를 따라가게 된다.
    • 하지만 () => { } 형태 안에서 this를 사용하게 되면, 해당 function을 정의한 Context(부모객체?)의 this를 따라가게 된다.
    • 위 말이 뭔 말인지 잘 모르겠으면 화살표 함수의 링크를 방문하여 읽어보면 된다. 자바스크립트에 대해서 잘 모른다면, 꼭 읽어 보는것이 좋다.

템플릿 문법

보간법(Interpolation)

  • 위에서 이야기 했듯이, "끼워 넣어 바꾸기" 정도로 해석하면 된다.
  • v-once 디렉티브를 사용하여 데이터 변경시 업데이트 되지 않는 일회성 보간을 수행할 수 있지만, 같은 노드의 바인딩에도 영향을 미친다는 점을 유의해야 합니다. 부분
    • 말 그대로, "v-once"로 설정된 tag 하위들은, {{msg}} 의 부분도 업데이트 되지 않는다는 말이다.
    • 한번 출력(?)되고 나면 app.msg="ccc"와 같이 값을 바꾼다고 해도, 업데이트 되지 않는다.
  • 실제로 Vue.js는 모든 데이터 바인딩 내에서 JavaScript 표현식의 모든 기능을 지원합니다. 부분
    • 쉽게 말하면 {{ msg }} 부분이나 v-bind:disabled="isButtonDisabled" 부분이나, v-bind:id="'list-'+id" 부분이나, v-if='seen', v-bind:href='url' 등 부분에서 값을 넣어 주는 '' 부분의 코드가 실행 되거나, 그 값이 할당 된다고 보면 된다.
      • 예 1 : v-if='seen'라는 코드가 있을때, seen이라는 변수의 값이 false 면, 해당 tag가 실제로 렌더링 되지 않는다.
      • 예 2 : <a v-bind:href='url'>라는 코드가 있을때, url이라는 변수의 값이 href에 할당 되는것으로 생각하면 된다. 예를 들면 <a href="http://naver.com"> 과 같이 변경 될 것이다.

계산된 속성과 감시자

계산된 속성

  • 하지만 차이점은 계산된 속성은 종속성에 따라 캐시된다는 것 입니다. 부분
    • 캐시되는 정보는 return value 이다.
    • 즉 해당 함수에서 console.log(..)를 출력한 뒤, return 'aa'라고 정의를 하면, 최초 값을 사용할 때는 console.log(..)가 출력 되지만, 그 이후 다시 해당 값을 사용하면 log가 출력되지 않는다.
    • 하지만 return value를 만들때, 변수를 사용한다면, 현재의 변수 값을 이용해서 반환된다. 변수 값이 바뀌면, return value 역시 바뀐다는것을 주의해야 한다.

폼 입력 바인딩

기본 사용법

  • 체크박스 설명중에 <label for="checkbox" ...> 부분에 대한 설명

    • 해당 label이 어느 tag와 연관이 있는지 연결해 주는 것이라고 생각하면 된다.
    • 지금은 for="checkbox" 라고 했으므로, 지금 HTML에서 id가 checkbox인 아이와 연관 되는 label이라는 뜻이다. type이 아니라 id가 checkbox인것과 연동된다는거다.
    • 이렇게 해 두면, <input> 위치가 아니라, <label>위치를 클릭해도, <input>을 누른것 처럼 동작하게 연결 할 수 있다.

      수식어

  • .lazy 설명 중에 .lazy 수식어를 추가하여 change 이벤트 이후에 동기화 할 수 있습니다. 라는 말.

    • v-model은 input text의 값이 변경되면 실시간으로 model 의 값도 같이 변경되도록 되어 있다.
    • change 이벤트는 input text에 값을 입력하는 도중이 아닌, 입력후 다른 곳으로 focus를 옮긴 이후나, 엔터를 입력하는 등의 상황에 발생한다.
    • 그러므로 말 그대로 실시간으로 적용하고 싶지 않은 경우에만 사용하면 된다.

이후 내용들...

  • 가이드 문서만 읽기에는 너무 어렵다. 
  • 소스들이 등장하기는 하는데, 전체의 소스가 아니라 소스 일부만을 설명이 나오는경우가 있어, 명확하게 파악하기 어려운 경우가 있다. 이후 중요하거나 이해를 꼭 해야 하는 부분만 정리 하도록 한다.
  • 나머지는 Do It! Vue.js 입문 책을 구매 했으니, 그 책에서 다시 정리 하던지 하자.



[도서]Do it! Node.js 프로그래밍

정재곤 저
이지스퍼블리싱 | 2017년 03월

내용     편집/구성     구매하기

책 읽기

  • 책 자체는 크기도 크고 두껍기도 하다. 하지만 안의 글자 크기가 작아서 많은 양의 정보가 담겨 있다. 아마도 책을 좀 작게 만들었거나, 읽기 쉽도록 글자를 좀 크게 했으면, 소위 "바이블"이라고 불리는 책 두께가 되었을듯...
  • 책 초반에는 아주 작은 정보까지 책에 나오고는 있으나, 그 작은 정보들을 아주 간단한 설명으로 퉁쳐 버리거나 설명하지 않은 부분에 대해서도 많은 단어들을 등장 시킨다.
    • 예를 들면 18 페이지의 작은 "주의" 박스에서는 "세션"이나 "쿠기"등의 단어를 사용하는데. 여기에 대한 설명은 없다. 그 전에는 "용어"칸을 통해서 조금 어렵다고 생각되는 단어들을 모두 설명하고 있는데 말이다.
    • 초보자들을 위한 책이라서, 세세한 설명이 달려 있는거 치고는, 설명이 부족하다. 그렇다고 설명이 안 달려 있는것도 아니니.
    • 만약 초보자가 해당 책을 읽는다면, 잘 모르는 단어가 나왔을때는 그냥 과감히 무시하고 넘어 가는것이 좋겠다. 설명 자체는 세세하게 잘 되어 있으니, 모르는 단어들은 아마도 책 뒷 부분에 설명이 나올듯 하다.

이것저것

읽으면서 정리하기

31페이지

  • 31페이지에 보면, 이런 문제를 해결하기 위해서 만든게 노드입니다라는 말이 있는데, 이 설명은 좀 이상하다고 생각된다. 노드의 특성이 비동기 입출력이지, 비동기 입출력을 위해서 노드를 만들었다고 적혀 있으니 말이다. 뭐.. 내가 모르는 무언가가 있는건가?

78 페이지

  • basename()에 대한 설명으로 파일 패스에서 파일의 확장자를 제외한 이름을 반환합니다.라고 되어 있는데 잘못된 설명이다. 기본적으로 basename은 파일의 반환하는 함수이고, 두번째 인자에 따라서 확장자를 제거해 주던지 아니면, 그대로 두던지 한다. 심지어 80페이지의 실제 출력 결과물에서도 확장자가 제거 되지 않고 nodepad.exe가 출력 된다.

84 페이지

  • 그리고 문자열은 큰 따옴표(" ") 또는 작은 따옴표(' ')를 사용하여 표기합니다. 라고 되어 있다. 이쪽 세상에서는 큰 따옴표보다는 작은 따옴표를 훨씬 더 많이 쓴다. 이 책에서 나오는 예제들도 대부분(?) 작은 따옴표를 사용하고 있다.

94 페이지 ~ 96 페이지

  • 94 페이지부터 96페이지까지 있는, push, pop, shift, unshift 관련 예제들은 의도를 제대로 표현하지 못한다. 이 함수들은 배열에 어느곳을 기준으로 아이템을 추가/삭제하는지가 중요한데, 예제에서는 단순히 배열의 count 만 하고 있다. 정확하게 동작하는지 확인해 보려면, 최소한 push, pop, unshift 를 사용하기 직전과, 사용한 직후에 console.dir(Users); 라도 있어야 확인할 수 있다.
  • 96 페이지 이후에 나오는 delete, splice, slice 의 경우에는 console.dir(Users); 로 화면에 객체 정보를 출력해 해당 함수들이 잘 동작하는지 확인하도록 예제가 만들어져 있다.

105 페이지

  • 사소하긴 하지만, 예제의 제일 아래쪽줄 바로 윗줄에 '객체의 walk(10)을 호출합니다.' 라고 되어 있는 부분에서 객체의 앞에 공백이 하나 들어가야 한다. 107 페이지의 결과에서는 Person.name 과 '객체의' 라는 글자 사이에 공백이 있다.

107 페이지

  • prototype에 대한 설명이 너무 퉁 쳐서 되어 있다. 메모리를 효율적으로 관리한다라고 하기에는 내부 동작이 복잡하기 때문에 이해하고 넘어 가는 것이 좋다. 오승환님의 글 : [Javascrip] 프로토타입 이해하기 라는 글을 보면 이해하기 쉽게 잘 설명 되어 있다. 물론 초보자라면 읽어도 이해하기 어려울 수 있으니, 그 때는 그냥 패스.

109 페이지

  • 이 작업을 쉽게 할 수 있도록 노드에서 미리 만들어둔 모듈이 url 모듈입니다. 라는 말이 있다. 실제로 url 모듈이 이러한 역할을 하지만, url 모듈에서는 "한글 도메인"을 "영문 도메인"으로 바꾸는 다른 기능들도 가지고 있다. 그러므로, 정확하게 바꾸자면 노드에서 미리 만들어 둔 url 모듈을 사용하면 이 작업을 쉽게 할 수 있다. 고 되는게 정확하겠다. 맞다. 그냥 꼬투리 잡는거다. ㅋㅋ.

119 페이지, 120 페이지

  • readFile(filename, [encoding], [callback]) 이라고 되어 있는데, 정확한 스펙은 readFile(filename, [options], callback) 이다. options에는 encoding을 적을 수 있기 때문에 그 부분은 적당히 맞다고해도, callback은 반드시 필요한데, [callback]으로 표시해서 마치 생략할 수 있는 것 처럼 표기 했다. 그러므로 수정되어야 한다. writeFile, open, read, write, close 모두 callback을 생략해서는 안된다.

127 페이지

  • 주의 상자에 기존 output2.txt 파일을 삭제한 후 output.txt 파일을 output2.txt 파일로 복사하는 과정이 비동기 방식으로 처리되므로 화면에 출력되는 순서는 다를 수 있습니다. 라고 되어 있는데.. 단순히 출력만 다른게 문제가 아니다.
  • 파일을 삭제하는 fs.unlink 역시 비동기로 동작된다. 즉, 언제 파일이 지워질지 모르는 상태에서 다음 코드인 infile.pipe(outfile)을 수행하게 된다. 이 때 fs.unlink보다 infile.pipe(outfile)이 먼저 실행 완료되면, 파일을 "복사"를 한 뒤, 파일을 "삭제"하게 되므로 복사가 제대로 되지 않을 수 있다.
  • 그러므로 단순이 출력만 다른 문제가 아니라, 코드 자체가 오류를 일으킬 수 있도록 되어 있다는것이 문제다.
  • 제대로 수정할 것이라면, fs.unlink의 callback 함수 안에서 복사하는 동작을 넣어야 한다고 생각된다.
  • 아님 말고. ㅋ

137 페이지

  • server.listen(port, host, '50000', function() { ... 부분에서 좀 잘못 된 부분이 있다. 물론, '50000'의 위치에 들어가야 하는 정보는 backlog 라는 값이 되어야 하는데, API 정의상에서는 이 값이 <number>여야 한다.
  • 물론 코드를 동작시켰을때, 잘 동작할 수도 있다. javascript의 오묘한 형 변환이 있기 때문이다. ㅎ. 물론 난 실제로 코드를 동작시켜서 확인해 보지는 않았다. ㅎㅎ.

145 페이지

  • 이 파일을 실행한 후 웹 브라우저에서 요청을 보내면 같은 결과를 볼 수 있습니다. 라고 되어 있다. 같은 결과를 볼 수 있는것이지 실제로 같은 응답을 보낸것은 아니다.
  • pipe를 이용해서 데이터를 바로 쏴 버리면, 현재 보내는 파일의 content-type을 보내지 않게 되므로, 웹 브라우져가 알아서 보여주는것이다.
  • 헤더 정보가 없는 응답

  • 그렇기 때문에 책에서도 이 방법으로 코딩을 했을때 헤더를 설정할 수 없는 등의 제약이 생기므로 필요할 때만 사용하길 권합니다. 라고 적어 둔 것이다.

  • 헤더도 제대로 셋팅하고 파일도 전달 하고 싶다면, infile.pipe(res); 바로 윗 줄에 res.writeHead(200, {'Content-Type': 'image/png'});를 추가해 주면 Content Type 헤더도 제대로 전달 할 수 있다.

  • 헤더 정보가 잘 추가 되어 있는 응답

149 페이지

  • 정박사의 한마디 부분 중 GET 방식은 헤더 부분에 요청 정보들을 넣어 보내고라는 부분이 있다. 정확하게 말하면 GET 방식은 URL 부분에 요청 정보를 전달하는것이다.
  • GET 이던 POST던 상관없이(심지어 PUT이나 DELETE에도) 헤더 부분에 요청 정보를 넣어 보낼 수 있다.
  • 초보를 위한 책이기 때문에, 이 모든것을 구체적으로 설명할 수 없다는것을 독자는 이해해야 한다.
  • 클라이언트에서 어떤 Method를 쓰던 request 헤더에 부가적인 정보를 보내고, 서버에서 그 정보를 받아서 처리하겠다고 약속하면 된다.

164 페이지

  • 첫 번째 코드 예제 중에서 app.use('/public', static(path.join(__dirname, 'public'))); 부분이 잘못 되었다.
  • 코드의 직전 설명에서는 [public] 폴더에 있는 모든 파일을 웹 서버의 루트 패스로 접근할 수 있도록 만들고 싶다면 이라고 표현되어 있다. 이 말대로 동작하려면 코드는 app.use(static(path.join(__dirname, 'public'))); 이 되어야 할 것이다.
  • 다음 페이지의 3번째 예제는 설명이 제대로 되어 있다.
  • 167 페이지의 예제에서도 app.use('/public', static( ... ) )으로 되어 있는데, 이 역시 app.use( static( ... ) )로 되어야 한다.
  • 그리고 static 이라는 이름의 변수를 쓰는건 좋지 않다. 오해를 일으킬 수도 있고, 추후 TypeScript 등을쓰게 되면 또 문제가 된다. 그러므로 static이라는 이름의 변수는 피하도록 하자.
  • 참고로 Express 4.x 이후로는 express.static 미들웨어를 기본으로 제공한다고 한다. 그래서 코드를 쓸 때 그냥, app.use(express.static('public')); 형태로 쓰면 된다. 여러 디렉토리를 설정할 수도 있다고 한다.
  • express.static

168 페이지

  • 아래쪽을 보면 서버 코드에서 use() 메소드로 설정한 함수는 login.html 문서에 접근했을때는 호출되지 않습니다. 라고 되어 있다. 근데... 잘은 모르지만... 아마도... 이 설명은 잘못 되었다.
  • 서버 코드에서 app.use( '/public', static(...)) 부분이 실행되었기 때문에, public 디렉토리에서 login.html을 찾아서, 웹 브라우져로 내려 주었기 때문에, 웹브라우저에서 해당 HTML을 볼 수 있게 된 것이다.
  • 그러므로 정확하게 말하면 서버코드에서 app.use( static(...) ) 메소드로 설정한 함수가 동작했고, 그 다음 app.use() 함수는 호출 되지 않습니다. 라고 이해하는게 좋겠다.
  • 그렇다면 왜, app.use(static(...)) 다음에 우리가 정의한 미들웨어는 실행되지 않는가? 바로, static(...) 함수 안에서 error가 나지 않는 이상 next() 함수를 호출하지 않도록 코드가 만들어져 있다. 관심이 있다면 직접 소스 코드를 확인해 보자.

262 페이지

  • 세번째 줄 I accept th license terms -> I accept the license termse가 빠진 듯 하다.

276 페이지 277 페이지

  • 276 페이지 select ?? from ?? where id = ? and password = ? 부분과 227페이지 SQL문 안에 들어 있는 ?? 또는 ? 기호를 대체한 후 다음과 같은 SQL 문을 만들어 냅니다. 에서 어떤때는 ?? 를 쓰고, 어떤때는 ?를 쓰는데 이건 오타가 아니다.
  • ?? 는 '' 없이 그냥 값이 대체 될 때 사용되고, ?는 ''를 감싸서 대체 된다.
  • 277 페이지 중간에 있는 select id, name, age from users where id = 'test01' and password = '123456' 을 보면 잘 알 수 있다.

348 페이지

  • 마지막 예제에서 <h2><% = title %></h2>가 있는데 %=를 붙여 적어야 한다. 즉, <h2><%= title %></h2> 가 되어야 함.
  • 그 외, 다른 곳에서도 <% =와 같이 띄워쓰는 코드들이 보이는데 이것은 모두 오타이다. 그러므로 다 붙어 있다고 생각하면 된다.
  • 그리고 여기에서 <%를 쓰는 경우와 <%=를 쓰는 경우가 있는데, 이것에 대한 자세한 사용법은 ejs홈페이지 에서 Tags 부분을 확인해 보면 좋다.

8장 뷰템플릿 적용하기...

  • 뷰 템플릿은 ejs와 pug에 대한 설명이 나온다.
  • 기존에 HTML을 알고 있는 사람이라면 ejs가 훨씬 더 직관적이다. 반면 pug는 pug만의 문법을 따라야 한다는 불편함이 있다.
  • 물론 pug가 HTML tag를 모두 적지 않아도 된다는 장점이 있지만, 요즘에는 대부분 IDE 툴에서 HTML 자동 완성을 제공해 주므로, 크게 차이가 나지 않을 것으로 보인다.
  • 암튼 난 ejs 가 훨씬 더 편해 보인다. 예전에 php를 해서 그런지 ejs가 훨씬 더 익숙해 보인다.

373 페이지

  • 두번째 예제 박스에서 passport.use(new LocalStrategy( 부분은 passport.use('local', new LocalStratgy(로 바꾸는것이 책을 읽는데 좀 더 수월하다. 다음장인 374 페이지의 윗 부분 그림에도 스트래티지 설정이라는 부분에 보면 'local'을 설정해 둔 것을 볼 수 있다.
  • 다만 'local'을 생략했다고 해서 잘못 동작하는것은 아닌데, passport.use()함수를 호출할 때, Strategy의 이름을 설정해 주지 않는다면, Strategy에서 name 값을 얻어와 셋팅하도록 되어 있기 때문이다.
  • https://github.com/jaredhanson/passport/blob/master/lib/authenticator.js
  • LocalStrategy의 코드를 보면 자신의 name 값을 설정하는 코드를 찾을 수 있다.
  • https://github.com/jaredhanson/passport-local/blob/master/lib/strategy.js
  • 그러므로 passport.use(new LocalStrategy(로 설정했다고 해도, 가져다 쓸 때는 passport.authenticate('local', ...) 으로 호출 할 수 있게 된다.

427 페이지

  • attach(httpServer, options)는 정확하게 하면, attach(httpServer[, options])이다. 즉, Options는 생략 가능. 물론 책에서 일일이 설명하기 어려우니 생략했을 것이라고 생각한다. 글쓴이보고 뭐라고 하지 말길...
  • 그 아래에 있는 listen(httpServer, options) 역시 마찬가지다.

431 페이지

  • 예제 중에서 socket.on(... 부분의 코드에서 들여쓰기가 잘못 되어 있으니 주의해서 볼 것.

435 페이지

  • 예제나 책 설명 중에 recepientrecipient의 오타이다. 하지만 책 전반에 걸쳐 오타인 recepient를 사용했으니, 실제 코드에서는 별 문제가 없을 것으로 예상된다.
  • 나도 자주 틀리는 단어라.. ㅎㅎ. 이해가 된다. ㅋㅋㅋㅋㅋ.

481 페이지

  • 이 callback 함수는 두 개의 파라미터를 전달받을 수 있는데, 첫 번째 파라미터는 오류 전달을 위해 사용하고 두번째 파라미터는 정상적인 데이터를 전달 할 때 사용합니다. 라고 설명되어 있다.
  • 487 페이지에 실제로 에러가 난 상황을 가정하고, 첫번째 파라미터에 "정보"를 넘기고 있는것을 볼 수 있다. 481 페이지의 내용을 대충 읽으면, 487 페이지의 내용을 이해하기 어려울 수 있을것 같아, 기록해 둔다.

484 페이지

  • 제일 아래 표에서 result : 응답 데이터가 배열 객체로 들어 있습니다. 라고 되어 있다.
  • 여기서는 성공한 경우에 대한 정보만 표로 나타내고 있다. 실패한 경우에는 error이라는 속성에 정보가 채워져 있다. 이 내용은 489 페이지에 설명되어 있다.
  • 다만 실패한 경우에 대한 설명은 표로 설명되어 있지 않아 놓치기 쉬울 것 같이 기록해 둔다.

621 페이지

  • 예제의 하단을 보면 Entities : Entities 부분이 있다.
  • 또한, 622 페이지 ejs 예제를 보면 var entities = new Entities(); 코드가 있다.
  • 뜬금 없이 갑자기 Entities를 사용하고 있는데, 이건 책에 코드가 누락되어 있기 때문이다.
  • 619 페이지 코드에서 제일 윗 줄에 .... 위치에 다음의 코드가 들어가야 한다. var Entities = require('html-entities').AllHtmlEntities;.
  • 인터넷에세 예제를 다운로드 받아보면 추가 되어 있으나, 책에서는 생략되어 있어 의아하게 생각할 수 있는 부분이다.
  • 책에서는 Entitites에 대한 설명을 아예 없다.
  • html-entities 모듈은 HTML을 인코딩/디코딩 시켜주는 역할을 한다. 설치는 npm install html-entities --save로 설치 할 수 있다.


[도서]타입스크립트 마스터 2/e

나단 로젠탈 저/김유성 역
에이콘출판사 | 2018년 03월

내용     편집/구성     구매하기

서두

자바스크립트를 공부하고, 실제로 사용하려고 했지만, 너무나 어려웠다. 가장 어렵다고 생각한 부분이 "자동완성"을 제대로 지원하지 않는것.

Type이 정확하지 않기 때문에, 내가 parameter로 전달해야 하는것도 명확하지 않았고, 모든것을 다 알고(혹은 외우고) 호출해야 하는 일이 어려웠다.

그래서 타입스크립트를 공부해 보기로 결심.

에이콘 출판사의 "타입스크립트 마스터 2/e - 예제로 배우는 타입스크립트"를 구매 했다.

출판사에 있는 정오표에 표시된 것 말고도 잘못 된 곳이 보여, 이곳에 정리해 두고자 한다. 지금 발견한건 딸랑 하나라.. 나중에 더 추가 될 수 있을지 모르겠다.

책을 다 읽었다. 당근 완벽하게 모든 내용을 이해한건 아니다. ㅋㅋ 아래 내용중 내가 잘 모르는 상태로 "잘못됐다"라고 이야기 하는것도 있으니, 이 글을 읽는 사람이 잘 가려서 읽기를 바란다.

책 후기

  • TypeScript의 문법은 거의 java와 비슷하다. 좀 다른게 있다면.

    • 변수나 함수의 type을 정의 할 때, 변수명 뒤 쓰는 형태 public id : string;
    • interface 정의에 field도 정의할 수 있음 interface A { id:string }
    • field에 접근할 때, get set 키워드를 통해서 hooking 할 수 있음 get id() { return this._id; }
    • 읽기 전용 속성은 final 대신, readonly readonly name: string;
    • 여러개의 type을 받고 싶을때는 Object 대신 any var a : any = "aa"
    • 여러개의 정해진 type(Union)을 받고 싶을때는 Object 대신 | var a : string|number;
    • 함수의 파라미터에서 같은 타입의, 다른 갯수에 대한 오버로딩시 모두 각각 정의하는 대신 ? 사용 funnction a( b: string, c?: string, d?: string) { ... }
    • 함수의 파라미터에서 타입이 다른 오버로딩시 모두 각각 정의하는 대신 아래 문법 사용

        function a ( b: string, c: string) : string;
        function a ( b: number, c: number) : number;
        function a ( b: any, c: any) : any {
            // code
        }
      
    • 상속 받은 클래스의 생성자에서는 반드시 super()를 수동으로 불러줘야 한다. Java에서는 super()가 자동으로 호출 되지만, 타입스크립트는 그렇지 않음. 자동으로 호출해 주자는 건의(?)가 많았지만, JavaScript의 super-set이라는 등의 이유로 안된듯 하다.

정오표 및 이해하기 어려운 곳 설명

3장 인터페이스, 클래스, 상속

216 페이지

  • Simple Class는 id, name의 속성과 -> Simple Class는 id 속성과
    • Simple Class에는 name 속성이 없다.

156 페이지

  • myClosure; -> myClosure();
    • 예제 소스에서 의도한것은 myClosure라는 함수가 실행되는것을 의도한 것이다(p157페이지의 상단 설명). 그러므로 함수 실행을 위해서 마지막에 () 를 붙여 주어야 한다.

159 페이지

  • 제일 아래의 예제 처음에 아래의 코드 추가

    enum PersonCategory {
        Audlt,
        Infant,
        Child
    }
    
    interface IPerson {
        Category: PersonCategory;
        canSignContracts(): boolean;
        printDetails(): void;
    }
    
  • 아마도 PersonCategory에 관한 enum과, IPerson이라는 interface 관련 코드가 아예 빠진것 같다. p160 내용이나, 그 뒤의 예제 결과등을 참고하면, 위의 코드가 있어야 될 듯 하다.

174 페이지

  • 제일 아래의 예제 코드에서 static staticName: string; -> static name: string;
    • 다음페이지에 나오는 출력 결과물과, 설명을 보면 코드상의 속성 이름이 staticName이 아니라 name이 되어야 한다.

203 페이지 (오타아님)

  • 유창한 구문으로 함수에 프라미스를 -> 플루언트 구문으로 함수에 프라미스를
    • Fluent의 번역을 "유창한"이라고 했다. 그래서 오히려 이해하기 어렵다. fluent 에 대한 개념은 여러 라이브러리의 사용에서 나오므로, 그냥 "플루언트"라고 그대로 적는게 어떨까 싶다. "데코레이트"도 그대로 사용했으니깐. 324페이지에도 "유창한"이라고 적혀 있으니, 이후에 나오는건 모두 걍 fluent라고 생각하면 된다.

226 페이지

  • containsErros 메서드와 ErrorHelper.trace 메서드를 -> ErrorHelper.containsErrors 메서드와 ErrorHelper.trace 메서드를
    • ErrorHelper는 containsError와 trace 메소드를 모두 가지고 있는데, 한쪽에만 ErrorHelper의 메소드인것 처럼 적어 두었다.

261 페이지

  • 정의에 모델 속성을 할당하려면 model: NoteModel 구문이나 -> 정의에 모델 속성을 할당하려면 model = NoteModel 구문이나
    • 260페이지의 마지막에서 이미 model: NoteModel은 오류가 발생한다고 적혀 있다. 그러므로 261페이지의 내용은 model = NoteModel 이 되어야 한다.

274, 463 페이지 (오타아님)

  • MVC는 모델-뷰-컨트롤러의 두 문자 집합으로 -> MVC는 모델-뷰-컨트롤러의 앞 글자의 집합으로
  • 객체지향 모범 사례의 두 문자를 모은 SOLID 디자인 -> 객체지향 모범 사례의 앞 글자를 모은 SOLID 디자인
    • 두 문자는 머리 두(頭)를 이야기 한 것으로 보인다. 그러므로 읽기 쉽게 "앞 글자" 라고 적어두는게 더 읽기 좋겠다.

323 페이지

  • Jasmine uses a simple format for writing tests. Consider the following TypeScript code: -> 삭제
    • 이미 위에 한글로 해석이 되어 있는데, 코드 쪽으로 설명이 내려와 있다. 그러므로 삭제

334 페이지, 335 페이지

  • doCallback 함수 -> doCallBack 함수
    • 예제에서는 "B"지만, 설명에서는 어떤 때는 "B", 어떤 때는 "b"로 사용한다. 그러니 잘 읽어내도록 하자. 심지어, 334페이지 아래쪽에는 let doCallback 으로 변수를 하나 만들어서 쓰고 있어서 더 혼동이 될 수 있다. 예제를 만들때 요런 점도 좀 고려해서 만들었으면 좋았을 텐데...

341 페이지, 342 페이지

  • jasminjquery -> jasmin-jquery
    • 라이브러리 이름이니 정확하게 "-"를 포함해서 적어야 한다.

363 페이지

  • Id: 0, DisplayName: "none"}}); -> { Id: 0, DisplayName: "none"}});
    • SelectedItem의 값으로 Id와 DisplayName을 넣는 Struture를 넣어야 하는것으로 보인다. 그렇게 되려면 "{"를 제일 앞에 추가해 줘서 객체(?) 형태로 만들어야 한다.

09장 타입스크립트 호환 프레임워크 테스트 는 생략함

  • TypeScript 자체를 공부하기 위해서 책을 보는것이라. 지금 당장은 테스트용 프레임워크를 열심히 볼 필요는 없다고 판단. ㅋ. 그래서 걍 이 부분은 읽지도 않고 패스함.ㅋ.

417 페이지

  • mod1.print(); -> m1mod1.print();
    • 첫번째 예제에서 let m1mod1 으로 정의해서 쓰고 있기 때문에, 다음줄에 있는 코드는 당연히 m1mod1.print(); 가 되어야 한다.

426 페이지 (오타아님)

  • 글 내용을 보면, "main.js파일에서는..." 이런 내용이 나오는데, 어디에도 그 main.js 파일을 찾을 수 없다. 그냥 작가가 있다고 생각하고 이야기 한 것인지 모르겠다. 그래서 책 내용을 읽을때, 독자도 상상력을 가지고 읽으면 될 것 같다.

429 페이지

  • 항목 이름은 'jasmineboot'로 -> 항목 이름은 'jasmine-boot'로
    • 소스코드에서는 물론이고, 직전의 설명에서도 'jasmine-boot'로 설명 되고 있다.

432 페이지

  • 다음 AMD 오류인 filenot-found 오류일 수 있다. -> 다음 AMD 오류인 file-not-found 오류일 수 있다.
    • 이전 설명에서도 file-not-found 와 같이 중간에 모두 - 을 넣어 두었다.

436 페이지 (오타아님)

  • 중간에 보면 http-server를 실행하고 결과를 보여주는 설명이 있는데, http-server를 설치하는 방법은 설명도 안 해 줬다. ( 설마.. 9장에서 해 줬나? ). http-server를 설치하려면 console(cmd)창에서 아래의 명령을 입력해 주면 된다.
  • npm install http-server -g
  • 그 이후 http-server를 실행시키면 된다.

448 페이지, 451 페이지, 565 페이지 등 (오타이님)

  • {{{body}}}

    • handlebars 템플릿은 일반적으로 {{ }} 처럼, 이중 괄호를 2개만 쓴다. 그런데 예제에서는 {{{ }}} 에 대한 설명 없이 body만 {{{body}}}로 사용중이다.
    • handlerbars는 {{ }} 를 사용하게 되면, 단순 String으로 치환하는것 이외에, HTML Tag와 관련되는것들을 모두 화면에 출력할 수 있도록 치환해 준다. 예를 들어 title이 "제목은 귀여워 >_<" 와 같다면, "제목은 귀여워 &gt;_&lt;" 와 같이 치환한다.
    • 하지만 {{{ }}} 를 사용하면 치환하지 않고, 그대로 String이 들어가게 된다. 여기에서는 {{{body}}} 영역에 각종 HTML 코드(<p> 나 <br>등)가 들어가야 페이지 HTML 페이지 구성을 할 수 있기 때문에 {{{ }}}를 사용한 것이다.


Facebook에서 Android dex를 압축(?) 해 주는 툴을 만들어 두었는데, 이것의 이름이 ReDex다.


https://github.com/facebook/redex


Docker로 Ubuntu 14.04 container를  만들고, 위 링크에 있는 가이드 문서대로 따라 했지만 문제가 발생했다.

몇 가지 빠진 부분이 있어 내가 했었던 작업의 기록을 남겨 둔다.

( 이 과정을 Dockerfile로 만들면 좋겠지만, 귀찮아서 통과 )



1. Ubuntu 14.04 로 Docker Container 만들기 

docker run -i -t --name ubuntu14.04_redex ubuntu:14.04 /bin/bash



2. 디렉토리 이동 ( root의 home directory에 모든것을 설치할 예정 )

cd ~



3. redex 가이드 문서에 Ubuntu 14.04 부분을 실행하기 전에 아래의 것을 먼저 실행해야 한다.

redex 설치 및 사용할 때, 그리고 인터넷에서 android sdk 를 다운로드 받을 때 필요하다.

apt-get update

apt-get install curl

apt-get install unzip

apt-get install openjdk-7-jdk

apt-get install wget



4. android sdk를 먼저 설치해야 한다. ( 아래 과정 중 test 과정에서 java를 컴파일 하고, 이를 dx로 묶는 과정이 있다. )

wget https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz 

tar zxvf android-sdk_r24.4.1-linux.tgz 

cd android-sdk-linux

tools/android update sdk --no-ui

( 이후 라이센스에 동의하라고 하면 y를 눌러 주면 된다. 혹시 화면이 멈추면 엔터를 한번 쳐 주면 라이센스 동의 문구가 나온다. )

tools/android update sdk -u -a -t 4,5

( 상황에 따라서 뒤에 있는 숫자인 4,5 를 바꿔야 하는 경우도 있다. 숫자에 맞는 version을 다운받게 된다. 목록을 알고 싶다면 tools/android list sdk -a -u 를 쳐서 번호를 확인하도록 하자. 여기서는 23.0.3을 사용한다. 24는 java version 8이 필요하기 때문에 openjdk 7으로 실행할 수 없다. )



5. ~/.bashrc 파일의 제일 아래쪽에 아래 내용을 추가하도록 하자. ( vi ~/.bashrc ) 

ANDROID_SDK=/root/android-sdk-linux/

PATH=$PATH:/root/android-sdk-linux/build-tools/23.0.3/:



6. 이후 bashrc 파일에 넣어 둔 환경을 현재 터미널에도 적용하기 위해 source 명령으로 상태를 업데이트 해 준다.

source ~/.bashrc



7. redex 가이드 문서에 따라서 각종 tool 다운로드 및 설치 ( 이하의 과정은 달라졌을 수도 있으니, 공식 홈페이지에 가이드 문서를 반드시 다시 확인하자. )

sudo apt-get install \
    g++ \
    automake \
    autoconf \
    autoconf-archive \
    libtool \
    libboost-all-dev \
    libevent-dev \
    libdouble-conversion-dev \
    libgoogle-glog-dev \
    libgflags-dev \
    liblz4-dev \
    liblzma-dev \
    libsnappy-dev \
    make \
    zlib1g-dev \
    binutils-dev \
    libjemalloc-dev \
    libssl-dev \
    libiberty-dev \
    libjsoncpp-dev



8. redex 가이드 문서에 따라서 다운로드, 빌드, 설치

git clone https://github.com/facebook/redex.git

cd redex

autoreconf -ivf && ./configure && make

sudo make install



9. 설치가 다 되었으면 잘 동작하는지 체크 한다.

./test/setup.sh

cd test

make check

근데 여기서 에러가 난다.. 이유는 모르겠음.. ;; 요건 무시해도 된다.



10. 이후 redex 명령을 이용해서 apk를 redex 시킬 수 있다.

( 만약 zipalign 문제가 생기면, 위에 설명한 환경 설정이 제대로 안 된 것이다. ./bashrc 파일에 ANDROID_SDK를 확인해 보도록 하자. )

redex a.apk -o a.redex.apk




그 결과 다음과 같은 결과를 얻었다.

c.apk -> c.redex.apk : 44.8M -> 44.5M

s.apk -> s.redex.apk : 39.8M -> 39.4M

w.apk -> w.redex.apk : 38.4M -> 38.1M

t.apk -> t.redex.apk : 33.8M -> 33.4M

원본 apk는 이미 proguard를 거친 뒤였음에도 약 250kb ~ 400kb 정도 용량이 줄어 들었다.

( 실제로 동작하는지는 확인하지 않았다 ;; ㅎㅎ )


  1. 발자국 2016.10.21 00:07 신고

    빠른나이는 아니지만 뒤늦게 프로그래밍에 재미를 찾아 공부하고있는 백수입니다.
    검색중에 들렸습니다.
    2003년부터 올해까지 꾸준히 공부하시고 포스팅 하신 흔적보고 감동받아 이렇게 덧글 작성해봅니다.
    존경합니다!!

    • 2016.12.20 12:01 신고

      오랫동안 관리되지 않던 블로그라 부끄럽습니다. 즐거운 나날들 보내세요~

자바스크립트를 공부하기로 했다.

그래서 책을 구매하려고 보니.. 제일 유명한게 "자바스크립트 완벽 가이드".

그런데.. 5e은.. 잘못 번역되어 있는게 너무 많아.. 욕을 꽤나 먹는 상황.


마침 3월 말에, 6e이 나와서.. 이 책을 구매 했다.


하지만, 여전히 몇가지 잘못 된 부분이 보여. 이곳에 정리하고자 한다.

인사이트에 올려두면 더 좋겠지만. 오랜만에 내 블로그에 글도 좀 적고..

꾸준히 공부하는 내용도 정리 좀 하고... 할 겸.


내용은 발견하는대로 귀찮지 않으면 업데이트 할 예정이다.



8페이지 중간 부분 수정


var square = function(x); { // 여기서 함수는 값이 되고, 변수 square에 할당된다.

var square = function(x) { // 여기서 함수는 값이 되고, 변수 square에 할당된다.


; 를 제거해야 한다.


9페이지 윗부분 수정


function abs(x) {    // 절대 값을 계산하는 함수를 정의한다.

    if ( x>= 0 ) {   // 괄호 안의 표현식 값이 참이면

        return x;    // if 절 안의 코드를 실행한다.

}                    // if 절의 끝을 나타낸다.

function abs(x) {    // 절대 값을 계산하는 함수를 정의한다.

    if ( x>= 0 ) {   // 괄호 안의 표현식 값이 참이면

        return x;    // if 절 안의 코드를 실행한다.

   }                 // if 절의 끝을 나타낸다


로 괄호의 위치가 잘못 되었다.


53페이지에서 String Warpper 객체 및 동작에 대한 설명 추가


var s = "test"; // 이 문자열을 사용한다.

s.len = 4;      // 문자열 프로퍼티에 값을 할당한다.

var t = s.len;  // 프로퍼티를 참조하라.


이 코드를 실행해 보면 t 값은 undefined이다. 2행은 생성된 임시 String 객체의 len프로퍼티에 4를 할당한다. 그리고 임시 객체는 바로 삭제 된다. 3행은 기존 문자열 값과 같은 값을 가진 새로운 String 객체를 생성하고 len 프로퍼티를 읽으려고 한다. 이 프로퍼티는 존재하지 않아서 undefined로 평가된다. ... 문자열이나 숫자, 불리언 값에서 프로퍼티(혹은 메서드)의 값을 읽으려고 할 때, 문자열, 숫자, 불리언 값이 객체처럼 동작하는 것을 보여준다. ... 값을 할당하는 것은 임시 객체에서 수행되며, 지속되지 않는다.


위의 문장을 이해하기 어려웠는데, 52 페이지에서 마지막에 설명된 내용과 53페이지의 마지막 내용을 잘 읽어보면 이해할 수 있다.


// javascript 에서 s 는 test라는 값을 가지는 단순한 문자열 리터럴일뿐이다.

var s = "test";


// javascript 에서 s의 프로퍼티에 접근하는 순간, s를 String 객체로 만든 뒤에 처리한다. 그 후 사용하고 난 String객체는 버린다.

s.indexOf(" ");   


// 위의 문장은 아마도 내부에서는 

// var anonymousStringObject = new String(s); 

// anonymousStringObject.indexOf(" ");

// 

// anonymousStringObject = undefined; 

// 또는 anonymousStringObject = (function () { return; })(); 로

// 처리가 될 듯 함.


// java 에서 "test" 자체가 String object이다. variable의 type역시 String class으로 받게 된다.

// java

String str = "test";


// 하지만 javascript에서는 "string"이라는 type이 따로 있고, new String(..)으로 정의할 수 있는 String object가 따로 있다.

// javascript

//

// typeof("AA") : "string"

//

// var str = new String("AA");

// typeof(str) : "object"





 63 페이지 중간 부분(3번째 단락)


+와 == 연산자는 객체를 원시 타입으로 변환할 때 Data 타입 객체는 특별하게 취급한다.

 +와 == 연산자는 객체를 원시 타입으로 변환할 때 Date 타입 객체는 특별하게 취급한다.


로 Data를 Date로 변경해야 한다.



 63 페이지 중간 부분 Date 동작이 특이한 점


javascript에서 객체타입이 원시타입으로 변환 될 때는, 기본적으로 객체에서 숫자로의 변환이다. 이 떄는 valueOf()가 사용된다. Date 객체가 참 특이하게, +와 == 연산자랑 같이 사용 할 때는 toString()이 사용되어 string type으로 변환되고, 나머지인 < 나 -(빼기) 등의 연산자랑 같이 사용 할 때는 valueOf()이 사용된다.



 66 페이지 두번째 소스 들여쓰기 잘못.


var scope = "global scope";


function checkscope() {

    var scope = "local scope";

        function nested() {

            var scope = "nested scope";

            return scope;

        }

    return nested();

}

checkscope();


var scope = "global scope";


function checkscope() {

    var scope = "local scope";

    function nested() {

        var scope = "nested scope";

        return scope;

    }

    return nested();

}

checkscope();


와 같이 들여쓰기가 잘못되었음.



 66 페이지 마지막 예제... 자바스크립트알못이지만... 설명이 애매하다... 또한, 들여쓰기도 잘못되어 있다.


function test(o) {

    var i = 0;

    if ( typeof o == "object") {

        var j = 0;

        for ( var k=0; k < 10 ; k++ ) {

            console.log(k);

        }

        console.log(k);

    }

    console.log(j);   // j는 정의되어 있고, 초기화 되어 있지 않을 것이다.

}



해당 부분은 javascript의 특징인 hoisting 을 설명하는 부분이다. hoisting이란, 변수의 정의 및 할당이 실제 코드의 순서에서 아랫쪽에 되어 있다고 하더라고, 해당 변수의 정의를 상단으로 끌어 올린것으로 처리 된다는 설명이다. 또한, javascript에서는 변수의 scope가 블록 기호인 { } 로 묶이는 부분에 대해서 scope를 가지는것이 아니라, 함수 전체에 영향을 받는것으로 되어 있다.


예제에는 j라는 변수가 if 문 안에서 정의되어 있으나, console.log(j)에서도 j의 선언부가 hoisting 되어서 접근이 가능하다는것을 표현하고 싶었던것 같은데, 그렇다면 parameter로 받는 o의 상태에 따라 j가 0이 될 수도 있고, undefined 상태일 수도 있다고 설명을 좀 더 정확하게 적었어야 했다.


hoisting 상태를 고려한다면 위의 코드는 아래와 같이 변할 것으로 예상된다.



function test(o) {

    var i = 0;

    var j;

    if ( typeof o == "object") {

        j = 0;

        for ( var k=0; k < 10 ; k++ ) {

            console.log(k);

        }

        console.log(k);

    }

    console.log(j);   // o가 object가 아닌 경우, j는 정의되어 있고, 초기화 되어 있지 않을 것이다.

}




73 페이지 첫번째 소스 코드


[?]          // 빈 배열: 대괄호 안에 표현식이 없으면 원소가 없음을 말한다.

[]        // 빈 배열 : 대괄호 안에 표현식이 없으면 원소가 없음을 말한다.


? 가 필요 없이 들어가 있다.



73 페이지 마지막 소스 코드


var p = { x:2.3, y:-1.2 };    // 두개의 프로퍼티를 가진 객체

var q = {?};                  // 프로퍼티가 없는 빈 객체

q.x = 2.3; q.y = -1.2;        // 객체 q와 p는 같은 프로퍼티를 갖는다.


var p = { x:2.3, y:-1.2 };    // 두개의 프로퍼티를 가진 객체

var q = };                  // 프로퍼티가 없는 빈 객체

q.x = 2.3; q.y = -1.2;        // 객체 q와 p는 같은 프로퍼티를 갖는다.


?가 필요 없이 들어가 있다.



83페이지 마지막 예제


1 + 2                   // => '3'

"hello" + " " + "there" // => 'hello there'

"1" + "2"               // => '12'

1 + 2                   // => 3

"hello" + " " + "there" // => 'hello there'

"1" + "2"               // => '12'


문자 3이 아니라, 숫자 3으로 계산되어야 한다.



84페이지 중간 예제


 1 + {?}     // => "1[object Object]" : 객체를 문자열로 바꾼 후 이어붙이기

 1 + {}     // => "1[object Object]" : 객체를 문자열로 바꾼 후 이어붙이기


로 필요 없이 ? 가 들어가 있다.



 94 페이지 첫번째 예제가 나온 뒤 4번째 줄에 대한 설명


또한 우변 피연산자가 함수가 아니라면 TypeError 예외가 발생한다.

일반적으로는 "함수"가 아니라, "클래스"가 아니라면 TypeError이 발생한다고 생각 할 수 있다.

하지만 javascript에서는 class라는 형태를 function으로 정의 하기 때문에, 함수가 아니라면 TypeError가 난다고 말 하는게 맞는듯 하다.



97 페이지 첫번째 예제 설명이 명확하지 않음


// max_width가 정의되어 있으면 이것을 사용한다.

// 이 외의 경우 preference 객체에 속한 값을 찾아 본다.

// 그것조차 정의되어 있지 않을 경우 하드코딩된 상수를 사용한다.

var max = max_width || preferences.max_width || 500;

 

위 설명에서 잘못된 부분이 있다.

"max_width가 정의되어 있으면 이것을 사용한다." 부분이다. 정의되어 있는것은 당연히 중요하고, max_width가 어떤 값을 가지느냐도 중요한 문제이다.

max_width가 정의되어 있지 않으면 당연히 max_width는 false로 처리 되고, 그 이후 prefrenerces.max_width를 평가할 것이다.

하지만 max_width가 정의되어 있고 0이라는 값을 가지고 있을때는 어떻게 동작할 것인가? 당연히 0은 false로 처리 될 것이고, prefrernces.max_width을 확인해야 할 것이다. 그러므로 해당 설명을 정확하게 하려면 다음고 같이 바뀌어야 한다.


// max_width가 정의되어 있으면서 0 이 아니라면, 이것을 사용한다. 정의되어 있더라도 0 이라면 이것을 사용하지 않는다.




 97 페이지 두번째 예제


function copy(o, p) {

    p = p || {?};   // 만약 인자 p가 null이면, 새롭게 객체를 생성한다.

    // ...

}


function copy(o, p) {

    p = p || {};   // 만약 인자 p가 null이면, 새롭게 객체를 생성한다.

    // ...

}


로 ? 가 쓸데 없이 들어 있다.


전반적으로 ;; 소스코드 부분의 들여쓰기나, 띄워쓰기(코딩 컨벤션)가 일정하게 되어 있지 않다. 세미콜론 처리 역시 어떤 예제는 꼬박꼬박 넣고, 어떤 예제는 두 줄이상의 소스코드인데도, 넣었다(99페이지 마지막 예제) 안 넣었다(97페이지 마지막예제) 한다. 그래서 이런건 또 왠만해서는 생략.


빈객체나 빈 문배열등을 만들때 쓸데 없이 ? 가 들어 있는 경우가 많다. ? 에 대한 문제는 너무 많아서 이제 부터는 생략.

{?} 나 [?] (?) 등을 보시게 되면... ? 는 없는 것으로 생각하고 코드를 보시라.



자바 스크립트는 기본적으로 Type Checking이라던지, 각종 코드에 대한 검사를 덜 하는것으로 판단된다. 예를 들면 변수를 선언하지 않고도 바로 사용한다던지, 읽기 전용 속성인데도 값을 할당한다던지 하는것에 대해서 에러를 발생시키지 않는다. 

이러한 방식은 코드를 더 쉽게, 그리고 유연하게 만들 수 있는 장점이 있겠지만, 나 같은 사람들에게는 오히려 혼란을 가중 시키기도 한다. 이러한 문제를 해결 하기 위해서 "strict mode"라는 것을 지원한다. 이 기능을 켜 두게 되면, 자바 스크립트가 말 그대로 "엄격"한 검사를 통해서 실행된다. 


"엄격"하게 자바스크립트를 작성하고, 실행하고 싶다면 js 파일의 첫번째 줄에 "use strict"; 를 추가해 주면 된다. 특정 함수만 "엄격"하게 실행하고 싶다면 함수 구현의 첫번째 줄에 "use strict"; 라고 넣으면 된다. ( " 를 빼 먹지 말고 다 적어야 한다. ) - IE 10 미만 버젼에서는 사용할 수 없다고 한다.



"use strict";  // 이 선언으로 인해서 현재 js 파일은 엄격 모드로 실행되게 된다.


function a() {

    // ....

}



function b() { // 아래와 같이 특정 함수만 엄격 모드로 실행되게 할 수도 있다.

    "use strict";

    // .....

}





106페이지 delete연산자 6번째줄 부터 있는 예제


var o = { x:1, y:2 };

delete o.x;

"x" in o;


뜬금 없는 문법 오류로 보일 수 있지만 in 이라는 연산자가 있는 것이다. 

"PROPERTY_NAME" in OBJECT 의 형태로 사용할 수 있다.



106페이지 중간 즈음에 delete 연산자 설명중


delete 연산자의 피연산자는 좌변값(lvalue)이다. 피연산자가 좌변 값이 아니면 연산자는 아무런 동작을 하지 않고, true를 반환한다. 피연산자가 좌변값이면 연산자는 해당 좌변 값을 삭제하려고 시도하며, 피연산자가 성공적으로 삭데되었을 경우 true를 반환한다.


좌변값이라는 용어를 먼저 알아야 한다.


변수에 값을 할달 하는 경우 아래와 같이 코드를 작성한다.


var x = 10;


위 코드에서 = 를 기준으로 왼쪽에 있는 것을 좌변값(left-value,lvalue)라고 하고, 오른쪽에 있는것 우변값(right-value,rvalue)라고 한다. variable이 좌변 값이 된다. 즉, 어떤 값을 할당 할 수 있는것들이 lvalue가 되고, 이것들을 delete 시킬 수 있다는 것이다.


설명상 "delete 연산자의 피연산자는 좌변값(lvalue)이다." 라는것은, delete의 왼쪽에 있는 값이 피연산자라는것이 아니라, delete 다음에 올 수 있는 피연산자는 lvalue 형태의 것들만 올 수 있다는 의미이다.


그 아래쪽에도 나오지만, 정확하게 말하면 위의 코드에서 x를 delete 시킬 수는 없다. delete 시킬 수 있는것은 property 형태만 가능하다. 107페이지 첫번째 예제에서 this.x 에 값을 할당 한 뒤, 그냥 x를 delete 시킬 수는 없었지만, this.x 를 delete 시킬 수는 있게 된다.


107페이지, 110페이지


부수효과


계속해서 부수효과라는 용어가 등장한다. 부수효과라는 용어는 Side Effect라는 용어의 한국어 번역 단어로 많이 사용된다. 우리가 일반적으로 생각하는 Side Effect는 "예상치 못했던 문제 상황"을 뜻하는데, 여기서는 그렇지 않다.

여기서 말하는 부수효과를 쉽게 말하면, state 를 바꾸는것을 생각하면 된다. 아래의 코드를 보자


var x = 3;


위의 코드는 사실상 아무것도 하지 않는것으로 느껴지지만, x라는 변수를 생성하고 이 변수에 3을 할당한 코드이다. x의 state가 변경된 것이다. 이렇다면 이는 "부수효과가 있다"고 볼 수 있다. 하지만 다음 코드를 보자.


Math.cos(Math.PI);


위 코드는 PI 값으로 코사인 정보를 얻어왔지만, 아무곳에서 할당하지 않았다. 즉, state의 변화가 없다. 이 경우 "부수효과가 없다." 고 볼 수 있다.



 

 



 

 



 

 



 

 


[ google drive에서 복사 해 온거라 ;;; 이미지가 깨질 수 있습니다. ]

[ 바로 보기 : https://docs.google.com/document/d/1-yNddpgu71tbFaupqrOrQ23xv8SV_6Y_8OfokPuEQTk/edit?usp=sharing ]


Windows 에서 

Docker Toolbox로 Docker 사용하기




작성자 : 조찬제 ( http://blog.ggaman.com )

작성일 : 2015년 11월 23일




Windows Docker Toolbox를 이용하여 Docker 환경 갖추기


Docker란?

 - “도커” 라고 읽음

 - Linux안에서 또 다른 Linux의 Isolate 한 환경을 만들어 줌

 - Windows에서는? VirtualBox에 boot2docker 간단한 Linux 이미지를 올리고 거기에, 또 다른 Linux를 올려 줌


Docker .vs. VM

 - VM : Linux 위에 가상화 환경을 처리한 Hypervisor가 뜨고, 그 위에 다시 Linux OS가 올라감

 - Docker : Linux 위에 Docker Engine이 있고 그 위에 OS 없이 바로 파일이 올라감. ( 속도 빠름 )

http://static1.squarespace.com/static/4ff3e918e4b03ec22b113d00/t/53cb75cae4b0cb413c8933d4/1405842891457/

Docker 이미지

 - Linux(Host OS)위에 뜰 Linux File들의 집합

 - 해당 Docker 이미지는 수정이 불가능 한 Read Only 상태이다.

 - Docker 이미지를 이용하면, 똑같은 환경의 여러 Linux를 띄울 수 있다.


Docker 컨테이너

 - Docker 이미지를 동작 시킨 Linux 상태 ( CentOS Docker 이미지의 복사본이 Ubuntu(Host OS)에 올라간 상태 )

 - 실제 동작중인 Docker 컨테이너에 접속하여 파일을 추가하거나, 삭제, 서버 실행 등을 할 수 있다.

 - Docker 컨테이너에서, Apache 서버를 띄우게 되면, 그 프로세스는 실제로는 Host OS의 자원을 사용하게 된다.

 - Docker 컨테이너가 종료되면, Docker 컨테이너에서 실행되던 모든 프로세스가 죽는다.

 - Docker 컨테이너가 종료된다고 해도, 편집된 파일들은 컨테이너에 그대로 유지 된다.

 - 종료된 Docker 컨테이너를 살릴 수도 있다. 살린 후 접속해 보면, 편집된 파일들이 유지 됨을 알 수 있다.

 - 다만, 이전에 실행되었던 모든 프로세스가 죽었으므로 다시 살려줘야 한다.

- 컨테이너가 다시 살아 날 때 기본적으로 수행해야 하는 명령어를 설정해 줄 수 있다.

- 이를 이용해 컨테이너가 뜨자마자 서버를 실행하는 등의 작업을 할 수 있다.




Windows 기준 Docker 환경 만들기 ( Mac 도 비슷할거라... )


가이드 문서 진행 기준

 - Windows 7

 - 가용 디스크 용량 50GByte 이상

Docker ToolBox

 - Docker 사용시 필요한 이것저것이 모두 묶여 있음.

 - Docker Toolbox(boot2docker, Kitematic)

 - Oracle Virtual Box

 - Git

다음의 경로에서 Docker Toolbox를 Windows용으로 다운로드 받아 설치한다.

https://www.docker.com/docker-toolbox

2015년 11월 23일 기준으로 DockerToolbox-1.9.0d.exe 다운로드를 할 수 있다.

https://thinkfree.netffice24.com/weboffice/resource/tfsi/8d13a148e467b7be3868675c3ab114b6/html/files/2080795046.png

설치 과정은 언제나처럼 OK와 Next를 연속해서 누르다가 Close 를 누르면 된다.

설치를 완료하면 아래와 같이 Quickstart Terminal 과 Kitematic을 실행할 수 있는 아이콘을 볼 수 있다.

혹시 설치를 하다가 실패하면, 시스템에 이미 설치 되어 있던, Virtual Box와 Git 등을 제거하고 다시 설치 시도해 본다.




Docker에서 사용할 VM을 만들기 전 할 일


Docker Toolbox의 제약 넘어서기

Docker를 실행할 수 있는 Terminal을 실행시키기 전에 몇가지 작업이 필요하다.

Docker를 동작시키기 위해 Docker Toolbox에서 만들어 주는 기본 VM의 Disk의 최대 용량이 약 10~18Gbyte 이기 때문에, Linux를 설치하고 HDCS등을 설치하면 용량이 부족해서 설치가 실패하는 경우가 있다. 또한 Docker용 VM Disk가 생성될 하드 디스크의 용량이 부족하면 실행시 문제가 될 수 있다.

그러므로 Docker를 동작시키기 위한 VM Disk의 위치와 용량을 변경할 필요가 있다. 여기서는 VM Disk가 생성될 하드 디스크의 위치를 "U:\_VM_\docker" 이라고 가정하고, 용량은 약 50GByte 를 잡는다고 가정하자.

Docker Toolbox 가 설치된 디렉토리로 이동해서 start.sh 파일을 문서 편집기로 수정해 준다.

https://thinkfree.netffice24.com/weboffice/resource/tfsi/8d13a148e467b7be3868675c3ab114b6/html/files/869933943.png

start.sh 파일을 문서 편집기로 열고

DOCKER_MACHINE=./docker-machine.exe

를 찾아서 아래와 같이 두 줄로 수정한다. ( Windows 지만 경로가 / 로 구분 되는것에 주의하자. )

DOCKER_MACHINE_CMD=./docker-machine.exe

DOCKER_MACHINE="./docker-machine.exe -s U:/_VM_/docker/"

또한 

if [ ! -f $DOCKER_MACHINE ] || [ ! -f "${VBOXMANAGE}" ]; then

를 찾아

if [ ! -f $DOCKER_MACHINE_CMD ] || [ ! -f "${VBOXMANAGE}" ]; then

로 수정한다.

이렇게 하면 Docker를 동작시키기 위한 VM Disk가 U:\_VM_\docker 에 disk.vmdk 파일이 생기고, 그 용량도 최대 약 50GByte까지 사용 가능해 준다. U:\_VM_\docker 디렉토리는 미리 만들어 두도록 하자.

이제 Docker 를 실행해 볼 수 있는 환경 준비가 끝났다.

https://thinkfree.netffice24.com/weboffice/resource/tfsi/8d13a148e467b7be3868675c3ab114b6/html/files/1025.png

Docker QuickStart Terminal을 실행 시키면 VirtualBox를 하나 띄우게 된다.

https://thinkfree.netffice24.com/weboffice/resource/tfsi/8d13a148e467b7be3868675c3ab114b6/html/files/1430971613.png

이후 조금 기다리면 아래와 같이 Docker Quick Terminal을 만날 수 있다. 

https://thinkfree.netffice24.com/weboffice/resource/tfsi/8d13a148e467b7be3868675c3ab114b6/html/files/567108051.png

여기서 뜨는 Linux 터미널은 사실상은 Windows 상에서 동작하는것이고, 단순히 Linux command를 사용할 수 있도록 되어 있는 터미널이다.

실제로 Docker 이미지가 올라갈 곳은 Virtual Box에 default 라는 VM에 올라 가게 된다. 궁금하다면 VirtualBox Manager UI를 띄워서 아래와 같이 확인할 수 있다.

https://thinkfree.netffice24.com/weboffice/resource/tfsi/8d13a148e467b7be3868675c3ab114b6/html/files/783967742.png

이로써 docker를 실행 시킬 수 있는 기본 환경이 마련되었다.





Docker를 이용하여 Dockerfile 로 이미지 만들기 준비



Docker 이미지를 만들기 위한 준비

여기서는 CentOS 6.6과 Java(Oracle JDK)를 설정하는 것으로 진행하겠다.

로컬 컴퓨터에 적당한 디렉토리를 만들고, 아래의 경로에 접속하여 다음의 파일을 다운로드 받자.

https://drive.google.com/folderview?id=0B4JVe12NOkhxUk41RkV5aWVRVHc&usp=sharing

 - Dockerfile ( Docker 자동 설치 스크립트 )

 - jdk-7u80-linux-x64.rpm ( Oracle JDK )

여기서는 U:\docker 라는 디렉토리에 다운로드 받았다는 가정으로 진행된다.

위 파일을 받고 나면 아래와 같이 총 2개의 파일을 다운로드 받게 된다. 



Docker 이미지 만들기


Docker Quick Terminal에서 Dockerfile이 있는 경로(u:\docker)로 이동하여 아래와 같은 명령을 내리면, Docker Image를 생성하게 된다.

docker build -t centos_oracle_java .

위 명령은 현재 디렉토리에 있는 Dockerfile 파일을 이용해서 centos_oracle_java라는 이름을 가지는 Docker Image를 생성하는 과정이다.

장비에 따라 다르겠지만, 3~5여분이 흐르면 CentOS와 Oracle Java가 설치 완료된 이미지를 생성하게 된다.

이미지가 제대로 생성되었는지 확인하기 위해 아래의 명령을 입력하여 확인하자.

docker images

centos_oracle_java는 이번에 우리가 만든 Docker 이미지이다. centos 이미지는 centos_oracle_java 이미지를 만들기 위한 기본 이미지이므로, 자동으로 받아 진 것이다. 즉, centos 이미지를 다운로드 받고, 그 이미지에다가 Oracle Java를 위한 이것 저것을 설치한 후, 그것들 다시 이미지로 만들어 둔 것이 centos_oracle_java이다. Dockerfile의 첫 번째 명령인 FROM 부분에서 centos 를 base 이미지로 사용한다고 적혀 있는 것을 발견할 수 있을 것이다.




Docker 컨테이너 만들기


이미지가 다 만들어 지고 나면, 8000 포트로 동작하는 서버를 위한 컨테이너를 만들어 보자.

docker run -i -t -p 8081:80 --name chat_server centos_oracle_java //bin/bash

위 명령은 centos_oracle_java 이미지를 이용하여 chat_server라는 이름을 가지는 컨테이너를 하나 만들게 된다.

chat_server 라는 이름을 가진 컨테이너에서 8081 포트를 Host OS에서 공개한 채로 서버가 뜰 것을이므로,  컨테이너 외부에서 접속할 포트컨테이너 내부 포트로 80을 연결하도록 설정하였다. 그러므로 컨테이너에 들어갈 프로그램을 작성 할 때엔 컨테이너 내부 포트인 80 포트를 기준으로 작성해야 한다.

컨테이너가 동작하고 난 직후에 자동으로 //bin/bash 명령이 수행되어 컨테이너 안에 shell로 접속할 수 있게 된다. ( 원래는 /bin/sh 처럼 제일 앞에 / 를 한개만 적어야 하나, Winodws에서 수행할 때에는 제일 앞에 / 를 두개 적어 줘야 한다. ) 컨테이너 안의 Shell로 잘 들어왔는지 확인하기 위해서 아래 명령을 쳐서 확인해 보도록 하자. Cent OS 정보를 볼 수 있을 것이다.

cat /etc/*release*

이제 컨테이너를 빠져 나오도록 하자.

exit

컨테이너에서 빠져 나오면 다시 Docker Quick Terminal 로 돌아 온 것을 알 수 있다.



Docker 컨테이너 상태 확인하기


docker 컨테이너 목록을 보고 싶다면 다음의 명령어로 확인 할 수 있다.

docker ps -a

출력은 다음을 뜻한다.

 - 컨테이너 ID는 9d77b23c2910 이다. 

 - 컨테이너는 centos_oracle_java라는 이미지를 바탕으로 생성된 것이다.

 - 컨테이너가 뜰 때 수행된 명령은 //bin/bash 이다.

 - 컨테이너가 만들어 진지 7분 정도 지났다.

 - 컨테이너가 2분전에 종료되었다.

 - 현재 할당된 포트는 없다.

 - 컨테이너 ID가 너무 어려워서  우리가 chat_server 라는 이름을 주었다.

우리가 이전에 아래와 같은 명령을 내려서 Docker 컨테이너를 띄웠다.

docker run -d -p 8081:80 --name chat_server centos_oracle_java //bin/bash

하지만, 현재 docker 컨테이너의 상태를 확인해 본 결과 port 할당이 하나도 이루어져 있다는것을 알 수 있다. 이는 docker 컨테이너가 내려 갔기 때문에 더 이상 Host OS의 port를 mapping 하지 않게 되었기 때문에 없는 것이다.



Docker 컨테이너 살리기/죽이기/없애기

죽었던 chat_server 컨테이너를 살려 보도록 하자.

docker start chat_server

이후 docker ps -a 를 통해서 chat_server 컨테이너가 동작 중인지 확인해 보자.

그 결과 STATUS 부분에서 “Up” 이라고 적혀 있으면 컨테이너가 실행 중인 것이다.

하지만, 이상하게도 이전과는 달리 chat_server 컨테이너에 shell로 들어 간 것이 아니고, Docker Quick Terminal로 빠져 나와 있다. 또한, 빠져 나와 있는데도 chat_server 컨테이너가 죽지 않고 살아 있다는것을 알 수 있다.

여기에 대한 설명은 어려운데, 간단히 설명하면, 컨테이너는 실행 할 때 수행한 명령의 process가 종료되지 않으면 컨테이너를 살려두고, process가 종료되면 컨테이너도 같이 종료시키게 된다. 우리가 chat_server를 실행시킬 때 내부적으로 //bin/bash를 실행 시키고, 그냥 나왔기 때문에 컨테이너 안에서는 여전히 //bin/bash process가 살아 있어서 컨테이너가 종료되지 않고 살아 있는 것이다.

만약 컨테이너를 죽이고 싶다면 아래의 명령을 이용할 수 있다.

docker stop chat_server

그렇다면 컨테이너를 띄운 상태에서 shell로 접근하고 싶을때는 어떻게 해야 할까? 두가지 방법이 있다.

  1. 새로운 컨테이너를 만들면서 shell로 접속하기

  2. 이미 만들어진  컨테이너에 shell로 접속하기

    1. 만들어져는 있지만 죽은 컨테이너를 띄우면서 shell로 접속하기

    2. 이미 띄워진 컨테이너에 shell로 접속하기

1의 방법인 컨테이너를 띄우면서 shell로 접속하기는 이전에 chat_server 컨테이너를 띄울 때 사용했던 방법과 동일하다.

docker run -i -t -p 8081:80 --name chat_server centos_oracle_java //bin/bash

2.a의 방법은, 우선 chat_server 컨테이너를 띄운 뒤에 2.b 방식을 따라하면 된다.

docker start chat_server

2.b 방식은, 이미 떠 있는 chat_sever 컨테이너에 명령을 수행하고, 그 명령의 입력과 출력을 주고 받을 수 있도록 설정하면 된다. 아래의 명령으로 shell에 접속 할 수 있게 된다.

docker exec -i -t chat_server //bin/bash

-i 과 -t 옵션을 주어야만 //bin/bash 명령을 컨테이너와 주고 받을 수 있다.

chat_server 컨테이너가 더 이상 필요 없다면 삭제를 하면 된다. 만약 삭제하고자 하는 컨테이너가 실행중이라면 먼저 docker stop 으로 멈추고 아래 명령을 이용하면 삭제 할 수 있다.

docker rm chat_server

다시, centos_oracle_java 이미지에서 chat_server2 라는 컨테이너를 만들면, 완전 clean 한 상태로 새로운 컨테이너를 만들 수 있다. 만약 chat_server 에 이것저것 작업하다가 환경이 잘 못 된 경우, 이런 방식으로 쉽게 clean 한 환경을 만들 수 있는 장점이 있다.

새로운 chat_server2 컨테이너를 만들고 shell로 접근해 보자. 아래의 명령을 이용하도록 하자.

docker run -i -t -p 8081:80 --name chat_server2 centos_oracle_java //bin/bash

컨테이너의 shell에 접근한 상태에서 exit 명령으로 shell을 종료하게 되면, 컨테이너 역시 종료 된다. 그렇다면 컨테이너를 종료 시키지 않은 상태에서, Docker Quick Terminal로 나갈려면 어떻게 해야 할까? 아래의 키 조합을 순차적으로 누르면 된다.

^P ^Q ( Control + P, Control + Q )

 

docker ps -a 명령으로 확인해 보면 chat_server2가 종료되지 않고 실행 중인 것으로 발견할 수 있다.



Docker Toolbox 지우기


0. VirtualBox 에 떠 있는 모든 VM 들 내리기

1. 제어판 -> 프로그램 제거 -> Oracle VirtualBox ( Virtual Box를 사용한다면 그냥 둬도 된다. )

2. 제어판 -> 프로그램 제거 -> Docker Tool box

3. 제어판 -> 프로그램 제거 -> Git ( Git 을 사용한다면 그냥 둬도 된다. )

3. c:\Users\사용자명\.boot2docker 삭제

4. c:\Users\사용자명\.docker 삭제



  1. 2018.01.16 02:51

    비밀댓글입니다

Google Android 개발자 간담회에 참석을 했다.


일시 : 2012년 9월 17일.

장소 : 강남 파이낸스 센터 21층 (구글코리아)



주요 내용은 아래와 같았다.


* Android Design Guide

* What's New in Android

* In App Billing / Subscription

* How to get featured in Play Store

* QnA


해당 세션들은 외국분들이 와서 영어로 진행하였다. - 정말 다국적 발표자들이었음.


기본적인 내용은 Google I/O 2012 행사에서 진행했던 세션들을 정리해서 다시 이야기하는 형식이었다.

( 유튜브에 가면 Google I/O 2012 행사를 모두 볼 수 있다! - 보러가기 )


나는 운 좋게도 Google I/O 2012에 직접 참석을 했었고, 회사에서 Google I/O 행사 발표 준비를 하느라 많이 본 내용이었기 때문에 몇몇 세션들은 어렵지 않았다.


그 중에서 내가 참석하지도, 유튜브에서 보지도 않았던 "In App Billing/Subscription" 관련 세션에서 재미나는 것을 알게 되었다. 이미 알고 있는 사람들은 뻔할 수도 있지만 말이다. Google Play 스토어에서 물건을 구매하면 총 금액의 30%를 구글측에서 가지고 가고, 나머지를 개발자가 가지고 간다고 알고 있었다.


(확대해서 사진을 찍었더니. 엉망이네 T_T )


하지만 정확하게 말하면 그 30%를 

텔레콤회사(통신사)

빌링회사(카드사)

구글

욜케 3군데서 나눠 먹는다고 한다. 어찌보면 당연한건데, 난 그냥 자연스럽게 구글이 다 먹는다고 생각하고 있었다.


나온 이야기중 몇가지 정리를 하자면.

1. 메뉴를 아랫쪽에 배치하지 말자.

허니컴과 ICS에서는 기본적으로는 하드웨어 버튼이 없으며, 스크린의 하단에 back, home 등의 버튼이 존재한다. 그러므로 App의 메뉴나 버튼이 하단에 존재하면 사용자가 잘못 누르게 될 가능성이 있다. 그러므로 하단에 버튼이나 메뉴를 배치하지 않도록 하자.


2. billing 시스템은 섞어 쓰지 못한다.

그러니깐 삼성AppStore에 올리면서 Google Billing system을 사용하지는 못한다. 만약에 삼성앱스와 구글플레이에 모두 올려야 한다면 삼성앱스에 올릴때는 삼성의 결재 시스템을, 구글 플레이에 올릴때는 구글플레이의 결재 시스템을 사용하는 두개의 apk을 각각 올려라.


3. 애플의 앱스토어에 있는 리딤코드 같은건 아직...(뒤에 앉아서 제대로 못 들어서 맞는지는 모르겠다. )


4. 구글 플레이에 나라별로 다른 스크린샷을 올릴 수 있을까? 없다.


5. 디벨로퍼 콘솔의 beta가 진행중( 난 안 들어가봐서 잘 모르겠네 ;; )


나머지는 I/O 2012 자료를 참고하는것이 좋겠다. ( 굳이 여기에 정리 안해도 찾아 볼 사람들은... ㅎㅎ )


생각보다 사람들이 질문을 많이 했다. 나도 궁금한게 한 3개쯤 됐는데 못 물어보고 말았다능.. 쩝.


그리고 한국말로 질문한 사람은 나 밖이다. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

영어 공부를 해야 겠다고 느끼긴 했으나, 영어로 질문을 못했다고 부끄럽거나 하지는 않다. ㅋㅋ

( 그래도 영어 공부는 정말로 해야 겠다. -_- 질문은 커녕 말을 알아 먹기도 힘드니 T_T )


재미난 것은 한국말로 질문을 했는데도, 통역을 통하지 않고도 내 질문을 알아 먹었다는것!!!

"Google Plus App이나 Youtube App에서는 Home Button을 누르면 Menu가 왼쪽에서 나오는데, 이렇게 Menu가 나오는 방식이...." 이라고 한국말로 물었는데 -_- 단박에 알아 들었다는게... 하긴 한국말 절반에 영어 절반이니... ㅎㅎ


이런 행사가 있을때마다 회사의 파트장님이나 팀장님에게 허락을 받아야 하는데(업무시간이 마치지 않은 시간이니깐 허락을 얻는건 당연) 매번 참석하도록 해 주셔서 감사합니다.(라고 뜬금 없는 아부를 좀... ) 


이런 행사에 참석을 하면서 느끼는건데, 무언가 목적을 가지고 참석하는 사람들은 부끄러움은 살짝 접고 참석하기 때문에 뭔가 더 활발한 모습을 볼 수 있는것 같다. QnA 시간에도 열의를 가지고 참석하고, 옆 사람에게 말도 쉽게 걸고. 그래서 보고 있으면 참 흐뭇하다. ㅎㅎ




질문에 대한 이야기가 나왔으니 잠시 다른길로 빠져서 예전부터 가지고 있던 생각을...

( 이걸로 다시 글을 하나 쓰자니... 좀 귀찮기도 하고 말야. ㅎㅎ )


가끔씩 세미나에 참석을 하면 이상한 버릇을 가지고 있는 사람들을 만날 수 있다. ( 이번 구글 세미나는 아니었으니 괜히 이번에 참석하신 분들은  신경쓰지 않아도 된다. )


세미나를 진행하면 어떤 수준의 사람들이 참석을 하는지 알 수 없고, 그렇기 때문에 세미나 발표자료를 만들 때 되도록이면 쉽게 만들기 위해서 노력을 하고, 그렇게 때문에 "정확하지 않은 내용"을 발표에 포함시키는 경우도 있다. 


그리고 나중에 질문의 시간에 "니가 뭘 모르는가 본데..." 라는 늬앙스를 잔뜩 풍기면서 질문을 던지는 사람들이 있다. 이런건 발표하는 사람에 대한 예의가 아니라고 생각된다. 소위 말하는 "우리끼리 하는 클래스명까지 나오는 디테일한 기술 세미나"도 아니고, 많은 사람들을 위한 공개 세미나에서 발표하는 건데... 설령 그 사람이 정말로 잘못 알고 있거나, 혹은 모른다고 해도 그런식으로 말을 해야 할까?


그 사람은 그 세미나를 준비하기 위해서 많은 준비를 했을 것이다. 실제 발표 내용을 조사하고, 발표내용을 다듬고, 실제 발표 연습을 해는것만으로 2-3일은 소비했을 것이다.

그런 사람들을 보면

"그럼 니가 해 보던가, 안 할 꺼면 닥치고 있어!" 

라고 외쳐 주고 싶다.



누군가가 이런류의 이야기를 했다. ( 잘 기억은 안 난다. )


"서로 잡아 먹기 위한게 아니라고"

"아무런 도움이 되지 않는다고"

"해결해 나가기 위한 것이라고"





암튼!

주저리 주저리 끝. ㅋ.



Google Android 개발자 간담회 덕분에 백만년만에 블로그에 포스팅 해 봄.

  1. 돼지왕왕돼지 2012.10.14 10:45 신고

    구글 아이오도 직접 다녀오셨어요? 좋으시겠어요 부럽부럽 ㅠㅠ

    • 2012.10.14 16:59 신고

      오잉~
      제가 언젠가 방문해서 댓글까지 남겼던
      돼지왕왕돼지 님이시다 ㅎㅎ

      완전 좋더라구요.
      내년엔 자비를 들여서라도 가볼려구요 ㅎㅎ

      방문 감사합니다 ㅎㅎ

안드로이드의 타겟은 모바일 환경이기 때문에, 프로그램을 만들거나 실행할때 몇몇 제약이 있다.

이야기 할 내용은 왠만해서는 접할 수 없는 안드로이드 프로그램의 메소드 갯수 제한에 관한 이야기다.

안드로이드 프로그램을 작성하기 위한 순서를 보자.

1. "자바언어"로 프로그램을 작성한다.
2. "자바 컴파일러"로 JavaVM의 Bytecode를 생성해 낸다. 
3. JavaVM의 Bytecode를 DalvikVM의 Bytecode로 변경하면서
4. .dex 파일을 만들어 낸다.
5. .dex 파일과 xml 파일과 각종 이미지들을 zip포맷으로 묶고, 확장자를 apk로 바꾼다.
( 5번 항목은 더 복잡한 내용이 있지만 여기서는 굳이 말할 필요가 없어서 대충. ㅋ )


안드로이드에서는 많은 class 및 jar들도 모두 한꺼번에 묶어서 .dex 파일로 묶어 주게 되어 있는데, 안드로이드 프로그램 배포 파일인  .apk 파일을  .zip 으로 확장자를 변경한 뒤에 압축을 풀어 보면 classes.dex 파일을 볼 수 있는데, 이것이 바로 DalvikVM의 Bytecode가 들어 있는 .dex 파일이다. 모든 class 파일과 jar 파일을 단 1개의 .dex 파일안에 다 넣게 되어 있다. ( 틀렸다면 제보해 주세요. )

이때, 발생 할 수 있는 문제가 바로 메소드 갯수 제한이다.

큰 프로그램을 작성하다 보면, 혹은 많은 양의 라이브러리를 가져다 쓰다 보면 당연히 프로그램의 덩치가 커지게 될 것이다. 이때 빌드를 돌리게 되면 위의  순서에서 2번 class파일이나 jar파일을 만드는데는 문제가 없으나, 3번 과정에서 dex 파일을 만들때 "format == null" 따위의 도저히 예측 할 수 없는 에러를 발생 시키고 죽어 버린다.

모바일프로그램이라 큰 프로그램을 짜지 않을것이라고 생각했는지, 아니면 가난한 리소스때문에 일부러 그렇게 설계했는지 몰라도 dex 파일에는 64k(6.5만개) 이상의  메소드가 정의 될 수 없도록 되어 있다. dex 파일 포맷에서 method index를 저장하는 공간이 16bit로 되어 있으므로 당연히 6.5만개 이상의 메소드를 만들게 되면, method index table의 공간이 부족하여 dex 파일을 못 만드는 것이다.

혹시나 6.5만개 이상의 메소드를 사용하는 프로그램을 만들게 되면 거기에 대한 대비책을 만들어야 한다.

1. 안드로이드에서 기본적으로 제공하는 라이브러리를 쓰자.
2. 프로그램 빌드시 실제로 사용되지 않는 라이브러리들을 제거 하자.
3. 필요 없는 메소드들을 생성하지 말자.
4. 그렇게 큰 프로그램은 모바일 프로그램으로 만들 생각을 하지 말자.


하지만, 프로그램에서 필요 없는 메소드들이 어디 있으랴? 좀 더 구조적이고 아름다운 코드를 짜기 위해서 메소드 갯수가 늘어 나는것은 어쩔 수 없는 일이다. 그러므로 좀 더 우아한 방법을 찾아야 하고, 그 방법이 바로 바로 ProGuard 이다.

ProGuard는 Java Bytecode를 난독화, 사용하지 않는 코드 제거, 최적화 등을 수행해 주는 오픈소스 툴이다. 구글 안드로이드측에서도  2.2 인가 부터 이 놈을 추천하고 있다. 해당 툴을 사용하여 최적화 및 사용하지 않는 코드를 제거하게 되면, 메소드 갯수가 많이 줄어 든다. 단, 최적화를 거쳤다면 메소드가 사라지기도 하고, com.ggaman.A.java 파일의 내용 및 메소드가 com.ggaman.B.java 으로 이동되어 있는 경우도 있다. 
그러므로 실제 출시된 제품의 디버깅이 힘든 점이 존재한다. 물론 옮겨진 위치나 변경된 정보를 로그로 남겨주지만 그거 비교하는 것도 얼마나 짜증나는 일이겠는가? 또한 ProGuard 역시 사람이 많든 프로그램이라 버그가 있어, 제대로 빌드를 만들어 주지 못하거나, 빌드는 되나 실행시 제대로 수행되지 않는 경우도 있다. 

하지만 6.5만개 이상의 메소드를 사용할 수 밖에 없는 경우에는 어쩔 수 없이 선택해야만 한다. 

ProGuard에 대한 설명을 하기 위해서 글을 적은것이 아니기 때문에  ProGuard에 관련된 더 자세한 사항은 아래 링크를 이용해 주시라.

구글의 ProGuard 검색 결과
http://www.google.co.kr/#q=proguard&newwindow=1



또 다른 방법도 있다. 문제 상황에 대해서 한번 더 생각해 보면 프로그램의 덩치가 크기 때문에 dex 파일을 만들때 메소드 갯수가 많아서 dex 파일을 못 만들어 내는 것이다. 그렇다면 dex 파일을 여러개 만들면 안될까?

몇 일 전 안드로이드 개발자 블로그에 이 문제를 해결 할 만한 방법이 올라 왔다.

dex 파일을 쪼개서 만들고, Custom Class Loader를 이용해서 dex 파일에 있는  class를 사용하는 방법이다. Android에서는 dex 파일을 읽기 위해서 DexClassLoader를 제공해 주고 있다. 이를 이용해서 파일로 저장되어 있는 dex파일세서 class를 읽어와서 사용할 수 있음을 보여준 예제이다.

다만 해당 방법은 항상 dex파일을 로컬 스토리지에 두고 사용해야 하므로 최초 수행시 dex파일을 로컬 스토리지로 복사해야 하는 점, 그리고 자바의 리플렉션을 이용해서 Class를 찾고 객체는 생성해주어야 하기 때문에 동작이 느린 이슈가 있을 수 있다. 

하지만 로컬스토리지 복사나 동작이 느린것것이 프로그램이 아예 빌드가 되지 않는 것 보다는 훨씬 더 좋은 선택으로 생각된다.

해당 블로그 글은 아래의 링크를 클릭해서 따라 가면 된다.



오랜만에 포스팅 끝. ㅋ.
(검색이 잘 되도록 제목 수정. ㅋ )
  1. 닉쑤 2011.08.03 03:23 신고

    저도 안드로이드 폰 씁니다 ㅎㅎㅎ

    하지만 개발 내용은 알고싶지 않은..ㅎㄷㄷ;

최근 들어 일을 하면서, 어떻게 하면 오해 하지 않도록 코드를 짤 수 있을까? 에 대해서 많은 생각을 한다.

과제 제출용으로 작성하거나, 혹은 테스트로 작성하는 코드가 아니라면, 일반적으로 하나의 프로그램을 여러명이서 작성하게 된다. 그러므로 내가 만들어 낸 코드를 나 혼자 쓰는것이 아니라, 다른 사람도 사용하게 된다. 이때 다른 사람들은 내가 만들 소스 코드를 정확하게 이해하고 쓸 수 있을까? 내가 소스를 이렇게 작성한 의도를 정확하게 파악할 수 있을까?

그렇기 때문에 나 스스로 코드를 작성할때, 되도록이면 오해하지 않는 코드를 작성할 수 있도록 노력하고 있다. 물론 노력하고 있다는 것은 여전히 그렇게 하지 못하고 있다는 의미를 포함하고 있다. ㅎㅎ

내가 생각하는 오해하지 않는 코드의 조건은 아래와 같다.
1. 읽기가 쉬워한다.
2. 의미가 명확해야 한다.

그래서 오해하지 않는 코드를 짜기 위한 생각해둔 방법중 하나인 "수직적인 코드"에 대해서 이야기 해 보고자 한다. "수직적 코드"는 내가 임의로 붙인 이름이다. 아마도 이미 나보다 백만배는 더 똑똑한 사람이 이런 개념을 정리해 두었을거라 생각하지만... 조사 따위 하지 않는다. ㅋㅋㅋㅋ



예를 들어...

간단한 프로그램을 예로 들어 설명하도록 하겠다.

프로그램의 동작

1. 사용자 이름과 암호를 입력 받아 로그인을 한다.
2. 파일 dialog를 띄워서 특정 파일을 선택한 후에 OK라는 글자를 저장한다.
3. 사용자에게 결과를 알려 준다.

이 프로그램을 작성하는 두가지 예제를 차례대로 보고, 수평적 코드와 수직적 코드에 대해서 알아 보자.


첫번째 예제

아래의 코드는 위에 제시한 프로그램을 구현한 아주 더럽게 작성된 예이다. 이 글에서 설명하고자 하는 것을 명확하게 하기 위해서 아주 극단적인 예제로 작성했으니, "세상에 저렇게 프로그램을 짜는 사람이 어디 있냐?" 라고 말하지 마시길... ( 알고보면 자신이 짠 코드들 중에 저런 코드가 존재할 수도 있으니... ㅎㅎ )

첫번째 예제

첫번째 예제


다시 한번 말하지만, 위의 코드는 조금 억지스러운 부분이 있다. 하지만 설명을 위해서 그러니 용서를... 위의 코드는 상당히 코드를 읽기가 힘들다. 하나의 메소드를 읽고 있는 도중에 다시 다른 메소드를 호출하고 있고, 다시 메소드를 읽고 있는 도중에 또 다른 메소드를 호출하게 된다. 이러한 코드는 한곳에서 코드를 모두 읽어 볼 수 없고, 하나의 메소드에서 또 다른 메소드로 계속 추적하면서 내용을 읽어 보아야 한다.

위에서 작성한 코드를 시퀀스 다이어그램과 비슷한 형태로 나타내면 아래와 같이 나타낼 수 있다.

수평적 코드 그림

수평적 코드


이 코드는 startSave에서 loginAndSave를 호출하고, 다시 loginAndSave가 save를 호출하고 있다. 하나의 메소드가 또 다른 메소드를 호출하게 되어, 코드의 흐름이 왼쪽으로 오른쪽으로 좌우로 넓게 나열되게 된다. 이러한 코드를 "수평적 코드"라고 하자.



두번째 예제

그럼 위의 동작을 하는 코드를 좀 더 알아 보기 쉽게 작성해 보자.

두번째 예제

두번째 예제


두번째 예시는 상당히 코드 읽기가 쉬워졌다. 각각의 메소드에서는 단지 1개의 일만 하고 있고, 각 메소드에서 다른 메소드를 호출하는 일도 없다. 그러므로 코드의 시작 위치인 main에서 여러 depth의 메소드를 계속 추적할 필요 없이, 하나의 메소드를 읽고, 다시 제자리로 돌아와서 또 다른 메소드를 읽으면 된다. 메소드명이 명확하므로 main에서 메소드명만 읽고, 굳이 코드를 읽지 않아도 한번에 코드의 흐름을 이해할 수 있다.

두분째 예제를 마찬가지로 시퀀스 다이어그램과 비슷하게 그려서 보도록 하자.

수직적 코드 그림

수직적 코드


위의 그림에서 볼 수 있다시피, 두번째 예제의 그림은 main에서 어떠한 메소드를 호출했다가도, 곧바로 자신에게 돌아오게 되어 있고, 호출한 메소드에서 또 다른 메소드를 호출하지 않고 있으므로, 프로그램의 흐름이 아랫쪽으로 흘러가게 된다. 이러한 코드를 "수직적 코드"라고 하자.


두 예제를 비교해보니...

첫번째 예와 두번째 예에서 메소드 이름을 자세히 보았다면 다른점을 느꼈을 것이다. 첫번째 잘못된 예의 경우에는 하나의 메소드가 두가지 일을 하고 있다. 이렇게 되면 하나의 메소드에서 여러가지 일을 해야 해서 당연히 코드가 복잡해 질 수 밖에 없고, 나중에 코드를 변경하려고 해도 신경써야 할 일이 많아 질 수 있다.


그리고, 첫번째 예제에서 LoginUI를 여러개를 바꿔가면서 사용하고 싶다면 어떻게 해야 할까? 혹은 Login을 체크하는 알고리즘이 여러개라면 어떻게 처리해야 할까?

첫번째 예제에서는 하나의 메소드(loginAndSave)에서 LoginUI도 띄우고 login도 하고 save도 하므로, LoginUI만 교체한다거나, LoginChecker를 다른것으로 교체하는것이 까다롭다. 상속관계를 이용해서 오버라이딩을 하려고 해도, LoginUI만을 다른것으로 교체하기가 힘들다.

하지만 두번째 예제의 경우에는 하나의 메소드가 단 한가지만의 일을 하도록 되어 있다. 물론 코드를 키워나가는것도 더 쉽다. 두번째 예에서 새로운 모양의 LoginUI를 만들고 싶다면, createLoginUI 메소드를 protected로 바꾸고, VCoding을 상속 받아서 createLoginUI만을 오버라이딩 하면 된다. LoginChecker도 마찬가지다.


그리고 method가 한개의 일을 하도록 작성했으므로, 문제가 발생했을때 이 문제를 발생시키는 메소드 1개만 확인하면 버그의 처리도 쉬워 질 것이다. 물론, 유닛테스트도 훨씬 쉽게 수행할 수 있을 것으로 생각된다.


그래서...

이렇듯 수직적인 코드는, 코드 읽기를 쉽게 만들어 줄 뿐만 아니라, 코드를 점점 키워나가는데도 도움이 된다. 그러므로 프로그래밍을 할때 수직적인 코드를 항상 생각하도록 하자.

수직적인 코드 작성하기 수칙

1. 1개의 메소드에서는 1개의 일만 한다.
2. 메소드에서 또 다른 메소드를 호출하지 않도록 노력한다.

수직적인 코드짜기의 제 1수칙이 "1개의 메소드에서는 1개의 일만 한다"는 것인데,
실상 두번째 예제를 보면 아시다시피 main() 메소드에서 아주 많은 일을 하고 있다.

그렇다면 "1개의 메소드에서 1개의 일만 한다"는 것은 좀 말이 안되는것 아냐? 라고 물을 수 있다. 물론 말이 안된다. "프로그램의 어느 정도 깊이에서 코드를 쉽게 읽을 수 있도록 할까?"는 오롯이 프로그래머의 마음에 달렸다.

  1. 닉쑤 2010.09.10 06:31 신고

    우하하하하하

    역시 진작에 때려쳤어야 되는건데..

    아까운 내 4년.. ㅋ

    • 2010.09.10 10:18 신고

      ㅋㅋㅋ 그때 때려친걸 고마워해야지~ ㅎㅎ.

  2. 닉쑤 2010.09.10 12:29 신고

    아,그런거임? ㅋ

    도와줘서 감사.ㅋ

프로그래밍 도중에 이미지를 이용해서 작업을 하는 경우가 있다. 이때 이미지 변환중에서 가장 자주 사용되는 변환은, 이미지 크기 늘리기, 회전하기 등이 있겠지만, 이미지 자체를 변환 시키는 것으로는 GrayScale, 즉 회색화(?)시켜야 하는 경우가 있다.

우리는 프로그래밍할때 어떤색을 분리해 보라고 하면, 빛의 3원색으로 빨간색, 녹색, 파란색, 즉 RGB로 분리하게 된다. HTML에서 색을 표현할때도,  #FF9933(Red=0xFF, Green=0x99, Blue=0x33)등으로 표현하니깐 말이다. 그러므로 최소단위인, RGB를 이용해서 색의 변환을 수행해야 한다. 물론 GrayScale도 이 세가지 색깔을 이용해서 회색으로 만들어 줄 수 있다.


제일 간단한 방법

조금만 생각하면 누구나 떠 올릴 수 있는, 제일 간단한 방식으로는 Red, Green, Blue를 모두 더해서 그 평균값을 이용해서 회색을 만드는 방법이 있다.

gray = ( Red + Green + Blue ) / 3

RGB의 평균값으로 만들어낸 이미지

RGB의 평균값으로 만들어낸 이미지



요렇게 회색화된 이미지를 만들어 냈으니 끝.이라고 생각한다면 오산.


YUV 표현 방법

색의 표현방법은 우리가 일반적으로 사용하고, 알고 있는 RGB 세가지 색깔로 표현하는 방법 이외에 더 많은 방법이 존재한다. 그 중에 하나가 Yxx 색 표현 방법이다. 이때 Yxx는 여러종류가 있다. YPbPr, YCbCr, YIQ등이 있는데, 이것을 그냥 통칭해서 YUV라고 부르도록 하자.

RGB만 있으면 모든색을 표현할 수 있는데, 왜 YUV를 사용할까?

아주 오랜 옛날(?) 이야기가 되겠지만, 이전에는 흑백 텔레비젼을 사용했다. 흑백에서는 색깔이 중요한것이 아니라 오직 밝기만이 중요했다. 말 그대로 흑백이니깐 말이다. 그런데 흑백 텔레비젼만 계속 사용했으면 모르겠지만, 얼마 후 칼라텔레비젼에 나오고 나서 문제가 생겼다. 바로 색깔 정보를 전송해야 한다는것이다. 그러면 색깔 정보를 다 포함한 RGB를 모두 쏴 주면 될것 아닌가? 라고 생각하겠지만, 기존에 흑백 텔레비젼을 사용하던 사람들도 TV는 계속 잘 볼 수 있어야 할 것 아닌가?

그래서 기존에 흑백 TV들이 칼라 정보를 잘못 처리 하지 않도록 밝기 정보는 그대로 유지해야 하는 문제가 생겼다. 즉, 밝기 정보(Y)만을 따로 분리해서 전송하고, 색깔 정보(U,V)는 따로 보내는 방법을 사용하게 되었다. 그렇게해서 생겨난것이 YUV 이다.

그렇다면 YUV중 하나인 YCbCr 은 무슨 말일까?

YCbCr (ITU-R BT.709)

Y = 색의 밝기 정보 ( 기존 흑백 TV를 위한 정보 )
Cb = 색의 밝기 정보에서 파란색의 차이 ( blue-difference chroma compoents )
Cr = 색의 밝기 정보에서 빨간색의 차이 ( red-difference chroma compoents )

밝기정보, Cb, Cr 을 이용하면 실제로 보여주어야 하는 Color을 표현할 수 있다.
( 녹색은 밝기정보, blue, red 정보를 이용하여 계산해 낼 수 있으므로 굳이 전송하지 않는다. )

YPbPr이라는것도 있는데, YPrPb는 아날로그 시스템을 위한 표현방법이고, YCrCb는 디지털(CRT, LCDl, PDP등)을 위해서 따로 만들어둔 표현방법이다. 여기서는 디지털의 색표현방법인 YCrCb에 대해서만 이야기 하도록 하자.

이로써, 색을 표현할때에는 RGB를 이용한 색의 표현 방법 말고 YCrCb를 이용한 색의 표현방법이 있다는것을 알게 되었다. 색의 표현방법이 다르므로 이 표현방법을 이용해서 회색으로 만들어 주는 방법 역시 다르다.

우리가 회색으로 만들고자 하는 이미지는 RGB의 정보를 가지고 있고, 이를 YUV 값으로 변환하게 되면 이때 만들어진 Y 값은 자동적으로 밝기 정보를 가지게 된다. 이 Y 값인 밝기 정보만으로 gray scale 이미지를 만들 수 있다. 그렇다면 Y 값을 구하는 공식을 알아 보도록 하자.

YCrCb
Y = Red * 0.2126 + Geeen * 0.7152 + Blue * 0.0722

YPrPb
Y = Red * 0.299 + Green * 0.587  + Blue * 0.114

우리는 YCrCb만을 볼 것 이므로 각 R, G, B 정보를 이용해서 Y 를 추출해 내고, Y 밝기 값을 이용해서 이미지를 grayscale로 만들어 보면 아래와 같이 나온다. ( 위의 공식에서 RGB에 곱하는 값이 서로 다른 이유는 사람의 눈에 더 민감한 색에 더 많은 가중치를 주어서 계산하기 위해서 위와 같이 복잡한 식이 된다. )

RGB to YCrCb에서 Y값을 이용한 GrayScale Image

RGB to YCrCb에서 Y값을 이용한 GrayScale Image


그렇다면 RGB 평균을 내서 만들어낸 GraySacle이미지와 YCrCb를 이용해서 만들어낸 GrayScale이미지를 서로 비교해 보도록 하자. 나뭇잎 부분의 밝기를 비교해보면 서로 다른 명암을 가지는것을 알 수 있을 것이다.
RGB평균과 YCrCb에서 Y를 이용한 이미지 비교

RGB평균과 YCrCb에서 Y를 이용한 이미지 비교


위와 같이 YCrCb에서 Y 값인 밝기를 이용해서 GrayScale이미지를 만들어 낼 수 있었다.


다른 방법은 없을까?

그렇다면 색을 표현하는 방법은 RGB와 YCrCb밖일까? 당근 그렇지 않다. 그 중에서도 밝기를 따로 나타내고 있는 색 표현방법에는 HSL이나 HSV(혹은 HSB), HSI가 있다.

HSL = Hue, Saturation, Lighness
HSV = Hue, Saturation, Value ( 혹은 HSB = Hue, Saturation, Brightness )
HSI = Hue, Saturation, Intensity

각 마지막에 있는 L, B, V, I 는 모두 밝기 정보를 나타낸다.
이때 RGB를 HSL, HSV(HSB), HSI 로 바꾸는 공식은 모두 다른데, 이때 각 표현 방법중에서 밝기를 계산하는 공식은 다음과 같다.

L = ( Max(R,G,B)  + MIN(R,G,B) ) / 2
V(B) = Max(R, G, B)
I = ( R + G + B ) / 3  (우리가 일반적으로 생각하는 공식)

위에서 보다시피 HSx 방식을 이용하기만 해도 여러가지 공식으로 밝기 값을 처리 할 수 있다.



결론

GrayScale이미지를 만들어 낼때는 별로 생각없이 만들어 내는것이 일반적이다. 혹은 누가 이미 만들어 놓은 그래픽 라이브러리를 잘 가져다가 쓰고, "아~ 뭐 잘 나왔겠지."라고 생각하는것이 일반적이다. GrayScale 이미지를 하나 만들어 낼려고 해도 생각할것이 아주 많다는것을 알 수 있다. 위와 같은 정보를 알지 못하고 그냥 생각없이 만들어진 라이브러리를 가져다 쓰게 되면 내가 원하지 않던 결과를 낼 수도 있기 때문에 잘 생각하고 쓰도록 하자.

당장 급하다면 그냥 가져다 쓴다고 해도, 어느정도 시간이 느긋하다면 "어떻게해서 이렇게 나오는 것일까?"에 대해서 생각해 보는것이 어떨까?


자바에서의 Gray Scale

자바에서도 GrayScale이미지를 만들어 낼 수 있는 방법이 여러가지가 있다. 하지만 내가 원하는 결과를 내기 위해서는 공부가 좀 필요하리라 본다. 자바에서 이미지를 Gray scale로 만드는 방법을 보고 싶다면 아래의 이미지와 소스를 받아서 직접 실행해 보도록 하자.

자바에서 각종 GrayScale 방법

Java의 각종 GrayScale 방법



테스트용 이미지

테스트용 이미지





참고자료

아래의 자료를 참고하면 정리하는데 도움이 될 것이다.
HSL, HSV : http://en.wikipedia.org/wiki/HSL_and_HSV
YCrCb : http://en.wikipedia.org/wiki/YCbCr
RGB, YUV에 대한 설명 : http://cafe.naver.com/camuser/234 - 원본 문서를 못 찾겠음 T_T

==
옛날에 대충 적어 두었던 글을 살짝 정리해서 올림.
적을 글은 많은데.. 언제 다... ;;
  1. 닉쑤 2010.08.02 07:50 신고

    오우 어려워 어려워~
    ㅡㅡ;

    역시 나는 프로그래머는 못되는거임? ㅋ

    • 2010.08.02 08:44 신고

      뭔... 그런걸 가지고... 그냥 하는거지... 궁금하니깐 찾아 보는거고...
      대신 영어 잘하잖아~ ㅎㅎ

  2. 닉쑤 2010.08.02 09:17 신고

    궁금하지 않아요. ㅋㅋ

    과연... 잘 할까요?

    서바이벌 잉글리쉬라는게 있음.. ㅋ

    • 2010.08.02 17:54 신고

      난 서바이벌하지 못할것 같아. ㅎㅎ.

  3. 如旻 2010.08.03 09:59 신고

    아- 저 사진!!
    우.. 오랜만이다.
    너의 역작!
    ㅋㅋㅋㅋㅋㅋㅋㅋ

    • 2010.08.03 10:14 신고

      풉.
      한때 똑딱이로도 저정도는 그냥 찍었는데..

      사진 안 찍은지 몇년. T_T

  4. 학부생 2012.02.07 17:15 신고

    감사합니다 좋은글 잘읽었습니다

  5. 컴퓨터공학도 2012.07.24 22:42 신고

    어휴..학술제 때 어플리케이션을 제작하는데, 영상처리가 필요해서 찾다가
    좋은 글을 보고 가네요 ㅎ
    너무 감사합니다!

    • 2012.07.26 17:04 신고

      영상처리까지야.
      그냥 알고 있는거 정리해서 적은건데요. ㅎㅎ

      방문 감사합니다.

  6. pica 2014.01.14 23:16 신고

    많은 도움이 되었습니다.
    궁금했던 부분이 많이 해결되었네요.
    감사합니다.

  7. 어벙이 2015.08.17 12:46 신고

    퍼갈게요.. 좋은글 보고 갑니다. `^^

블로그 글 정리하면서 오래전에 참고 했던 자료를 공개.

Status Code

Associated Message

Meaning

100

Continue

클라이언트로부터 일부 요청을 받았으니 나머지 요청 정보를 계속 보내 주시오. (HTTP 1.1에서 처음 등장)

101

Switching Protocols

서버는 클라이언트의 요청대로 Upgrade 헤더를 따라 다른 프로토콜로 바꿀 것임. (HTTP 1.1에서 처음 등
장)    

200

OK

모든 것이 정상적임. GET이나 POST 요청 뒤에 문서가 온다. 이것은 서블릿의 기본 상태다. setStatus를 사용하지 않으면
이 상태코드를 얻게 된다.

201

Created

서버에서 문서를 만들었음. Location 헤더는 그 URL을 가리킨다.    

202

Accepted

요청이 수행되었지만 처리는 끝나지 않았음.

203

Non-Authoritative Information

문서는 정상적으로 반환되었지만 복사본이 사용되었으므로 응답 헤더중 일부가 정확하지 않을 수
도 있음. (HTTP 1.1에서 처음 등장)

204

No Content

새 문서 없음. 브라우저는 이전 문서를 계속 보여줘야 한다. 이것은 사용자가 페이지를 주기적으로 리로드를 하던
중 이전 페이지가 이미 만료되었을 때 사용할 수 있다. 하지만 Refresh 응답 헤더나 <META HTTP-EQUIV="Refresh" ...> 같은 헤더를 사용
해서 페이지를 자동으로 리로드 시켰을 때는 동작하지 않는다. 왜냐하면 이 상태 코드를 반환하면 추후의 리로딩이 멈추기 때문이다. 하지
만 자바 스크립트로 리로드하게 해 주는 것은 작동한다.

205

Reset Content

새 문서 없음. 하지만 브라우저는 문서 창을 리셋해야 한다. 브라우저가 CGI 폼 필드를 전부 지우도록 할 때 사용
된다. (HTTP 1.1에서 처음 등장)

206

Partial Content

클라이언트가 Range 헤더와 함께 요청의 일부분을 보냈고 서버는 이를 수행했음. (HTTP 1.1에서 처음 등장)

300

Multiple Choices

요청된 문서가 여러 군데서 발견되었음. 이 때 서버는 해당하는 모든 문서들을 나열할 것이다. 만약 서버가
선호하는 선택이 있으면 Location 응답 헤더에 나열해야 한다.

301

Moved Permanently

요청된 문서는 어딘가에 있고 그 문서에 대한 URL은 Location 응답 헤더에 주어졌음. 브라우저는 자동적
으로 새 URL의 링크를 따라가야 한다.

302

Found

301과 비슷하지만 새 URL은 임시 저장 장소로 해석된다. 이 메시지는 HTTP 1.0에서는 ‘Moved Temporarily’였다. 그리고
HttpServletResponse의 상수는 SC_FOUND가 아니라 SC_MOVED_TEMPORARILY다. 이것은 매우 유용한 헤더인데 이 헤더를 통해 브라
우저가 자동적으로 새 URL의 링크를 따라가기 때문이다. 이 상태 코드는 아주 유용하기 때문에 이 상태 코드를 위해 sendRedirect 라는
특별한 메소드가 있다.  response.sendRedirect(url)을 사용하는 것은 response.setStatus(response.SC_MOVED_TEMPORARILY)과
response.setHeader("Location", url)를 쓰는 것에 비해 몇 가지 장점이 있다. 첫째, 더 쉽게 사용할 수 있다. 둘째, sendRedirect을 써서
서블릿이 그 링크를 포함한 페이지를 자동으로 만들어 준다(자동으로 redirect를 따라갈 수 없는 오래 된 브라우저에서도 볼 수 있게 해 준
다). 마지막으로, sendRedirect에서는 상대 URL이 절대 URL로 해석되기 때문에 상대 URL도 다룰 수 있다.  이 상태 코드는 종종 301번과
혼용된다. 예를 들어 <http://host/~user(> (맨 마지막에 ‘/’이 빠짐)과 같이 오류가 있는 요청에 대해 어떤 서버는 301을 어떤 서버는 302
를 보낸다.  기술적으로 브라우저는 원 요청이 GET이었다면 자동적으로 리다이렉션을 따라 가도록 되어 있다. 더 자세한 사항은 307 헤더
를 보라.      

303

See Other

301/302과 같지만 원래 요청이 POST였을 경우 리다이렉트 되는 문서(Location 헤더에 주어졌다) GET을 통해 받아
야 한다. (HTTP 1.1에서 처음 등장)

304

Not Modified

클라이언트의 캐시에 이 문서가 저장되었고 선택적인 요청에 의해 수행됨(보통 지정된 날짜보다 더 나중의 문서만
을 보여주도록 하는 If-Modified-Since 헤더의 경우). 서버는 클라이언트에게 캐시에 저장된 이전 문서를 계속 사용해야 한다고 말할 것이
다.

305

Use Proxy

요청된 문서는 Location 헤더에 나열된 프록시를 통해 추출되어야 함. (HTTP 1.1에서 처음 등장)

307

Temporary Redirect

Temporary Redirect      이것은 302 ("Found" 또는 "Temporarily Moved")와 같다. 많은 브라우저에서 메시지가 POST일 때 원래는 303 응답의 POST 요청의 리다이렉션을 따라 가야 함에도 불구하고 302의 응답을 따르기 때문에 HTTP 1.1에서 추가되었다. 303 응답은 모호하지 않도록 의도되었다. 303 응답의 경우에 대해서는 리다이렉트 된 GET과 POST 요청을 따르고 307 응답의 경우에는 GET  요청만 따른다. 몇 가지 이유로 HttpServletResponse에는 이 상태코드에 해당하는 상수가 없다. (HTTP 1.1에서 처음 등장)  

400

Bad Request

요청에 문법적으로 잘못된 부분이 있음.

401

Unauthorized

클라이언트가 올바른 허가를 받지 않고 허가가 필요한 페이지에 접근하려 함. 여기에 대한 응답으로 브라우저가 대화창을 열어 사용자 이름과 암호를 받아들이도록 하는 WWW-Authenticate 헤더를 포함해야 한다.    

403

Forbidden

사용 권한에 관계없이 내용을 볼 수 없음. 종종 파일 이름이 잘못되었거나 서버의 디렉터리 퍼미션이 잘못 되었을 때 나온다.  

404

Not Found

이 주소에서는 어떤 내용도 발견할 수 없음. 이것은 표준 ‘no such page’응답이다. 이 상태 코드는 아주 일반적인 응답이다. 그래서 이 상태코드를 위한 HttpServletResponse:sendError(message)라는 특별한 메소드가 있다. sendError는 serStatus에 비해 에러 메시지를 보여주는 에러 페이지를 자동적으로 만들어 준다는 장점이 있다.

405

Method Not Allowed

요청 메소드(GET, POST, HEAD, DELETE, PUT, TRACE 등) 를 특정 자원에 대해서는 쓸 수 없음. (HTTP 1.1에서 새로 등장)

406

Not Acceptable

지정된 자원이 클라이언트의 Accept 헤더에 명시된 것과 호환 되지 않는 MIME content-type을 생성함. (HTTP 1.1에서 새로 등장)

407

Proxy Authentication Required

401과 비슷하지만 서버가 Proxy-Authenticate 헤더를 반환해야 한다. (HTTP 1.1에서 새로 등장)

408

Request Timeout

클라이언트가 요청을 보내는 데 너무 오랜 시간이 걸림.(HTTP 1.1에서 새로 등장)

409

Conflict

보통 PUT 요청과 관계 있다. 보통 틀린 버전의 파일을 업로드할 경우 발생한다. (HTTP 1.1에서 새로 등장)

410

Gone

문서가 사라졌고 포워딩할 주소도 없음. 404와 다른 점은 이 경우 문서가 완전히 사라졌다는 것을 서버가 안다는 점이다.
404는 어떤 이유인지는 모르는데 단지 요청한 것을 사용할 수 없다는 것을 의미한다. (HTTP 1.1에서 새로 등장)

411

Length Required

클라이언트가 Content-Length를 보내지 않으면 서버가 처리할 수 없음.(HTTP 1.1에서 새로 등장)

412

Precondition Failed

요청 헤더에 설정되어 있는 어떤 조건이 맞지 않음. (HTTP 1.1에서 새로 등장)

413

Request Entity Too Large

요청된 문서가 현재 서버가 다룰 수 있는 크기보다 큼. 만약 서버에서 나중에 다룰 수 있다고 생각
되면 Retry-After 헤더를 포함시켜야 한다. (HTTP 1.1에서 새로 등장)

414

Request URI Too Long

URI가 너무 길다. (HTTP 1.1에서 새로 등장)

415

Unsupported Media Type

요청이 알려지지 않은 형태임(HTTP 1.1에서 새로 등장)    

416

Requested Range Not Satisfiable

클라이언트가 요청에 적당하지 않은 Range 헤더를 포함시켰음 (HTTP 1.1에서 새로 등장)

417

Expectation Failed

Expect 요청 헤더의 값이 맞지 않음. (HTTP 1.1에서 새로 등장)    

500

Internal Server Error

일반적인 ‘server is confused’ 메시지. 종종 CGI 프로그램이나 서블릿의 결과가 잘못되거나 적절하지 않은
헤더를 만들었을 때 발생한다.    

501

Not Implemented

요청한 것을 서버에서 지원하지 않음. 예를 들면 클라이언트가 서버에서 지원하지 않는 PUT과 같은 명령을
내렸을 때 발생한다.         

502

Bad Gateway

프록시나 게이트웨이의 역할을 하는 서버에서 볼 수 있다. 초기 서버가 원격 서버로부터 부적절한 응답을 받았음
을 나타낸다.  

503

Service Unavailable

처리할 수 있는 한계를 벗어나 과도하게 요청이 들어와서 서버가 응답할 수 없음. 예를 들면 스레드나 데이
터베이스 연결이 가득 차 있을 때 서블릿에서 이런 헤더를 반환한다. 서버는 Retry-After 헤더를 낼 수 있다.

504

Gateway Timeout

프록시나 게이트웨이의 역할을 하는 서버에서 볼 수 있다. 초기 서버가 원격 서버로부터 응답을 받을 수 없
음을 나타낸다. (HTTP 1.1에서 새로 등장)

505

HTTP Version Not Supported

서버가 요청 라인에 지정된 HTTP 버전을 지원하지 않음. (HTTP 1.1에서 새로 등장)

  1. 닉쑤 2010.07.22 03:04 신고

    404가 제일 익숙하군요! ㅋ
    지금 제 목표는 형 하루 방문자 수 추월. ㅋ
    목표를 향해 돌진~ 두두둗두!!!!

    • 2010.07.22 14:46 신고

      난 별로 글을 안써서. ㅋㅋ.
      니가 곧 추월할꺼야~ ㅎㅎ

  2. 닉쑤 2010.07.22 15:28 신고

    세개로 나눠서 댓글단거임? ㅎ

    글쎼요. 저는 어제 54.... 겨우 100넘나 했더니. ㅎ

2008년 9월에 Codein ( http://codein.co.kr ) 카페에 적어 두었던 글을 여기에 다시 옮겨둠.

====

안녕하세요.
 찬 입니다.

GPL를 적용한 소스코드를 사용하면, 모든것을 공개해야 하는것에 대한 의문이 있을 수 있습니다.

그래서 이번에 찾은 내용이 있는데 정리하는 겸 올려둡니다.
http://www.gnu.org/licenses/gpl-faq.ko.html


이 중에서 몇가지 모호했던것 정리

1. GPL 라이센스가 걸린 라이브러리를 사용하면, 내가 만든것도 GPL을 적용해야 하나?

    - GPL 라이센스가 걸린 source code의 결과물을 linking ( static, dynamic 포함 ) 하면 무조건 GPL로 해야 한다.

    - 관련 문서 : 코드를 GPL 프로그램과 링크시켜야만 제가 만들고자 하는 독점 프로그램을 만들 수 있습니다. 이것은 제가 만든 프로그램이 GPL 프로그램이 되어야 한다는 것을 의미합니까? ( http://www.gnu.org/licenses/gpl-faq.ko.html#TOCLinkingWithGPL )


2. GPL 라이센스가 걸린 Source를 실행파일(exe)로 만들었을때, 내 프로그램에서 fork나 exec로 수행하면, 내가 만든것도 GPL을 적용해야 하나?

    - 아니다. 실행파일(exe)을 단지 fork나 exec로 수행할때에는 plug-in 형태로 보아서, 내가 만든것은 GPL을 적용하지 않아도 된다.

    - 관련 문서 : 플러그인 (plug-in)을 사용하는 프로그램을 GPL로 공표한다고 할 때, 플러그인의 라이선스에 대한 조건이 있습니까? ( http://www.gnu.org/licenses/gpl-faq.ko.html#TOCGPLAndPlugins )


3. 링킹해서 사용하는 경우와 exec나 fork를 이용해서 수행하는것이 뭐가 다른가?

    - 링킹은 같은 메모리 구역을 사용하는것이고, exec나 fork는 서로 다른 메모리 영역에 사용되는것이다.

    - ``단순 집합(aggregation)''과 ``두개의 모듈을 결합하여(combine) 하나의 프로그램으로 만든다''는 의미의 차이는 무엇입니까? ( http://www.gnu.org/licenses/gpl-faq.ko.html#TOCMereAggregation )

    - 모듈들이 특정한 실행 파일 안에 함께 포함되어 있다면 이것은 명확히 하나의 프로그램으로 결합되어 있는 것

    - 파이프와 소켓, 명령행 인자 등은 두개의 독립된 프로그램간의 통신을 위해서 사용되는 매커니즘입니다. 따라서 모듈들이 이러한 형식을 사용한다면 모듈들은 독립된 프로그램으로 볼 수 있습니다.




GPL 라이센스가 걸린 소스의 결과물과 링킹을 했다고
소스를 공개해야 하는것은 아닙니다.
(위에서는 무조건 공개해야 하는것 처럼 적혀 있지요.)

Linux의 core들도 GPL로 되어 있다고 본것 같은데,
그렇다면 Linux core(API)를 사용하는것이면 모두 공개 되어야 하는게 아닌가? 라고
생각할수 있는데 OS에 밀접하게 연관되어 있는 (main component?)에 대해서는
문제가 되지 않는다고 합니다.

문제는 저기서 말하는 "main component가 어디까지인가?"이겠죠.

여전히 책을 읽고 있다. 정확하게 말하면 요즘에는 거의 못 읽고 있다. ㅠ_ㅠ

  자바 병렬 프로그래밍 - 멀티코어를 100% 활용하는  더그 리 외 지음, 강철구 옮김

아무튼 이 책에는 BlockingQueue에 대한 설명이 잠시 나온다.
이를 이용하면 Producer/Consumer 패턴을 만들기 쉽다고 나와서 직접 코드를 짜 본다.

synchronized block를 이용한 생산자/소비자 패턴은 아래와 같다.
( 간단하게 짜 본거라.. 뭐.. 잘못 되었을 수도 있다. 책임 못짐. ㅎㅎ )

  1. import java.util.ArrayList;  
  2. import java.util.Random;  
  3.  
  4.  
  5. public class PlainProsumer {  
  6.     private static ArrayList<Integer> queue = new ArrayList<Integer>();  
  7.       
  8.     public static void main(String[] args) {  
  9.         Consumer c1 = new Consumer("1", queue); c1.start();  
  10.         Consumer c2 = new Consumer("2", queue); c2.start();  
  11.         Consumer c3 = new Consumer("3", queue); c3.start();  
  12.           
  13.         Producer p1 = new Producer(queue);  p1.start();  
  14.     }  
  15.       
  16.     // 생산자. - 무언가를 열심히 만들어 낸다.  
  17.     static class Producer extends Thread {  
  18.         // INDEX  
  19.         private volatile static int i = 1;  
  20.           
  21.         private ArrayList<Integer> queue;  
  22.           
  23.         public Producer(ArrayList<Integer> queue) {  
  24.             this.queue = queue;  
  25.         }  
  26.           
  27.         public void run() {  
  28.             // 0.5초씩 기다렸다가 데이터를 하나씩 넣어 주자.  
  29.             while(true) {  
  30.                 try {  
  31.                     Thread.sleep(new Random().nextInt(1000));  
  32.                 } catch (InterruptedException e) {  
  33.                     e.printStackTrace();  
  34.                 }  
  35.  
  36.                 synchronized (queue) {  
  37.                     // 데이터를 집어 넣고 나면, 데이터가 들어 갔다고 notify 시켜 줘야 한다.  
  38.                     // 그래야 소비자들 중에서 wait하고 있는 놈들을 깨울 수 있다.  
  39.                     queue.add(i++);  
  40.                     queue.notify();  
  41.                 }  
  42.             }  
  43.         }  
  44.     }  
  45.       
  46.     // 소비자.. 생산해 낸 것을 열심히 사용하자.  
  47.     static class Consumer extends Thread {  
  48.         private ArrayList<Integer> queue;  
  49.         private String name;  
  50.         public Consumer(String name, ArrayList<Integer> queue) {  
  51.             this.name = name;  
  52.             this.queue = queue;  
  53.         }  
  54.           
  55.         public void run() {  
  56.             while ( true ) {  
  57.                 synchronized (queue) {  
  58.                     try {  
  59.                         // 데이터가 들어 있지 않고 비었다면 데이터가 올때까지 기다리자.   
  60.                         if ( queue.isEmpty() ) {  
  61.                                 queue.wait();  
  62.                         }  
  63.                           
  64.                         // 생산자에서 데이터를 집어 넣고 notify해 줘서 wait를 벗어나 아래의 코드가 수행된다.  
  65.                         Integer index = queue.remove(0);  
  66.                         System.err.println("Consumer : " + name + "\tCount : " + index);  
  67.                           
  68.                     } catch (InterruptedException e) {  
  69.                         e.printStackTrace();  
  70.                     }  
  71.                 }  
  72.             }  
  73.         }  
  74.     }  
  75. }  

위의 코드를 확인해 보면 알 수 있다시피, queue를 사용할때 synchronized block를 사용하여 queue에 대한 권한을 획득한 뒤에, notify 및 wait를 해 주어야 한다. 이렇게 하면 괜히 코드가 복잡해 지고 synchronized block를 사용하게 되므로 하나의 block를 더 만들어 주어야 해서 코드에 점차 { } 가 많아져서 코드가 보기 어렵게 된다.

하지만 BlockingQueue를 사용하면 synchronized block를 사용하지 않고도 똑같은 구현을 할 수 있다.

  1. import java.util.Random;  
  2. import java.util.concurrent.ArrayBlockingQueue;  
  3. import java.util.concurrent.BlockingQueue;  
  4.  
  5.  
  6. public class BlockingProsumer {  
  7.     private static BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);  
  8.       
  9.     public static void main(String[] args) {  
  10.         Consumer c1 = new Consumer("1", queue); c1.start();  
  11.         Consumer c2 = new Consumer("2", queue); c2.start();  
  12.         Consumer c3 = new Consumer("3", queue); c3.start();  
  13.           
  14.         Producer p1 = new Producer(queue);  p1.start();  
  15.     }  
  16.       
  17.     // 생산자. - 무언가를 열심히 만들어 낸다.  
  18.     static class Producer extends Thread {  
  19.         // INDEX  
  20.         private volatile static int i = 1;  
  21.           
  22.         private BlockingQueue<Integer> queue;  
  23.           
  24.         public Producer(BlockingQueue<Integer> queue) {  
  25.             this.queue = queue;  
  26.         }  
  27.           
  28.         public void run() {  
  29.             // 임의의 시간마다 데이터를 넣어 준다.  
  30.             while(true) {  
  31.                 try {  
  32.                     Thread.sleep(new Random().nextInt(500));  
  33.                     // 수정사항 - offer에서 put으로 변경
                       
    // 데이터를 넣고 나면 알아서 notify시켜 준다.
                        queue.put(i++);
                    } catch (InterruptedException e) {  
  34.                     e.printStackTrace();  
  35.                 }  
  36.   
  37.             }  
  38.         }  
  39.     }  
  40.       
  41.       
  42.     // 소비자.. 생산해 낸 것을 열심히 사용하자.  
  43.     static class Consumer extends Thread {  
  44.         private BlockingQueue<Integer> queue;  
  45.         private String name;  
  46.         public Consumer(String name, BlockingQueue<Integer> queue) {  
  47.             this.name = name;  
  48.             this.queue = queue;  
  49.         }  
  50.           
  51.         public void run() {  
  52.             while ( true ) {  
  53.                 try {  
  54.                     // queue에 data가 없으면 알아서 wait하고 있다.  
  55.                     Integer index = queue.take();  
  56.                     System.err.println("Consumer : " + name + "\tIndex : " + index);  
  57.                 } catch (InterruptedException e) {  
  58.                     e.printStackTrace();  
  59.                 }  
  60.             }  
  61.         }  
  62.     }  
  63.       
  64. }  

보다시피 BlockingQueue는 자기가 알아서 wait 상태로 들어 가고 notify를 하게 된다.
이러한 BlockingQueue의 기능을 이용하면 생산자 소비자 패턴을 좀 더 쉽게 만들 수 있다.

BlockingQueue는 대략 아래와 같은 기능을 가지고 있다.

1. queue에 data를 넣을때 가득 차 있으면, queue에 빈칸이 생길때까지 대기
boolean put(E o) throws InterruptedException;
boolean offer(E o)

2. queue에 data를 넣을때 가득 차 있으면, queue에 빈칸이 생길때까지 시간을 두고 대기
boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException;

3. queue에 data가 없을 경우, 데이터가 들어 올때까지 대기
E take() throws InterruptedException;

4. queue에 data가 없을 경우, 데이터가 들어 올때까지 시간을 두고 대기
E poll(long timeout, TimeUnit unit) throws InterruptedException;

사실은 BlockingQueue를 사용해서 생산자/소비자 패턴을 만드는 예제는 이미 BlockingQueue의 API문서에 소개 되고 있다 ^^ ( 즉, 나는 이미 있는 예제를 만든다고 삽질한거다. ㅎㅎ )
http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html


그리고 아래의 링크를 따라 가면 적당한 예제 및 사용법을 볼 수 있다. ( 한글임 )
Core Java Technologies Tech Tips - QUEUE와 DELAYED 프로세싱
http://kr.sun.com/developers/techtips/c2004_1019.html#1

책을 한권 읽다가 발견한 내용이 있어 글을 적어 본다. 참고로 읽고 있는 책은 "자바 병렬 프로그래밍 - 멀티코어를 100% 활용하는" 이다. ( 아직 출간 된지 1년이 안된책이라 -_- 할인이 별로 안된다. 쩝... ) 아직은 읽고 있는 중이라, 다음에 다 읽으면 그때 정리해야지. ㅋ.

  자바 병렬 프로그래밍 - 멀티코어를 100% 활용하는  더그 리 외 지음, 강철구 옮김


각설하고 이 책을 읽다 보면, 멀티 스레드에서 버그가 많이 발생할 수 있다는 이야기를 하면서, FindBugs라는 프로그램에 대해서 설명이 아주 짧게 나온다. 이 FindBugs라는 프로그램은 Java 소스에서 잠재적으로 버그가 일어날만 한 부분에 경고를 보여 준다.

직접 프로그램을 설치 할 수도 있지만, 이클립스 플러그인도 있다. 이클립스 플러그인을 설치하는 법을 알아 보고, 어떤식으로 사용되는지 확인해 보도록 하자.

우선 FindBugs( http://findbugs.sourceforge.net/ )의 홈페이지에 접근해서 Download 해 보자.

사용자 삽입 이미지


좌측메뉴에서 Downloads( http://findbugs.sourceforge.net/downloads.html ) 를 클릭하게 되면
standard version과 eclipse plugin을 받을 수 있는 링크를 제공하고 있다. 우리는 이클립스 플러그인을 사용해 볼것인데, 이는 굳이 직접 다운로드 받지 않고, 이클립스에서 플러그인 업데이트를 통해서 설치 하도록 하자.

1. 이클립스를 띄운다.
2. Help -> Software Updates
3. Add Site에 주소를 입력한다. ( http://findbugs.cs.umd.edu/eclipse )
4. FindBugs 플러그인을 선택/설치 한다. ( 설치하는데 속도가 느려서 시간이 좀 걸린다. )

사용자 삽입 이미지

5. 설치한 뒤에는 이클립스를 재시작 한다고 한다.
설치 되었으니, 이제 프로그램을 간단하게 하나 짜 보도록 하자.
간단하게 아래의 코드정도면 어떨까?
( 이 코드에는 과연 어디가 문제가 될만한지 직접 찾아 보는것도 좋다. )

  1. public class BugClass {  
  2.     public static int ZERO = 0;  
  3.       
  4.     int i;  
  5.     int value;  
  6.       
  7.     public int getValue() {  
  8.         return value;  
  9.     }  
  10.       
  11.     public synchronized int setValue(int value) {  
  12.         this.value = value;  
  13.         return value;  
  14.     }  
  15.       
  16.     @Deprecated 
  17.     public boolean getTrue() {  
  18.         return true;  
  19.     }  
  20.       
  21.       
  22.     public static void main(String[] args) {  
  23.         BugClass bc = new BugClass();  
  24.         bc.setValue(bc.ZERO);  
  25.         bc.setValue(10);  
  26.           
  27.     }  
  28. }  

위의 코드는 어떤 문제가 존재할 수 있을까? 이제 FindBugs를 이용해서 이 코드에 잠재적으로 문제를 발생할 수 있는 부분이 어떤것인지 확인해 보자. FindBugs로 해당 프로젝트에서 마우스 오른버튼을 누르고, 아래쪽에 보이는 Find Bugs -> Find Bugs를 누르면 동작한다.

사용자 삽입 이미지


자.. 이제 Find Bugs가 찾아낸 "잠재적인 문제점"을 확인해 보도록 하자. Find Bugs를 누르게 되면 소스코드의 좌측에 벌레가 등장한다. 여기에 마우스를 올리면 어떠한 문제가 있을 수 있는지를 알려 주게 된다.

사용자 삽입 이미지

마우스를 올려서 일일이 볼 수 없지 않은가? 이클립스 하단에 있는 View에서 Problem 탭을 선택하면 어떠한 문제가 생겼는지 바로 볼 수 있도록 만들어 두었다.

사용자 삽입 이미지

글자가 작아서 안 보일텐데, 어떠한 문제가 있냐고 적혀 있는지 대략 살펴보면 아래와 같다.

H V MS: BugClass.ZERO isn't final but should be : FindBugs Problem (High Priority)
ZERO가 final로 선언되지 않았다. (중요)

M M UG: BugClass.getValue() is unsynchronized, BugClass.setValue(int) is synchronized    : FindBugs Problem (Normal Priority)
setValue메소드는 synchronized로 되었으나, getValue메소드는 unsynchronized되어 있다. (보통)

M P UuF: Unused field: BugClass.i : FindBugs Problem (Normal Priority)
i라는 놈을 쓰지도 않는다. (보통)


조금 더 편하게 보라고 FindBugs 이클립스 플러그인은 Perspective를 제공해 준다.
이클립스 메뉴에서 Window -> Open Perspective -> Other... 을 선택하자.
사용자 삽입 이미지


그림에서 보다시피 Find Bugs Perspective가 생긴것을 볼 수 있다. 이를 선택하면 이클립스가 Find Bugs Perspective로 변신을 한다. 이 Find Bugs의 Perspective는 하단에 "Bugs Details"라는 탭이 생긴다. 이 탭에는 현재 문제가 되는곳이 왜 문제가 되며, 그것을 수정하기 위해서는 어떻게 해야 하는가를 설명해 두어서, 문제를 해결하는데 도움을 준다.
사용자 삽입 이미지

버그 없는 프로그램은 있을 수 없다. 그렇기 때문에 안전한 프로그래밍을 해야 한다.
그 안전한 프로그래밍을 도와 줄 수 있는 툴이 바로 FindBugs이다.


  1. 나미 2009.01.14 16:56 신고

    훌륭한데...
    자바소스만 되는거지..? ㅡㅜ;
    요새 자바랑 거리먼 1人

    • Chan 2009.01.14 23:01 신고

      ㅋㅋ 내가 알기론~ ㅎㅎ 자바만~ ㅎㅎ

다시 오랜만에 쓰는 프로그래밍 관련 팁.
별로 쓰일 일이 없고, 생각안하고 써도 크게 문제가 되지 않을 수 있는 부분이다.

최근의 IT 계열 뉴스에서 자주 올라오던 기사인데,
1초가 더 생겼으니 1초를 소중히 생각하자면서 나오던 뭐 그런뉴스와 관련되는것이다.
( http://www.dt.co.kr/contents.html?article_no=2008122202011757731006 )

응? 근데 1초가 더 생긴다고? 어떻게 시간이 더 생길 수 있지?
그것이 바로 윤초의 문제이다.

2005년에 후지쯔 코리아 사이트에 올라온 공지를 보면 잘 설명되어 있다.
( http://www.fujitsu.com/kr/news/pr/notice20051230.html )

귀찮으신 분들을 위해서 간단하게 정리하자면,

정의.
태양의 위치로 재는 시간과, 세슘원자로 재는 시간과의 차이를 보정하기 위해서 1초를 더하거나 빼는 것( 태양의 위치는 지구의 자전에 따라서 결정되며, 세슘원자의 진동수는 고정적이다. - 물론 아닐 수도 있다. )

적용법 ( 1초를 더하는 "양의 윤초"인 경우 )
1. 그리니치천문대(영국)를 기준으로 23시 59분 59초 다음이, 24시 59분 60초, 그 다음이 0시 0분 0초가 된다.
2. 그러므로 한국에서는 1월 1일 8시 59분 59초 다음에 60초로 1초가 추가 된다. ( GMT +9 이니깐 )

요것만 알아도 상식을 늘리는것으로 도움이 되겠지만,  프로그래밍에 관련된 정보를 알아 보고 가도록 하자. 후지쯔코리아에서 제공한 링크의 내용중에서  신경쓰일만한 부분만 긁어서 보도록 하자.


윤초 대응 유무에 따른 시스템 시각 변화 패턴

  • 윤초 보정한 시스템
    - Solaris, Linux : NTP STEP 모드 사용 시
                 표준시각 : 8:59:59 → 8:59:60 → 9:00:00
                 시스템시각 : 8:59:59 →8:59:59 → 9:00:00 ( 같은 시각이 2회 반복)
    - UXP/DS(NTP STEP 모드 사용 시), Windows(Windows Time Service)
                 표준시각 : 8:59:59 → 8:59:60 → 9:00:00 → 9:00:01 → 9:00:02
                 시스템시각 : 8:59:59 → 9:00:00 → 9:00:01 → 9:00:02 → 9:00:02
    ※ 보정타이밍은 시각동기시(위의 예는 9:00:02 에 동기한 예)
    - Linux : RHEL3 UR6/RHEL4 UR2제공의 zoneinfo를 사용 시
                 표준시각 : 8:59:59 → 8:59:60 → 9:00:00
                 시스템시각 : 8:59:59 → 8:59:60 → 9:00:00 (8:59:60초에 주의)
    ※상기 zoneinfo(/usr/share/zoneinfo/right/Asia/Seoul) 를 사용하지 말것을 추천
  • 윤초보정을 하지 않는 시스템
    - Solaris, Windows, Linux, UXP/DS
                  표준시각 : 8:59:59 → 8:59:60 → 9:00:00
                  시스템시각 : 8:59:59 → 9:00:00 → 9:00:01 ( 표준시각보다 1초 빨리감)


OS에서 지원하느냐 마느냐, 혹은 설정에 따라서 어떻게 되느냐의 문제도 있겠지만, 프로그램 언어에서 지원해주지 않으면 말짱 황인거 아니겠는가?


그럼 우선 Java API문서에서 시간에 관련된 클래스의 API문서 중 일부를 보도록 하자.
Date클래스에 보면 시간을 얻어오는 getSeconds()라는 메소드가 있다.  비록 Deprecated 되었지만 말이다. ( http://java.sun.com/javase/6/docs/api/java/util/Date.html#getSeconds() )

java.util
Class Date

getSeconds

@Deprecated
public int getSeconds()
Deprecated. As of JDK version 1.1, replaced by Calendar.get(Calendar.SECOND).
Returns the number of seconds past the minute represented by this date. The value returned is between 0 and 61. The values 60 and 61 can only occur on those Java Virtual Machines that take leap seconds into account.
Returns:
the number of seconds past the minute represented by this date.
See Also:
Calendar

일반적으로 생각하지 못했던 부분이 바로 API문서에 적혀 있다. 우리는 초의 반환값으로 0부터 59까지의 숫자중에 하나가 반환된것이라 생각하지만, 실제로는 60이라는 숫자나, 혹은 61이라는 숫자가 등장할 수도 있다. API에서 그렇게 적어 두었기 때문에, 언제든지 발생할 가능성은 존재한다. ( 하지만 과연 프로그래밍할때 이런것을 생각하는 사람이 몇이나 될까? )

재미삼아 다른언어에서는 윤초를 어떻게 다루고 있는지도 확인해 보자.

php ( http://kr2.php.net/manual/en/function.date.php )

s Seconds, with leading zeros 00 through 59


perl ( http://search.cpan.org/~drolsky/DateTime-0.4501/lib/DateTime.pm )
Dave Rolsky > DateTime-0.4501 > DateTime

  • $dt->second()
  • $dt->sec()

    Returns the second, from 0..61. The values 60 and 61 are used for leap seconds.


  • python ( http://docs.python.org/library/time.html )

    5

    tm_sec

    range [0,61]; see (1) in strftime() description


    C ( http://www.cplusplus.com/reference/clibrary/ctime/strftime.html )
    size_t strftime ( .... );
    %S Second (00-61) 02

    .Net ( http://msdn.microsoft.com/en-us/library/system.globalization.calendar.getsecond.aspx )
    .NET Framework Class Library
    Calendar..::.GetSecond Method

    Return Value

    Type: System..::.Int32
    An integer from 0 to 59 that represents the seconds in time.



    php와 .Net만 지원을 하지 않고 있는것인가?
    ( 사실은 내가 잘 검색안해 봐서 그럴 수도 있다. - 엉뚱한 클래스를 뒤졌을 수도 있고. )

    뭐 결론은, 크게 없다. 그냥 알아는 두자!!

    카페에 적었던글을 다시 옮겨 둡니다.
    ----
    안녕하세요.
     찬 입니다.

    오늘도 기초시리즈.
    String의 intern()에 대해서 이야기 해 보도록 하죠.

    intern() 에 대해서 알기 위해서는, 우선 String 자체에 대해서 좀 알아 봐야 합니다.

    String str1 = "Hello";
    String str2 = "Hello";
    String str3 = "Hello";


    이렇게 해 두면 str1과 str2와 str3는 모두 하나의 객체를 가리키고 있습니다.
    왜 그런지 알아 봅시다.

    .java파일을 컴파일 하게 되면, .class파일이 만들어 지게 됩니다.
    .class 파일 안에는 현재 클래스의 정보가 들어있게 되겠지요.

    complie할때에 이미 저 문자를 사용해야 한다는것을 알수 있기 때문에 .class파일안에다가 바로 문자열을 저장해 두는것입니다.
    이때 "Hello"라는 문자열은 .class파일의 "String pool"에 들어가게 됩니다.
    ( 이 class파일이 실제로 로딩이 되면, "Hello"가 메모리의 "String pool"에 로딩됩니다. )

    그런데, 총 3개의 "Hello"라는 문자열이 있는데, .class파일에 3개씩이나 넣어야 할까요?
    모두 같은 문자열이니 이때는 "String pool"에 1개의 "Hello"만 들어가게 됩니다. 이게 가장 효율적이니깐요..
    ( 왜 그렇냐고 물으신다면, 원래 그렇게 만들어서 그렇다고 말할 수 밖에 없습니다. ^^ )

    위의 소스를 컴파일을 해 만들어진 .class파일을 editor를 이용해서 열어 보면 Hello가 하나만 있는것을 알 수 있죠.

    사용자 삽입 이미지


    그렇다면 아래의 코드를 한번 보도록 합시다.

      String str1 = new String("Hello");
      String str2 = new String("Hello");
      String str3 = new String("Hello");

      System.err.println(System.identityHashCode(str1));
      System.err.println(System.identityHashCode(str2));
      System.err.println(System.identityHashCode(str3));

    결과
    3526198
    7699183
    14285251

    위의 소스는 str1, str2, str3는 모두 new String(String)을 이용해서 만들어 보았습니다.
    그렇게 했더니 str1, str2, str3의 hashcode가 서로 다르게 나왔습니다. 이는 서로 다른 객체임을 뜻합니다.

    "Hello"를 가지고 String을 new 했으나,
    실제로 만들어진 객체는 "Hello"라고 인자로 준객체가 아니라, Hello를 가지는 새로운 객체를 만들어 준것입니다.


    위의 소스를 컴파일해서 실행하면, 총 4개의 "Hello"가 메모리에 로딩되게 됩니다.

    1. 위의 java화일에서 "Hello"라고 되어 있는 놈 ( String pool영역에 있음 )
    2. "Hello"를 이용해서 새로운 String을 만들어준 str1이 가리키고 있는 놈 ( Heap에 있음 )
    3. "Hello"를 이용해서 새로운 String을 만들어준 str2이 가리키고 있는 놈 ( Heap에 있음 )
    4. "Hello"를 이용해서 새로운 String을 만들어준 str3이 가리키고 있는 놈 ( Heap에 있음 )


    헉!! 이런 무려 4개나 만들어진단 말야?
    왜 똑같은 String인데 4개나 만들어야 하는거야? 그냥 무조건 1개만 쓰도록 하면 되지 않을까?

    그래서 String에는 intern()이라는 놈이 있습니다.
    intern() 메소드는, 새롭게 만들어진 String객체를 상수화 시켜 줍니다.
    만들어진 String 객체가 이미 상수로 만들어진 문자열이라면, 지금 만들어진 놈을 버리고, 상수를 가리키게 합니다.
    즉, Heap에 새롭게 만들어진 객체를 버리고, 상수를 재활용하도록 하게 하는것이죠.

    뭔말인지 모르겠다면, 예제를 보면 알 수 있습니다.
    intern()을 사용한 아래의 예제를 보겠습니다.

      String str1 = "Hello";
      String str2 = new String("Hello").intern();
      String str3 = new String("Hello").intern();
      System.err.println(System.identityHashCode(str1));
      System.err.println(System.identityHashCode(str2));
      System.err.println(System.identityHashCode(str3));

    결과
    3526198
    3526198
    3526198

    우와! 세상에! 결과로 모두 같은 값이 나왔습니다.
    str1에 대한 "Hello"는 상수에 있는 놈을 가리키고 있습니다.
    str2는 새로운 String 객체를 만들었지만, intern()을 호출하여 만들어진 객체를 버리고 "Hello" 상수를 가리키게 했습니다.
    str3도 str2와 같은 작업을 하게 됩니다.

    예전코드는 무려 4개나 Hello 객체가 있었지만, 지금은 딸랑 1개만 있습니다.
    우와! 세상에 이렇게 좋은게 있다니!!
    오예~ 그럼 이제 무조건 intern()을 써야 겠다!!
    라고 생각하면 큰일입니다.

    intern에는 엄청난 함정이 있습니다.
    intern에 대한 API문서를 확인해 보도록 하죠.


    intern

    public String intern()
    Returns a canonical representation for the string object.

    A pool of strings, initially empty, is maintained privately by the class String.

    When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

    It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

    All literal strings and string-valued constant expressions are interned. String literals are defined in §3.10.5 of the Java Language Specification

    Returns:
    a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

    너무 기니깐, 굵게 표시해둔 글자만 대충(!) 읽어 보도록 하면,
    intern 메소드가 호출이 되면,

    1. String pool에 있는 각종 문자열에 equals해서 같은게 있다면 그 놈을 반환하고,
    2. 같은게 없다면 String pool에 String object를 추가하고, 추가한 놈을 반환한다.

    intern은 Heap에 만들어진 객체를 놓아주고, String pool에 있는 객체를 가리키게 합니다.
    그렇게 됨으로써 Heap의 메모리를 아낄 수 있습니다.

    하지만 intern() 메소드를 사용함으로 인해서 손해를 보는것도 생각해 보아야 합니다.
    1. 우선 String 객체를 하나 만들어야 합니다.
    2. String의 equals 메소드를 이용해서 String pool에 있는 놈을 찾아서 비교해야 합니다. ( 시간이 걸림 )
    3. String pool에 들어 갔으므로, 더 이상 GC(가비지컬렉션)의 대상이 될 수 없습니다. ( 메모리 관리 불가 )

    시간이 얼마나 걸리는지 간단한 예제를 가지고 테스트 해보도록 하면 아래의 결과를 얻을 수 있습니다.

      long startTime = System.currentTimeMillis();
      for ( int i = 0 ; i < 5000000; i++ ) {
           String str = "Hello";
      }
      long endTime = System.currentTimeMillis();
      System.err.println("String pool = "  + ( endTime - startTime));
     
      long startTime1 = System.currentTimeMillis();
      for ( int i = 0 ; i < 5000000; i++ ) {
           new String("Hello");
      }
      long endTime1 = System.currentTimeMillis();
      System.err.println("new String = "  + ( endTime1 - startTime1));
     
      long startTime2 = System.currentTimeMillis();
      for ( int i = 0 ; i < 5000000; i++ ) {
           new String("Hello").intern();
      }
      long endTime2 = System.currentTimeMillis();
      System.err.println("new String intern = "  + ( endTime2 - startTime2));


    결과
    String pool = 31
    new String = 188
    new String intern = 1796


    intern을 하게 되면, new String하는 시간과 String pool을 뒤지면서 equals하는 시간까지 걸리므로
    당연히 그 속도가 느릴 수 밖에 없습니다.

    그리고
    보통 intern()의 특징인 "메모리를 아낄 수 있다" 만 생각을 하고 프로그래밍을 하지,
    intern()하는데 시간이 오래 걸린다와, GC의 대상이 될 수 없다. 라는것은 생각하지 않고 프로그래밍을 하지요.

    특히, "메모리를 아낄 수 있다" 라는 생각에 10번도 안쓰는 String 객체를 intern()을 이용해서 상수화 시키게 되면,
    영원히 GC의 대상이 되지 않기 때문에 오히려 "메모리를 버리는 꼴"이 되는것을 조심해야 합니다.

    똑같은 문자열을 가지는 String을 100개를 생성한다고 해도, 나중에 GC가 되어서
    메모리에서 사라질 가능성이 있다면, 오히려 그것이 메모리를 아낄 수 있는 길입니다.


    결론.
    intern을 제대로 이해하지 못하고 사용한다면,
    메모리를 아끼는것이 아니라, 메모리를 마음껏 버리는 짓을 하게 될 것입니다.


    - 내용추가(2013.10.23)
    생각해 보니 String.intern()된 애들도 GC의 대상에 포함될 수 있을 듯 합니다.
    CustomClassLoader를 만든뒤에 Class를 loading했을때, CustomClassLoader가 GC가 되면class자체에 대한 reference도 없어 질테니, class에 설정된 String들도 GC의 대상이 될 가능성이 있겠네요.

    - 내용추가(2018.06.22)
    ssossohow 님께서 댓글로 업데이트 되어야 하는 부분 알려 주셨습니다. 댓글 중 일부를 글에 남겨 둡니다. ssossohow님 감사합니다. ^_^

    string constant pool은 java7 이전에 Perm영역에 있었는데 java7부터 Heap영역에 위치한다. 변경된 이유는 Perm 영역은 고정된 사이즈이기 때문에 문자열이 지속적으로 늘어나면 OOM이 발생할 확률이 높은 영역이다. pool을 Heap으로 옮김으로써 GC의 대상이 될 수 있어 OOM 발생확률이 극도로 낮아지기 때문에 위치를 옮긴 것이다.



    -----
    잘못된 내용이나, 오해할 소지가 있는 내용은 언제든지 코멘트 남겨 주세요~ ^^
    ( 상수와 String pool은.. 참.. 애매하군요. 설명하기에.. )

    1. 옷장수 2008.12.15 15:38 신고

      오~ 썬메일에 찬찬찬의 블로그가 떴어요~

      • Chan 2008.12.16 10:24 신고

        오우. 월요일 휴가라서 오늘에서야 보네요~ ㅎ
        방문 감사~ ^^

    2. 버리 2008.12.15 18:57 신고

      ㅋㅋ 저도 썬메일보고 반가운 마음에 들렀어요.
      내가 아는 사람 블로그다! 하구요.ㅎㅎ

      • Chan 2008.12.16 10:25 신고

        신기하군요 -_- 제 글이 뜨다니.. 풉;

    3. UncleJoe 2008.12.16 14:10 신고

      그냥 지나치고 간과할수 있는 부분에 대해서 다시 생각할수 있는 좋은 글이네요.

    4. cetauri 2009.12.16 15:32 신고

      구글링중 찬대리님 블로그가 제일 위에 나오네요 ㅋㅋ

      • Chan 2009.12.19 16:55 신고

        이런데서 ;; 신분을 밝혀 버리다니~~

    5. 2010.08.30 15:58

      비밀댓글입니다

      • 2010.08.30 19:18 신고

        저도 정확하게는 잘 모르지만

        1. permanent영역도 GC가 되나요? 제가 알기론 클래스와 메소드가 들어 있는것으로 아는데, 그게 GC가 된다 치고, 그렇다면 있던 class가 GC가 되고나면, 똑같은 class인데 class loading이 두번 일어 날까요? ^^ 전 그렇지는 않을꺼라 생각합니다. ^^

        2. 어쩔때 사용하면 좋은지는 저도 잘 모르겠습니다. ^^
        String.intern()에 관련된 자료를 몇개 찾아보면, "String.equals()를 쓰지 않고, == 로 비교할 수 있기 때문에 비교연산이 많은 경우에 유리하다" 라고 되어 있지만, 그렇게 사용하려면 관련된 모든 String들을 intern() 시켜야 하겠지요. 코드가 매우 잘 만들어져 있지 않다면, intern()시켜야 할 지점을 잡아 내는게 쉽지 않으리라 생각합니다.

        그리고 String.intern()을 호출하게 되면 어차피 이미 메모리에 올라가 있는 String들을 대상으로 String.equals과 비슷한 일을 해야 할 것입니다. 그렇게 치면 "반드시 ==를 이용하기 때문에 비교 연산이 빠르다" 라고 할 수는 없을 듯 합니다. ^^ ( 하지만 다른분의 글에서는 확실히 빠르다고는 합니다. ㅎㅎ )

        예상할 수 있는 String만 온다면 문제가 없겠지만, 사용자의 입력에 의해서 좌우되는 String이라면 절대 사용해서는 안되겠죠?? ㅎㅎ.

        저는 일을 하면서 아직도, intern()을 사용해야 할 만한곳을 아직 찾지는 못했습니다. 하지만 intern()의 특성을 잘 이해 할 수 있다면 언젠가는 한 번 써 볼 수 있는 날이 오지 않을까? 생각해 봅니다. ^^

    6. ... 2010.08.31 10:42 신고

      답변달아주셔서..
      덧글달려다 차단됐다고 나오네요.
      글을 남기지는 못하고 갑니다ㅠ

      • 2010.08.31 12:52 신고

        헉.. 왜 차단 되었을까요 T_T
        제가 그렇게 만든건 아닙니다. ㅎㅎ ^^;

    7. 2010.09.02 10:49

      비밀댓글입니다

      • 2010.09.23 02:05 신고

        다시 방문 감사드립니다. ^^
        (뒤늦게 인사드리네요. ㅎ )

    8. Java 2012.06.29 18:06 신고

      - intern은 String pool 에 들어가지만 String pool에 등록되었다고 gc의 대상에서 벗어나는것은
      아닙니다. 예전부터 자주보던 글이라 다른사람들도 오해할것같아서 적게되었습니다.

    9. Java 2012.06.29 18:06 신고

      - intern은 String pool 에 들어가지만 String pool에 등록되었다고 gc의 대상에서 벗어나는것은
      아닙니다. 예전부터 자주보던 글이라 다른사람들도 오해할것같아서 적게되었습니다.

      • 2012.07.08 01:21 신고

        아.. 잘못된 정보었네요. ㅜㅡㅜ
        근데 어떤 경우에 GC의 대상이 되는지 좀 알려주세요. ㅜㅡㅜ

    10. 이정섭 2013.12.20 15:39 신고

      메소드 자체를 synchronized할 필요가 없고
      String으로 key에 따라 synchronized를 해야할 경우 사용하면 좋을거같습니다.
      (key값은 jsp에서)
      synchronized(key){
      .....
      }

      • 2013.12.22 04:06 신고

        아마도 [Java/Tip] Hashtable을 제대로 활용하지 못하는 경우... ( http://blog.ggaman.com/917 ) 이 글의 코멘트에 있는 내용에 답 글을 다신것 같습니다.

        안타깝게도 제 생각은 다릅니다.
        key만으로 synchronized 걸어 주면 안됩니다.
        key는 String 객체이고, String 객체는 매번 새롭게 생성 될 수 있습니다.
        그러므로 똑같은 "A"라도 서로 다른 객체를 여러개 만들 수 있고,
        그러므로 서로 다른 객체를 synchronized 잡는것은 의미가 없을 수 있습니다.
        만약 할 것이라면
        syncfhorized(key.intern()) 으로 처리한다면 할 수 있겠지요.

    11. ssossohow 2018.06.05 20:41 신고

      최근에 공부한 내용 기반으로 수정되어야 하는 부분을 남깁니다.
      글 수정에 참고가 되셨으면 좋겠습니다.

      string constant pool은 java7 이전에 Perm영역에 있었는데 java7부터 Heap영역에 위치한다. 변경된 이유는 Perm 영역은 고정된 사이즈이기 때문에 문자열이 지속적으로 늘어나면 OOM이 발생할 확률이 높은 영역이다. pool을 Heap으로 옮김으로써 GC의 대상이 될 수 있어 OOM 발생확률이 극도로 낮아지기 때문에 위치를 옮긴 것이다.

      • 2018.06.22 16:23 신고

        좋은 내용 감사합니다. ^_^
        글 내용에 업데이트 해 두도록 하겠습니다. ^_^

    기초시리즈입니다.
    오늘은 Hashtable에 대해서 잠시 이야기해 보도록 하죠.

    Hashtable은 key를 이용해서 value를 꺼낼 수 있도록 해 주는 자료구조죠.

                       Hashtable table = new Hashtable();                

                       table.put("영화, "유쥬얼서스펙트);

                       table.put("오락, "황금어장라디오스타);

                       table.put("음악, "윤종신노래짱 ㅎㅎ);

    뭐 이런식으로, 우선 table에 key와 value를 넣어 주고

                       String key = "영화";
                       String value = (String) table.get(key);

    "영화"를 key로 가지고 있는 value, 즉 "유쥬얼서스펙트"를 꺼낼 수 있도록 되어 있습니다.
    보다시피 아주 간단한 방법으로 사용할 수 있습니다.

    그래서 간단하게 코드를 한번 짜 보았습니다.

                       Hashtable table = new Hashtable();

                       table.put("영화, "유쥬얼서스펙트);
                       table.put("오락, "황금어장라디오스타);
                       table.put("음악, "윤종신노래짱 ㅎㅎ);                 

                       String key = "오락;

                       String value = null;

                       

                       // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

                       boolean isContains = table.containsKey(key);


                       if ( isContains ) {
                                 // 값이 있으니깐 가져다 쓰도록 하자.

                                 value = (String) table.get(key);

                       } else {

                                 // 값이 없으니깐 안 정해 놨다고 하자.

                                 value = "뭘좋아하는지안정해놨네";

                       }


                       System.err.println(key + " : " + value);

    하는 일은,
    1. Hashtable을 하나 만들고,
    2. 각종 값을 넣고,
    3. 가지고 오고자 하는 key가 있다면, value를 가지고 오고
    4. 없으면 다른 값을 설정해 준다.

    아주 자연스러운 과정으로 보인다.

    하지만 위의 코드는 수정되어야 할 부분을 가지고 있다.
    위의, 코드를 짤때 Hashtable의 특성을 생각하지 않고, 쉽게 짰기 때문에 수정할 부분이 발생된다.

    재미삼아 한번 맞춰 보세요~ 일부러 공란을 조금 두겠습니다.
    ( 블로그에 오시는 분들은 ;; 고수분들이실꺼라 ;; 걍 공란 없습니다. ㅋㅋ )

    그 부분은 바로 아래의 코드이다.

    // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

    boolean isContains = table.containsKey(key);


    if ( isContains ) { ....

     


    해당 key에 설정된 값이 있는지 확인한 뒤에 값을 가지고 오는게 뭐가 틀렸냐고 하겠지만,
    위의 코드로 인해서 약간의 손해를 보게 된다.

    왜 그런지 Hashtable의 API를 확인해 보자.
    get(Object) 메소드를 살펴 보도록 하자.

    ------

    http://java.sun.com/javase/6/docs/api/java/util/Hashtable.html#get(java.lang.Object)

    get

    public V get(Object key)
    Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.

    .....

    Returns:
    the value to which the specified key is mapped, or null if this map contains no mapping for the key

    ......

    ------

    get의 return부분은 아래와 같이 설명되어 있다.

    인자로 들어온 key에 맵핑되어 있는 value를 반환하거나,
    map에서 인자로 들어온 key로 맵핑된것이 없으면 null을 반환한다.

    즉, get했을때 인자로 key를 주어서 null이 나오면, map(table)안에 해당 정보가 없다는 말이다.

    그렇다면 put할때, key에 해당하는 value로 null을 줄 수 있지 않을까?
    key에 해당하는 value를 null을 줄 수 있다면, get(key) 했을때 null이 반환될 가능성이 있기 때문이다.

    하지만 그런 걱정은 안해도 된다.
    Hashtable에 대해 설명해둔 API문서를 다시 확인해 보자.

    ------
    http://java.sun.com/javase/6/docs/api/java/util/Hashtable.html

    public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, Serializable

    This class implements a hashtable, which maps keys to values. Any non-null object can be used as a key or as a value.

    .....

    ------

    Hashtable은 key혹은 value로 non-null인 어떠한 Object도 사용가능하다고 한다.
    즉, key와 value는 절대로 null이 될 수 없다.


    자,
    Hashtable에 대한 대충의 정리가 끝났으니, 생각없이 짜 놓았던 소스를 다시 보도록 하자.

                       Hashtable table = new Hashtable();

                       table.put("영화, "유쥬얼서스펙트);
                       table.put("오락, "황금어장라디오스타);
                       table.put("음악, "윤종신노래짱 ㅎㅎ);          

                       

                       String key = "오락;

                       String value = null;

                       

                       // "오락"이라고 되어 있는 key에 값이 있는지 확인하다.

                       boolean isContains = table.containsKey(key);


                       if ( isContains ) {
                                 // 값이 있으니깐 가져다 쓰도록 하자.

                                 value = (String) table.get(key);

                       } else {

                                 // 값이 없으니깐 안 정해 놨다고 하자.

                                 value = "뭘좋아하는지안정해놨네";

                       }


                       System.err.println(key + " : " + value);



    위의 소스에서 손해 보고 있는 코드는 아래와 같다.



    boolean isContains = table.containsKey(key);

    if ( isContains ) { ....




    위의 소스대로라면

    1. containsKey로 실제 key가 존재하는지 확인하고,

    2. key가 존재하면 get을 이용해서 값을 가지고 온다.



    하지만, 다음과 같이 고쳐 쓸 수도 있다.


                       String key = "오락;

                       String value = table.get(key);


                       if ( value == null ) {

                                 // 값이 없으니깐 안 정해 놨다고 하자.

                                 value = "뭘좋아하는지안정해놨네";

                       }




    get(key)메소드만을 사용해서 null체크를 이용해 똑같은 일을 수행할 수 있다.

    굳이 containsKey를 이용해서 확인할 필요가 없다는 말이다.


    그렇다면 재미삼에 containsKey메소드와 get 메소드의 내용을 잠시 살펴 보자.



    ------

    JDK 1.6.0_05 ( c:\program files\java\jdk1.6.0_05\src.zip 파일안에 있음 )


     

       public synchronized boolean containsKey(Object key) {

                 Entry tab[] = table;

                 int hash = key.hashCode();

                 int index = (hash & 0x7FFFFFFF) % tab.length;

                 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {

                     if ((e.hash == hash) && e.key.equals(key)) {

                               return true;

                     }

                 }

                 return false;

       }

     

       public synchronized V get(Object key) {

                 Entry tab[] = table;

                 int hash = key.hashCode();

                 int index = (hash & 0x7FFFFFFF) % tab.length;

                 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {

                     if ((e.hash == hash) && e.key.equals(key)) {

                               return e.value;

                     }

                 }

                 return null;

       }

    ------



    보다시피, containsKey와 get의 메소드는 거의 동일하며,


    마지막에 true, false를 반환하느냐 ( containsKey )

    혹은 Object, null을 반환하느냐만 ( get )

    다를 뿐이다.


    그러므로 constainsKey대신에 get을 사용해도 똑같은 일을 할 수 있다.

    ( 사실은 get을쓰게 되면 Object의 레퍼런스가 하나 더 생성되겠지만.. 자세한 이야기는 집어 치우자. )




    get을 하려고 containsKey를 사용해서 값이 있는지 확인하는것은

    전혀 필요 없는짓이며 이로 인해서 많은 손해를 보게 된다.



    사실 containsKey를 get하기 전에 사용한다고 해서,그렇게 많이 손해를 보겠느냐~ 라고 생각할 수도 있다.

    기껏해야 0.00001초 정도 아닐까. 라고 생각할 수 있다.

    그렇다고 치면 10000번씩 호출해도 0.001초 차이 날까 말까 하지 않을까?


    그래 그 말도 맞을 수도 있다.


    하지만, 위의 코드중에서 containsKey 메소드를 다시 한번 보도록 하자.


       public synchronized boolean containsKey(Object key) {

                 Entry tab[] = table;

                 int hash = key.hashCode();

                 int index = (hash & 0x7FFFFFFF) % tab.length;

                 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {

                     if ((e.hash == hash) && e.key.equals(key)) {

                               return true;

                     }

                 }

                 return false;

       }


    위의 코드에서 눈여겨 볼만한 부분은 synchronized 키워드이다.


    Hashtable은 put, remove, get등 모든 데이터 입출력에 대해서 "동기화" 된다.

    ( 왜 그러냐고 물으면 안된다. Java에서 그렇게 정의해 두었다. 정의라는것에 이유는 없다. )

    ( 동기화에 관련된 자세한 이야기는 다른 강좌등을 참고하도록 하자. )

    ------

    As of the Java 2 platform v1.2, this class was retrofitted to implement the Map interface, making it a member of the Java Collections Framework. Unlike the new collection implementations, Hashtable is synchronized.

    ------


    Hashtable 객체의 put, remove, get, containsKey 등의 메소드가 호출 될때마다

    1. 다른 Thread에서 자신의 객체에 접근할 수 없게 무조건 Lock를 잡고

    2. 해당 메소드를 수행하고 결과를 반환한 뒤에

    3. 잡았던 Lock를 놓게 된다.


    이때 위의 동작만 보더라도 결과만 반환하는것이 아니라, Lock를 잡고, 푸는 과정만해도 2번의 동작이 추가 된다.

    그리고 일반적으로 Lock를 잡고, 푸는것은 많은 시간이 소모되는 작업으로 알려져 있다.




    그러므로

    Hashtable의 특성인

    1. cotainsKey대신 get을 이용할 수도 있음.

    2. 동기화

    에 대해서 잘 알고, Hashtable을 사용하도록 하자.



    --------

    문제가 될만한 사항이 있으면 언제든지 알려 주세요.

    1. 무적조로™ 2008.11.20 09:35 신고

      난 containsKey() 메서드를 안쓸 뿐이고....
      넣었으면 빼야된다는 생각만 할 뿐이고....
      내가 안넣었으니 null 올꺼라고 생각할 뿐이고..

      • Chan 2008.11.20 10:00 신고

        난....

        하고, 시간을 두는 센스~ 를 보여야징~ ㅎㅎ

    2. 버리 2008.11.20 10:34 신고

      아주 사소한 문제긴 한데...ㅋㅋ
      string을 감싸는 "가 왼쪽에만 있는건 새로 추가된..건 아니죠?ㅋㅋㅋ

      • Chan 2008.11.20 13:16 신고

        너무 예리하세요~ ㅎㅎ
        복사해서 붙여 넣다 보니~ ㅎㅎ

    3. 쇼니 2008.11.24 11:45 신고

      있는지만 확인할땐 containsKey() 값을 결국 써야할땐 get()...
      값을 사용하는 경우가 거의 다라고 보면 되겠지만... ^^
      단일쓰레드환경에서는 HashMap을 사용하면 되는거궁..
      HashMap은 value로 null을 넣을 수도 있고..
      ㅋ 뭐든지 상황에 맞게!! ㅎ

      • Chan 2008.11.24 13:45 신고

        빙고~ 옳소~ ㅎㅎ

        HashTable을 HashMap으로 바꾸려고 하면,
        1. 단일 Thread에서 동작하는가?
        2. null을 넣을 가능성이 있냐?
        를 살펴 보아야 겠고~ ㅎㅎ

        추가하자면 Multi Thread환경에서도
        1. static하게 put하고 이후에 get만 하는 경우에는
        HashTable을 HashMap으로 바꾸어도 상관없다는거..

        뭐든지 상황에 맞게~ ㅋㅋ
        ( 점점 ;; java 공부 블로그가 되어 가고 있나~ ㅎ. )

        모르는게 너무 많아.. ㅠ_ㅠ

    4. Richpapa 2009.08.27 12:51 신고

      왜 더 낫다는 거죠? 둘다 sync 이고, 내부 소스가 거의 동일하고... 성능에 지장이 없을 것 같은데... 또한 위의 예가 단편적일 수도 있지만 키든 vaule든 객체가 null인지 체크해야만 하는 null object 패턴까지 써야 되는 것처럼 보입니다. 그렇다면 아싸리 불린값이 더 깔끔해보이는데... 한 수 알려주세요.

      • Chan 2009.08.27 13:45 신고

        위 글에서 세번째에 있는 코드들을 확인해 보면
        위의 예제가 하는 일은,
        1. 해당 값이 있는지 확인하고,
        2. 값이 있으면 가지고 오는 코드입니다.

        즉,
        1. contains()로 확인을 한 뒤에
        2. get()을 수행한다.
        는 것입니다.

        하지만,
        contains와 get()은 같은일을 하고,
        결과의 형태만 다르게 반환하므로,

        contains()와 get()을 순차적으로 수행할때에는
        그냥 get()을 이용하는것이 더 효율적이다.
        라는 말을 하고 싶었습니다.

        당연히, 있는지 없는지 체크할때에는
        contains만 사용하는것이 의미상 더 바람직하겠지요.


        이하의 이야기는 Richpapa님에게 드리는 말은 아닙니다.
        오래된 글인데 여러분들이 글을 남겨주셔서 ^^; 혹시나 오해하지 않도록 설명을 달아 둡니다.

        참고로,
        위에 예제로 제시한 코드는 Multi Thread에서 안전한 코드가 아닙니다. Hashtable를 썼으니 안전한거 아니냐? 고 생각되시는 분들은, Thread 공부를 조금 더 하셔야 할 듯 합니다. ^^

    5. 초보 2013.10.11 20:41 신고

      궁금한게 있어서 질문을 드립니다.

      "위에 예제로 제시한 코드는 Multi Thread에서 안전한 코드가 아닙니다. Hashtable를 썼으니 안전한거 아니냐? 고 생각되시는 분들은 ..."라고 하셨는데 설명을 부탁드립니다.

      그리고 "추가하자면 Multi Thread환경에서도 1. static하게 put하고 이후에 get만 하는 경우에는 HashTable을 HashMap으로 바꾸어도 상관없다는거.." 이 부분에 대해서도 설명을 부탁드립니다.

      조금 고민을 해봤는데 잘 모르겠네요. ㅎㅎ

      • 2013.10.23 20:47 신고

        답변이 많이 늦었습니다.

        "Hashtable을 사용했다고 MultiThread에 안전하다고 생각해서는 안된다" 에 대한 답변 드립니다.

        Hashtable은 put, remove의 각각의 메소드에 대해서는 MultiThread에 안전하게 동작합니다. 하지만 이러한것이 섞여 있을때는 불안전해 질 수 있습니다.

        예를 들어...
        Hashtable table = new Hashtable();

        1: boolean contains = table.contains("AA")
        2:
        3: if ( !contains ) table.put("AA","aa");

        위와 같은 코드가 있다고 한다면

        Thead-1에서는 1번 Line 문장을 수행하여 contains값이 true라고 받았다고 가정합니다.

        이때 Thread-1이 3번 라인을 수정하기 전에
        Thread-2가 table.remove("AA") 를 수행했다고 합시다.

        실제 table에는 "AA"가 없는 상태인데도,
        Thread-1에서는 이미 있다고 판단하였기 때문에
        table에 "AA"를 추가해 주지 않습니다.

        이렇듯 단순 Hashtable만을 사용했다고 해서 Thread에 안전한 코드를 작성했다고 볼 수는 없다는 말입니다.

      • 2013.10.23 20:52 신고

        "추가하자면 Multi Thread환경에서도 1. static하게 put하고 이후에 get만 하는 경우에는 HashTable을 HashMap으로 바꾸어도 상관없다는거.." 에 대한 설명 드립니다.

        위에 설명한 Hashtable의 예에서도 보았다시피, Hashtable을 썼다고 해서 안전한 코드를 작성했다고 보기 어렵습니다.

        반대로 HashMap을 사용한다고해도 잘만 작성한다면, MultiThread에 안전한 코드를 작성할 수 있습니다.

        public synchronized void put(String key, String value ) {
        hashmap.put(key, value);
        }

        public synchronized void remove(String key) {
        hashmap.remove(key);
        }

        위와 같이 작성한다면 hashmap은, 동기화 되는 method내부에서만 사용됩니다. 즉 hashmap에 접근할 수 있는것은 한번에 1개의 thread만 접근할 수 있게 된 것입니다.

        그러므로 hashmap을 사용해도 코드를 작성하는 방법에 따라서 MultiThread에 안전하게 작성할 수 있게 되는것입니다.

    6. MJS 2016.08.17 13:50 신고

      와우.. 좋은정보 감사드립니다.

    네이버 카페 코드인에 심심해서 적어 놨던 글을 다시 옮겨옴.
    ( 워낙 포스팅이 없어서 ;; 요런걸로 때움. ㅋㅋㅋ )
    -------
    안녕하세요.
     찬 입니다.

    우리가 일반적으로 Map이나 HashTable을 쓸때 다음과 같이 사용하지요.

    Map map = new HashMap(100);

    map.put("찬", new Person( Person.MEN, 29 ) );
    map.put("철수", new Person( Person.MEN, 15) );
    map.put("영희", new Person( Person.WOMAN , 13 ) );

    이때 map에
    key로 "찬", "철수", "영희" 와 같이 String을 주고,
    value로는 Person 객체를 만들어서 넣어 줍니다.

    이때,
    map에서는 key 값이 중복되면 기존에 있던 value에다가 새로운 value를 덮어 써 버리게 되는 것처럼 보입니다.

    map.put("찬", new Person( Person.MEN, 29 ) );
    map.put("철수", new Person( Person.MEN, 15) );
    map.put("영희", new Person( Person.WOMAN , 13 ) );
    map.put("철수", new Person( Person.WOMAN, 15 ) ) ;  // 으악! 철수를 여자로 만들어 버렸어!!

    put할때에는 key의 hashcode를 보고 쓰게 되어 있죠.
    즉, "철수".hashCode() 값이 같기 때문에, 예전에 있던 값에다가 덮어써 버리게 됩니다.
    ( 정확하게 말하면, 원래의 Hash 개념으로는 hashCode가 같은 애들은 bucket에 저장됩니다. 덮어 써 버리지는 않습니다. 하지만 자바의 기본 구현에서는 Key의 hashCode에 맞춰서 하나만 들어 가요. )


    그렇다면 String의 hashCode()에 대해서 좀 알아 봅시다.
    아래는 String의 hashCode()에 대한 API문서 내용입니다.

    hashCode

    public int hashCode()
    Returns a hashcode for this string. The hashcode for a String object is computed as
     s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
    using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.)
    Overrides:
    hashCode in class Object
    Returns:
    a hash code value for this object.
     

    보다시피 String의 hashCode()는 "s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]" 요런 공식에 의해서
    값이 반환되게 되어 있는데요, 공식이 무진장 어렵죠. 무슨 말인지도 잘 모르겠고..

    공식보다는 hashCode()의 반환값에 대해서 이야기 해 보죠.
    hashCode()의 반환값은 int입니다. String 객체의 hashCode()를 가지고 오면 int형태를 반환하게 되죠.


    자, 그럼 다시 생각해 봅시다.
    int는 총 4byte를 차지할 수 있는 정수형 기본 타입입니다.
    4byte로 표현할 수 있는 갯수는 0부터 0xFFFFFFFF(4,294,967,295) 입니다.
    즉, hashCode()의 결과는 그 많은 int값 중에서  하나를 반환됩니다.


    그렇다면 또 다시 String의 hashCode를 생각해 봅시다.

    하지만 String은 무한개를 생성해 낼 수 있습니다. ( "A", "A1",..."AZ", "AA1", "ZZ....ZZZ", "가1A" .... 등등 )
    그런데, 이렇게 무한개로 생성해 낼 수 있는 String 객체의 hash code는 int가 반환할 수 있는 숫자중에 하나 입니다.

    확률상 서로 다른 String 인데도, 같은 hashcode를 가지는 String이 있을 수 있습니다.


    그렇기 때문에,
    HashMap이나 Hashtable를 사용할때 아무런 생각없이 String을 key으로 주고 쓰고 있습니다.
    재수가 없으면 데이터가 날아 가는 문제가 발생할 수 있습니다.
    ( 또 다시 이야기 하자면, 데이터가 날아 가지는 않습니다. - 자세한 사항은 HashMap의 코드를 분석해 보세요~ )


    그러므로 정말로 반드시 유일한 값이 필요하다고 한다면, hashCode()를 사용해서는 안될것입니다.
    char[]을 일일이 가지고 와서 적당히 조작해서 int값으로 만드는 방법을 쓰던지 해야 할것입니다.
    ( 하지만, 위에서도 말했다시피 - int는 제한적이고 string은 무제한이므로, int를 가지고 어찌할 방법은 없습니다. )

    2009년 8월 27일 추가 - 최근에 오래된 글에 대한 코멘트가 많이 달리네요. ㅎ.

    지민아빠님의 코멘트처럼, 사실은 hashCode가 같다고 해서 꼭 문제가 되는것은 아닙니다. 
    이미 들어 있는 hashCode를 가진 key를 이용해서 put하면, return으로 이전에 들어 있던 Object가 나오거든요.
    이 글에서 이야기 하고 싶은것은,
    String.hashCode()는 유일한 값을 반환하지 않는다는 것입니다.
    그러므로 이글 오해할 가능성이 있는 내용을 포함하고 있습니다.

    잘 이해가 되지 않으시는 분들은 이 게시물에 cobus 님께서 달아 두신 comment를 참고하시면 이해가 되실 겁니다. ^_^
    2011년 6월 23일 추가 -
    오늘 또 어느 익명의 정의에 사도께서는 "이 딴 글은 내려버려라~" 고 적어 주셔서, 내용을 일부 수정합니다.
     
    어흑. 진짜 글을 내려 버려야 하나.. 쩝ㅋㅋㅋ


    1. 지민아빠 2008.11.19 00:40 신고

      hashcode 는 같은 값이 나오지만, 그래서 equals 도 같이 사용하는 거잖아요. 내부적으로는 그래서 예비 버킷이 있는.... 흠. 암튼. 유용한 글.. 좋아요. ㅎㅎ

    2. 2009.08.27 12:41 신고

      해쉬가 같으면 같은 빠겟스에 담는다는거고...
      빠겟스 수는 적절하게 조절되어질테고~

      결론이 좀....ㅋㅋㅋ

      여튼 잘봤음

      • Chan 2009.08.27 13:50 신고

        우왕~
        요즘에 오래된 글에 많은 사람들이 코멘트를 남겨주시네~ ㅎㅎ
        위의 글은 제대로 오해할 소지가 많지~ ㅎㅎ
        하지만, 고치긴 귀찮아. ㅋㅋ

    3. 분홍 2010.10.11 17:04 신고

      잘봤습니다. hashcode 의 문제점을 아주 잘 파악한 좋은글이네요...

    4. 흠.. 2011.06.20 18:34 신고

      오해할 소지가 있는 글이 아니고
      아예 개념을 잘 모르고 쓴 글이네요...

      아직도 안지우고 냅두고 있다는 것이 신기합니다..
      나는 바로 내릴 것 같은데..

      • Chan 2011.06.23 18:24 신고

        허허.
        이렇게 오래 된 글을 찾아 주셔서 감사합니다.
        아무래도 내용을 수정해야 겠군요. ^^

    5. 지나가다 2013.08.29 01:55 신고

      해쉬코드는 문자열에 유일합니다.
      char val[] = value;

      for (int i = 0; i < value.length; i++) {
      h = 31 * h + val[i];
      }
      hash = h;
      위가 hashCode() 소스입니다.
      value는 해싱할 문자열이고
      각 글자자릿수의 지수승을 해서 각 문자의 ASCII코드를 더합니다.
      abc 를 입력하면 96354이 나옵니다.

      • 2013.10.23 20:57 신고

        바로 위 "지나가다"님의 코멘트는 잘못 된 내용입니다.
        문자열이 달라도 같은 hashCode가 나올 수 있습니다.

        그 이유는 위의 글에 나와 있습니다.

    6. cobus 2015.11.27 11:10 신고

      글 내용을 잘 이해를 못하신건지 아니면 .hashCode()를 맹신하시는건지...
      본문에 String class의 hashCode() 를 사용했을 때 같은 값이 나오는 예제를 추가하면 논란이 없을 것 같습니다.

      약간 수정되어야 할 부분은 hashCode() 값이 중복될 수 있는 이유를 int의 수와 String의 표현 가능 수가 차이가 나서 중복이 생길 수 있다고 하셨는데 사실은 ASCII code값을 이용해 비트연산의 합으로 hash값을 만들다보니 길이와 상관없이 Hash값 중복이 발생할 수 있습니다. 본문에 추가하면 좋을것 같은 hashCode() 중복 문자열은 아래와 같습니다.

      String a = "Z@S.ME";
      String b = "Z@RN.E";
      if(a.hashCode() == b.hashCode()) {
      System.out.println("same hashcode");
      } else {
      System.out.println("different hashcode");
      }

      결과: same hashcode

      • 2015.12.15 19:41 신고

        cobus님 //
        댓글 감사합니다. ^_^

        저희 의도는 int의 표현 가능 갯수보다 String은 표현가능 갯수가 더 많기 때문에
        String을 int로 mapping 시킨다면 당연히, int 값이 겹치는 경우가 발생할 수 밖에 없다라는 의미로 적었습니다. ( 원론적인건 코드가 필요 없으니까요 ^_^ )

        제가 그 의미를 정확하게 표현하지 못했네요 ^_^

        이 글을 읽으시는 분이 코멘트 남겨 주신 내용을 읽으시면,
        훨씬 더 잘 이해할 수 있을것으로 보입니다. ^_^
        자세한 설명과 구체적인 예를 들어 코멘트 남겨 주셔서 고맙습니다. ^_^

        해당 글에, cobus 님의 코멘트를 확인하라는 내용을 추가 했습니다. ^_^

    자바에서 Swing을 사용할때 별 생각없이 만들다 보면,
    EDT가 아닌곳에서 Swing Component에 대한 작업을 많이 하려고 한다.

    다른 Thread에서 Swing Component에 대해서 어떠한 작업을 했을때,
    dead lock이 일어나기 전에는,
    어느곳에서 그런것을 썼는지 일일이 알아 내기가 힘들다.

    Print 관련 자료를 찾다가 TroubleshootingGuide for Java를 발견해서
    살짝 보았는데 참고해 두고 담에 체크할때 먹으면 좋아 보여서 긁어 놓는다.

    TroubleshootingGuide for Java SE 6DesktopTechnologies
    http://java.sun.com/javase/6/webnotes/trouble/TSG-Desktop/TSG-Desktop.pdf

    4.2.1 IncorrectThreading
    Randomexceptions and painting problems are usually the result of incorrect threading usage of Swing. All access to Swing components, unless speciically noted in the javadoc,must be done on the event dispatch thread. This includes anymodels (TableModel, ListModel, and others) that are attached to Swing components.

    The best way to check for bad usage of Swing is by way of an instrumented RepaintManager,as illustrated by the following code:

    public class CheckThreadViolationRepaintManager extends RepaintManager {

        // it is recommended to pass the complete check
        private boolean completeCheck = true;

        public boolean isCompleteCheck() {
            return completeCheck;
        }

        public void setCompleteCheck(boolean completeCheck) {
            this.completeCheck = completeCheck;
        }

        public synchronized void addInvalidComponent(JComponent component) {
            checkThreadViolations(component);
            super.addInvalidComponent(component);
        }

        public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
            checkThreadViolations(component);
            super.addDirtyRegion(component, x, y, w, h);
        }

        private void checkThreadViolations(JComponent c) {

            if (!SwingUtilities.isEventDispatchThread() && (completeCheck || c.isShowing())) {

                Exception exception = new Exception();
                boolean repaint = false;
                boolean fromSwing = false;
                StackTraceElement[] stackTrace = exception.getStackTrace();

                for (StackTraceElement st : stackTrace) {
                    if (repaint && st.getClassName().startsWith("javax.swing.")) {
                        fromSwing = true;
                    }
                    if ("repaint".equals(st.getMethodName())) {
                        repaint = true;
                    }
                }

                if (repaint && !fromSwing) {
                    //no problems here, since repaint() is thread safe
                    return;
                }

                exception.printStackTrace();
            }
        }
    }
    1. 무적조로™ 2008.11.05 12:59 신고

      어떤 내용인지 감이 잘 안잡힌다 ㅋㅋ
      다시 자세히 설명 좀 해보삼..

      • Chan 2008.11.08 00:55 신고

        뭐.. ㅎㅎ 굳이 깊이 알 필요는 없는.. ㅎㅎ
        알아두면 좋은거고. ㅎㅎ

        1. Swing에서 무언가 작업을 할때에는 EDT에서 해야 한다.
        2. 스윙에서 무슨 작업을 하고나면 보통 repaint를 호출하거나, 혹은 호출이 되는데
        3. 이때 만약 EDT가 아닌 Thread에서 호출하게 되면, 에러를 출력하는 코드

        정도 되는것 같은데. ㅎㅎ

        아직 한번도 안써봐서 ^^;;

    + Recent posts