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

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

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

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

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

    • Chan 2012.02.09 19:25

      읽어 주셔서 감사합니다.

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

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

    + Recent posts