몇일전 테스트 주도 개발이라는 책을 다 읽었다.

1장부터 16장까지는 은행에서 사용하는 돈(통화)에 관련된 작업을
어떻게 TDD로 만들어 가는지를 실제 코드를 보여주면서 보여주고 있다.

17장 부터는 TDD 자체에 대한 이야기로 되어 있다.

이전에 적었던 부분을 빼고, 책을 읽으면서 줄쳐 놓은 부분을 옮겨 본다.
  • 이러한 접근 방식은 "깔끔한 코드" 부분을 먼저 해결한 후에, "작동하는" 부분을 해결해 가면서 배운것들을 설계에 반영하느라 허둥거리는 아키텍쳐 주도개발과는 정반대다.
  • 시스템이 크다면, 당신이 늘 건드리는 부분들은 절대적으로 견고해야 한다. 그래야 나날이 수정할때 안심할 수 있기 때문이다.
  • "다음에 할일은 무엇인가?"에 관련된 또 다른 질문은 "어떤 테스트들이 추가로 더 필요할까?"다.
  • 그리고 지금 이 글을 쓰는 동안 "Expression(수식)" 메타포를 생각했는데, 설계가 기존과는 완전히 다른 방향으로 흘렀다. .... Expression 메타포는 중복되는 통화를 합치는 세세한 일단의 문제에서 날 해방시켰다. 나는 수식의 성능에 대해 우려 했지만 최적화를 시작하지 전에 어느정도의 사용 통계를 볼때까지 기다리는것에 만족한다.
  • 아주 확신에 찬 사람과 정말 얼렁뚱땅 넘어가는 사람들 제외한다면, 아무리 작은 변화라도 테스트 하지 않고 릴리즈 하지 않는다. ( 만든것만 테스트하는 임시 테스트 코드 )
  • 당신이 변화를 테스트 할 수 있다고 해도, 실제로 변화를 테스트하는 것은 "테스트를 가지고 있다."는 것과 똑같지 않다. ( 계속 유지되는 자동화된 테스트 )
  • 이 경우엔 '테스트'를  '자동화된 테스트'로 치환하면 된다. "내가 이걸 고치면 뭔가 다른 부분을 망가트리지 않았을까?" .... 자동화된 테스트가 있다면 스트레스를 받기 시작할때 테스트를 실행할것이다. .... 테스트를 실행하면 즉시 좋은 느낌을 받게 되고 그러면 작업중에 에허를 낼 일도 줄게 되며, 스트레스도 적어진다.
  • 테스트를 실행하는 것이 서로 어떤 식으로 영향을 미쳐야 좋은가? 아무 영향이 없어야 한다. .... 앞 부분에서 실행된 테스트가 실패한 후 그 영향으로 다음 테스트부터는 시스템이 예측 불가능한 상태에 놓이는 경우가 허다하다.
  • 테스트는 전체 애플리케이션을 대상으로 하는것 보다 좀 더 작은 스케일로 하는것이 좋다. 어쨌건 주된 교훈은 각각의 테세트는 다른 테스트와 완전히 독립적이어야 한다는것이다. 즉, 문제가 하나면 테스트도 하나만 실패해야하고, 문제가 둘이면 테스트도 두개만 실패해야 한다.
  • 테스트를 언제 작성하는것이 좋을까? 테스트 대상이 되는 코드를 작성하기 직전에 작성하는것이 좋다. 코드를 작성한 후에는 테스트를 만들지 않을것이다.
  • 시스템을 배갈할 때 무슨 일 부터 하는가? 완료된 시스팀이 어떨 거라고 알려주는 이야기 부터 작성한다. ( USER STORY를 말한다. 기존의 방법론의 요구사항 문서와 비슷한데 훨씬 짧고 비격식적이다. )
  • 테스트 할 때 어떤 데이터를 사용해야 하는가? 테스트를 읽을 때 쉽고 따라가기 좋을 만한 데이터를 사용하라. .... 1과 2 사이에 어떠한 개념적 차이도 없다면 1을 사용하라.
  • 시스템이 여러 입력을 다루어야 한다면 테스트 역시 여러 입력을 반영해야 한다.
  • 길을 잃은 느낌이 들땐 어떻게 해야 할까? 코드를 다 지워버리고 처음부터 다시 해 보자.

더 많은 내용에 줄을 그어 놓았지만, 그만 옮겨적어야겠다.


아직은 "테스트 주도 개발"에서 "테스트"에 대해서만 와 닿았고, "주도"에 대해서는 잘 모르겠다.
다음에 정리를 다시하면서 "주도" 부분도 다시 한번 알아 봐야 겠다.
  1. leonid 2008.03.19 19:01 신고

    안녕하세요. 이번에 TDD에 대해서 공부를 시작해보았습니다. 아직까지는 온라인에 있는 글과 동영상들만 봤지만 역시 책을 사서 읽어야 할 것 같네요 :)

    • Chan 2008.03.19 22:34 신고

      어이쿠~ 답변 감사합니다. ^_^

      제가 내린 결론은
      TDD는 아직잘 모르겠지만, 확실히 느낀건 TestCase는 많으면 많을 수록 좋다 였습니다. ^_^

내용에 대해서 책임지지 않습니다. ㅋㅋ


CP949는 MS-Windows에서 정한 한글을 나타내는 문자set 이다.
index가 주루룩 나열되어 있고, 거기다가 한글이 하나씩 들어 있는것이다.


원래 글자를 나타낼때에는 index는 총 2byte로 구성되어져 있어도 문제가 없을것 같았나 보다.
하지만 2byte만을 가지고는 세계의 모든 글자를 표시 할 수 없기 때문에
CP( CodePage ) 라는 구분을 해 주어, CP에 따라서 다른 글자가 보이도록 한다는거다.


CodePage는 국가 마다 다르다.
하나의 index가 있을때 CodePage를 바꾸어 버리면 서로 다른 모양의 글자가 나온다는것이다.

9A98이라는 index가 있을때
이것을 CP49 (Korean) 로 인식하고 보면 "슆"이 나온다. 하지만
이것을 CP932 (Japanese Shift-JIS) 로 인식하고 보면 "口"가 나온다.


CP949에 있는 글자들은 Unicode 처럼 조합할 수 있도록 제공해 주지 않는다.
즉, 하나의 index는 완전한 하나의 글자를 이루게 되어 있다.
( 반면 유니코드는 초성 "ㄱ", 중성 "ㅏ" 처럼 글자를 조합해서 나타낼 수 있게도 제공해 준다. )


CP949는 "EUC-KR http://en.wikipedia.org/wiki/EUC-KR#EUC-KR "의 확장판이고
EUC-KR은 "완성형 한글(KSC5601) http://www.itscj.ipsj.or.jp/ISO-IR/149.pdf + 기타등등문자 "의 확장판이다.
"완성형한글(KSC5601)" 역시 index가 정해져 있고 완전한 하나의 글자가 배치되어 있지만,
"똠" 과 같이 표현하지 못하는 문자들이 매우 많았다.
그래서 CP949는 "완성형한글(KSC5601)"에 더 많은 글자를 추가하게 된 것이다.



CP949에서는 Unicode에서 제공해주는 글자보다 덜 제공해 주므로,
CP949에서 나타나지는 글자들은 모두 Unicode로 변환이 가능하나,
반대로 Unicode에서 제공되는 글자를 CP949에서는 표현하지 못할 수 있다.



여담.
사실 CodePage라는 것은 IBM에서 먼저 만들었다.
그리고 시간이 지난뒤 MS에서 한글을 표현하기 위해서 CP949를 정했는데,
Java에서는 이를 MS에서 만든것이라서 MS949로 말하게 된다.

내용에 대해서 책임지지 않습니다. ㅋㅋ


Unicode provides a unique number for every character,
no matter what the platform,
no matter what the program,
no matter what the language.

http://www.unicode.org/standard/WhatIsUnicode.html


위에 설명되어 있는대로다.




유니코드란?

unicode는 모든 문자에 index를 줘 놓은 것이다. 더 이상도 아니고, 더 이하도 아니다.
이 index를 code point라고 부르는데, 그냥 index라고 칭하도록 하자.

'A'라는 글자는 0x0041 이라는 index를 가진다.
'a'라는 글자는 0x0061 이라는 index를 가진다.
'가'라는 글자는 0xac00 이라는 index를 가진다.
( 더 많은 글자와 index를 보려면 http://www.unicode.org/charts/ 를 참고하자 )




표현방법

저렇게 정해져 있는 index를 표시하는 방법에는 UTF와 UCS두가지 종류가 있다.
( UTF - Unicode Transformation Format ,    UCS - Universal Character Set )

UCS
UCS는 몇바이트로 index를 표현할 수 있느냐를 나타낸다.
즉 UCS-2는 2byte로 index를 나타낼꺼고 UCS-4는 4byte를 이용해서 index를 나타낼거라는거다.

UTF
UTF는 몇 비트단위로사용해서 index를 나타낼것인가를 말한다.
UTF-8은 8bit씩 늘려가며 index를 나타낼꺼라는거고,
UTF-16은 16bit씩 index를 나타낼꺼고, UTF-32는 32bit씩 index를 나타낼꺼라는거다.
( 실상 UTF-16과 UCS-2는 같다고 볼 수 있다. 마찬가지로 UTF-32와 UCS-4도 마찬가지다.  하지만 unicode 3.1에 오면서 달라 졌다. )





UTF-16

원래 처음에 unicode의 index는 2byte로 나타낼 수 있었다.
그랬는데, unicode 가 버젼업되어 4.0이 나왔을때에는 0x10FFFF 까지의 index가 생겼다.

처음에는 UTF-16으로 모든 문자를 나타낼 수 있었으나,
( 2byte로 표현할 수 있는 index를 가진 문자 목록을 BMP Basic Multilingual Plane 라고 부른다. )
유니코드 4.0이 나오면서, 2byte로는 0x10FFFF 같은 값을 가리킬 수 없게 되었다.

그래서 UTF-16으로는 BMP에 있는 문자들은 2byte로 처리하고, 
BMP보다 더 높은 index를 가지는 놈들은 4byte로 처리 한다.
문자 index 0x0000 부터 0xFFFF 까지는 2byte로 처리 하고
문자 index 0x10000 부터 0x1FFFF 까지는 4byte로 처리 된다.





UTF-32

UTF-32는 기본적으로 4byte를 사용하기 때문에, 위와 같은 짓을 하지 않아도 된다.





UTF-8

영어권에 있는 사람들은 UTF-16을 쓰면 손해다.
모든 영어는 1byte만 있으면 256개를 표현할 수 있으므로, 모든 문자를 넣을 수 있기 때문이다.

그래서 나온게 UTF-8이다.
영어권은 1byte로 표현하고, 그것보다 높은 index를 가지는것은 2byte 혹은 3byte 혹은 4byte ..
요렇게 늘려 가면서 쓰도록 되어 있다.





서로간의 변환

UTF-8, UTF-16, UTF-32, UCS-2, UCS-4 는
모두 unicode의 문자 index를 나타내기 위한 방법이기 때문에,
서로간의 변환은 당연히 잘 된다. ( UCS-2는 한계를 가지고 있다. )




글자처리

우리가 글자 "가"를 쓴다고 해 보자.  글자 "가"는 1글자이다.
그러므로 "가"를 나타내는 index가 있다. 물론 "나"를 나타내는 index도 있다.

한글로 표현할 수 있는 글자는 매우 많다.
그 많은 글자 모두에게 index를 줄 수가 없다.

현재 사용하고 있는 모든 글자에 index를 준다고 해도,
시간이 지나서 새로운 글자가 추가 되어 index가 모자르게 된다면 어떻게 할것인가?

그래서 유니코드는 완전한 글자를 제공해 주기도 하지만,
글자를 조립할 수 있도록 조립가능한 글자를 제공해 준다.

다시 "가"를 쓴다고 해 보다.
"가"라는 글자는 1개이지만, 실제로는 초성 "ㄱ"과 중성"ㅏ" 가 합쳐져서 만들어진 글자이다.

그러므로 "가"를 표현하는 방법은 완성된 글자 "가"0xAC00가 될 수도 있고,
초성"ㄱ"과 중성"ㅏ"를 조립한 "가"0x1100,0x1161 로 나타낼 수도 있다.
( 초성 "ㄱ"은 0x1100 - HANGUL CHOSEONG KIYEOK )
( 중성 "ㅏ"는 0x1161 - HANGUL JUNGSEON A )

이를 조합할 수 있게 해 주는 index는 1100 부터 있다.
( Hangul Jamo - Korean combining alphabet - http://www.unicode.org/charts/PDF/U1100.pdf )


이는 비단 한글뿐만 아니라,
일본어 역시 완성된 글자가 있기도 하고, 조합할 수 있게도 되어 있다.

영어 역시 그렇다. 영어에서 무슨 글자를 조합하냐 라고 말하겠지만, 
이력서를 나타내는 Resume 의 경우에는 e 와 ' 의 조합으로 이루어 질 수도 있다.


  1. 지민아빠 2008.02.28 09:47 신고

    맞는 말이구만 왜 내용에 책임을 안져요! ㅋㅋ

    • Chan 2008.02.28 09:59 신고

      유니코드랑 UTF관련은 머릿속에 정리 된것을 열심히 읽었는데,
      그 뒤에 글인 CP949 관련 글은 아직 머릿속에 확립이 안되어서.. ㅋㅋㅋ.
      CP949에 "책임질수 없음" 넣어 놓고, 유니코드 글에도 걍 넣었어요.. ㅋㅋ

    • 지민아빠 2008.03.11 12:05 신고

      아참. 펄에서 사용되는 이름이랑 자바에서 사용되는 이름이랑 약간 달라요. CP949, MS949, EUC-KR 의미가 약간 다름.

  2. sanaigon 2009.01.09 09:49 신고

    퍼가용ㅋ

  3. finebe 2009.04.24 14:11 신고

    퍼가요~^^; 공개글로 원치않으시면 말씀해주세요

    • Chan 2009.04.24 18:53 신고

      출처만 밝힌다면 상관없습니다. ㅋㅋ

  4. 2009.07.24 15:40

    비밀댓글입니다

  5. 킴킴 2014.01.20 17:13 신고

    퍼갈게요 ㅎㅎ

  6. carrot1st 2015.07.27 10:39 신고

    퍼갑니다 잘보겠습니다~

  7. 어벙이 2015.08.17 12:41 신고

    퍼갈게요..~~ ^^ 좋은 내용 잘 보고 갑니다..

  8. 라면사리누구야!! 2016.07.28 09:33 신고

    잘보고 갑니다~
    퍼갈게요~~ ^^;

  9. 뚝딱콩망치 2017.03.26 16:51 신고

    내용 참고해서 글써두 될까요 ㅠㅠ 출처는 꼭 남기겠습니다!

웹서비스

기본 개념

  • 네트워크 상의 접근 가능한 소프트웨어의 기능단위
  • 플랫폼, 프로그래밍 언어, 컴포넌트 모델에 독립적인 기술
  • SOA ( Service-Oriented Architecture ) 기반
    • SOA에서는 소프트웨어의 기능이 서비스의 집합으로 분류된다. 
  • SOA의 메카니즘 
    • Service provider : 어떤 동작을 기술(description) 하고 이를 Registry 에 등록( publish ) 한다.
    • Service registry : provider를 등록하고, consumer에게 provider의 정보를 제공한다.
    • Service comsumer : registry에서 provider정보를 찾아 ( find ) , provider를 연결(bind) 한다.

Web Service Stack

  •  Web service stack - 특정 플랫폼이나 제조사에 의존적이지 않음 ( platform- and vender-netural )
  • Stack 구조
    • Service publication Discovery : UDDI
    • Service Description : WSDL
    • XML Messaging : SOAP
    • Transport network : http, smtp, ftp, https over tcp/ip
  • Stack에 대한 설명 
    • UDDI : Universal Description and Discovery Interface : 웹 서비스를 공개하고 탐색하는 표준 메카니즘
    • WSDL : Web Service Descritpion Language : 웹 서비스를 기술하는 표준 메카니즘
    • SOAP : Simple Object Access Protocol : 웹 서비스를 호출하는 표준 메카니즘


Web Service Call ( XML RPC , SOAP )

XML-RPC

  1. RPC - Remote Procedure Call - 원격지의 메소드를 호출 한다.
  2. XML RPC - XML을 이용해서 원격지의 메소드를 호출한다.
  3.  특징
    1. XML 사용
    2. Http를 사용

SOAP

  • 기본적으로는 RPC 와 같음
  • Http 이외의 다른 전송 프로토콜 지원
  • 복잡한 Data Type 지원
  • 메세지의 처리 방법을 기술
  • 응답은 HTTP 방식을 이용
    • 200일 경우에는 Content-Type 이 XML 형태이나
    • 500일 경우에는 text/html 형태로 들어올 수 있다.
  • SOAP의 호출 동작 
    •  SOAP Client  <->  Web Server <-> SOAP Server <-> Web Service


WSDL

  • WSDL : Web Service Descritpion Language
  • 웹 서비스를 기술 ( 표현, 설명 ) 한다. 
    • 웹서비스가 하는 일
    • 호출 가능한 메소드
    • 전달해야 하는 파라미터
    • 파라미터의 Type
    • 사용하는 바인딩 프로토콜
  • 즉, WSDL 문서가 있다면 웹서비스의 기능을 호출할 수 있다.
  • WSDL 문서의 구조 
    • Implementation Specific 
      • Service : 특정 바인딩을 어떠한 Port ( 주소 ) 로 연결할것인가?
      • Port : 웹 서비스로 접근 가능한 주소 ( End-point? )
    • Abstract Definition 
      • Binding : 사용될 전송 프로토콜 ( Port type에서 사용할 프로토콜 - SOAP ) , 요청방식 ( rpc , document )
      • Port Type : 공개된 ( 호출할 수 있는 ) 메소드 집합.
      • Message : 메소드에서 사용할 파라미터
      • Types : Data Type ( integer , float, String ... )


UDDI

  •  UDDI : Universal Description and Discovery Interface 
  • 웹 서비스 제공자와 클라이언트들이 함께 사용할 수 있는 중앙 저장소 제공  
    • 클라이언트가 필요한 웹 서비스를 찾아 내 줄 수 있게 해야 함.
    • 웹서비스 제공자가 서비스를 공개할 방법이 필요 

이 글은 스프링노트에서 작성되었습니다.

java.lang.Character

  • JDK 1.4 : Character information is based on the Unicode Standard, version 3.0.
  • JDK 5.0 : Character information is based on the Unicode Standard, version 4.0. 

Java에서 char 은 16bit 고정 길이를 가지도록 되어 있다.

하지만 unicode 의 값 중에는 U+0000 ~ U+FFFF 를 넘어서는 값도 있기 때문에 이에 대한 처리가 문제가 된다.

1

JDK 1.4의 api document 중에서 Character 에 관한 문서를 확인해 보고

( http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Character.html  )

JDK 5.0의 api document 중에서 Character 에 관한 문서를 확인해 보면 서로 틀린것을 알 수 있다.

( http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Character.html )



JDK 5.0에서는 Unicode Character Representations 항목이 추가 된 것을 알 수 있다.

  1. 유니코드의 표준이 16비트 이상도 표현하도록 변경되었다.
  2. 현재는 U+0000 부터 U+10FFFF 까지 정의되어 있다.
  3. U+0000 부터 U+FFFF 까지는 Basic Multiingual Plane(BMP)를 참고한다.
  4. U+FFFF 보다 큰것들은 supplementary cahcaters가 있어야 한다.
  5. Java 2 에서는 UTF-16을 사용한다.
  6. supplementary chacters는 char 두개로 이루어 진다.
  7. 첫번째 char은 U+D800 ~ UDBFF를 , 두번째 char은 U+DC00~U+DFFF 까지의 범위를 가진다.
  8. char은 BMP나 UTF-16인코딩만을 가리킬 수 있다. 
    1. 그러므로 char를 사용하는 메소드를 쓸때에는, supplementary char에 대해서는 메소드를 호출 하면 잘못된 값이 나올 수 있다.
    2. 예)  Character.isLetter('\uD840') 를 사용하면 false로 return 된다.
  9. int는 supplementary code point 도 가리킬 수 있다. 
    1. 하위 21bit 는 Unicode값을 가리키고, 상위 11비트는 항상 0로 채워져 있다.
    2. 21bit인 이유는 현재 Unicode 가  U+10FFFF 까지 가리킬 수 있기 때문이다. ( U+10FFFF를 2진수로 변경하면 총 21bit 만을 차지하게 된다. )
    3.  그러므로 int를 사용하는 char 관련 메소드를 사용할때에는 supplementary char에 대해서는 제대로 된 응답이 반환된다.
      1. 예)  Character.isLetter(0x2F81A) 를 사용하면 true로 return 된다.

FileSystem에서 File의 lastModifiedTime 에 관하여...

자바에서는 화일을 다룰 수 있는 File 라는 클래스를 제공한다. ( java.io.File )

이 클래스에서는 화일의 정보를 get 하거나 set 할 수 있도록 제공해 주는데.

그중에서 지금 확인해 볼 것은 lastModified Time 에 관한 내용이다.

 

 자바의 File 에서는 해당 화일의 마지막 변경 시간을 얻어 올 수도 있으며, 지정해 줄 수 있는 메소드를 제공해 준다.

 java.setlastmodified.gif

 

 그리고 위의 코드는 OS에 맞는 FileSystem의 setLastModified를 호출하게 되며, Windows에서는 일반적으로 Win32FileSystem.setLastModified를 호출하게 된다.

 Win32FileSystem.setlastmodi.gif

 

이 놈은  결국은 native 코드를 사용호출 하게 된다. 이에 대한 native 코드는 아래와 같이 이루어져 있다.

 native.setlastmodified.gif

 

그래서 결국은 windows api에서 제공하는 SetFileTime 이라는 함수를 호출하게 되어 있다.

이것에 대한 msdn 내용을 살펴 보면 아래와 같이 되어 있다.

 msdn.setFileTime.gif

 

파일 시스템 마다 다르게 동작할 수 있단다.

FAT 에서는 만든 시간은 10밀리초 까지 해상도를 제공하고,

수정한 시간은 2초의 해상도를 제공하고,

접근한 날자는 하루 단위로 제공한다고 한다.

 

아래는 FAT 에 관한 표 중 일부분이다.

 FAT32.gif

 

그렇기 때문에 해당 File 객체를 만들어서 setLastModified()를 수행 한다고 해서,

모든 시스템에서 정해진 시간으로 저장 되는것이 아닐 수도 있다.

 

이 글은 스프링노트에서 작성되었습니다.

  1. 박서은 2007.07.07 00:18 신고

    그랬군요. 그런거였군요.

    • Chan 2007.07.08 16:33 신고

      ㅎㅎ 네~ ㅎㅎ ;;;;; 그런거였답니다. ㅎㅎ

다음의 내용을 가지고 테스트를 해 보자.

  1. public class A {
  2.     public String getString() {
  3.         return "call A.getString() Method";
  4.     }
  5. }

  1. public class B extends A {
  2.     public String getString() {
  3.         return "call B.getString() Method";
  4.     }
  5. }

  1. public class C {
  2.     public static void main(String... strs) {
  3.         B b = new B();
  4.         System.out.println ( b.getString() ) ;
  5.     }
  6. }


이렇게 만든 상태에서 모두 컴파일을 하고, C 화일을 실행 시키면  "call B.getString() Method" 가 나온다.


 그 후에 B.java 화일은 그대로 두고 A 클래스에 메소드를 하나 추가 하여 다시 작성하자.

  1. public class A {
  2.     public String getString() {
  3.         return "call A.getString() Method";
  4.     }
  5.     // 아래의 메소드를 추가 한다.
  6.     public String getString2() {

            return "call A.getString2() Method";

        }  

  7. }


그리고 C 화일을 수행 부분인 main 메소드를 다음과 같이 변경 시킨다.

  1. public class C {
  2.     public static void main(String... strs) {
  3.         B b = new B();
  4.         System.out.println( b.getString2() );
  5.     }
  6. }


그리고 A 와 C 만을 컴파일 하고 ( B 는 꼭 그대로 둔다. ), C 클래스를 수행하면 어떤 결과가 날까?

( B class 는 아직까지 컴파일 되지 않았고, B의 부모인 A 만 새로운 메소드를 추가 하고 새롭게 컴파일 되었다. )


결과는 다음과 같다. call A.getString2() Method


B클래스는 전혀 새롭게 컴파일 하지 않았는데, 어떻게 A클래스에 있는 메소드를 알아서 호출해 주는것일까?


물론 저렇게 만들지 않으면 정말로 불편하게 된다. 상위클래스에서 자식으로 넘겨 줄 수 있는 제한자를 가지고 있는 것들을 추가 할때마다 자식클래스들도 모두 다시 컴파일 해야 한다면 얼마나 큰 낭비인가? 하지만 그렇게 상식적으로 생각하지 말자. 잘못된 상식은 수두룩하니깐.


그렇게 해서 내가 테스트 해 본 방법은 다음과 같다.


  • 직접 수행 해 보기. - 이미 위의 방법을 통해서 확인해 보았다.
  • 원래의 B.class 화일과 새롭게 컴파일 된 B.class 화일을 바이너리 비교
    • 비교해 보았으나 두개의 바이너리 내용이 똑 같음.


이렇게 한다고 해도, 이것은 테스트 밖에 되지 않는다. 우연찮게 두개의 메소드 테이블이 같아서 똑같은 바이너라가 나올 수도 있다고 생각 할 수도 있다. 그러므로 좀 더 정확한 답을 찾아 보도록 하자.


.class 화일 Format 를 직접 찾아 보자.

 The class File Format : http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html


 class 화일은 다음과 같은 구조로 이루어져 있다.

    ClassFile {
     u4 magic;
     u2 minor_version;
     u2 major_version;
     u2 constant_pool_count;
     cp_info constant_pool[constant_pool_count-1];
     u2 access_flags;
     u2 this_class;
     u2 super_class;
     u2 interfaces_count;
     u2 interfaces[interfaces_count];
     u2 fields_count;
     field_info fields[fields_count];
    u2 methods_count;
   
method_info methods[methods_count];
     u2 attributes_count;
     attribute_info attributes[attributes_count];
    }


그 중에서 우리가 관심이 있는 부분은 메소드이고, 그것과 관련 있는 부분은 아래의 두개이다.

u2 method_count;

method_info methods[methods_count];


우선 method_count 에 대한 설명을 보자.

methods_count
The value of the methods_count item gives the number of method_info structures in the methods table.

methods_count 는 methods 테이블에 있는 method_info 갯수란다. - 별로 중요하지 않군. 흠.


methods[]
Each value in the methods table must be a method_info (§4.6) structure giving a complete description of a method in this class or interface. If the method is not native or abstract, the Java virtual machine instructions implementing the method are also supplied.

The method_info structures represent all methods declared by this class or interface type, including instance methods, class (static) methods, instance initialization methods (§3.9), and any class or interface initialization method (§3.9). The methods table does not include items representing methods that are inherited from superclasses or superinterfaces.


우리가 원하는 내용을 찾아 버렸다. .class 화일에서는 methods[] 이 있고, 이 안에는 method_info가 들어 있는데, 이 method_info에는, 현재 클래스나 인터페이스에서 선언된 모든 메소드들이 들어 있다고 한다. 즉, 부모에 선언되어 있거나 하는 메소드들은 현재의 .class 화일에서 가지고 있지 않다는 말이 된다.


== 추가한 부분 ==
B.class 화일에서는 자기에게서 선언되지 않은, getString2() 라는 메소드 정보를 class 화일 안에 가지고 있지 않다.
( 왜냐하면 위에서 말했다 시피 .class 화일 안에는 자신에게서 선언된 메소드 정보만을 가지고 있어야 하기 때문이다. )

C 클래스에서 B 객체의 getString2() 메소드를 호출하게 되면,
B 객체에서는 자신의 메소드테이블을 검색해서 getString2() 가 있는지 확인하게 된다.

하지만 B 객체에서는 getString2() 이라는 메소드가 존재하지 않기 때문에,
부모인 A 객체에게 getString2() 을 요청하게 될 것이다.

자식 클래스인 B에서 선언되지 않았지만, 부모 클래스인 A 에서 메소드를 추가하여도,
C 클래스에서 A에서만 추가된 메소드를 B 에게 호출 하여도 문제가 없게 된다.

즉, B 클래스는 전혀 수정없이 확장성 있게 코드를 작성할 수 있게 된다.



그렇기 때문에,  위의 예에서 B.java 화일은 새롭게 컴파일 하지 않아도 정상적으로 동작하는게 옳다는 결론이 나온다.

일을 하다가 발견한 문제이다.

특정한 동작을 하는것이 있었는데..
이 놈이 유독스럽게 Mac 에서만 무진장 오래 걸린다는 문제 였다.

윈도우에서는 단 1초도 안되어서 끝나는 작업이었지만,
Mac 에서만 22초가 소요되고 있었다.

문제가 어디서 발생하는지는 찾았는데,
왜 Mac 에서만 유독 느린지 이유를 밝혀 내지는 못했다.


여하튼, 문제는 ArrayList의 removeAll 메소드에 있었다.
removeAll 메소드에 대해서 좀 살펴 보자.



ArrayList 의 상속 관계를 보면 아래와 같다.

사용자 삽입 이미지



여기서 removeAll 이라는 메소드는 Collection 에서 interface를 제공하고 있으며,
실제 그 구현은 AbstractCollection 에 아래와 같이 되어 있다.

사용자 삽입 이미지


보다시피 현재의 컬렉션에서 param 으로 들어온 컬렉션에 있는 item들을 모두 지우기 위해서
iterator 를 구하고, next를 하나꺼내와서
param으로 들어온 c 에 이미 들어 있는지 확인을 하고,
들어 있다면 지워주는 형태로 되어 있다.


여기서 다시 "c에 이미 들어 있는지 확인하는 c.contains( .. ) 의 코드는
indexOf(Object)가 0 보다 큰지 확인하게 된다.
이때 ArrayList의 indexOf(Object) 는 아래와 같이 구현되어 있다.

사용자 삽입 이미지

열심히 이미 가지고 있는 배열에서 루프를 돌면서 같은 놈이 있는지 확인을 한다.
물론 이 놈들이 Object 형태를 비교해야 하기 때문에 equals() 메소드를 사용하여서
비교를 한다.


그렇다면 내가 원래 있던 20개의 Collection 에서
10개의 데이터를 removeAll 한다고 해 보자.

1. AbstractCollection의 removeAll을 호출한다.
2. 20개에 대해서 iterator 를 구해서 20번 루프를 돌아야 한다.
2.1. 현재 선택되어 있는 아이템이 10개에 포함되어 있는지 확인해야 한다.
2.2. 이것을 확인하기 위해서는 indexOf() 를 수행해야 한다.
2.3. indexOf 내부에서는 10개에 대해서 루프를 돌아야 한다.
2.4. Object 비교이기 때문에 equals 를 수행하기 위해서 내부적인 연산을 해야 한다.


중요한것은 전체 배열에 대해서 이중 for loop 를 돌아서 처리 한다는것이다.




그렇다면 이 loop를 좀 더 덜 돌릴 수 있는 방향이 없을까?
항상 cpu 와 memory 사이에는 trade-off 가 있다.
물론 지금 이야기 하고자 하는 방법은 memory 측면에서는 손해 볼 수 있다.




내가 그래서 선택한 방법은 HashMap을 사용하는 방법이다.
안타깝게도 HashMap 에서는 removeAll 같은 메소드를 제공해주지 않기 때문에.
직접 for 루프를 돌아 가면서 데이터를 지워주어야 하는 귀찮은 점은 있다.

HashMap의 remove(Object) 코드는 아래와 같이 구성되어 있다.

사용자 삽입 이미지


remove(Object)를 수행하게 되면 removeEntryForKey(Object)를 수행하게 된다.

이때 removeEntryForKey(Object) 에서는
param으로 들어온 key에 대해서 hashCode() 를 구하고,
전체 배열 중에서 구해진 hash와 같은 값을 가지는 위치만을 비교 하게 된다.


그렇다면 HaspMap 에서 데이터를 지우는 순서를 보자.

1. removeAll이 없기 때문에 어쩔 수 없이 외부에서 for를 돌려서 remove 를 여러번 시켜 준다.
2. hashCode() 값을 구하고, hash 같은 배열 부분만을 검색한다.
3. equals()를 호출하여 비교하면서 처리 한다.

여기서도 사용자가 for를 외부에서 돌려 주고,
내부에서도 while loop를 도는 2중 loop 구조이기는 하지만,
hash 가 같은것만 돈다는것이 핵심이다.


이 결과는 equals()과 hashCode()를 구현하는 방식에 따라서 다른 결과를 도출할 수도 있다.



약 400개의 데이터에 대한 작업이었는데
ArrayList.removeAll()에서는 22 초가 걸렸었고,
HashMap.remove() + for 로 수정한 뒤에는 0.5 초대의 시간이 걸렸다.


이 결과는 Mac 에서만 유용할 수도 있다. 실제 윈도우에서도 약간 더 빨리 동작하기는 했다.
시간 날때  똑같은 코드가 왜 Mac 에서만 이렇게 느리게 동작하는가에 대한 조사를 해 보아야 겠다.


대학교 자료구조 수업 시간에 자주 등장하는 내용이지만,
실제로 이렇게 -_- 겁나게 차이를 느껴 보기는 처음이었다. -_-;


대학교때 공부 잘했던 사람들이 나와서도 잘 사는 이유가 있나 보다. ㅋㅋ
  1. 유야 2007.06.10 00:30 신고

    역시 난 제대로 디버깅한거였어!!!!
    Font의 canDiaplay도 무쟈게 느리더만 -_-

    • Chan 2007.06.10 01:18 신고

      대단해. ㅋㅋ.
      근데 -_- 왜 느릴까 -_-? 쩝쩝쩝..

  2. 옷장수 2007.06.10 12:55 신고

    Font의 canDisplay()가 느린이유는 char를 glyph으로 변환하는 과정을 거치기 때문이 아닐런지..

    • Chan 2007.06.10 21:27 신고

      ㅇㅎㅎ ;; 뭔 말인지 몰겠따.. ㅎㅎ ;;;

  3. 유겸애비 2007.06.11 11:40 신고

    이거 맥하고 상관이 없는글이자나욧!!
    글구 canDisplay()는 글립으로 만들지 않고 그냥 폰트에 어떤 문자가 정의되어있는지를 보는것일듯 싶네요

    • Chan 2007.06.11 18:17 신고

      수정했습니다. ㅋㅋ;;
      그리고 ;; 또 어려운 말들을 하시는군요.;; ㅎㅎ ;;

    • 옷장수 2007.06.12 09:39 신고

      오랜만에 보는거라서 소스를 봤었는데 canDisplay가 호출되면 CharToGlyphMapper라는 놈을 찾아서 여기에서 glyph이 보여질 수 있는지를 체크하던데요.

  4. 유겸애비 2007.06.12 19:16 신고

    Re:옷장수// Font란 char to glyph의 map이라고 추상화 시킬수 있는데 key가 정의되어있난 보는거지 char에서 glyph으로 변환을 시키는건 아닐껍니다.

    • Chan 2007.06.12 23:36 신고

      심도 있는 댓글이군요~ ㅋ
      찾아봐야 겠당~ ㅋㅋ

    • 옷장수 2007.06.14 14:03 신고

      음.. char를 glyph으로 변환한다는게.. char 값을 glyph의 index 값으로 변환한다는 의미였는데요.

+ Recent posts