'Know/Java'에 해당되는 글 27건

  1. 2005.12.20 불필요한 JSP 재컴파일을 피하는 방법
  2. 2005.08.08 유니코드(Unicode)와 유니코드 인코딩
  3. 2005.07.15 올바른 프레임워크의 선택과 사용법
  4. 2005.06.26 리팩토링 냄새표
  5. 2005.03.25 Triple DES Encryption (DESede) (2)
  6. 2005.03.17 ExclusiveOR 연산자의 번외게임
  7. 2005.02.22 디자인패턴 예제 및 데모사이트

불필요한 JSP 재컴파일을 피하는 방법

Know/Java 2005.12.20 16:13
[ 출처: http://www.dev2dev.co.kr/pub/a/2005/01/jsp_reloaded.jsp ]
[HTML]


불필요한 JSP 재컴파일을 피하는 방법


by Nagesh Susarla

2005/01/05






개요



JSP(JavaServer Pages) 뉴스 그룹에 관한 가장 일반적인 질문 중 하나는 JSP 재컴파일과 관련된 질문입니다. 원치 않는데도 JSP를 재컴파일해야 하는 작업은 많은 개발자들이 겪는 골칫거리입니다. 이 문서에서는 WebLogic JSP 컨테이너의 내부적인 작동에 대해 먼저 설명한 다음 보기에도 명백한 "원치 않는" 시나리오에 컨테이너의 stale checking 알고리즘을 적용하면서 재컴파일을 유발시키는 시나리오에 대해 설명합니다. 또한 이 문서에서는 JSP 및 서블릿 클래스의 재로딩 작업을 제어하는 파라미터에 대해서도 설명합니다. 이러한 파라미터는 운영 중인 모드에서 실행되는 서버의 경우 강력히 권장됩니다.



JSP 컨테이너의 stale checking 메커니즘



WebLogic에서 JSP는 .class 파일로 컴파일됩니다. 여기서 말하는 stale checking 메커니즘이란 특정 JSP .class 파일이 현재의 JSP 파일 보다 이전 것인지("stale") 여부를 결정하는 데 사용하는 로직을 말합니다. WebLogic JSP 컨테이너는 JSP 및 그 종속 파일을 수정하는 경우에만 재컴파일됩니다. 생성된 Java 코드를 살펴보면 JSP 컨테이너의 내부적인 작동을 가장 잘 이해할 수 있습니다. 그 예로 명령줄 JSP 컴파일러를 사용하여 JSP를 컴파일한 다음 생성된 소스 코드를 살펴보겠습니다. JSP 컴파일러(weblogic.jspc)는 기본 WebLogic Server 설치 키트와 함께 제공됩니다.



foo.jsp라는 간단한 JSP 페이지를 생각해 봅시다.



A simple JSP page


이제 명령줄 JSP 컴파일러를 사용하여 JSP를 컴파일하고 keepgenerated라는 옵션을 지정합니다. 이름에서 알 수 있듯이 이 옵션은JSP 페이지에 해당 Java 코드를 생성하고 그것을 디스크에 보관합니다.



java weblogic.jspc -keepgenerated -d .\WEB-INF\classes foo.jsp

[jspc]warning: expected file /WEB-INF/web.xml not found,
tag libraries cannot be resolved.
<Jul 11, 2004 7:29:26 PM PDT> <Warning> <HTTP>
<BEA-101181> <Could not find web.
xml under WEB-INF in the doc root: ..>


컴파일러는 위 옵션에서 지정한 대로 출력 디렉터리(-d)에 .java 파일 및 해당 .class 파일을 생성합니다. 컴파일러가 생성된 클래스 파일을 jsp_servlet이라는 패키지에 넣어두면 weblogic.xml에서 무시되지만 않는다면 기본 JSP 패키지 접두어가 됩니다. 그러므로 생성된 Java 파일은 .\WEB-INF\classes\jsp_servlet에서 찾을 수 있고 __foo.java라고 부르게 됩니다.



실제로 태그 라이브러리를 사용하여 작업하고 있는 것이 아니기 때문에 컴파일러에서 보낸 web.xml 파일을 찾을 수 없다는 경고 메시지는 무시할 수 있습니다.



생성된 코드(__foo.java)에서 설명해야 할 내용과 가장 관련되는 부분은 아래와 같이 나타나는 staticIsStale() 메서드입니다.



목록1. staticIsStale() 메서드



public static boolean _staticIsStale(weblogic.servlet.jsp.StaleChecker sci) {
if (sci.isResourceStale("/foo.jsp", 1089594167518L, "8.1.2.0",
"America/Los_Angeles"))
return true;
return false;
}


위 코드에서 JSP가 수정되었는지 확인하기 위해 weblogic.servlet.jsp.StaleChecker 인터페이스에서 isResourceStale() 메서드를 호출했습니다. isResourceStale() 메서드에 대한 파라미터는 다음과 같으며 아래에 나타난 순서대로 적용됩니다.




  1. 확인할 리소스(예: /foo.jsp)

  2. JSP 페이지의 타임스탬프

  3. WebLogic 릴리스 빌드 버전

  4. 사용 중인 시스템의 기본 표준 시간대



JSP 컨테이너는 StaleChecker 인터페이스를 구현하여 _staticIsStale() 메서드를 호출합니다. 이 구현은 목록 1에 나타난 파라미터를 가진 콜백(isResourceStale())을 수신합니다. 이 파라미터를 사용한 구현은 주어진 리소스의 stale 여부를 추론하기 위해 필요한 모든 정보를 수집합니다. JSP 컨테이너는 리소스(파라미터 1) /foo.jsp의 타임스탬프(파라미터 2)가 컴파일된 클래스 파일 안에 저장된 타임스탬프 보다 새 것(크면)이거나 릴리스 빌드 버전이 서로 다르면 JSP .class 파일이 "stale"하다고 판단합니다.



여기서 중요한 결과를 살펴보겠습니다.




  • JSP 페이지의 타임스탬프는 클래스 파일 내부에 저장되고 컴파일 시 계산되기 때문에 클래스 파일의 타임스탬프를 수정해도 stale checking 프로세스에 아무런 영향을 주지 못합니다. (이것은 가장 잘못 알려진 통념이며 위 예제가 이 통념이 잘못된 것임을 명백하게 반증하기를 바랍니다.)



  • 네 번째 파라미터 즉, 표준 시간대는 배포가 보관된 포맷(.war)인 경우에만 사용됩니다.



  • WebLogic 릴리스 빌드 버전은 각 서비스 팩에 따라 달라지므로 각 서비스 팩의 모든 JSP를 미리 컴파일해야 합니다. 이러한 요구 사항은 JSP 클래스가 최신 서비스 팩 또는 릴리스에서 컴파일러 버그 수정 또는 런타임 변경의 이점을 이용할 수 있도록 하기 위해 설정되었습니다.




Static Includes의 경우



사람들이 가지는 또 다른 논리적 질문은 JSP 페이지의 static includes 중 하나를 수정했을 경우에도 JSP 컨테이너가 주어진 JSP 페이지를 재컴파일할 것인가라는 문제입니다. 대답은 예입니다. static include 같은 종속 파일을 수정한 경우에도 전체 페이지("컴파일 단위"라고 함)를 재컴파일합니다. 컨테이너가 이러한 종속성을 처리하는 방법을 살펴보기 위해 다음과 같이 baz.inc라는 static include를 가진 JSP를 생각해 봅시다.



목록 2. foo.jsp



A simple jsp page.
<%@ include file="baz.inc"%>


목록 3. baz.inc



--
Simple Static Include
--


foo.jsp에 JSP 컴파일러의 이전 명령줄을 다시 실행하면 아래와 같은 코드를 가진 Java 파일이 만들어집니다. 보시다시피, 종속성(이 경우 baz.inc)을 수정하더라도 전체 JSP 또는 "컴파일 단위"가 재컴파일되어 각 종속성이 _staticIsStale() 메서드에 일괄 처리됩니다. JSP 컨테이너는 루트 JSP 페이지(foo.jsp)가 stale 여부를 나타내는 boolean을 반환할 것으로 예상합니다. 그러므로 생성된 각 JSP 클래스 파일은 모든 종속성 파일도 확인하는 코드를 생성합니다.



public static boolean _staticIsStale(weblogic.servlet.jsp.StaleChecker sci) {
if (sci.isResourceStale("/foo.jsp", 1089616972487L, "8.1.2.0", "America/Los_Angeles"))
return true;
if (sci.isResourceStale("/baz.inc", 1089616984268L, "8.1.2.0", "America/Los_Angeles"))
return true;
return false;
}


요약하면, WebLogic JSP 컨테이너는 각 JSP .class가 자체의 종속성 목록을 유지하도록 하고 원래 각 JSP .class에 따라 JSP(및 그 종속 파일)의 상태(타임스탬프)를 저장합니다. 컨테이너가 JSP .class에 _staticIsStale() 메서드를 호출하면 차례로 단일 리소스의 stale 여부를 결정하는 데 필요한 모든 정보를 전달하는 weblogic.servlet.jsp.StaleChecker.isResourceStale()을 사용하여 JSP 컨테이너로 다시 호출합니다. 이렇게 하면 stale checking 작업이 대폭 간단해지고 각 JSP의 타임스탬프를 별도의 장소에 유지할 필요가 없어집니다.



JSP 재컴파일을 발생시키는 시나리오



JSP 컨테이너가 stale checking을 수행하는 경우 고려해야 할 요소를 살펴보았습니다. 이제 JSP가 재컴파일하게 되는 일반적인 시나리오를 몇 가지 살펴보겠습니다.




  1. 빌드 스크립트를 사용하여 파일을 복사하면 JSP의 타임스탬프를 수정할 수 있습니다. 그러면 모든 JSP를 재컴파일하게 됩니다.



    모든 JSP가 src라는 디렉터리에 있는 시나리오를 생각해 봅시다. 흔히 빌드 스크립트는 모든 JSP를 복사하고 서블릿 Java 파일을 빌드 디렉터리에 컴파일합니다. 그런 다음 이 스크립트는 src 디렉터리에서 weblogic.jspc를 실행하고 컴파일된 모든 JSP를 빌드 디렉터리에 둡니다. 여기서JSP를 빌드 디렉터리로 복사하면 빌드 스크립트에서 cp –p/-m을 사용하여 파일의 타임스탬프를 유지하지 않는 한 JSP의 타임스탬프를 아주 잘 변경할 수 있습니다. 그리고 이러한 웹 애플리케이션을 빌드 디렉터리에서 서버로 배포하면 JSP 클래스가 배포되기 이전의 JSP 즉, 이 경우 빌드 디렉터리로 복사한 JSP를 사용하여 컴파일되었기 때문에 모든 JSP가 재컴파일됩니다. 이것이 바로 가장 일반적인 재컴파일 사례로, 복사하여 파일 타임스탬프를 유지하도록 하면 이러한 재컴파일을 피할 수 있습니다.



  2. weblogic.xml의 packagePrefix 파라미터를 수정하면 재컴파일하게 됩니다. stale checking 메커니즘은 특정 웹 애플리케이션의 weblogic.xml 파일에서 packagePrefix를 찾아 /foo.jsp의 <packagePrefix>.__foo.class 클래스를 검색합니다. weblogic.jspc를 사용하여 모든 JSP를 사전에 구축하고 웹 애플리케이션의 WEB-INF/classes 디렉터리에 넣어둔 다음 이 웹 애플리케이션 아카이브(WAR)를 서버에 배포했다고 가정해 봅시다. 이 웹 애플리케이션에 foo.jsp라는 JSP 가 있다고 가정해 봅시다. weblogic.xml에 "packagePrefix"가 없을 경우 stale checking 메커니즘은 jsp_servlet.__foo.class 클래스를 찾게 될 것입니다. 이제 weblogic.xml을 수정하고 일명 com.bar라는 패키지 접두어를 추가한 다음 동일한 WAR를 서버에 다시 배포했다고 가정해 봅시다. 이 때 foo.jsp에 액세스하면 stale checking 메커니즘이 "com.foo.__foo.class"라는 클래스를 찾게 되므로 JSP가 재컴파일됩니다. -package 파라미터를 사용하여 weblogic.jspc 명령을 호출하고 동일한 패키지 이름을 사용한다면 이러한 재컴파일을 피할 수 있습니다.



  3. 또한 weblogic.xml의 workingDir 파라미터를 수정해도 재컴파일됩니다. 이 경우 JSP 컨테이너는 일반적인 웹 애플리케이션 클래스 경로 이외에 새 "workingDir"에서 JSP 클래스를 찾게 됩니다. 새 버전을 배포하기 이전의 작업 디렉터리에 JSP 클래스가 있었기 때문에 JSP 컨테이너는 이 클래스를 찾을 수 없으므로 요청된 JSP를 재컴파일합니다.



    참고: 시나리오 2와 3은 weblogic.xml을 수정할 경우에도 웹 애플리케이션을 다시 빌드하거나 미리 컴파일해야 할 필요성을 명백하게 보여 줍니다. 수정한 후 미리 컴파일된 WAR을 배포하면 JSP는 재컴파일되지 않습니다.



  4. 미리 구축된 WAR을 WebLogic Server의 최신 버전에 배포하면 모든 JSP가 재컴파일됩니다. stale checking 메커니즘에 관한 설명에서 이미 설명한 대로, JSP 컨테이너를 버전이 다른 서버에 배포할 경우 모든 JSP를 재컴파일합니다. 이것은 모든 JSP 컴파일러/런타임 향상 또는 주어진 버전 또는 서비스 팩의 버그 수정을 생성된 코드에서 이용할 수 있다는 것을 확인하기 위해 실행됩니다. (이러한 제한이 없다면 JSP 런타임에 메서드 없음 이라는 클래스로 끝나고 말 것입니다.) JSP를 미리 컴파일하는 빌드 스크립트는 배포하려는 서버에서 사용하는 동일한 버전의 weblogic.jar를 사용하는 것이 이상적입니다. 서버와 함께 사용될 패치 또는 롤링 픽스는 빌드 스크립트에서 사용하는 클래스 패스에 추가하는 것이 좋습니다. 한마디로 요약하면 빌드 및 배포 환경이 정확히 같아야 합니다. 그러면 배포 후 불필요한 재컴파일 문제를 방지할 수 있습니다.




stale checking에 대한 추가 제어



컨테이너가 stale checking을 수행할 때 제어하면 컨테이너가 더 잘 수행될 수 있도록 조정할 수 있어서 서블릿 뿐만 아니라 JSP의 응답 시간이 향상됩니다. stale checking을 할 때마다 JSP 컨테이너는 디스크로 이동하여 해당되는 특정 JSP에 대한 마지막 수정 시간을 다시 읽어야 합니다. 이러한 프로세스를 너무 자주 호출할 경우 JSP의 응답 시간에 영향을 미치므로 성능이 저하될 수 있습니다. 그러므로 일반적으로 애플리케이션을 많이 변경하는 개발 시 stale checking을 상당히 활성화시켜 볼 필요가 있습니다. 브라우저에서 새로 고침/다시 로드를 클릭하고 새 페이지를 다시 로드함은 물론 JSP 컨테이너를 재컴파일하도록 하여 특정 JSP에 변경한 사항을 테스트하는 것이 좋습니다. 그러나 운영 중인 모드일 경우 이와 같이 하면 성능이 저하될 수 있습니다.



다음과 같은 파라미터의 기본값이 개발 모드에 가장 적합합니다. 운영 중인 시나리오에 배포할 경우에는 상황에 따라 이를 변경하는 것이 좋습니다.



PageCheckSeconds



이미 컴파일된 JSP에 대한 새로운 요청이 발생할 때마다 컨테이너는 설정 파일(이 경우 weblogic.xml)에서 pageCheckSeconds의 값을 확인하고 최종 stale checking 시간과 현재 시간과의 간격이 pageCheckSeconds 보다 클 경우 stale checking 작업을 수행합니다. 예를 들어pageCheckSeconds 값이 10초로 설정되어 있다고 합시다. foo.jsp에 대한 요청에서 컨테이너는 현재 시간과 최종 stale 검사 시간과의 간격이 ageCheckSeconds 보다 큰지 확인합니다. 이 경우 페이지가 재컴파일되었고 10 초가 지난 후 액세스했다고 가정하면 컨테이너는 클래스가 stale한지 확인합니다. 이 간격 이내에 발생한 foo.jsp에 대한 요청은 모두 stale checking을 하지 않습니다.



개발 모드를 사용하지 않고 JSP 페이지를 매 초(기본값)마다 확인할 필요가 없다면 이 값을 -1(stale checking 절대 수행 안 함) 또는 60초처럼 아주 높은 값으로 변경하는 것이 좋습니다. 그러면 File.lastModified()를 호출하여 JSP에 액세스할 때마다 JSP 클래스를 다시 로드하고 타임스탬프를 확인할 필요가 없습니다.



목록 4. 이 파라미터의 설정 방법을 보여주는 weblogic.xml의 코드



<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 8.1//EN"
"http://www.bea.com/servers/wls810/dtd/weblogic810-web-jar.dtd">
<weblogic-web-app>
<jsp-descriptor>
<jsp-param>
<param-name>pageCheckSeconds</param-name>
<param-value>10</param-value>
</jsp-param>
</jsp-descriptor>
</weblogic-web-app>


웹 애플리케이션에서 개별적으로 JSP를 절대로 변경하지 않는 운영 중인 환경에서는 서블릿 및 JSP 모두에 대해 stale checking을 절대 수행하지 않도록 컨테이너를 구성하는 것이 가장 좋습니다.



servlet-reload-check-secs



개발 모드에서는 서블릿을 수정하고 WAR의 WEB-INF/classes 디렉터리로 재컴파일한 경우 브라우저에서 요청하면 컨테이너가 서블릿의 최종 버전을 호출할 것으로 예상합니다. 이것을 처리하기 위해 WebLogic 웹 컨테이너는 WEB-INF/classes의 파일이 servlet-reload-check-secs 간격마다 수정되었는지 확인합니다. 이 파라미터의 기본값은 1초입니다. 이 값은 애플리케이션을 다시 배포하지 않고 서블릿 클래스에 대한 최종 변경 사항을 확인하려는 개발 모드에 알맞은 기본값입니다. 그러나 운영 중인 모드 이전에는 이 값을 -1(서블릿 파일 절대 다시 로드 안 함)로 변경해야 합니다. 개별 클래스를 변경하지 않는 운영 중인 모드에서는 항상 servlet-reload-check-secs 값을 -1로 설정하는 것이 가장 좋습니다.



목록 5. servlet-reload-check-secs 값을 -1로 설정한 weblogic.xml 샘플



<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 8.1//EN"
"http://www.bea.com/servers/wls810/dtd/weblogic810-web-jar.dtd">

<weblogic-web-app>
<container-descriptor>
<servlet-reload-check-secs>-1</servlet-reload-check-secs>
</container-descriptor>
</weblogic-web-app>


JSP 클래스 로더



WebLogic Server가 JSP 클래스를 로드하는 방법을 살펴보는 것으로 토론을 마치겠습니다. 각 JSP는 자체의 클래스 로더(일반적으로 일회용 클래스 로더라고 함)에 로드됩니다. 이 클래스 로더는 웹 애플리케이션 클래스 로더의 자식이며 관련된 JSP 클래스 및 내부 클래스(있을 경우)를 로드합니다. 호기심 많은 독자라면 왜 WebLogic이 모든 JSP를 자체의 클래스 로더에 로드하는 지 궁금할 것입니다. 이런 복잡함이 정말로 필요할까? WebLogic이 웹 애플리케이션 클래스 로더를 사용하면 더 수월하지 않을까? 이런 질문은 합당하며 WebLogic 클래스 로더의 편리함을 추구하는 추종자라면 당연히 질문해 보아야 합니다. 이 문제를 다루려면 웹 애플리케이션에 몇 개의 JSP, 몇 개의 서블릿, 하나의 필터, 태그 핸들러 클래스도 포함된 수 백 개의 유틸리티 클래스가 있다고 가정하고, 이제 이러한 클래스를 모두 단일 클래스 로더에 로드했다고 합시다. 만일 단일 JSP를 수정한 다음 브라우저에서 다시 로드를 클릭하면 다음과 같은 상황이 발생됩니다.




  • 이 페이지는 JSP 컨테이너에 의해 재컴파일됩니다.

  • 클래스의 이전 버전을 로드하는 데 사용된 전체 웹 애플리케이션 클래스 로더는 버려집니다.

  • 새로운 웹 애플리케이션 클래스 로더가 생성되고 모든 서블릿 및 JSP(방금 변경한 JSP도 포함)가 다시 로드되고 다시 초기화됩니다.



Java에서는 새로운 버전의 클래스를 다시 로드하기 위해 클래스 로더를 재사용할 수 없습니다. 오히려 클래스 로더를 버리고 새로운 것을 만들어야 합니다. 이런 이유로 위 시나리오는 상당히 바람직하지 않습니다. 단일 클래스만 변경하더라도 애플리케이션 서버가 상당히 많은 클래스를 다시 로드해야 합니다.



이제 WebLogic이 자신의 클래스 로더 스키마를 구현하는 방법을 살펴보겠습니다. 위에서 언급한 동일한 시나리오에서 JSP를 수정하고 브라우저에서 다시 로드를 클릭하면 서버는 다음 작업을 수행합니다.




  • JSP 컨테이너가 이 페이지를 재컴파일합니다.

  • 이 JSP 클래스의 이전 버전을 로드하는 데 사용했던 단일 JSP 클래스 로더를 버립니다.

  • 웹 애플리케이션 클래스 로더를 부모로 사용하는 새로운 JSP 클래스 로더를 만들어 페이지를 처리합니다.



보시다시피 단일 JSP 클래스만 다시 로드하고, JSP만 약간 수정된 채 전체 웹 애플리케이션 클래스 로더는 수정되지 않고 영향을 받지도 않았습니다. 그러므로 단일 JSP를 수정하는 경우 컨테이너는 이전 클래스 로더를 버리고, 재컴파일하고, 이 JSP에 대해 생성된 클래스만 다시 로드합니다. 이렇게 하면 전체 웹 애플리케이션 클래스 로더를 다시 로드할 필요가 없습니다. 특정 웹 애플리케이션에서 일부 JSP만 자주 변경해야 하는 경우 이러한 방법은 커다란 도움이 됩니다.



결론



JSP 컨테이너의 내부에 관한 이러한 지식이 있으면 JSP를 불필요하게 재컴파일하는 상황을 피할 수 있을 뿐 아니라 pageCheckSeconds 및 servlet-reload-checks-secs 파라미터를 사용하여 페이지 응답 시간을 향상시킬 수 있습니다.



Nagesh Susarla는 BEA Systems의 WebLogic Server 개발 팀의 선임 소프트웨어 엔지니어입니다.






Return to dev2dev.

[/HTML]

'Know > Java' 카테고리의 다른 글

Jakarta Commons HttpClient  (0) 2006.01.10
캐릭터셋 관련  (0) 2006.01.05
불필요한 JSP 재컴파일을 피하는 방법  (0) 2005.12.20
유니코드(Unicode)와 유니코드 인코딩  (0) 2005.08.08
올바른 프레임워크의 선택과 사용법  (0) 2005.07.15
리팩토링 냄새표  (0) 2005.06.26
Trackbacks 0 : Comments 0

유니코드(Unicode)와 유니코드 인코딩

Know/Java 2005.08.08 09:47
[[출처: http://jinsuk.pe.kr/Unicode/Unicode_intro-kr.html ]]


유니코드(Unicode)와 유니코드 인코딩
원래 유니코드에 관한 개괄적인 내용을 담은 문서는 만들지 않을 작정이었습니다. 그런데, 유니코드를 처리하는 부분에 대한 작업을 진행하다보니 용어나 인코딩 방식 등에 대해 혼동이 되는 부분이 너무 많군요. 제 자신을 위해서도 유니코드에 대한 정리를 할 필요가 있을 듯하여 이 문서를 작성합니다. 이 문서에서는 유니코드의 기본 구성 및 인코딩 방식에 대해 비교적 자세하게 설명할 것입니다. 성격 급하신 분이라면 Unicode Consortium이 제공하는 코드간 변환 소스(C 언어)를 바로 보시는 게 더 도움이 될 수도 있겠군요 :-)

차례

유니코드 야사 (아마도 이랬을 것입니다)
유니코드의 구조
유니코드 3.1에 정의된 언어 영역
유니코드 용어의 이해
유니코드 인코딩
UTF-8 인코딩 (8 비트)
UTF-16 인코딩 (16 비트)
UTF-32 인코딩 (32 비트)
보충언어판에 대한 UTF-32와 UTF-16의 인코딩
UTF-16과 UTF-8의 대행코드 인코딩에 관한 오해
기타
참고문헌
유니코드 야사 (아마도 이랬을 것입니다)

거의 모든 프로그래밍 언어에서 사용하는 데이타 기본형으로 char가 있습니다. 이것이 8개의 비트, 즉 1바이트로 이루어져 있다는 것은 모두 알고 계시죠? 왜 하필 8비트였을까요? 당시의 메모리 사용환경이라던지(지금에 비하면 무지무지하게 열악했을 겁니다), CPU 연산속도 때문이라던지(애니악의 경우 폰노이만보다 계산속도가 느렸다는 이야기가 있습니다) 이런 이유들 때문이었을 까요? 가장 궁극적인 원인은 컴퓨터를 만든 사람들에게 있어서 8비트(실제로는 7비트)면 자신들의 언어(영어)를 모두 표현할 수 있었다는 데 있을 것입니다. 즉 7비트이면 27(128)개의 코드를 표현할 수 있는데, 26x2(알파벳 대소문자) + 10(숫자) + 특수문자 + 통제문자 등등을 모두 합쳐 128개이하로 표현이 가능합니다.

세상 모든 사람들이 영어만 쓴다면 Unicode를 만들겠다는 생각을 아무도 하지는 않을 것입니다(훗날 외계인들의 언어를 수용한다면 모를까...). 1바이트로 모든 문자를 표현할 수 없는 언어를 사용하는 국가에서는 각각 자신들만의 인코딩 방법으로 이용하여 문자를 표현해 왔습니다. 한국의 경우 KSC5601 표준(완성형)이 대표적이죠. 그러다 보니 한글, 한자, 일본어 등을 섞어쓸 수 있는 방법이 묘연했었나 봅니다(제한적이나마 가능하긴 합니다만). 그래서 소프트웨어 업체들이 생각한 것이 "그러면 세계의 모든 문자를 표현할 수 있는 코드 체계를 만들자!"라고 해서 만들어지기 시작한 게 UniCode입니다.

세상에 존재하는 모든 문자의 수가 216(65,536)개 이하라면 어떻게 될까요? char를 16비트로 증가시키면 간단하게 해결되겠죠? 처음에는 다들 그렇게 생각했나 봅니다. 유럽과 같이 영어 알파벳 쓰는 자잘한 언어들의 경우 몇백자면 될테고, 한글도 1만자 조금 넘고, 한자도 2만자 조금 넘게 주면 될 것 같고... 모두 합쳐도 6만자도 안되겠네... 그랬나 봅니다. 그래서 유니코드는 16비트의 공간에 모든 문자를 집어넣으려고 했습니다. 이 16비트 영역을 기본다중언어판(BMP, Basic Multilingual Plane)이라고 부릅니다. Unicode 3.0버전까지는 여기에만 문자 코드가 부여되어 있었습니다.

세상일이란 그렇게 만만치 않죠... 한중일에서 고문서를 다루는 사람들도 컴퓨터를 사용해야 되지 않겠습니까? 우리말 고어는 어떻게 표현하고, 지금은 사용되지 않는 옛 한자는 또 어떻게 하란 말입니까? 새로운 인코딩 방식을 별도로 만들어서 사용할까요? 이렇게 되면 유니코드라는 게 의미가 없어질 것입니다. 그래서 유니코드 3.0부터 보충언어판(Supplementary Planes)을 정의하였습니다. 이를 위해 BMP의 2,048자를 대행코드 영역(Surrogates)으로 할당하고 이중 1,024자를 상위대행(high surrogates), 1,024자를 하위대행(low surrogates)으로 정의하여 이 둘의 조합으로 다시 1백만여자의 - 1024x1024=1048576 - 문자를 추가로 정의할 수 있도록 하였습니다. 유니코드 3.1부터는 실제로 이 영역에 문자를 정의했습니다. 가장 크게 할당 받은 것은 역시 한자로 4만여자가 추가로 정의되었습니다.

이러한 세계표준코드를 만들려는 움직임은 소프트웨어 업체들의 연합인 유니코드 컨소시엄외에도 있었습니다. 세계표준기구(ISO)가 바로 이 움직임의 주체였는데, 다행스럽게도 두 단체가 서로 합의하여 1991년이후로 동일한 표준을 만들고 있습니다. 유니코드 표준과 동일한 세계표준기구의 표준안은 ISO/IEC 10646으로 명명되었습니다. 두 표준이 내용은 동일하지만 사용하는 용어나 논리적인 설명이 약간 다르기 때문에 혼동스러울 때도 있습니다. 이러한 이유때문에 제가 이 문서를 작성한 것입니다 :-)


유니코드 3.1에 정의된 언어 영역 (List of block names for Unicode Standard 3.1)


범위 언어 설명
0000..007F Basic Latin 기본 라틴(영어 알파벳)
0080..00FF Latin-1 Supplement
0100..017F Latin Extended-A
0180..024F Latin Extended-B
0250..02AF IPA Extensions
02B0..02FF Spacing Modifier Letters
0300..036F Combining Diacritical Marks
0370..03FF Greek 그리스어
0400..04FF Cyrillic 키릴어
0530..058F Armenian 아르메니아어
0590..05FF Hebrew 히브리어
0600..06FF Arabic 아랍어
0700..074F Syriac 시리아어
0780..07BF Thaana
0900..097F Devanagari
0980..09FF Bengali 벵골어(인도)
0A00..0A7F Gurmukhi
0A80..0AFF Gujarati
0B00..0B7F Oriya
0B80..0BFF Tamil
0C00..0C7F Telugu
0C80..0CFF Kannada
0D00..0D7F Malayalam
0D80..0DFF Sinhala
0E00..0E7F Thai
0E80..0EFF Lao
0F00..0FFF Tibetan
1000..109F Myanmar
10A0..10FF Georgian
1100..11FF Hangul Jamo 한글 자모
1200..137F Ethiopic
13A0..13FF Cherokee
1400..167F Unified Canadian Aboriginal Syllabics
1680..169F Ogham
16A0..16FF Runic
1780..17FF Khmer
1800..18AF Mongolian 몽고어
1E00..1EFF Latin Extended Additional
1F00..1FFF Greek Extended
2000..206F General Punctuation
2070..209F Superscripts and Subscripts
20A0..20CF Currency Symbols
20D0..20FF Combining Marks for Symbols
2100..214F Letterlike Symbols
2150..218F Number Forms
2190..21FF Arrows
2200..22FF Mathematical Operators
2300..23FF Miscellaneous Technical
2400..243F Control Pictures
2440..245F Optical Character Recognition
2460..24FF Enclosed Alphanumerics
2500..257F Box Drawing
2580..259F Block Elements
25A0..25FF Geometric Shapes
2600..26FF Miscellaneous Symbols
2700..27BF Dingbats
2800..28FF Braille Patterns
2E80..2EFF CJK Radicals Supplement 한자 부수
2F00..2FDF Kangxi Radicals 강희자전 부수
2FF0..2FFF Ideographic Description Characters
3000..303F CJK Symbols and Punctuation
3040..309F Hiragana 일본어 히라가나
30A0..30FF Katakana 일본어 카타카나
3100..312F Bopomofo
3130..318F Hangul Compatibility Jamo 한글 호환 자모
3190..319F Kanbun
31A0..31BF Bopomofo Extended
3200..32FF Enclosed CJK Letters and Months 원문자
3300..33FF CJK Compatibility 한중일 호환
3400..4DB5 CJK Unified Ideographs Extension A 한자 확장 A
4E00..9FFF CJK Unified Ideographs 한중일 공통 한자
A000..A48F Yi Syllables
A490..A4CF Yi Radicals
AC00..D7A3 Hangul Syllables 한글
D800..DB7F High Surrogates 상위대행코드
DB80..DBFF High Private Use Surrogates Private Use용의 15,16 Plane 지정을 위한 상위대행코드
DC00..DFFF Low Surrogates 하위대행코드
E000..F8FF Private Use
F900..FAFF CJK Compatibility Ideographs 한중일 호환
FB00..FB4F Alphabetic Presentation Forms
FB50..FDFF Arabic Presentation Forms-A
FE20..FE2F Combining Half Marks
FE30..FE4F CJK Compatibility Forms
FE50..FE6F Small Form Variants
FE70..FEFE Arabic Presentation Forms-B
FEFF..FEFF Specials
FF00..FFEF Halfwidth and Fullwidth Forms
FFF0..FFFD Specials
10300..1032F Old Italic
10330..1034F Gothic
10400..1044F Deseret
1D000..1D0FF Byzantine Musical Symbols
1D100..1D1FF Musical Symbols 음표
1D400..1D7FF Mathematical Alphanumeric Symbols
20000..2A6D6 CJK Unified Ideographs Extension B 한자 확장 B
2F800..2FA1F CJK Compatibility Ideographs Supplement
E0000..E007F Tags
F0000..FFFFD Private Use 개인 사용 목적(언어판 15)
100000..10FFFD Private Use 개인 사용 목적(언어판 16)

유니코드의 구조

이제는 유니코드의 구조에 대해 대강 감을 잡으셨을 줄 압니다. 유니코드의 구조는 크게 17개(1개의 기본언어판(BMP)와 16개의 보충언어판)의 언어판으로 구성되어 있습니다. 각 언어판은 216, 즉 65,536개의 문자를 지정할 수 있으니까 유니코드에서 지정할 수 있는 문자의 수는 17x65,536=114112개입니다. 하지만 대행코드영역(Surrogates) 2048개를 제외해야 하니까 실제로는 1112064개의 문자를 지정할 수 있겠군요. Unicode 3.1 표준에서 각 언어판은 다음과 같이 정의되어 있습니다.

표. Unicode 3.1의 각 언어판 정의 언어판 UCS4 영역 이름 문자수
0 (0x00) 0x00000000 - 0x0000FFFF 기본언어판(BMP, Basic Multilingual Plane) 49,196
1 (0x01) 0x00010000 - 0x0001FFFF 보충언어판(SMP, Supplementary Multilingual
Plane for scripts and symbols) 1,594
2 (0x02) 0x00020000 - 0x0002FFFF 보충표의문자판(SIP, Supplementary
Ideographic Plane) 43,253
3 ~ 13
(0x03~D) 0x00030000 - 0x000DFFFF 아직 사용하지 않음 0
14 (0x0D) 0x000E0000 - 0x000EFFFF 특별보충판(SPP, Supplementary
Special-purpose Plane) 97
15 ~ 16
(0x0F~0x10) 0x000F0000 - 0x0010FFFF 개인사용목적으로 지정(Private Use) 0
합계 94,140


유니코드 표준 3.0에서는 49,194 문자가 정의되었고 이들은 모두 BMP에 한정되어 정의되었으나, 3.1에서는 BMP에 2개의 문자를 추가하고, 보충언어판에 44,944개의 문자를 추가하였습니다. 새로 추가된 문자들은 음표, 고대문자, 한자(CJK Ideographic Extension B) 등입니다.

제 15, 16 언어판에 대한 설명이 조금 필요할 듯 합니다. 이 두개의 언어판은 개인사용(Private Use)로 지정되었습니다. 개발자들에게는 일종의 보너스라고 말하는 사람들도 있군요. 어쨌던 이 두 언어판은 상위대행코드중 마지막 128개 코드와 하위대행코드의 조합으로 지정됩니다(UTF-16 인코딩의 경우). 이 때문인지 유니코드 3.1에서는 U+DB80 ~ U+DBFF 영역을 별도로 분리하여 High Private Use Surrogates라고 이름을 붙였더군요.

2002년 1월에 Unicode Standart 3.2 베타판이 발표되었습니다. 3.2버전이 고정되는 대로 이 문서를 갱신할 예정입니다.

유니코드 용어의 이해

위의 표에서 뜬금없이 UCS4라는 용어를 사용했습니다. 유니코드 관련 문서를 읽다보면 가장 많이 마주치는 용어들이 UCS2, UCS4, UTF8, UTF16, UTF32 등과 같은 단어들입니다. 제가 아주 많이 헷갈렸던 관계로 용어들에 대한 정리를 간단하게 할 까 합니다.

기본언어판, BMP
BMP는 Basic Mulitilingual Plane의 약자입니다. 유니코드의 첫 65,536개의 코드를 의미합니다.
언어판, Plane
256x256 즉 65,536 개씩의 코드 묶음을 이릅니다. 유니코드에서는 현재 17개의 언어판을 사용할 수 있습니다. 모두 그룹 00에 포함됩니다.
언어판 그룹, Group
256개씩의 언어판을 묶어 하나의 그룹으로 명명합니다. 유니코드의 17개 언어판은 모두 Group 00에 있습니다. 유니코드는 17개의 언어판에 한정되어 정의됩니다. 반면 ISO 표준(UCS-4)에서는 모두 128개의 언어판 그룹이 정의될 수 있습니다.
1 Plane = 65,536 code points
1 Group = 256 planes = 256x65,536 = 16,777,216 code points
UCS-4 = 128 groups = 128x16,777,216 = 2,147,483,648 code points
인코딩, Encoding
문자집합을 표현하는 방식을 말합니다. 유니코드는 코드체계 또는 문자집합을 명명하는 것이며 이를 표현하기 위해서는 UTF-8, UTF-16, UTF-32 등과 같은 인코딩이 필요합니다.
UCS-2: Universal Character Set 2(octets)
좀더 정확하게는 Universal Multipe-Octet Coded Character Set 2입니다. ISO/IEC 10646의 용어로 BMP의 65,536 코드를 정의하며, 2바이트로 표현됩니다. 1개의 언어판, 즉 BMP만이 이에 해당합니다. UCS-2는 인코딩 방법이 아니며 문자코드 자체입니다. 인코딩으로 봐도 무방하겠군요. 여기서 octet이라는 용어를 사용했는데 이 용어는 ISO쪽에서 사용하는 용어로, 유니코드 진영에서 사용하는 바이트와 같은 뜻입니다
UCS-4: Universal Character Set 4(octets)
ISO/IEC 10646의 용어로 4바이트로 표현됩니다. 모두 128개의 언어판 그룹, 즉 128*256 언어판 = 32,768 언어판을 정의합니다. 이는 대략 231 = 2,147,483,648개의 코드에 해당합니다. UCS-4는 인코딩 방법이 아니며 문자코드 자체입니다.
UTF-8: UCS Transformation Format, 8-bit form
Unicode 표준의 인코딩 방식중의 하나입니다. 표준에서는 17개 언어판의 문자만을 표현할 수 있으나 기술적으로는 UCS-4 전영역의 문자를 표현할 수 있습니다. 문자에 따라 1 ~ 4(또는 6) 바이트로 표현됩니다.
UTF-16: UCS Transformation Format, 16-bit form
유니코드 3.0에서는 16을 16비트로 해석한 것이 아니라, 그룹 00의 16개 언어판이라고 써 놓았군요. UTF-32의 32가 32비트를 지칭하므로 통일성을 위해 16비트로 이해하시는 게 좋습니다. 16비트로 표현한다는 점에서는 UCS-2와 흡사하지만 대행문자영역(Surrogates)을 이용하여 16개의 보충 언어판 코드를 표현할 수 있는 인코딩입니다. 대행문자영역 2개로 16개의 보충 언어판을 표현할 수 있습니다. UCS-2에서는 65536개의 코드만을 정의할 수 있으나 UTF-16에서는 1백만여자를 더 표현할 수 있습니다.
UTF-32: UCS Transformation Format, 32-bit form
32비트 즉 4바이트로 각 문자를 표현합니다. 이점에서 UCS-4와 동일하지만 17개의 언어판만을 정의한다는 점에서는 UCS-4의 부분집합으로 간주하면 됩니다. UCS-4와 동일하나 0x00000000 ~ 0x0010FFFF 범위만을 문자코드로 간주한다고 이해하시면 됩니다.
표. 각 인코딩별 표현가능한 문자 수 인코딩 그룹 언어판 문자수(표준) 문자수(이론적)
UCS-2 0 0(BMP) 216=65,536 216=65,536
UCS-4 0 ~ 127 0 ~ 32,767 231=2,147,483,648 231=2,147,483,648
UTF-8 0 0 ~ 16 17*216-211=1,112,064 231=2,147,483,648
UTF-16 0 0 ~ 16 17*216-211=1,112,064 17*216-211=1,112,064
UTF-32 0 0 ~ 16 17*216-211=1,112,064 231=2,147,483,648


위의 표에서 표준이라 함은 유니코드 표준에서 정의하는 것을 의미하며, 이론적이라는 것은 표준의 정의를 무시할 때 표현할 수 있는 문자의 수를 의미합니다. 211을 뺀 부분이 있는 데, 이것은 상위대행코드(high surrogates) 1,024개와 하위대행코드(low surrogates) 1,024개의 합입니다. UCS-2에서는 이들이 하나의 문자로 취급되지만, UTF-8, UTF-16, UTF-32의 인코딩에서는 보충언어판의 코드를 지정하는데 사용되므로 코드로 취급할 수 없습니다.

유니코드 인코딩(Unicode Encodings)

유니코드에서 지원하는 인코딩 방식은 UTF-8, UTF-16, UTF-32의 세가지 방식입니다. UTF는 UCS Transformation Format의 약자이며, 뒤에 붙은 숫자는 인코딩에 사용되는 단위의 비트수를 의미합니다. 즉 UTF8은 8비트 단위, UTF16은 16비트 단위, UTF32는 32비트 단위로 문자를 표현합니다. 세가지 방식의 공통점이라면 16개의 보충언어판에 위치한 1,048,576개의 코드를 표현할 때는 4바이트를 사용한다는 점입니다. 하지만 그 방식은 모두 다릅니다. UTF8은 4개의 바이트로, UTF16은 2개의 16비트로, UTF32는 1개의 32비트 단위로 표현합니다. 이제 각 인코딩 방식에 대해 좀더 상세하게 설명하겠습니다.


UTF-8
가장 완벽하게 유니코드 표준을 표현하는 인코딩 방식은 UTF-16이라고 할 수 있습니다. 그런데 이 인코딩에서는 16비트 단위로 하나의 문자가 표현되기 때문에 전통적인 char 형과는 맞지 않는 부분이 있습니다. UTF-16으로 문자열을 표현했을 때 전통적인 char 형으로 이 문자열 취급하게 되면 중간에 널(null, 0)값이 들어가게 되어 문제가 발생합니다. 즉 유니코드를 지원하려면, 지금까지 개발된 모든 프로그램을 재개발해야 한다는 부담이 발생합니다. 현실적으로 이것은 거의 불가능한 일이라고 할 수 있습니다.

이의 대안으로 제시된 인코딩이 바로 UTF8입니다. UTF-8은 문자열의 중간 바이트에서 0이 나타나지 않도록 고안되었습니다. 이를 위해 각 유니코드 문자는 1바이트에서 4바이트까지 가변적으로 인코딩되도록 하고 있습니다. UTF-8에서는 U+0000 ~ U+007F까지의 128자는 1바이트로 표현되는데 이는 ASCII와 동일합니다. 또 U+0080 ~ U+07FF까지는 두 바이트, U+0800 ~ U+FFFF까지는 세 바이트로 표현됩니다. 즉 BMP내의 모든 문자는 1 ~ 3바이트로 표현됩니다. 그리고 나머지 16개의 보충언어판에 위치하는 1,048,576개의 코드는 네 바이트로 인코딩됩니다. 다음 표에서 UCS-4와 UTF-8간의 변환 방법을 나타내고 있습니다.

표. UTF-8과 UCS-4간의 변환 규칙 UCS-4 UTF-8
0x00000000 - 0x0000007F 0xxxxxxx
0x00000080 - 0x000007FF 110xxxxx 10xxxxxx
0x00000800 - 0x0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 - 0x001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0x00200000 - 0x03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0x04000000 - 0x7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx


위의 표에서는 UCS-4와 UTF-8간의 변환 규칙을 보여주고 있습니다만, UCS-4의 모든 영역에 대해서 보여주고 있습니다. 그러나 빨간색으로 칠한 부분을 0x0010FFFF로 변경하고 나머지는 삭제해야 정확한 유니코드의 UTF-8 인코딩이라고 할 수 있습니다. 이를 모두 표현한 것은 6바이트까지 확장하면 UTF-8로도 UCS-4의 전역을 인코딩할 수 있음을 보여주기 위한 것입니다. 아마도 1백만자의 코드이면 지구상에 존재했거나 존재하는 모든 문자를 표현하기에 충분할 것으로 예상됩니다. 우주의 모든 언어코드를 표현해야 할 때가 오면 0x00110000 이후의 영역도 사용할 가능성이 조금이나마 있을까요?

어쨌든, 현재 유니코드 표준에서는 UTF-8이 4바이트까지로 인코딩됩니다. 유니코드에 정의된 각 코드는 반드시 다음과 같은 범위에서 인코딩되어야 합니다. 다음 표는 UTF-8 인코딩에서 각 바이트에 올 수 있는 값을 보여주고 있습니다.

표. 올바른 UTF-8 바이트 배열 Code Points 1st Byte 2nd Byte 3rd Byte 4th Byte
U+0000 ~ U+007F 00 ~ 7F
U+0080 ~ U+07FF C2 ~ DF 80 ~ BF
U+0800 ~ U+0FFF E0 A0 ~ BF 80 ~ BF
U+1000 ~ U+CFFF E1 ~ EC 80 ~ BF 80 ~ BF
U+D000 ~ U+D7FF ED 80 ~ 9F 80 ~ BF
U+D800 ~ U+DFFF ill-formed (surrogate 부분이므로 인코딩되어서는 안됨)
U+E000 ~ U+FFFF EE ~ EF 80 ~ BF 80 ~ BF
U+10000 ~ U+3FFFF F0 90 ~ BF 80 ~ BF 80 ~ BF
U+40000 ~ U+FFFFF F1 ~ F3 80 ~ BF 80 ~ BF 80 ~ BF
U+100000 ~ U+10FFFF F4 80 ~ 8F 80 ~ BF 80 ~ BF



UTF-16
UCS-2와 거의 동일합니다. 이 인코딩의 기본 단위는 16비트, 즉 2바이트입니다. 대행문자 영역 2,048개를 제외한 63,488개의 코드를 문자로 사용할 수 있으며(BMP), 대행문자 영역 2개의 쌍을 이용하여 16개의 보충언어판에 위치한 1,048,576개의 코드를 표현할 수 있습니다.

대행 문자 2개의 쌍으로 1개의 문자를 인코딩한다는 점에서 UTF-16의 인코딩은 가변적인 인코딩이라고 할 수 있겠군요. 즉 기본언어판의 문자는 2바이트로 보충언어판의 문자는 4바이트로 인코딩됩니다. 이러한 인코딩 방식때문에 UTF-16에서는 유니코드 표준에서 지원하는 17개의 언어판 코드만 표현이 가능합니다. 가장 유니코드다운 인코딩이라고 할 수 있겠군요 :-)

이제 대행코드 및 이를 이용한 보충언어판 코드의 인코딩 방식을 알아보겠습니다. 유니코드 3.0에서 BMP의 U+D800부터 U+DFFF까지의 2,048 코드가 대행코드 영역(surrogates)으로 정의되었습니다. 이중 U+D800 ~ U+DBFF의 1,024개를 상위대행코드(high surrogates), U+DC00 ~ U+DFFF의 하위대행코드(low surrogates)라고 합니다. 상위대행코드 1개와 하위대행코드 1개의 조합으로, 즉, 16비트 단위 2개로 보충언어판의 1백만여개의 코드를 지정할 수 있습니다.

두개의 대행코드로 조합된 코드를 UTF-32 코드로 대응시키는 공식은 다음과 같습니다.

CodeValue = (HighSurrogate - 0xD800)*(LowSurrogate - 0xDC00) + 0x10000
여기서 CodeValue는 UTF-32 코드값입니다. 0xD800의 상위대행코드 영역의 시작점이며 0xDC00은 하위대행코드 영역의 첫번째 값입니다. 보충언어판은 0x10000부터 시작하므로 마지막에 이 값을 더해줍니다. 이 공식에 의해 CodeValue는 U+10000 ~ U+10FFFF까지 1048576개의 값을 가질 수 있게 됩니다.


UTF-16에서는 상위대행코드가 나타나면 반드시 뒤이어 하위대행코드가 따라와야 합니다. UCS-2에서는 그렇지 아니한 점이 UTF-16과 UCS-2의 차이점입니다.


UTF-32
UTF-32는 32비트, 즉 4바이트로 모든 유니코드 문자를 표현합니다. UTF-8과 UTF-16에 대비해서 고정길이 인코딩이라고 할 수 있겠군요. 4바이트 단위로 표현한다해서 UCS-4와 동일하게 볼 수는 없습니다. 17개의 언어판만을 대상으로 하는 UCS-4의 부분집합이라고 보시면 됩니다(표준상 그렇습니다). 즉 UTF-32의 인코딩 영역은 0x00000000에서 0x0010FFFF로 제한되어야 합니다(이 제한을 무시하면 UCS-4와 동일합니다).


보충언어판에 대한 UTF-32와 UTF-16의 인코딩
위의 세 인코딩간의 변환은 알고리즘의 문제라고 할 수 있습니다. 유니코드 컨소시엄에서 제공하는 C 언어로 된 변환 코드를 보면 아주 간단하게 세 인코딩간의 변환이 이루어지는 것을 알 수 있습니다.

다음 표에서는 보충언어판(Supplementary Planes)의 코드 인코딩을 UTF-32와 UTF-16 인코딩에 대해 보여줍니다.

표. UTF-32와 UTF-16의 보충언어판 코드 인코딩 언어판(plane) UTF-32 Encoding UTF-16 Encoding
1 0x00010000-0x0001FFFF 0xD800DC00-0xD83FDFFF
2 0x00020000-0x0002FFFF 0xD840DC00-0xD87FDFFF
3 0x00030000-0x0003FFFF 0xD880DC00-0xD8BFDFFF
4 0x00040000-0x0004FFFF 0xD8C0DC00-0xD8FFDFFF
5 0x00050000-0x0005FFFF 0xD900DC00-0xD93FDFFF
6 0x00060000-0x0006FFFF 0xD940DC00-0xD97FDFFF
7 0x00070000-0x0007FFFF 0xD980DC00-0xD9BFDFFF
8 0x00080000-0x0008FFFF 0xD9C0DC00-0xD9FFDFFF
9 0x00090000-0x0009FFFF 0xDA00DC00-0xDA3FDFFF
10 0x000A0000-0x000AFFFF 0xDA40DC00-0xDA7FDFFF
11 0x000B0000-0x000BFFFF 0xDA80DC00-0xDABFDFFF
12 0x000C0000-0x000CFFFF 0xDAC0DC00-0xDAFFDFFF
13 0x000D0000-0x000DFFFF 0xDB00DC00-0xDB3FDFFF
14 0x000E0000-0x000EFFFF 0xDB40DC00-0xDB7FDFFF
15 0x000F0000-0x000FFFFF 0xDB80DC00-0xDBBFDFFF
16 0x00100000-0x0010FFFF 0xDBC0DC00-0xDBFFDFFF



UTF-16과 UTF-8에서의 대행코드 인코딩에 관한 오해
본 절에서는 대행코드(Surrogates)를 이용하여 보충언어판 코드영역을 인코딩할 때 사람들이 종종 오해하는 부분에 대해서 설명하고자 합니다. 앞서 설명했듯이 UTF-16에서는 U+10000 ~ U+10FFFF까지의 코드를 인코딩하기 위하여 상위대행코드(high surrogate) 1개와 하위대행코드(low surrogate) 1개의 쌍으로 표현한다고 하였습니다. 그렇다면 UTF-8에서도 두개의 대행코드를 각각 3바이트씩으로 인코딩할 수 있을 것입니다(대행코드가 제안되었을 초기에 개발된 많은 프로그램들이 실제로 이렇게 구현했었다고 합니다).

이 방식이 제대로 동작할 수는 있지만 세가지 문제점이 있습니다. 첫째 문제점은 보충언어판의 모든 코드가 6바이트로 인코딩된다는 것입니다 - 정상적인 방법으로는 4바이트로 인코딩됩니다. 두번째는 인코딩된 문자열의 해석입니다. 보충언어판의 실제 코드 값은 U+10000 ~ U+10FFFF인데 대행코드를 UTF-8 인코딩에 이용하게 되면, 두개의 대행코드로부터 실제 코드값으로 다시 계산해주어야 하는 번거로움이 있습니다. 세째, 텍스트 인코딩의 안정성이 떨어진다는 것입니다. 세 바이트로 인코딩된 상위대행코드가 나타나면 그 다음에는 반드시 하위대행코드가 따라와야 하므로, 정상적인 방법에 비해 오류가 발생할 위험성이 그만큼 더 커진다는 것이죠.

이러한 문제점들 때문에 UTF-8에서는 대행코드 자체를 "절대로" 인코딩하지 않습니다. 그래서 대행코드영역의 byte sequence를 보면 ill-formed라고 나타내고 있습니다. 또한 대행코드를 인코딩한 것을 사용하게 되면 17개의 언어판 밖에는 표현할 수 없다는 단점도 가지게 될 것입니다. 이러저러한 이유로 UTF-8은 규칙에 따라 순서대로 코드값을 인코딩합니다. 따라서 대행코드영역(Surrogates)은 UTF-16 인코딩에서만 됩니다.

기타

제가 프로그램해야 할 유니코드 처리부분은 UTF-8 인코딩을 사용합니다. 이 인코딩은 바이트단위이기 때문에 컴퓨터 기종에 따라 발생하는 바이트 배열(byte ordering) 순서를 무시할 수 있습니다. 하지만 UTF-16이나 UTF-32와 같은 다바이트 인코딩을 이용하려면 이 문제를 신경써야 한다고 합니다. -- little-endian, big-endian 문제... 이 때문에 바이트순서표식(BOM, Byte Oder Mark)을 사용하기를 권장합니다. 즉 UTF-16이나 UTF-32 문자열의 처음에 BOM을 삽입하면 됩니다. 각 인코딩의 BOM은 다음 표와 같습니다.

표. Little-endian, Big-endian 바이트순서에 따른 BOM 구분 Little-endian Big-endian
UTF-16 0xFFFE 0xFEFF
UTF-32 0x0000FFFE 0x0000FEFF
플랫폼 Intel 프로세서, DEC/Alpha 프로세서 RISC 프로세서, Motorola의 Microprocessor
운영체제 MS Windows, 리눅스/인텔 Unix, MacOS


요즘 생산되는 RISC CPU(MIPS나 ARM 같은)는 big-endian과 little-endian을 모두 지원한다고 하는군요. 나의 생각: 개발자들에게는 무지하게 골치아프겠군요 - 프로그램할 때 두가지 경우 모두에 대해 고려해야 할 테니... 어쨌든 제가 타겟으로 삼고 있는 인코딩은 UTF-8로 정보검색시스템에서 사용할 예정이기 때문에 바이트순서에는 별로 상관없을 것 같아서 다행이군요 :-)

참고문헌[Ken Kunde]에 엔디안문제에 따른 좋은 예제가 있어서 이를 설명할까 합니다. UTF-16에서 0x4E와 0x00 두 바이트로 예를 들겠습니다. little- 또는 big-endian에 따라 둘의 조합은 0x4E00 또는 0x004E가 될 것입니다. Endian 방식에 따라 두 글자는 서로 다른 글자를 나타내게 됩니다. 결과는 다음과 같습니다.

0x4E00/Big-endian : '一' (한자 1)
0x4E00/Little-endian : 'N' (대문자 N)
0x004E/Big-endian : 'N' (대문자 N)
0x004E/Little-endian : '一' (한자 1)
마지막으로 유니코드 컨소시엄이 제공하는 UTF8, UTF16, UTF32 인코딩간의 변환 소스를 소개하겠습니다. 이 소스는 C 언어로 되어 있으며 http://www.unicode.org/Public/PROGRAMS/CVTUTF/에서 다운로드받을 수 있습니다. 이 소스를 분석해보면 각 인코딩 방법에 대해 명확하게 이해할 수 있을 것입니다.

유니코드 관련 문서들을 접하면서 제가 받은 느낌은 유니코드는 유니코드 컨소시움 답게, ISO/IEC 10646은 ISO답게 코드를 정의했다는 것입니다. 그것도 동일한 내용을 정의하면서 느낌이 다르니 참 존경스럽더군요. 무슨 말인고 하니, 유니코드는 업체가 제안한 코드이고 10646은 표준기구에서 제안하는 코드라는 것입니다. 따라서 유니코드 쪽은 구현이 중요한 쟁점이 되고 표준기구에서는 표준의 범용성, 확장성 등 구현 외의 관점들이 더 중요했을 것이라는 것입니다. 이런 이유로 유니코드는 코드의 범위를 1백만자 정도로 한정함으로써 구현이 쉬울 뿐만니라 코드자체로 인한 오류 발생 가능성을 최소화 했다고 볼수 있습니다. 하지만 ISO의 입장에서는 그것이 먼 미래의 일일지라도 새로운 문자의 수용 등을 고려할 수 있는 의미론적인 접근이 더 중요했을 것 같습니다. 그래서 4바이트 정수형 전체를 코드로 사용할 수 있는 가능성을 열어 놓은 것으로 보입니다. 같은 표준으로 참 절묘한 차이를 보여주는군요.

긴 글을 다 읽어주셔서 감사합니다. 그리고 많은 도움이 되었기를 바랍니다. -- 김진숙

참고문헌

Unicode 홈페이지, http://www.unicode.org
Unicode Online Data, http://www.unicode.org/Public
Unicode Public Programs, http://www.unicode.org/Public/PROGRAMS
Ken Lunde, 2001, "Unicode encodings: How to interoperate between UTF-8, UTF-16, and UTF-32" Adobe Systems.
The Unicode Consortium, 2000, "The Unicode Standard 3.0", Addison-Wesley.

'Know > Java' 카테고리의 다른 글

캐릭터셋 관련  (0) 2006.01.05
불필요한 JSP 재컴파일을 피하는 방법  (0) 2005.12.20
유니코드(Unicode)와 유니코드 인코딩  (0) 2005.08.08
올바른 프레임워크의 선택과 사용법  (0) 2005.07.15
리팩토링 냄새표  (0) 2005.06.26
Triple DES Encryption (DESede)  (2) 2005.03.25
Trackbacks 0 : Comments 0

올바른 프레임워크의 선택과 사용법

Know/Java 2005.07.15 13:25

올바른 프레임워크의 선택과 사용법
잘 고른 프레임워크는 프로젝트 성공의 열쇠

이경원 | 한국오라클 테크니컬 컨설턴트

언젠가부터 아파치, 소스포지 등 오픈소스 진영의 프레임워크 공세로 우리는 필요한 프레임워크를 쉽게, 그리고 많이 구할 수 있게 되었다. 이에 따라 개발자들도 자신의 입에 딱 맞는 프레임워크 사냥을 즐기고 사용법을 익히는 데 많은 시간을 투자한다. 하지만 우리는 프레임워크의 개념이나 사용 방식에 익숙하지 않다. 이번 시간을 통해 필자가 쌓아 온 프레임워크의 내공(?)을 소개하도록 하겠다.
현재 한국오라클에서 테크니컬 컨설턴트를 맡고 있다. 스트럭처, 객체지향, CBD, SOA 순으로 전문영역을 넓혀 왔으며 현재는 프레임워크와 아키텍처에 많은 관심을 가지고 있다.
컴퓨터가 역사에 등장한 지 채 100년이 되지 않았다. 하지만 발전 속도는 다른 산업에 비해 매우 빨라서 새로 나오는 기술이나 개념을 따라잡기 힘든 경우가 많다. 특히나 소프트웨어 분야에서 이러한 경향은 더욱 두드러지는데, 수없이 많은 ‘수단’이나 ‘방법’들이 나타나 각광을 받기도 하고 사라지기도 한다. 그 중에 프레임워크는 각광을 받고 있는 쪽에 속한다. 이젠 소프트웨어 개발의 필수요소로 그 입지를 확고히 차지하고 있으며, 새로 등장하는 프레임워크는 그 수를 헤아리기조차 어려울 정도다. 말 그대로 프레임워크의 홍수, 프레임워크의 춘추전국시대다. 신발 가게에 신발이 많다고 해서 모두 발에 맞는 것이 아니듯, 이들 중 자신에게 적합한 프레임워크를 식별하는 기술은 개발자의 조건으로 중요한 자리를 차지하게 된다.
프레임워크는 반쪽 애플리케이션?
일반적으로 프레임워크는 사전적으로 ‘무언가를 이루는 뼈대나 기반 구조’를 의미한다. 컴퓨터 소프트웨어 분야에서의 프레임워크도 ‘특정 애플리케이션 소프트웨어를 만들 때 사용하는 기반 구조’라는 점에서 의미는 같다. 소프트웨어를 만들 때 문제영역에 대해 잘 설계된 디자인이 있다면 같은 문제영역을 개발하는 많은 사람들은 그 디자인을 차용하여 소프트웨어를 설계할 것이다. 그 디자인이 재사용해도 좋을 정도로 유용하다면 사람들은 곧바로 그들의 디자인 모델에 포함시킬 것이다. 그리고 이 디자인을 이미 구현해 놓았다고 한다면 사람들은 디자인 과정을 생략하고 개발시에 그 코드를 재사용할 것이다. 이렇게 특정 문제영역의 일반적인 솔루션 구현물을 프레임워크라고 해도 무방하다. 물론 같은 기능을 하는 라이브러리나 컴포넌트 등이 있지만, 이는 박스기사에서 자세히 다루도록 하자.
여기서 포착할 특징은 프레임워크 자체는 완전한 애플리케이션 소프트웨어가 아니라는 것이다. 혹자는 프레임워크를 반쯤 완성된 애플리케이션(semi-complete’ application)으로 정의하기도 한다. 어떤 문제영역을 해결하기 위한 잘 설계된 일반적인, 재사용 가능한 모듈(이미 만들어진 ‘반’)이기 때문에 완결된 애플리케이션으로 제공되지 못한다. 따라서 사용자가 프레임워크를 확장하여 비즈니스 요구사항을 만족시키는 완전한 애플리케이션 소프트웨어를 완성시키는 작업(나머지 ‘반’)이 요구된다.
이제 프레임워크에 대한 간단한 실례를 보기로 하자. 데이터베이스에서 값을 읽어들여 이를 메모리 캐시에 저장해 두었다가 애플리케이션이 요청을 하면 값을 전달해 주는 캐시 시스템을 구현해야 한다. 일반적으로 애플리케이션의 마스터 데이터나 메타 데이터와 같이 변경은 잘 되지 않으면서 사용 빈도가 높은 데이터를 캐시 시스템에 저장해서 사용한다. 캐시 시스템의 동작 원리는 간단하다. 클라이언트가 원하는 값을 캐시 시스템에 요청하면 캐시 시스템은 자신의 캐시된 데이터를 검색하여 있다면 리턴한다. 만약 값이 없다면 데이터베이스에서 값을 읽어와 캐시에 저장하고 이를 클라이언트에 돌려준다. 그 전체적인 구조는 <그림 1>과 같다.




여기서 값이 저장되어 있는 매체(데이터 소스)가 데이터베이스가 아닌 다른 것일 경우는 어떻게 될까? 가령 데이터 소스는 로컬 디스크에 있는 플랫 파일이 될 수도 있고, 원격 시스템에 있는 XML 파일이 될 수도 있다. 만약 이렇게 되었을 경우 앞의 데이터베이스의 예에서처럼 값을 가져오는 방식만 달라질 뿐이지 값을 가져온 이후 처리되는 컴포넌트들과는 영향을 미치지 않는다. 그렇다면 영향을 끼치지 않는 나머지 부분은 재사용 가능하게 된다. 단지 값을 가져오는 부분을 각 애플리케이션마다 상호작용하는 데이터 소스에 맞게 확장하여 사용해도 큰 무리가 없을 것이다. 여기서 실제로 값이 저장된 매체(데이터 소스)로부터 값을 가져온 이후까지는 사용자 확장이 필요한 부분이 된다. 데이터 소스로부터 데이터를 얻어와 애플리케이션에 전달해 주는 부분까지는 프레임워크로 정의한다면 데이터에 대한 캐시가 필요한 여러 클라이언트에 두루 사용할 수 있다.
즉, 프레임워크는 Open-Closed 원칙(이하 OCP)을 그대로 따르고 있다. 재사용되는 공통된 부분은 프레임워크로 구현되어 다른 사람이 그 내부를 가공없이 이용하도록 제공하고 있다(Closed). 확장이 필요한 부분은 사용자 요구사항에 맞게 정의하여 확장시키므로 문제영역에 최적화된 애플리케이션 설계가 완성된다(Open).
<그림 2>는 캐시 프레임워크의 일반적인 구조를 도식화한 것이다. 프레임워크를 애플리케이션에 적용할 때 변경되지 않고 반복적으로 재사용되는 부분을 프레임워크의 코어로 정의한다. 변경이 잘 일어나지 않는 프레임워크의 코어 부분을 콜드 스팟(Cold spot)이라고 부르기도 한다(OCP에서 Closed 모듈에 해당한다).




프레임워크 코어는 각 애플리케이션마다 확장 모듈을 연결하는 확장점을 제공하는데, 이를 훅 포인트(hook point)라고 한다. 대개 훅 포인트는 추상 클래스나 인터페이스의 형태로 나타난다. 애플리케이션마다 이 훅 포인트를 통해 확장 모듈을 바인딩한다. 이 확장 모듈을 핫 스팟(Hot Spot)이라고 부른다(OCP에서 Open 모듈에 해당한다).
일반적으로 훅 포인트는 사용자가 소스코드 상에서 함수 호출을 통해 확장 모듈을 등록(혹은 바인딩)하게 된다. 하지만 바인딩 코드들이 사용자 코드 곳곳에 분포되어 산만하고 반복 작업을 하게 한다. 이런 불편 때문에 현재 프레임워크는 메타 데이터를 이용하여 훅 포인트로의 연결, 정책 설정들을 정의하고 있는 추세다. struts의 struts-config.xml이나 J2EE의 사용자 정의 파일들이 대표적인 예다. 전자의 경우 프로그램에 의한(programmatic) 바인딩이라 하며 후자의 경우 선언에 의한(declarative) 바인딩이라고 한다.
이렇게 프레임워크에 기반해 애플리케이션을 개발하게 되면 생산성이 향상될 뿐 아니라 여러 애플리케이션이 비슷한 구조를 가지게 되므로 관리하기도 쉬워진다. 물론 프레임워크에 맞춰 애플리케이션을 개발해야 하므로 창조적인 소프트웨어 생산을 원하는 사람들은 좋아하지 않는 경우도 있다. 이런 사람들은 프레임워크 자체를 만드는 쪽이 더 적합할 것이다.
프레임워크의 다양한 분류 방법
프레임워크는 컴퓨터 소프트웨어와 관련된 거의 모든 영역에서 사용되고 있는 개념이다. J2EE나 닷넷 프레임워크 같은 아키텍처 모델에서도 프레임워크라는 용어가 사용되는데 이 경우 아키텍처 프레임워크라고 한다. 한편 IBM RUP(Rational Unified Process)나 마이크로소프트 솔루션 프레임워크 같이 방법론에서도 프레임워크 개념을 사용한다. 이렇게 프레임워크는 전형적인 버즈워드(buzzword: 전문적인 어감을 풍기는 유행어)가 된 듯한 느낌이다. 일반적으로 프레임워크라고 하면 애플리케이션 프레임워크를 말한다. 여기서는 애플리케이션 프레임워크를 몇 가지 기준에 따라 분류해 보기로 하겠다.
프레임워크의 계층에 따른 분류
프레임워크가 이용되는 계층에 따라 몇 가지로 분류할 수 있다.
시스템 인프라스트럭처 프레임워크
시스템의 인프라스트럭처의 효과적인 개발을 위한 프레임워크이다. OS나 커뮤니케이션 인프라, 사용자 인터페이스, 언어 처리 툴과 같은 애플리케이션을 이식성 좋고 효과적으로 개발할 수 있도록 해준다. 보통 소프트웨어 벤더 조직 내부에서 쓰이는 것이 일반적이다. TAO나 프레임워크로 제공되지 않으며, 자바 기본 패키지의 경우가 여기에 속한다.
미들웨어 통합 프레임워크
분산된 애플리케이션이나 컴포넌트들의 통합에 사용되는 프레임워크들이다. 미들웨어 통합 프레임워크는 프로그래머들이 마치 단일 환경에서 작업하는 것과 같은 소프트웨어 인프라스트럭처를 제공함으로써 기존 방식의 프로그램 제작 방법을 무리 없이 사용할 수 있도록 해준다. 미들웨어 통합 프레임워크에는 ORB 프레임워크, MOM (Message Oriented Middleware) 등이 있다.
엔터프라이즈 애플리케이션 프레임워크
실제 비즈니스 도메인(통신, 항공, 조업, 금융 등)에서 사용할 애플리케이션을 개발하기 위한 프레임워크다. 대부분의 대형 비즈니스 애플리케이션이 이 엔터프라이즈 애플리케이션 프레임워크를 기반으로 작성된다. CMS나 ERP 프레임워크들이 대표적인 예이다.




확장 방법에 따른 분류
프레임워크에 사용자가 확장하는 모듈을 정의에 따라 분류할 수 있다. 이 정의 방식은 바인딩(훅킹 방식)과도 연관이 깊다.
화이트 박스 프레임워크
객체지향 언어가 제공하는 상속이나 동적 바인딩에 의존해서 확장을 하는 프레임워크 방식이다. 프레임워크 코어의 베이스 클래스를 상속하고 미리 정의된 훅 포인트를 구현함으로써 프레임워크를 확장한다. 화이트 박스 프레임워크는 애플리케이션 개발자가 프레임워크의 내부 구조에 대한 지식을 필요로 한다. 프레임워크가 정의한 클래스를 상속하여 클래스의 인터페이스를 구현해야 하기 때문에 각 인터페이스의 역할, 규약, 정책 등에 대해 숙지해야 한다. 화이트 박스 프레임워크를 확장한 경우에는 프레임워크의 세부 구조와 밀접하게 연관된 애플리케이션이 만들어지는 경향이 있다.
블랙 박스 프레임워크
프레임워크에서 정의한 핫 스팟 인터페이스를 구현해야 하며, 이 구현한 객체를 인터페이스와 컴포지션을 통해 확장한다. 객체의 상속 없이 컴포지션과 딜리게이션(위임)으로 확장되기 때문에 프레임워크 내부 구조의 의존성이 상대적으로 낮다. 따라서 프레임워크에 대한 이해가 다소 적게 요구되며 쉽게 확장할 수 있다. 반면, 프레임워크 개발자는 미래에 잠재적으로 발생할 확장 포인트까지 미리 정의해야 하는 예지력이 필요하다. 따라서 프레임워크 자체의 개발이 어려워지는 경향이 있다.
처리 영역에 따른 분류
기능 프레임워크
전체 애플리케이션 중에서 특정 기능 부분의 구현에 사용되는 프레임워크이다. 데이터베이스 핸들링이나 사용자 인터페이스 구현과 같이 한정된 영역을 해결해 주는 역할을 한다. 즉, 애플리케이션이 동작하기 위해 필요한 여러 영역들(DB, 그래픽, 로깅, 리소스 풀링, 캐싱 등)의 문제점을 전담하는 각각의 프레임워크들이다. 현재 많은 개발자들이 이 프레임워크들의 조합으로 시스템을 구축하고 있으며 자바의 경우 classpath에 여러 jar 파일들을 사용하는 게 그 증거다.
우리가 사용하는 가장 일반적이고 보편적인 프레임워크에 해당하며 대부분의 프레임워크들이 기능 프레임워크로 출발한다. Log4X 시리즈와 같은 로깅을 위한 프레임워크나 OR-맵핑 프레임워크, 스트럿츠와 같은 웹 프레임워크 등이 대표적이다. 과거의 아키텍트들은 시스템의 전체의 설계를 많이 고민했다. 하지만 우수한 기능 프레임워크들 덕택에 프레임워크 사용에 대한 가이드라인이나, 샘플 코드, 프레임워크를 잘 사용할 수 있는 유틸리티 등을 고민하는 모습을 많이 발견하게 된다.
지원 프레임워크
애플리케이션을 개발하는 작업에 도움을 주는 개발 지원 프레임워크를 말한다. 기능 프레임워크는 운용 환경에 같이 배치되는 데 반해 지원 프레임워크는 애플리케이션에 포함되지 않는다. 애플리케이션 빌드를 위한 프레임워크나 Xunit 시리즈와 같은 테스팅 프레임워크 등이 여기에 속한다.
통합 프레임워크
여러 기능 프레임워크들을 한 곳에 모아 통합한 프레임워크를 말한다. 특정 아키텍처 모델에 기반한 애플리케이션의 전체 영역을 모두 다룬다. 따라서 통합 프레임워크에서 개발을 하면 아키텍처와 디자인의 상당수를 프레임워크 구조에 맞추게 된다. 규모가 큰 소프트웨어의 경우 통합 프레임워크에 기반해서 개발하는 것이 일반적이다. 왜냐하면 작업량이 많고 개발해야 할 시스템이 거대하기 때문에 생산성 향상을 위해 구현해야 할 많은 부분을 프레임워크에 맡기기 때문이다. 대체로 대형 소프트웨어 벤더들이 개발 환경과 통합하여 제공하는 경우가 많으며, BEA의 비하이브나 오라클의 ADF 등이 여기에 속한다.

프레임워크 사용 가이드라인
이제까지 프레임워크의 자체에 대한 내용을 살펴봤다. 하지만 개발자에게 ‘프레임워크란 무엇인가?’ 즉, WHAT의 문제보다도 ‘어떻게 사용해야 하는가?’ 즉, HOW의 문제가 주된 관심사다. 이제 프레임워크를 어떻게 실제로 적용하는 것이 좋을지에 대해 살펴보자.
만들고자 하는 애플리케이션의 성격 파악하기
애플리케이션은 한번 만들어지면 결국 직·간접적으로 우리 삶에 영향을 미치게 된다. 그 영향의 정도는 미미할 수도 있고 아주 클 수도 있다. 애플리케이션의 성격을 명확히 해야 적절한 프레임워크를 선택할 수 있다. 애플리케이션은 성격에 따라 다음과 같이 크게 네 가지로 나눌 수 있다.
첫 번째 경우
애플리케이션이 잘못되었을 때 아무런 물질적 손해가 없거나 있다 해도 경미한 애플리케이션. 이러한 애플리케이션은 비즈니스에 직접적으로 미치는 영향이 거의 없다고 봐도 무방하다. 실제로 이 유형의 애플리케이션은 비즈니스에 직접적으로 사용되는 경우도 거의 없으며 대부분 테스트 목적인 경우가 대부분이다. 다음과 같은 경우가 이에 속한다.
1. 개인이 취미 삼아 만들어 보는 애플리케이션
2. 기술 수준을 높이기 위한 연구 목적의 작업
3. 무언가가 타당한지를 확인해 보기 위한 파일럿 프로젝트
대개 한 명 내지는 약간 명으로 구성된 하나의 팀이 작업을 수행하게 되고 이들은 보통 한 자리에 모여서 작업을 하게 되는 경우가 일반적이다. 프로젝트를 수행하는 구성원은 학생부터 기술적 수준이 높은 개발자까지 다양하다. 6개월 이내로 짧게 끝나는 경우가 일반적이다.
두 번째 경우
애플리케이션이 잘못되었을 때 사용자에게 다소간의 불편만을 초래하는 애플리케이션. 애플리케이션의 잘못이 궁극적으로는 물질적 손해를 발생시키지만 그것이 그리 큰 문제가 되지 않는 애플리케이션이다. 다음과 같은 경우가 이에 속한다.
1. 개인이 취미로 운영하는 웹 사이트
2. 회사의 비즈니스에 큰 영향을 주지 않는 웹 사이트
3. 회사가 내부적으로 운영하는 인트라넷 상의 애플리케이션
대개 약간 명에서 10여 명으로 구성된 하나 내지는 두 팀이 작업을 수행하게 되고 이들은 보통 한 자리에 모여서 작업을 하게 된다. 보통 작업 기간이 1년을 넘지 않는다.
세 번째 경우
애플리케이션이 잘못되었을 때 금전적인 손해를 초래하는 애플리케이션. 애플리케이션의 잘못으로 인한 금전적인 손해가 회사 내부에서 발생하는 경우로 금전적인 손해의 금액이 얼마인가보다는 금전적인 손해 자체가 의미가 있는 경우이다. 애플리케이션의 영향이 회사 내부에만 국한된다. 다음과 같은 경우가 이에 속한다.
1. 회사 내부의 인사·회계 애플리케이션
2. 인터넷 쇼핑몰
3. 돈을 받고 제공하는 인터넷 서비스
대개 10~40명 정도의 사람이 여러 팀으로 나뉘어 작업을 수행하게 된다. 자리가 여러 곳으로 나눠질 수 있으며, 작업기간은 1~2년 정도가 소요된다.
네 번째 경우
애플리케이션이 잘못되었을 때 인명 피해가 발생하거나 큰 금전적인 손해가 발생하는 애플리케이션. 비즈니스적으로 매우 중요한 애플리케이션으로 검증된 아키텍처와 기술을 사용해야 한다. 다음과 같은 경우가 여기에 속한다.
1. 공장을 운영하는 조업 시스템
2. 원자력 발전소 운영 시스템
3. 금융기관의 전산 시스템
참여하는 사람이나 투입되는 자원의 양이 상당하다. 매우 엄격하게 통제·관리되고, 작업 기간은 보통 수년에 이른다.
이러한 애플리케이션의 성격은 프레임워크를 선택할 때 완성도라는 측면의 기준이 된다. 애플리케이션이 잘못되었을 경우 미치는 파장이 별로 크지 않은 경우라면 프레임워크 선택의 폭이 넓어진다. 따라서 시스템의 리스크, 중요도, 목적이 낮다면 업계에서 검증된 완성도 높은 프레임워크부터 그렇지 않는 새로운 프레임워크까지 다양한 종류의 프레임워크를 사용해 볼 수 있다.
반면 애플리케이션이 비즈니스에 미치는 영향이 큰 경우라면 다양한 환경에서 사용되는, 어느 정도 검증된 완성도가 높은 프레임워크를 선택해야 한다. 메이저 벤더에서 만든 상용 프레임워크를 사용하는 것도 좋은 방법이다. 왜냐하면 프레임워크에 대한 지원이 얼마나 잘 되는지, 문제가 발생했을 때 얼마나 빠르고 정확하게 문제를 해결할 수 있는지가 프로젝트 위험요소의 완충지대가 되기 때문이다. 메이저 밴더의 경우 이 시행착오를 오랫동안 많은 사이트에서 관리했기 때문에 좀 더 신뢰가 간다.
개발하려는 애플리케이션의 아키텍처를 정의하기
적절한 프레임워크를 선택하기 위해서 개발하는 애플리케이션의 구조를 정의하는 것이 선행되어야 한다. 이른바 애플리케이션의 아키텍처를 명확히 해야 하는데, 이는 애플리케이션 개발에서 가장 먼저 해야 할 일이기도 하다. 예를 들어 엔터프라이즈 비즈니스 애플리케이션을 만들고자 한다면 먼저 몇 개의 계층으로 시스템을 구축할 것인지, 사용자 인터페이스는 무엇으로 할 것인지, 데이터 소스는 어떤 것을 사용할 것인지, 비즈니스 로직은 어떠한 기술을 사용해 구현할 것인지 등을 정의해야 한다.
이런 경우 각 아키텍처의 구성 요소마다 선택하고자 하는 기술들이 경합을 이룬다. 예를 들어 데이터베이스를 다루는 부분의 경우 간단한 DAO(Data Access Object)를 이용하는 형태로 구현할 수도 있고 OR-맵핑을 통해서 처리할 수도 있다. 사용자 인터페이스와 관련해서는 웹을 지원하는 하나의 사용자 인터페이스로 처리할 수도 있고, 휴대전화나 PDA에서도 보여지도록 다중 인터페이스를 처리해야 할 수도 있다. 데이터 소스도 데이터베이스만이 아니라 레거시 시스템이나 파일도 동시에 처리해야 할 수도 있다. 이렇게 같은 문제영역에 대한 프레임워크도 요구사항, 규약들에 의해 필요/불필요가 결정된다. 이 필요/불필요의 기준을 명확히 하기 위해서 아키텍처를 명확히 정의하는 것이 필요하다.
프로젝트 구성원을 고려해 프레임워크 선택하기
프레임워크를 이용한 프로젝트에서 가장 필요한 부분이 프레임워크와 운용 환경, 문제 환경 등에 대한 이해도이다. 즉, 프로젝트 기간에 프레임워크 사용에 대한 적당한 시간이 필요하며 이 학습 시간과 학습하기 쉬운 프레임워크의 균형은 프로젝트를 위험에 노출시키지 않는 요령이다. 따라서 개발자가 프레임워크나 프로그래밍 언어에 익숙하지 않은 상황에서 교육할 시간도 부족하다면 보다 사용하기 쉬운 프레임워크를 선택해야 한다. 이런 경우 프로그램에 의한 바인딩 프레임워크보다 선언에 의한 바인딩을 제공하는 프레임워크를 추천한다. 메타 데이터를 이용하는 방식은 프레임워크 구조에 대한 이해가 비교적 낮게 요구되며 프레임워크에서 원하는 인터페이스만 구현해 주면 된다.
통합 개발환경이나 위자드와 같은 개발 도구와의 연계도 중요한 고려대상이다. 실제로 많은 프레임워크들이 코드 생성이나 메타 데이터 정의와 같은 부분을 개발 도구와 연계하고 있다. 이러한 개발 도구들의 지원이 있다면 많은 수의 개발자가 참여하는 애플리케이션 개발 프로젝트에서도 쉽게 프레임워크를 도입해 사용할 수 있게 된다.
실제로 프레임워크 차세대 버전으로 프레임워크 + 비주얼 빌더 + 프로그래밍 언어 지원 기능을 전망하고 있다. 프로그램 코드에 의한 사용이 불편하기 때문에 선언에 의한 사용을 제공하지만 역시 선언 방식이나 규약들을 알아야 한다. 이런 학습곡선(Learning Curve)과 선언 작업을 최소화시키기 위해 위자드 등의 비주얼 빌더를 통해 관계 방식을 설정하고 스크립트 언어를 통해 메타 코드를 제너레이팅하는 기능을 목표로 하고 있다. 이를 프레임워크 열반(Framework Nirvana)라고 한다.
프레임워크를 적절히 조합하기
프레임워크는 해당 문제영역을 해결한다. 하지만 소프트웨어는 전체의 관점에서 여러 문제영역을 포함하고 있다. 따라서 소프트웨어 개발에선 여러 프레임워크들을 조합하여 개발한다. 하지만 프레임워크는 프레임워크간의 조합을 위한 인터페이스를 제공하고 있지 않다. 가령, 데이터베이스에서 DB 프레임워크를 통해 10개의 항목을 얻어와 웹 프레임워크를 통해 HTML 테이블로 리턴한다고 했을 때 두 개의 프레임워크를 사용하는 코드가 필요하며, 각 프레임워크간의 데이터 조작 방식이 다르기 때문에 맵핑을 해주어야 한다. 이렇게 프레임워크를 조합하는 데 필요한 비용이 따르게 되며 적절한 조합은 이 비용과 시스템 품질을 향상시킨다. 다음은 프레임워크를 조합하는 방법들이다.
프레임워크를 직접 선택하는 방법
애플리케이션에서 사용할 프레임워크를 프로젝트팀에서 일일이 선택하고 이들을 조합해서 사용하는 방법이다. 자유도가 가장 높은 방법이지만 프레임워크를 직접 선택해야 하므로 해야 할 일이 많다. 각 프레임워크간의 연계는 잘 되는지, 기능은 충분한지 등을 확인해야 한다. 각 프레임워크의 버전업이나 특정 기능 프레임워크를 다른 프레임워크로 교체한다거나 할 때도 일일이 확인해야 할 것이 많아지기도 한다.
프레임워크 배포판을 사용하는 방법
리눅스와 관련된 여러 소프트웨어를 하나로 묶어 리눅스 배포판을 내듯이 프레임워크들을 하나로 묶어 프레임워크 배포판을 내는 경우가 있는데 이를 사용하는 방법이다. 프레임워크간의 연계나 기능에 대한 검토는 배포판을 내는 쪽에서 확인하기 때문에 편하게 프레임워크를 사용할 수 있다. 프레임워크의 배포판을 사용할 경우에는 배포판을 내는 쪽이 얼마나 지원해 줄 것인가를 잘 살펴야 한다. 어느 날 갑자기 배포판에 대한 관리가 이루어지지 않는다면 그 후에는 배포판을 구성하는 프레임워크를 직접 일일이 관리해야 하는 경우가 생길 수도 있다.
메이저 벤더의 프레임워크를 사용하는 방법
메이저 벤더가 제공하는 통합 프레임워크를 사용하는 방법이다. 구입 비용이 많이 드는 반면 가장 안정적으로 프레임워크를 사용하는 방법이다. 메이저 벤더가 제공하는 기술 지원을 받을 수 있다는 점과 프레임워크의 완성도가 높다는 점에서 매력적이다. 규모가 큰 엔터프라이즈 애플리케이션을 개발하는 경우라면 이 방법이 가장 적절한 선택이라고 할 수 있다.
세상은 넓고 프레임워크는 많다
지금까지 프레임워크에 대해 간단히 정리하고 프레임워크를 사용하는 방법에 대해서 간단히 살펴보았다. 글이 다소 추상적인 개념을 위주로 했고 실제 프레임워크에 대한 언급을 최소화한 경향이 있는데, 이것은 필자가 알고 있는 몇몇 프레임워크에 대한 설명으로 글을 이끌어 가지 않기 위해서였다.
애플리케이션의 규모가 대형화되고 그 제작에 많은 인원이 관여되고 시간적인 여유도 적은 경우가 많은 프로젝트 상황이 요즘의 현실이다. 이 상황에서 프레임워크는 매우 효과적으로 개발 비용, 시간을 줄이는 애플리케이션 개발 수단이다. 따라서 적절한 프레임워크를 사용하는 것은 성공의 보증 수표가 된다. 한 가지 분명하게 말할 수 있는 것은 여러 곳에서 사용돼 검증된 프레임워크를 선택해야 한다는 것이다. 세상은 넓고 프레임워크는 많다. 보다 많은 사람들이 효과적으로 프레임워크를 사용해서 성공적인 프로젝트를 수행하기를 희망한다.
[ 프레임워크와 관련된 주요 개념들 ]

다음은 프레임워크와 관련된 주요 개념들이다. 이들은 프레임워크로 실현되기도 하고, 상호 포함관계를 갖기도 하고, 이용관계를 갖기도 한다.

패턴
패턴은 특정 도메인 영역에서 자주 발생하는 문제와 이에 대한 해결책을 나름의 형식을 통해 정리한 것이다. 아키텍처 패턴, 분석 패턴, 설계 패턴 등 그 적용 분야도 다양하며 이미 상당수의 패턴이 여러 분야에서 다양하게 사용되고 있다.
패턴과 프레임워크는 이미 성공한 솔루션에서 유래했다는 점과 다른 유사한 사례에서 재사용할 수 있다는 점에서 공통점을 갖고 있다. 하지만 패턴이 추상화된 구조나 설계 등에 대한 개념(모델)인 반면, 프레임워크는 실제로 구현된 무엇이라는 점에서 큰 차이가 있다. 즉, 패턴은 디자인의 재사용을 제공하고 프레임워크는 구현된 코드의 재사용을 제공한다. 필자가 서두에 설명한 ‘어떤 문제영역에 대한 잘 설계된, 재사용 가능한 디자인’이 패턴의 위치에 해당하고 이 디자인이 구현된 모듈을 프레임워크로 대입하면 될 것이다.
프레임워크는 디자인 패턴들을 실제로 구현한 결과물이고 이런 관계로 프레임워크를 문서화할 때 패턴을 통해 기술하는 것이 효과적이다. 실제로 잘 만들어진 프레임워크의 내부를 보면 유명한 패턴을 구현한 것인 경우가 많다. 이런 관계로 프레임워크 설명서로 디자인 패턴들을 소개하는 경우도 많다(J2EE 아키텍처, JUnit 설명서). 패턴은 다음 세 가지 점에서 프레임워크와 다르다.

1. 패턴은 ‘추상적인 무엇’이고 프레임워크는 좀 더 ‘실제적인 어떤 것’이다. 프레임워크는 코드로 구현되어 있지만 패턴은 추상화된 디자인 모델이다. 그러므로 패턴에 대한 샘플코드 정도가 있을 뿐이다.

2. 디자인 패턴은 프레임워크보다 작은 단위의 개념이다. 하나의 프레임워크는 보통 여러 가지 패턴으로 구성되고 있다.

3. 디자인 패턴은 보다 일반적이고 프레임워크는 대부분 특정 애플리케이션 도메인 영역에 특화되어 있다.

클래스 라이브러리
애플리케이션은 여러 클래스들이 상호작용하면서 그 기능을 수행하는데, 특정 기능을 수행하는 클래스들을 클래스 라이브러리 혹은 툴킷이라고 한다. 클래스 라이브러리는 범용적으로 사용할 수 있는 기능을 제공하는 재사용할만한 연관된 클래스들의 묶음이다. 리스트나 테이블 스택과 같은 자료 구조를 구현해 놓은 컬렉션 라이브러리, STL, IO 스트림 라이브러리 같은 것들이 그 전형적인 예이다. 클래스 라이브러리는 일반적인 기능만 제공한다는 점에서 프레임워크와는 다르다. 따라서 클래스 라이브러리의 목적은 일반적인 기능을 개발자가 다시 구현하지 않도록 해주는 수준에 속한다. 따라서 클래스 라이브러리를 사용함으로써 보다 용이하게 프레임워크를 개발할 수 있다.
클래스 라이브러리와 프레임워크간의 가장 큰 차이는 제어 권한의 위치에 있다. 클래스 라이브러리의 경우 사용자 코드가 필요에 의해 호출하여 사용하는 반면, 프레임워크의 경우 프레임워크에서 플로우가 시작하여 사용자가 등록한 핫 스팟 모듈을 호출한다. 이를 역제어(Inversion of Control)라고 한다.

컴포넌트
컴포넌트는 표준으로 정의된 컨테이너 규약 하에서 독립적으로 사용할 수 있는 소프트웨어 모듈이다. 컴포넌트의 기능은 인터페이스로 정의되며 그 내부 구현은 감추어져 있다. 프레임워크가 애플리케이션 기반 구조에 더 초점을 맞춘 개념이다. 반면 컴포넌트는 컨테이너라고 하는 기반 구조에서 작동하는 컴포넌트 모듈에 초점을 맞춘 개념이라는 점에서 차이가 있다.
프레임워크와 컴포넌트의 컨테이너는 애플리케이션을 이루는 기반 구조라는 점에서 매우 유사하다. 또한, 프레임워크에 등록하는 사용자정의 확장 모듈은 같은 종류의 프레임워크에서 재사용 가능하기 때문에 컴포넌트의 경우와 그 형태가 유사하다. 이런 관계 때문에 컴포넌트와 프레임워크를 혼용하게 되거나 분류가 어려워진다. 컴포넌트는 컨테이너-컴포넌트간의 관계 구조나 컨테이너, 컴포넌트 각각의 내부 구조를 구현하는 데 있어 프레임워크를 사용하기도 한다. 프레임워크는 핫 스팟과 콜드 스팟 구현 단위나 핫 스팟 인터페이스 설정에 있어 컴포넌트의 개념을 사용하기도 한다. 이런 관계로 일반적으로 프레임워크가 오래 사용되어서 기반 구조가 안정화되고 그 프레임워크를 확장해서 구현한 모듈이 많아지게 되면 그 자체가 바로 컴포넌트와 컨테이너가 된다.



출처 : 마이크로스프트웨어 [2004년 12월호]
Trackbacks 0 : Comments 0

리팩토링 냄새표

Know/Java 2005.06.26 23:57
리팩토링 中

3. 코드속의 나쁜냄새 ( 냄새표를 참조하라 , ()안의 숫자는 참조 페이지번호 )
리팩토링을 해야하는 시점
인간의 직관. 코드속의 나쁜냄새를 직관으로 처리

3.1. 중복된 코드(uplicated Code)
악취퍼레이드의 일등
- Extract Method(136) : 한곳 이상에서 중복된 코드가 나타날때
- Pull Up Method(370) :동일한 슈퍼클래스를 갖는 두 서브클래스에서 같은 코드가 나타나는경우
- Extract Method(136), Form Template method(393) :코드가 비슷하기는 하지만 같지는 않다면,
- Substitute Algorithm(167) : 메소드들이 같은 작업을 하지만 다른 알고리즘을 사용한다면 두 알고리즘중 더 명확한 것을 선택
- Extract Class(179) : 서로 관계가 없는 두 클래스에서 중복된 코드가 있는경우 한쪽 클래스에서 를 사용한 다음 양쪽에서 이 새로운 클래스를 사용하도록 하는것을 고려

3.2 긴메소드(Long Method)
짧은 메소드로 된 코드를 이해하기 쉽게 하려면 무엇보다도 이름을 잘 지어야하며, 핵심은 메소드의 길이가 아니라 메소드가 하는 일과 일을 처리하는 방법 사이의 의미적거리(Semantic distance)이다.

- 대부분의 경우 메소드의 길이를줄이기 위해 하는것은 Extract Method(136) 이다.
- 메소드에 파리미터와 임시변수가 많다면, 메소드를 추출하기 어렵다. 임시변수를 제거하기 위해서는 Replace Temp wth Query(147)을 사용
- 긴 파라미터 리스트는 Introduce Parameter Object(339 : 파라미터를 객체(파라미터그룹)로전달), Preserve Whole Object(331: 어떤객체에서 여러값을 얻어서 파라미터로 던진다면 그 객체를 던져라)로 짧게 할수 있다.

조건문과 루프 또한 메소드 추출이 필요
- 조건을 다루기 위해서 Decompose Conditional(276 : 조건,then,else부분에서 메소드추출) 사용
- 루프의 경우는 루프와 그 안의 코드를 추출하여 하나의 메소드로 만든다.

3.3 거대한 클래스(Large Class)
지나치게많은 인스턴스변수가 나타날때 많은 변수를 묶기 위해 Extract Class(179)를 사용할 수 있다.
클래스내에서 서로에게 의미가 있는 변수를 골라서 묶는다. 클래스안에서 변수들의 어떤 부분집합에 대한 공통적인 접두사,접미사가 있으면 클래스를 따로 뽑아낸다.
만약 새로만들 클래스가 서브클래스로서 의미가 있으면 종종 Extract Subclass(278) 이 더쉬움

- 코드가 많은 클래스는 Extract Class, Extract Subclass
- 클라이언트가 클래스를 어떻게 쓰게 할것인지를 결정하고 각각의 사용방법에 대해 Extract Interface를 사용--> 클래스를 어떻게 분해할지에 대한 아이디어를 줄것이다.
- 큰클래스가 GUI클래스라면 Duplicate Observed Data(224)

3.4 긴 파라미터 리스트(Long Parameter List)
이미 알고있는 객체에 요청하여 파라미터의 데이타를 얻을 수 있으면 Replace Parameter with Method(335: 파라미터를 제거하고 수신자가 그 메소드를 호출, 수신자안에 계산과정을 추가)를 사용.
한 객체로부터 주워모은 데이타뭉치를객체 자체로 바꾸기위해 Preserve Whole Object(331) 사용
호출하는 객체와 큰객체사이에 종속성을 두고싶지 아니할때는 파라미터로 넘겨라. 고통이 뒤따를것이다.

3.5 확산적 변경(Divergent Change)
한클래스가 다른이유로 인해 다른방법으로 자주 변경되는 경우에 발생 --> 두개의 클래스로 나누어 하나의 클래스만 수정
Extract Class를 사용하여 하나의 클래스로 묶음

3.6 산탄총 수술(Shotgun Surgery)
확산적 변경과 비슷하지만 정반대.
변경을 할때마다 많은 클래스를 수정하는경우

- move Method(170), Move Filed(175) 을 사용하여 변경해야할부분을 모두 하나의 클래스로 몰아놓고 싶을 것이다.
- 종종 Inline Class(184)를 사용하여 모든동작을 하나로 모을수도 있다.
확산적변경은 여러종류의 변경때문에 하나의 클래스가 시달리는것이고 산탄총 수술을 하나를 변경햇을때 많은 클래스를 고쳐야하는경우이다.

3.7 기능에 대한 욕심 (Feature Envy)
어떤값을 계산하기 위해 다른 객체에 잇는 여러개의 get 메소드를 호출하는 경우

- 그 메소드는 분명히 다른곳에 있고 싶은것이고 따라서 Move Method(170 메소드 이동)을 사용한다.
- 때로는 메소드의 특정부분만 이런 욕심으로 고통받는데 이럴때는 욕심이 많은 부분에 대해서 Extract Method(136 메소드로 추출)을 사용한담음 Move Method 를 사용

3.8 데이타 덩어리(Data Clump)
함께 몰려다니는 데이터의 무리는 그들 자신의 객체로 만들어져야한다.

- 이 덩어리를 객체로 바꾸기 위해 이 필드들에 대해 Extract CLass(179)를 사용한다.
- 그런다음 관심을 메소드 시그너처로 돌려 Introduce parameter Object(339: 파라미터를 객체로 전환)나 Preserve Whole Obejct(331 파라미터값을 구하는 객체자체를 마라미터로 던진다)을 사용하여 파라미터 리스트를 단순하게 한다.

3.9 기본 타입에 대한 강박관념(primitive Obsession)
기본타입과 레코드타입을 객체로 바꿔라.

- 각각의 데이타 값에 대해 Replace Data Value with Object(209 : 추가적인 데이타나 동작을 필요로 하는 데이타아이템이 있을경우 데이타를 객체로 바꿔라)를 사용
- 데이터 값이 타입 코드이고, 값이 동작에 영향을 미치지 않는다면 Replace Type Code with Class(255 : 클래스의 동작에 영향을 미치지 않는 숫자로 된 타입코드가 있으면 숫자- 를 클래스로 바꾸어라)를 사용
- 만약 타입타입코드에 의존하는 조건문이 있는 경우에는 Replace Type Code With Subclass , Replace Type Code With State/Strategy(265)를 이용
- 항상 몰려다녀야 할 필드 그룹이 있따면 Extract Class(179)
- 파라미터 리스트에서 이런 기본타입을 보면 Introduce Parameter Object(339)를 사용하라.
- 배열을 쪼개서 쓰고있는 자신을 발견하거든 Replace Array with Object(220)을 사용하라.

3.10 Switch문(Switch Statements)
siwtch 문의 본질적인 특징은 중복된다는점
항상 다형성을 생각.

- Extract Method(136)을 사용하여 switch문을 뽑아내고, Move Method(170)를 사용하여 다형성이 필요한 클래스로 옮긴다
- 이시점에서 Replace Type Code with Subclasses(261)를 사용할것인지, Replace Type Code with State/Strategy(265)를 사용할것인지 결정해야한다.
- 상속구조를 결정했으면 Replace Conditional with Polymorphism(293)을 사용할수 있다.
- 만약 하나의 메소드에만 영향을 미치는 몇개의 경우가 있따면 굳이 바꿀 필요가 없다. Replace Parameter with Explict Methods(327)
- 조건중 null 이 있는경우 Introduce Null Object(298)

3.11 평행 상속 구조(Parallel Inheritance Hierarchies)
산탄총 수술의 특별한 경우
한 클래스의 서브클래스를 만들면 다른곳에도 모두 서브클래스를 만들어주어야하는경우
중복을 제거하는 일반적인 전략은 한쪽 상속구조의 인스턴스가 다른쪽 구조의 인스턴스를 참조하도록 만드는것

- Move Method(170), Move Filed(175)를 사용

3.12 게으른 클래스(Lazy Class)
충분한 일을 하지 않는 클래스는 삭제되어야한다.

- 별로 하는일도 없는 클래스의 서브클래스는 Collapse Hierarchy(392), 거의 필요없는 클래스는 Inline Class(184)

3.13 추측성 일반화(Speculative Generality)
- 필요하지도 않은 것을 처리하기 위한 추상클래스가 있으면 Collapse Hierarchy(392)
- 불필요한 위임은 Inline Class(184)
- 메소드에 사용되지 않는 파라미터가 있다면 Remove Parameter(318)
- 메소드 이름이 이상하고 추상적일 때는 Rename Method(313)

3.14 임시필드(Temporary Field)
사용되지 않는 변수가 왜 있는지를 이해하려는것은 짜증나는일
임시필드는 복잡한 알고리즘이 여러변수를 필요로 할때 흔히 나타남.

- Extract Class, Intoduce Null Object

3.15 메시지 체인 (Message Chains)
클라이언트가 객체의 클래스 구조와 결합되어있을때, 객체가 객체에 물어보고.. 이 객체는 다른객체에.. 또 이객체는 다른 객체에..

- Hide Delegate(187)을 사용.
- 때로는 Extract Method, Move Method로 체인의 밑으로 밀어넣는다.

3.16 미들맨(Middel Man)
메소드의 태반이 다른 클래스로 위임을 하고 있다면 Remove Middle Man(191 클라이언트가 대리객체를 직접 사용)을 사용.
몇몇 메소드가 많은 일을 하지 않는다면 inline Method(144)를 사용. 추가동작 필요시 Replace Delegation with Inheritance(404) 사용

3.17 부적절한 친밀(Inappropriate Intimacy)
클래스가 지나치게 친밀하게 되어 서로 사적인 부분을 파고드느라 너무 많은 시간이 걸릴때.

- Move Method,Move Field를 사용하여 조각으로 나누고 친밀함을 줄인다.
- Change Bidirectional Association to Unidirectional(236)이 적용가능한지 살피고,
- 공통관심사가 있다면 Extract Class를 사용하여 공통된부분을 별도의 클래스로 만들어라.
- 또는 Hide Delegate(487)을 사용하여 다른 클래스가 중계하도록 하라.

3.18 다른 인터페이스를 가진 대체 클래스 (Alternatice Classes with Different Interface)
- 같은 작업을 하지만 다른 시그너쳐를 가지고 있는경우는 Rename Method를 사용
- 포로토콜이 같아질때까지 Move Method를 이용하여 동작을 이동시켜라.
- 너무 많은 코드를 옮겨야할경우 Extract Superclass(384)를 사용.

3.19 불완전한 라이브러리 클래스(Incomplete Library Class)
라이브러리 클래스를 수정하는것은 거의 불가능하다.

- 라이브러리 클래스가 가지고 있었으면 하는 메소드가 몇개 있다면, Introduce Foreign Method(194)를 사용하라.
- 별도의 동작이 잔뜩 있다면 Introduce Local Extension(196)이 필요하다.

3.20 데이터 클래스(Data Class)
멍청하게 데이타만 저장하고 거의 대부분 다른 클래스에 의해 조작된다.

- public 필드는 Encapsulate Field(224)적용
- Collection 필드는 Encapsulate Collection(244)
- 값이 변경되면 안되는 필드에 대해서는 Remove setting Method(344) 적용
- get/set 메소드가 다른 클래스에서 사용될때 Move Method, Extract Method사용하여 데이터 클래스로 옮긴다.

3.21 거부된 유산(Refused Bequest)
만약 서브클래스가 동작은 재사용하지만 수퍼클래스의 인터페이스를 지원하는 것은 원치 않는다면 거부된 유산의 냄새는 심각한 문제.

- 클래스구조를 손보는것보다는 Replace Inheritance with Deligation(401)을 적용

3.22 주석 (Comments)
주석을 써야 할 것 같은 생각이 들면, 먼저 코드를 리팩토링하여 주석이 불필요하도록 하라.

- 코드 블록이 무슨 작업을 하는지 설명하기 위하여 주석이 필요하다면 Extract method를 시도.
- 그래도 주석이 필요하다면 Rename Method 사용
- 만약 시스템의 필요한 상태에 대한 어떤 규칙을 설명할 필요가 있다면 Introduce Assertion(306)을 사용.
Trackbacks 0 : Comments 0

Triple DES Encryption (DESede)

Know/Java 2005.03.25 11:44



DES-EDE 기본기능을 구현해보았다.

JDK1.4이상에서 지원이 되는 패키지를 썼으므로 당근 JDK1.4 밑으로는 안된다.


JDK1.4이하에서 사용하려면 아래를 참조

----------------------------------------------------------------------------------------------------------------------
1. JSDK 1.3이하 버젼 & JCE 1.2.1 글로벌 버전 JCE 1.2.1 버전은 http://java.sun.com 사이트에서 회원가입을 해야지 Down 받을수 있다. JCE는 미국에서 무기로 관주 되기 때문에 글로벌 버전은 미국, 케나다 버전과 다르다.
2. JSDK 1.4에는 Java Cryptography Extension 1.2.1 버전이 포함되어 있다.
3. 다운 받은 JCE의 압축을 풀고 lib방 밑에 있는 모든 jar파일을[JavaHome]\jre\lib\ext방으로 카피한다. 그리고 JCE 알고리즘을 사용하기 위해서 SunJCE 프로바이터를 설치 해야 한다.
설치하기 위해서는 [JavaHome]\jre\lib\security\java.security 파일에 다음을 추가한다.

security.provider.1=sun.security.provider.Sun,
security.provider.2=com.sun.crypto.provider.SunJCE
----------------------------------------------------------------------------------------------------------------------




실행시키면, 아래와 같이 문자열을 입력받는다.
--------------------------------------------------
Input Password:
marine.pe.kr <- 입력문자열
--------------------------------------------------

문자열을 입력후 Enter를 치면
아래와 같이 입력한 문자열, 암호화된 문자열, 복호화된 문자열이 출력이 된다.
--------------------------------------------------
Entered: marine.pe.kr
Encrypt : HC46t4BzpxIuw+HldvVJuA==
Recovered: marine.pe.kr
--------------------------------------------------





소스는 아래와 같다.

import javax.crypto.Cipher;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import java.security.Key;
import java.security.InvalidKeyException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import sun.misc.*;
import java.io.*;


public class LocalEncrypter {

private static String algorithm = "DESede";
private static Key key = null;
private static Cipher cipher = null;
private static DESedeKeySpec desEdeKeySpec = null;

//Encryption에 사용될 키값.
private static String key_value = "AAAAAAAAAAAAAAAAAAAAAAAA";
private static byte[] salt = key_value.getBytes();




/**
* DES-EDE에 쓰일 Key 값과 cipher 객체의 초기화
*
* @throws Exception
*/
private static void setUp() throws Exception {

//DES-EDE ( 「트리플 DES」) 열쇠를 지정합니다.
desEdeKeySpec = new DESedeKeySpec(salt);

//지정된 비밀열쇠 알고리즘의 SecretKeyFactory 오브젝트를 생성합니다
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm);

//지정된 열쇠 데이터로부터 SecretKey 오브젝트를 생성합니다
key = secretKeyFactory.generateSecret(desEdeKeySpec);

cipher = Cipher.getInstance(algorithm);

}


public static void main(String[] args) throws Exception {

setUp();

System.out.println( "Input Password: ");
BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
String str = br.readLine();


if (str.length() < 1) {
System.out.println( "USAGE: java LocalEncrypter " + "[String]");
System.exit(1);
}

String encryptionString = "";
String input = str;

System.out.println("Entered: " + input);
encryptionString = encrypt(input);

System.out.println("Encrypt : "+encryptionString);
System.out.println("Recovered: " + decrypt(encryptionString));


}

/**
* 입력받은 String의 DES-EDE 암호화
* 암호화된 byte[]를 저장하기 위하여 Base64Encoding을 하여 Return한다.
*
* @param input
* @return
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
private static String encrypt(String input) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {


cipher.init(Cipher.ENCRYPT_MODE, key);

byte[] inputBytes = input.getBytes();
byte[] result = cipher.doFinal(inputBytes);

String encode_result = Base64Util.encode(result);

return encode_result;
}


/**
* 입력받은 Base64Encoding 된 String을 다시 Decoding하여
* DES-EDE 복호화된 String을 반환한다.
*
* @param encryptionString
* @return
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
private static String decrypt(String encryptionString) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptionBytes = Base64Util.decode(encryptionString);
byte[] recoveredBytes = cipher.doFinal(encryptionBytes);
String recovered = new String(recoveredBytes);
return recovered;
}
}



class Base64Util {

public Base64Util() {}

/**
* Base64Encoding을 수행한다. binany in ascii out
*
* @param encodeBytes encoding할 byte array
* @return encoding 된 String
*/
public static String encode(byte[] encodeBytes) {

BASE64Encoder base64Encoder = new BASE64Encoder();
ByteArrayInputStream bin = new ByteArrayInputStream(encodeBytes);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = null;

try{
base64Encoder.encodeBuffer(bin, bout);
} catch(Exception e) {
System.out.println("Exception");
e.printStackTrace();
}
buf = bout.toByteArray();
return new String(buf).trim();
}

/**
* Base64Decoding 수행한다. binany out ascii in
*
* @param strDecode decoding할 String
* @return decoding 된 byte array
*/
public static byte[] decode(String strDecode) {

BASE64Decoder base64Decoder = new BASE64Decoder();
ByteArrayInputStream bin = new ByteArrayInputStream(strDecode.getBytes());
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = null;

try {
base64Decoder.decodeBuffer(bin, bout);
} catch(Exception e) {
System.out.println("Exception");
e.printStackTrace();
}

buf = bout.toByteArray();

return buf;

}
}




Trackbacks 0 : Comments 2

ExclusiveOR 연산자의 번외게임

Know/Java 2005.03.17 11:42


a와 b 서로 바꾸기

(1) a ^= b;
(2) b ^= a;
(3) a ^= b;

: 정수에서만 가능하다.

4비트만가지고 테스트해보면..


a : 1101
b : 1010
a ^= b : 0111

a : 0111
b : 1010
b ^= a : 1101 (처음의 a값과 같다)


a : 0111
b : 1101
a ^= b : 1010 (처음의 b값)


즉 이것은.. 아래와 같다..(라고 생각한다..)
맞는지는 모르겟따...

(1) a = a ^ b -> t1
(2) b = b ^ t1 -> t2 = b ^ a ^ b = b ^ b ^ a = 0000 ^ a = a
(3) x = a ^ b = t1 ^ t2 = t1 ^ t1 ^ b = 0000 ^ b = b


Trackbacks 0 : Comments 0

디자인패턴 예제 및 데모사이트

Know/Java 2005.02.22 15:07
[HTML]
 

http://home.earthlink.net/~huston2/dp/patterns.html


 



GoF Design Patterns











Pattern-Oriented Software Architecture



Core J2EE Patterns



Design Patterns Java Workbook



Design Patterns Explained
A New Perspective on Object-Oriented Design



Java Design Patterns: A Tutorial



Patterns in Java, Volume 1



Applied Java Patterns


     

Pattern Languages of Program Design 1



Pattern Languages of Program Design 2



Pattern Languages of Program Design 3



Pattern Languages of Program Design 4



Pattern Languages of Program Design 5


San Francisco Design Patterns



Analysis Patterns



Concurrent, Parallel, and Distributed systems




Other patterns

















       







    See also:









            02 Jun 01 --- 54150
    07 Jul 01 --- 63725
    04 Aug 01 --- 70560
    01 Sep 01 --- 77815
    06 Oct 01 --- 88175
    03 Nov 01 --- 102050
    01 Dec 01 --- 118150
    05 Jan 02 --- 133220
    02 Feb 02 --- 149190


    [/HTML]
    Trackbacks 0 : Comments 0