레이블이 국제화인 게시물을 표시합니다. 모든 게시물 표시
레이블이 국제화인 게시물을 표시합니다. 모든 게시물 표시

2015년 6월 27일 토요일

독특한 달력을 사용하는 고집 센 나라들 (15)

미국에서 미터법을 잘 쓰지 않듯이 국제 표준이 있는데 자국만의 표준을 고집하는 나라들이 있다. 우리가 모든 나라의 문화를 다 이해하지 못하는 상황에서 이를 비난 해서는 안된다. 다만 소프트웨어를 개발하는데 엄청 번거로울 뿐이다. 날짜에서도 전세계 대부분의 나라가 그레고리력을 사용하는데 그레고리력을 사용하지 않는 몇몇 나라가 있다. 물론 그레고리력과 혼용을 하기도 한다.

게다가 달력은 로케일 표준 카테고리에 해당하지 않기 때문에 대부분의 개발툴이나 라이브러리에서 별도로 제공하지 않는다. 개발자가 직접 제공을 해야 하는 경우가 많다.




사실 한국도 19세기까지는 음력을 사용하다가 독자 연호를 거쳐 현재는 그레고리력을 쓴다. 우리나라는 국제 표준을 상당히 잘 따르는 나라 중에 하나다. 오랫동안 써왔던 면적 단위인 “평”을 못 쓰도록 법으로 금지했는데 이렇게 할 수 있는 나라는 그렇게 많지 않을 것이다. 그래도 그런 강제 표준 적용이 소프트웨어 개발에는 더 유리하다.
이렇게 전세계가 완벽하게 그레고리력으로 통일되지 못한 상황에서 소프트웨어가 그레고리력만 제공을 하면 해당 국가에서는 받아들여지기 힘들 수도 있다. 그래서 그레고리력 외에 크게 “불기”와 일본의 “연호”를 제공해야 한다. 그 외에는 여러 가지 달력이 추가로 더 있으니 소프트웨어의 성격에 따라서 추가 지원을 고려해야 한다.




먼저 “불기”를 알아보자. “불기”란 석가모니가 입적(불멸)한 해인 기원전544년을 기준으로 삼는 달력이다. “서기”는 예수의 탄생을 기준으로 삼는데 불교에서는 정반대의 기준을 사용하는 것으로 보아서 문화에 따른 생각의 차이를 엿볼 수 있다.

표준 “불기”로는 올해가 2559년이지만 태국에서는 이보다 1년 늦은 2558년이다. “불기”도 나라별로 약간 다른 것도 소프트웨어에서는 여간 귀찮은 일이 아니다. 또한 태국에서는 그레고리력을 쓰는 사람들도 있기 때문에 그레고리력을 선택할 수도 있도록 해야 한다. 즉, 로케일로 달력을 결정할 수 없고 별도의 설정이 필요하다.

다음은 일본이다. 일본은 일왕에 따른 연호를 사용한다. 올해는 平成27年(헤이세이)이다. 일본 역시 그레고리력도 쓰기 때문에 둘 다 지원을 해야 한다. 일본의 연호를 고려하지 않고 소프트웨어를 개발하게 되면 올해가 2015년이 아니고 0027년으로 처리가 될 수도 있다. 일본은 연도별로 연호가 다르기 때문에 대단히 귀찮은 변경작업을 해줘야 한다. 하지만 연호를 잘 제공하면 그만큼 일본 시장에서 소프트웨어의 품질이 올라갈 것이다.

그 외에 몇 개의 달력을 더 알아보자. 필자도 위의 달력 외에도 아래 소개하는 몇몇 달력 때문에 고생한 적이 있다.

이슬람력은 많은 이슬람 국가에서 사용하고 대부분의 나라에서는 그레고리력과 병기를 한다. 위에서 언급한 불기와 일본의 연호는 연도만 다르고 월과 일은 그레고리력과 같다. 하지만 이슬람력은 태음력을 사용하기 때문에 날짜가 완전히 다르다. 라마단도 이슬람력을 기준으로 한다.

이란과 아프가니스탄에서 사용하는 이란력(페르시아력)도 있다. 이는 태양력이다.

그 외에도 히브리력, 인도 국민력, 에티오피아력, 대한민국 단기, 음력, 북한의 주체연호가 있다. 대부분은 그레고리력과 병기를 한다.

어떤 달력을 지원하던지 날짜의 변환 작업과 날짜 선택기를 제공해야 한다. 그레고리력 날짜 선택기를 쓸 수 없는 달력들이 있다.

소프트웨어를 개발할 때 달력을 어디까지 제공할지는 소프트웨어의 성격과 판매하려는 지역과 전략에 따라서 매우 달라진다. 우리가 각 나라들의 문화를 다 알지 못하는 상황에서 무조건 그레고리력을 써야 한다고 주장하는 것은 소프트웨어 판매에 걸림돌이 될 수도 있고 그렇다고 모든 달력을 제공하는 것은 배보다 배꼽이 더 클 수도 있다. 전략에 따라서 적절히 판단을 해야 한다.

날짜 표기 국제 표준은 무엇일까? (14)

지난 12회에서 국가별, 로케일별로 날짜 표기 형식이 매우 다르다는 것을 보았다. 하지만 이런 방식을 따르기만 한다고 해서 날짜 표기 형식 문제가 모두 해결되는 것은 아니다. 시스템에서 제공하는 날짜 표기 형식이 실제로 해당 국가에서 오류라고 생각할 수도 있고, 입력 시 사용자의 실수로 인한 혼동도 무시 못한다.

그래서 국제화가 잘 된 소프트웨어에서는 날짜 형식에 대해서 조금 더 고민을 해야 한다. 우선 날짜를 출력하는 방법은 크게 3가지가 있다. 각각 장단점이 있다.

첫 번째 지난번 12회에서 봤듯이 로케일별로 각각 다른 날짜 형식으로 출력을 하는 것이다. OS나 개발툴, 라이브러리에 따라서 그 형식이 조금씩은 다르지만 웬만큼은 현지에서 받아들여 질만한 형식을 제공한다. 장점으로는 개발자가 날짜 형식을 직접 연구하고 다루지 않기 때문에구현이 상대적으로 간단하다. 하지만 단점으로는 라이브러리나 프레임워크에 따라서 제공하는 포맷이 일정하지 않고 원하는 날짜 형식을 제대로 제공하지 못할 수도 있다. 또한 그렇게 제공한 날짜 형식에 버그가 있을 경우 개별적으로 수정을 해야 하는 번거로움이 있다.
C에서는 strftime()함수를 사용하거나 QT Framework에서는 QDate::toString() 함수를 이용할 수 있다. 




두 번째사용자가 날짜 형식을 선택하거나 직접 입력하도록 하는 방식이다. 개발자가 완벽하게 사용자가 원하는 날짜 포맷을 제공하는 데는 한계가 있다고 생각하고 그 책임을 사용자에게 맡기는 것이다. 예를 들어 yyyy/MM/dd 라고 지정을 하면 2015/05/27이라고 출력을 할 것이다. 유독 월을 표시하는 M만 대문자인 이유는 분을 표시하는 m(minute)와 구분하기 위해서이다. 이 경우에도 요일과 월은 숫자가 아니라 문자로 표시를 할 수 있으므로 똑 같은 형식이라도 로케일별로 다르게 표시가 된다. 
소프트웨어에서 몇 가지 날짜 형식을 보여주고 사용자가 고르는 방법도 있고 사용자가 완전히 자유롭게 형식을 편집할 수 있도록 할 수도 있다. 물론 고르는 방법이 구현도 편하고 좀더 안전하다. 이 방법의 장점은 개발자가 각 나라의 날짜 형식에 대해서 너무 고민할 필요가 없는 것이다. 단점으로는 사용자가 뭔가 선택을 해야 하는 불편함이 있고, 사용하는 개발언어나 라이브러리에 따라서 날짜 포맷 표기법이 조금씩 다르다는 것이다.
그래서 첫 번째 방법과 두 번째 방법을 섞어서 사용하기도 한다.




세 번째국제 날짜 형식 표준을 이용하는 방법이다. 국제화된 소프트웨어를 개발할 때는 항상 날짜 표시 문제가 있다. 그래서 1988년도에 국제 날짜 표준 형식이 제안되었고, ISO8601이 발표되었다. ISO8601에서는 여러 가지 날짜와 시간의 표준 형식을 다루고 있다. 먼저 국제 날짜 표준 형식은 어떤 것인지 보자.
날짜는 2015-05-28과 같이 YYYY-MM-DD의 형식을 따르고 있다. 연도가 뒤에 있을 경우에는 앞에 온 숫자가 월일지 일일지 헷갈리지만 연도가 맨 앞에 오면 전세게 거의 모든 사람들이 연-월-일로 인식을 한다. 물론 예외는 있을 것이다. 이런 국제 날짜 표준 형식은 우리나라에서 흔히 사용하는 형식이라서 다행이다. 
시간과 같이 표시를 할 때는 2015-05-28T09:15:52와 같이 중간에 “T”를 넣어서 표시한다. Time Zone을 포함해서 2015-05-28T09:15:52+09:00과 같이 표시하는 방법도 있다. 기간을 표시하기 위해서 우리는 흔히 ‘~’를 사용하는데 ISO8601에서는 ‘/’를 사용한다. 2015-05-28/2015-06-28과 같이 표시한다.
이렇게 국제 표준 날짜 형식을 사용하면 소프트웨어를 개발하기 매우 편리하다. 나라별, 로케일 별로 고민을 할 필요가 없고 이로 인한 혼란이나 버그가 거의 없다. 하지만 고집이 센 나라에서 받아들여지지 않고 버그로 보고가 될 수도 있는 단점이 존재한다.

위의 모든 경우에 입력의 문제는 여전히 존재한다. 국제화가 잘된 소프트웨어에서도 흔히 날짜 입력의 문제를 해결하지 못하는 경우가 많다. 날짜 출력은 그 나라의 문화에 알맞게 지원을 하는데 날짜 입력에서 제대로 된 형식을 지원하지 못하는 경우도 흔하다. 또한 날짜 입력은 사용자의 실수를 무시할 수 없다. 그래서 날짜는 날짜 선택기 위젯 같은 것을 이용해서 마우스 클릭을 통해서 사용자가 날짜를 선택하게 하는 UI를 제공하는 것이 좋다. 물론 날짜 선택기가 그레고리력만 제공하는지 그 외의 달력도 제공하는지 이슈가 있기는 하다. 또한 날짜 선택기도 지역화를 제공해야 한다.




위에서 어떤 방식을 제공할지는 소프트웨어의 성격 및 회사의 전략에 따라서 달라질 수가 있다. 단, 일단 개발을 해 놓고 문제가 있다고 고치기 시작하면 이미 좀 늦은 것이다. 사전에 국제화 전략을 정해서 제대로 적용하는 것이 가장 효율적인 방법이다.

멀티유저 국제화 소프트웨어 만드는 방법 (13)

소프트웨어 아키텍처는 창의력의 산물이기 때문에 정답이 있는 것은 아니지만 몇 가지 소개를 하려고 한다. 다시 한번 강조하지만 국제화가 잘 된 소프트웨어의 아키텍처 원칙은 다음과 같다.

“하나의 소스코드, 한번의 빌드, 하나의 팩키지”




나라별로 별도의 소스코드를 관리하고 별도로 빌드를 하거나 제품이 각각 따로 나온다면 이를 관리하기 위해서 열배, 백배의 노력을 들여야 한다.

국제화된 소프트웨어는 크게 “싱글 로케일”과 “멀티 로케일”로 구분할 수 있다. “싱글 로케일”은 소프트웨어가 동작하면서 하나의 로케일만 지원하는 것이다. 이런 소프트웨어는 지원하는 로케일을 바꾸려면 소프트웨어의 설정을 바꾼 후 Restart 하거나 L10n 모듈을 Reload 해야 한다. 몇몇 데스크탑 소프트웨어가 여기에 해당한다.




 위 그림처럼 어플리케이션이 시작될 때 필요한 L10n 모듈만 Load해서 하나의 로케일로만 동작하는 것이다. 이 경우 L10n 모듈은 DLL이나Shared Object 형태로 제작해서 Dynamic load를 할 수 있다. 이런 아키텍처는 복잡도가 낮고 필요한 L10n 모듈만 Load를 하기 때문에 메모리를 절약할 수 있는 장점이 있다.

아래 그림처럼 한국인이 사용할 때는 L10n ko_KR 라이브러리만 Load를 하고 일본인이 사용할 때는 L10n ja_JP 라이브러리를 Load하면 된다. 이런 방식은 로케일을 중간에 바꾸거나 섞어서 사용할 수 없는 단점이 있다. 예를 들어서 날짜는 한국식으로 표현을 하다가 독일인에게 메시지를 보내기 위해서 숫자의 형식을 바꿔준다든지 하는 등의 기능은 구현할 수 없다. 소프트웨어의 성격에 알맞게 아키텍처를 선택하면 된다.

 

두번째 방식을 “멀티 로케일”을 지원하기 위한 방식이다. “멀티 로케일”은 소프트웨어가 동시에 여러 가지 로케일을 지원하는 것이다. 여러 L10n 라이브러리를 모두 Load 해 놓고 한국인이 사용할 때는 L10n ko_KR 라이브러리를 이용해서 한국인에게 알맞게 동작하고 일본인이 사용할 때는 L10n ja_JP 라이브러리를 사용하는 방식이다. 대부분의 서버 소프트웨어는 “멀티 로케일”지원이 필요하다. 데스크탑 소프트웨어도 요구사항에 따라서 멀티 로케일지원이 필요하다. 사용자가 접속할 때마다 필요한 L10n 라이브러리를 Load하는 것은 부담이 심하기 때문에 모두 Load를 해 놓는 것이다. 
 

“멀티 로케일”을 지원하는 소프트웨어는 한 화면에서 로케일을 바꿔가면서 동작할 수도 있고 기능별로 다른 로케일을 사용할 수도 있다. 즉, 날짜는 한국식, 숫자는 독일식, 통화는 미국식으로 동작하게 할 수도 있다. 이런 소프트웨어는 메모리를 많이 쓰는 단점이 있기는 하지만 메모리를 많이 사용하는 것은 요즘 세상에 단점으로 볼 수도 없다. 장점으로는 소프트웨어 국제화 지원에 제한이 없다는 것이다. 

그럼 이런 방식을 구현하기 위해서는 어떤 아키텍처를 사용해야 할까? 수많은 방법이 있겠지만 그 중에서 한가지 방법은 아래와 같이 클래스 상속을 이용하는 방법이다. L10n Base Class에서 필요한 국제화 함수들을 정의하고 기본 기능을 구현한 후에 각 로케일별 서브클래스를 구현하는 방식이다. 로케일별로 각기 다른 기능은 서브클래스에서 각각 구현하면 된다. 그리고 접속한 사용자에 따라서 필요한 로케일의 서브클래스를 호출하면 된다. 이때 i18n 라이브러리는 적절한 분기를관장하게 된다.


 이 방법 외에도 Function Pointer를 이용하는 방법도 있고 방법은 무궁무진할 것이다. 소프트웨어의 성격에 맞게 창의력을 발휘해서 가장 알맞은 방법을 선택하면 될 것이다. 

소프트웨어를 개발할 때 아키텍처를 결정하는 것은 매우 중요하다. 한번 정하면 쉽게 바꾸기 어렵기 때문이다. 소프트웨어의 전략, 비전 그리고 회사의 목표와 방향도 알아야 소프트웨어의 아키텍처를 잘 정할 수 있다. 국제화된 소프트웨어를 개발할 때는 회사의 국제화 전략을 잘 이해해야 한다. 

목표와 비전을 모르고 후다닥 만들어진 소프트웨어는 십리도 못가서 발병이 날 것 이다. 이런 소프트웨어는 아니 만드니 만도 못한 경우가 허다하다.

01/02/03는 며칠일까? (12)

오늘은 날짜 표기에 대해서 다뤄보자. 날짜 표기 형식도 나라마다 다르다. 그런데 많은 소프트웨어들이 국가별, 로케일별로 다른 날짜 표기 형식을 제대로 처리하지 않아서 특정 국가에서 불만이 커지거나 잘못 사용되는 사례도 빈번하게 발생한다. 심지어는 날짜 표기를 제대로 고려한 소프트웨어에서도 입력의 복잡함과 모호함으로 문제가 발생하곤 한다. 국제화된 소프트웨어를 개발하는 개발자라면 날짜 형식을 다루는 지식과 노하우는 어느 정도 보유해야 한다.







우선 01/02/03은 무엇으로 보이는가? 물론 이런 날짜 형식은 애초에 모호함 때문에 잘 사용하지 않는다. 하지만 꼭 날짜를 이렇게 표현한다면 어떻게 보일까? 100%는 아니지만 미국, 호주, 한국 사람들은 각각 다른 날짜로 읽는 것이 일반적이다.

미국 사람들은 2003년 1월 2일로 볼 것이다. 물론 미국에는 워낙 많은 민족이 있어서 다르게 볼 수도 있지만 대체로 그렇다는 것이다. 호주 사람들은 2003년 2월 1일로 볼 것이다. 하지만 알다시피 한국사람들은2001년 2월 3일로 볼 것이다. 2002년으로 읽는 나라가 없다는 것은 그나마 다행이다.

추가로 그레고리력을 사용하느냐 다른 달력을 사용하느냐에 따라 다른 부분도 있다. 참고로 북한은 주체연호를 사용하고 일본과 태국도 다른 달력을 혼용한다. 이 주제는 따로 다루겠다.




그럼 국가별, 로케일별로 날짜 형식은 어떻게 되면 소프트웨어를 개발할 때 이를 어떻게 다뤄야 하는지 알아보자.

우리가 날짜를 표시하는 방법은 수십 가지가 넘는다. 연도만해도 두자리 또는 네자리로 사용하고 요일도 “월” 또는 “월요일”이라고 쓴다. 영어에서는 월을 표기할 때 “Aug” 또는 “August”로 표기를 한다. 시간까지 같이 표시할지 여부와 시간의 표시 형식까지 합하면 크게 나눠도 열 가지는 넘는다. 소프트웨어를 설계할 때는 소프트웨어의 성격에 따라서지원해야 할 날짜 형식을 표준화해야 한다. 개발자들이 멋대로 여러 가지 날짜 형식을 사용하게 되면 관리가 제대로 안된다. 나중에 버그가 발견되면 수많은 소스코드를 고쳐야 한다. 표준화된 날짜 함수를 제공해서 개발자에게 제공해야 하고 개발자들은 정해진 날짜 함수만 사용해야 한다.

날짜 함수는 입력, 출력 두가지 형태의 함수를 제공해야 한다. 입력은 문자열을 날짜 데이터로 변환하는 것이고 출력은 날짜 데이터를 문자열로 변환하는 함수다. 예를 들어 StrToDate(), DateToStr() 이런 함수를 만들면 된다. 날짜의 형식은 가장 먼저 긴 형식과 짧은 형식을 제공한다. 이는 소프트웨어마다 다르니 개발자가 정해야 한다.



국가별 로케일별로 서로 다른 날짜 형식만 살펴보도록 하자. 먼저 긴 날짜 형식은 어떻게 될까? 우선 로케일별 국가 이름을 소개한다.

ar_SA : 사우디아라비아
de_DE : 독일
en_US : 미국
es_ES : 스페인
fr_FR : 프랑스
id_ID : 인도네시아
it_IT : 이탈리아
ja_JP : 일본
ko_KR : 대한민국
pl_PL : 폴란드
pt_PT : 포르투갈
ru_RU : 러시아
vi_VN : 베트남
zh_CN : 중국
zh_TW : 타이완
de_LI : 리히텐슈타인
th_TH : 태국

로케일(국가)별 긴 날짜 형식은 다음과 같다.

ar_SA : "الاثنين، ١٨ مايو، ٢٠١٥"
de_DE : "Montag, 18. Mai 2015"
en_US : "Monday, May 18, 2015"
es_ES : "lunes 18 de mayo de 2015"
fr_FR : "lundi 18 mai 2015"
id_ID : "Senin, 18 Mei 2015"
it_IT : "lunedì 18 maggio 2015"
ja_JP : "2015年5月18日月曜日"
ko_KR : "2015년 5월 18일 월요일"
pl_PL : "poniedziałek, 18 maja 2015"
pt_PT : "Segunda-feira, 18 de Maio de 2015"
ru_RU : "понедельник, 18 мая 2015 г."
vi_VN : "Thứ hai, ngày 18 tháng năm năm 2015"
zh_CN : "2015年5月18日星期一"
zh_TW : "2015年5月18日星期一"
de_LI : "Montag, 18. Mai 2015"
th_TH : "วันจันทร์ที่ 18 พฤษภาคม 2015"

그럼 짧은 날짜 형식은 국가별, 로케일별로 어떻게 출력될까? 

ar_SA : "١٨‏/٥‏/٢٠١٥"
de_DE : "18.05.15"
en_US : "5/18/15"
es_ES : "18/05/15"
fr_FR : "18/05/15"
id_ID : "18/05/15"
it_IT : "18/05/15"
ja_JP : "2015/05/18"
ko_KR : "15. 5. 18."
pl_PL : "18.05.2015"
pt_PT : "18/05/15"
ru_RU : "18.05.15"
vi_VN : "18/05/2015"
zh_CN : "15-5-18"
zh_TW : "15/5/18"
de_LI : "18.05.15"
th_TH : "18/5/2015"

물론 위 날짜 형식 모두다 정답이 아닐 수도 있다. 필자가 특정 Framework을 사용해서 출력된 결과를 표시한 것뿐이다. 해당 Framework의 국제화 개발팀에서 적절하지 않는 날짜 형식을 구현해 놓았을 수도 있다. 특히, 한국의 짧은 날짜 형식은 마음에 들지 않는다. 하지만 일단 Framework이나 시스템에서 제공하는 날짜 형식을 믿고 개발해야 한다. 개발자가 국가별, 로케일별 날짜 포맷을 직접 연구할 수는 없다. 추후 버그가 보고 될 경우 수정은 할 수 있겠다.

필자가 테스트를 해본 결과 같은 C언어를 쓴다고 하더라도 C언어에서 제공하는 strftime()함수만 하더라도 Microsoft C와 gcc가 제공하는 날짜 형식이 다르다. 이는 OS나 개발툴 개발사마다 날짜 형식을 미묘하게 다르게 제공하고 있다는 것을 알아야 한다. 크로스 플랫폼 개발자는 더욱 유념해서 개발을 해야 한다.

위에서 예로든 두가지 형식 외에도 여러 가지 중간 길이의 날짜 형식이 필요하다. 이런 형식을 제공하기 위해서는 개발자가 날짜 형식에 대해서 조금 더 연구를 해야 한다. 기본 날짜 함수 제공은 쉽지만 여러 가지 형식을 제대로 제공하려면 점점 어려워진다. 소프트웨어 성격에 알맞게 제공할 날짜 포맷을 연구해서 국제화 라이브러리에 추가를 해야 한다.

다음에는 날짜를 다루는 방법을 조금 더 보겠다.

바이트 순서와 BOM이 이렇게 복잡해진 이유 (11)

오늘은 BOM(Byte Order Mark)에 대해서 알아보려고 한다. BOM은 바이트 순서를 나타내는 표식이고 바이트 순서뿐만 아니라 이 파일이 어떤 인코딩을 사용했는지도 나타낸다. BOM이 생겨난 사연에 대해서 알아보자. 

개발자라면 모두 알고 있겠지만 CPU마다 바이트 순서가 다르다. 바이트 순서를 선택할 수 있는 CPU도 있다. 0x1234는 0x12, 0x34라고 표시하는 것이 자연스럽다. 이런 순서를 빅 엔디언(Big-endian)이라고 부르며 초창기 대부분의 CPU는 이런 아키텍처를 사용했다. 하지만 인텔에서 x86 CPU를 만들면서 적용한 아키텍처는 이와는 정반대다. 0x1234를 0x34, 0x12라고 표시를 했다. 이를 리틀 엔디언(little-endian)이라고 한다. 

인텔이 x86 CPU에 이런 아키텍처를 적용한 이유는 덧셈을 조금 더 단순하게 해서 덧셈 회로를 조금이라도 작게 만들기 위한 것이라고 전해지고 있다. 숫자를 반대로 하면 앞쪽 바이트부터 더해서 순서대로 자리 올림을 하면 된다. 우리가 현실에서 덧셈을 할 때 1의 자리부터 거꾸로 더해나가는 것이 더 간단하다는 것을 생각해보면 숫자를 반대로 표시하는 것이 왜 덧셈을 할 때 간단하지 알 수 있다. 지금은 CPU가 한 바이트씩 더하지를 않기 때문에 별 차이도 없어져버렸다.





이렇게 인텔은 주로 리틀 엔디언 아키텍처를 사용하고 대부분은 빅 엔디언을 사용하지만 우리가 사용하는 PC는 거의 인텔 계열 CPU를 사용하기 때문이 어떻게 보면 리틀 엔디언 환경에 접할 가능성이 더 높다. 

이렇게 세상에는 두 가지 바이트 순서가 존재하게 되어서 파일을 저장한 후에 다른 컴퓨터에서 읽으면 제대로 읽혀지지 않는 일이 빈번하게 발생하게 되었다. 또한 네트워크를 통해서 데이터를 전송할 때도 문제가 발생했다. 그래서 바이트 순서 규칙을 하나로 정해놓고 자료를 전달한 후에 현재 시스템과 반대로 된 바이트 순서 데이터를 읽을 때는 바이트 순서를 다시 반대로 바꿔줘야 했다. 

개발자들이 흔히 하는 실수는 바이트 순서를 현재의 시스템에서 바이트 순서를 보고 짐작하여 메모리를 직접 접근하도록 개발을 해서 다른 시스템에서 동작하지 않게 되는 것이다. 또 시스템간에 파일을 교환할 때바이트 순서를 고려하지 않는 것이다. 소프트웨어를 설계할 때 신경을 써야 할 부분이다.

이런 현상은 인코딩과 관련해서도 똑같이 일어난다. 현재 데이터의 문자가 어떤 인코딩을 사용하고 있는지도 헷갈리는 것이다. 앞부분 몇 바이트를 보니 ASCII 같은데 실제로는 UTF-8 일수도 있고 EUC-KR 문서일 수도 있다.





그래서 문서나 데이터는 자신이 사용한 인코딩과 바이트 순서를 스스로 알려주는 것이 좋다. 이를 Self-Identification이라고 한다. 이런 용도로 사용하기 위해 표준을 정한 것을 BOM이라고 한다. BOM은 파일의 맨 앞에 붙으며 기존의 정상적인 문자에서는 나올 수 없는 문자로 구성이 되어 있다. 그럼 BOM은 어떤 것이 있는지 보자.

EF BB BF : UTF-8
FE FF : UTF-16 빅 엔디언
FF FE : UTF-16 리틀 엔디언
00 00 FE FF : UTF-32 빅 엔디언
FF FE 00 00 : UTF-32 리틀 엔디언

UTF-16과 UTF-32는 엔디언에 따라서 글자 내의 바이트 순서가 반대이다. UTF-8은 바이트 순서가 고정이므로 BOM이 한가지 밖에 없다. 여기서 가장 문제가 되는 것은 UTF-8의 BOM이다. 가장 널이 쓰이는 유니코드 파일의 인코딩이 UTF-8인데 UTF-8의 BOM은 개발자를 여간 괴롭히는 것이 아니다. 

UTF-8의 BOM은 BOM 자체가 문제라기 보다는 시스템 또는 어플리케이션에 따라서 BOM를 사용하기도 하고 사용하지 않기도 한다. 또는 두 경우를 모두 지원하기도 한다. 그 조합이 워낙 많아서 헷갈리곤 한다. UTF-8 문서에는 BOM이 없어도 대부분의 경우 UTF-8 문서인지 알 수가 있다. 그래서인지 Unix나 Linux 시스템에서는 BOM이 없는 UTF-8을 이용한다. 반대로 마이크로소프트는 철저히 BOM을 사용한다. 





Unix나 Linux에서 또는 오픈소스 개발툴에서는 소스코드에 BOM이 있어서 제대로 읽지 못하기도 하고 HTML 파일에 BOM이 있을 경우 웹서버에 따라서 BOM이 웹브라우져 화면에 출력되기도 한다. 특히, 윈도우의 노트패드는 UTF-8 파일에 BOM을 붙여버려서 종종 곤란한 일을 겪게 된다. 이런 혼란을 문제없이 해결하려면 노하우가 꽤 필요하다.

나는 개발을 할 때 부득이한 경우가 아니라면 BOM이 없는 UTF-8 파일을 표준으로 삼고 있다. 그리고 필요한 경우에만 예외적으로 BOM을 붙이는 것이 수월하다. 그러기 위해서는 언제 BOM이 필요하고 어디서는 BOM이 없어야 되는지 잘 알고 있어야 한다.

UTF-8에 BOM을 붙여야 하느냐, 필요 없느냐는 논쟁은 쉽게 결론이 나지 않을 것이다. 개발자가 상황에 알맞게 알아서 BOM을 붙이기도 제거하기도 해야 한다. 그래서 개발용 편집기는 BOM을 자유롭게 다룰 수 있어야 한다. 나는 개발언어나 환경에 따라서 수많은 편집기를 사용하지만 대표적인 텍스트 편집기로는 Notepad++를 사용한다. 수많은 인코딩과 BOM을 자유롭게 다루고 변환할 수 있다.

윈도우에 내장된 Notepad는 사용하지 않을 것을 추천한다. 본의 아니게 BOM이 붙어서 고생을 할 수가 있다.

추가로 빅 엔디언과 리틀 엔디언이란 용어의 유래에 대해서 알아보자.걸리버여행기를 보면 달걀 깨는 법으로 대립 중인 두 나라가 나온다. 달걀을 뾰족한 곳을 깨느냐 뭉툭한 곳을 깨느냐 논쟁을 하며 나중에는 전쟁도 하는데 여기서 뭉툭한 곳을 깨는 사람을 빅 엔디언이라고 하고 뾰족한 곳을 깨는 사람을 리틀 엔디언이라고 한다. 

소프트웨어 세계도 전쟁은 하지 않았지만 두 표준이 쉽게 통일되지 않고 있다.

2015년 6월 26일 금요일

123.456이 무엇으로 보이는가? (10)

123.456 숫자를 보면 우리나라 사람들은 대부분은 123에 소수점을 찍은 후 0.456이 추가된 것으로 생각을 할 것이다. 하지만 독일사람에게 123.456을 보여주면 뭐라고 생각할까? 독일에서 ‘.’은 천단위 구분자다. 그래서 123.456은 123456과 같은 숫자다. 만약에 123.456톤 원자재를 주문하면 어떻게 될까? 원래 의도보다 1000배많은 물량을 주문한 결과가 된다. 이런 것이 처리가 안된 소프트웨어를 과연 독일에 팔 수 있을까?

그럼 독일이 이런 것을 알았으니 독일에 맞춰서 소프트웨어를 개발한다고 하면 매번 새로운 나라가 나올 때마다 조사하고 연구해서 지원을 해줘야 한다. 그런 식으로는 끝이 없다. 나라별로 소숫점과 천단위 구분자는 천차만별이다. 게다가 아랍은 아라비아 숫자가 아닌 별도의 숫자를 사용하고 있고, 중국과 일본은 과거 천이 아닌 만단위 구분자를 사용했었다. 

Application 개발자가 매번 숫자를 출력할 때마다 이런 고민을 할 수는 없다. 국제화 라이브러리를 만드는 개발자가 이를 고민해야 하고 Application 개발자는 숫자를 출력하기 위해서 마음대로 개발을 하면 안되고 꼭 국제화 라이브러리를 사용해야 한다. 국제화 라이브러리 개발자는 내용은 나중에 채우더라도 Application 개발자가 쓸 수 있는 함수 정의를 먼저 제공해야 한다. 처음에는 한국의 숫자 형식으로 출력이 되겠지만 국제화 라이브러리 개발자가 로케일별 숫자 형식을 지원하는 라이브러리를 완성하면 숫자가 로케일별로 다른 형식으로 출력되게 된다. 


(소수점 사용 지도)

그럼 나라별로 어떤 형식의 숫자를 사용하는지 먼저 좀 알아야 한다. 또한 자신이 개발하고 있는 OS와 사용하고 있는 개발툴, 프레임워크가 어떤 국제화 함수들을 지원하는지도 잘 알아야 한다. 먼저 나라별 숫자 형식을 살펴보자.

1,234,567.89와 같은 숫자를 쓰는 나라는 한국, 미국, 캐나다, 중국, 일본, 영국, 호주 등이 있다. 물론 소프트웨어 내에서는 국가가 아니고 로케일로 지정을 해야 한다.

이와 반대로 1.234.567,89 형식의 숫자를 쓰는 나라는 독일, 그리스, 덴마크, 이탈리아, 인도네시아, 러시아 등이 있다. 네덜란드는 통화표시 때는 이 형식을 사용한다. 인도네시아는 과거 네덜란드의 식민지여서 이 숫자 형식을 쓰게 된 것으로 보인다. 아시아 나라들의 국제화 표준은 식민지 역사와 관련이 있는 것이 씁쓸하다.

특이하게 스위스에서는 1'234'567.89 형식으로 숫자를 사용한다. 

1 234 567,89와 같이 천단위 구분자로 띄어쓰기를 하고 소수점으로 콤마를 쓰는 나라로는 벨기에, 프랑스, 네덜란드(비 통화표시) 등이 있다.




사우디아라비아에서는 ١٬٢٣٤٬٥٦٧٫٨٩와 같이 표기한다. 천단위 구분자도 다르고 소수점도 다르다. 아리비아숫자도 쓰지만 아랍어의 숫자도 쓴다. 아랍권도 로케일마다 숫자표기가 다르다. 천단위 구분자는 뒤집힌 콤마인데 폰트 때문인지 제대로 안나온다. 특이한 점은 아랍어는 오른쪽에서 왼쪽으로 쓰지만 숫자는 왼쪽에서 오른쪽으로 쓴다는 점이다.

아라비아숫자는 최초에 인도에서 만들어졌다는데 대다수 역사가들이 동의를 하다. 하지만 인도숫자가 아니고 아라비아숫자라는 명칭을 얻게 된 이유는 인도에서 만들어졌지만 이슬람세계를 거쳐 점점 변형이 되면서 유럽으로 전파가 되었기 때문에 유럽 사람들은 아랍에서 온 숫자로생각했다. 

이외에도 여러가지 숫자 표기 형식이 더 있다. 하지만 개발자가 이 모든 것을 다 알 필요는 없다.  이렇게 다양하다는 것 정도와 나중에 버그가 보고 될 때 버그를 고치기 위해서 필요한 정도만 알면 된다.

우리나라 및 한자권의 나라들은 숫자가 만 단위로 되어 있어서 만단위구분자를 찍는것이 읽기는 더 편하다. 하지만 숫자표기 표준화에 따라서 우리나라에서도 천단위 구분자를 사용하는데 불편하다. 또한 관습적으로 백단위와 천단위 구분자를 섞어서 쓰는 나라도 있지만 지금은 거의 천단위 구분자를 사용하는 것으로 통일되고 있다.

숫자를 위한 국제화 라이브러리를 설계할 때는 다음 순서를 따르면 된다.

첫째, 지원 범위를 결정한다.
얼마나 많은 로케일을 지원해야 하나? 현재는? 미래에는?
지원할 숫자의 종류는? 정수? 실수? 숫자의 길이는?
천단위 구분자를 지원할 것인가?
출력만 지원할 것인가? 입력, 출력 모두를 지원할 것인가? 입출력별 지원할 숫자 형식은?
Application 종류마다 지원 범위가 다르므로 미래 요구사항까지 고려하여 스펙을 정해야 한다.

둘째, 함수 프로토타입을 정의한다.
함수가 정의되어야 국제화 라이브러리가 완성되지 않아도 Application 개발자가 사용할 수 있으므로 프로젝트 초기에 정해야 하며 나중에 바뀌지 않도록 신중하게 결정해야 한다.
보통 정수를 문자열로, 문자열을 정수로 바꿔주는 함수와 실수를 문자열로, 문자열을 실수로 변환하는 함수를 정의한다. 천단위 구분자를 옵션으로 주기도 하고 로케일에 영향을 받지 않는 옵션을 제공하기도 한다.

셋째, 함수 내부를 구현한다.
숫자 함수는 보통 L10n 라이브러리를 로케일별로 각각 개발하지 않고 시스템에서 제공하는 국제화 함수들을 사용한다. 그만큼 숫자는 일반적인 국제화 항목이라서 대부분의 시스템에서 제공한다. C언어를 사용한다면 atof(), atoi(), atoll(), strtod(), strtol(), printf(), sprintf() 등의 함수가 로케일을 바꿔주면 로케일에 따라서 다르게 동작한다. 물론 각 함수들은 와이드캐릭터(wchar_t) 버전이 따로 있으니 사용하는 문자의 데이터형에 따라서 알맞은 함수를 사용해야 한다. printf() 함수의 국제화 버전을 사용하려면 libintl라이브러리를 포함해야 한다.위 함수들이 로케일에 따라 다르게 동작하게 하려면 setlocale(LC_NUMERIC, "ko_KR")과 같이 숫자형식의 로케일을 바꿔줘야 한다. LC_ALL을 이용해서 모든 카테고리를 다 바꿔도 동작한다.

그 외에 어떤 개발언어, 라이브러리, 프레임워크를 사용하냐에 따라서 숫자함수들의 사용법이 다르니 환경에 알맞게 구현을 해야 한다. 
자세한 시스템 국제화 숫자 관련 함수의 사용법은 환경에 따라서 매우 다양하여 이 글에서 다 소개하기는 어렵다. 추가로 궁금한 것은 스스로 조사를 하던가 필자에게 직접 문의를 하는 것이 좋겠다. 
입력함수는 출력함수와는 다르게 엄격하지 않게 구현하는 것이 일반적이다. 천단위 구분자를 포함해서 입력하는 함수라도 천단위 구분자가 입력되지 않은 경우에도 처리를 하는 등 유연성을 발휘하는 것이다.

추가로 35%, 10mm 등 뒤에 단위가 붙은 숫자들도 국제화 함수로 미리 정의를 해 놓는 것이 좋다. 이또한 나라별로 국가별로 어떤 표기법으로 바꿔야 할지 알 수 없기 때문이다.

이렇게 숫자를 나라별, 로케일별로 제대로 표현하는 것은 소프트웨어 국제화의 시작이다.

이 글은 네이버 포스트에 게재한  입니다.

유니코드 인코딩이 이렇게 복잡하게 된 사연 (9)




유니코드 인코딩의 종류는 UTF-1, UTF-5, UTF-6, UTF-7, UTF-8, UTF-9, UTF-16, UTF-18, UTF-32, UTF-EBCDIC, UTF-16BE, UTF-16LE, UTF-32BE, UTF-32LE와 같이 다양하다. 지금은 사장된 것들도 있지만 초급 개발자에게는 여간 헷갈리는 것이 아니다. 

애초에 ASCII가 아니고 유니코드를 사용했다면, 특히 4바이트 유니코드를 사용했다면 지금과 같이 수많은 유니코드 인코딩 때문에 헷갈거나 변환을 해야 하는 번거로움은 없었을 것이다. 현재와 고대 문자만 표현한다면 3바이트로도 충분하지만 4바이트가 컴퓨터 연산에 편리하고 먼 미래에 어떻게 될지 모르기 때문에 4바이트가 안전할 것이다.  하지만 뒤늦게 나온 유니코드는 ASCII와 호환성 등 여러 가지 이유로 인해서 수많은 인코딩이 존재한다. 

인류가 유일한 표준 문자세트로 4바이트 유니코드를 사용하고 있다면 영어 문서를 저장하기 위해서 지금의 4배 용량의 저장장치가 필요하다. 지금은 별일 아니지만 과거에는 저장 용량은 매우 큰 이슈였다. 필자가 대학생 때 샀던 PC에는 40만원 주고 산 40MB짜리 그 당시로는 대용량 HDD가 달려 있었다. 지금은 2바이트, 4바이트 유니코드가 있지만 여전히 저장 용량 절약 이슈가 존재한다. 언제가 될지 기약할 수 없지만 모든 시스템과 파일이 4바이트 유니코드 단일 체계를 사용하는 날이 올 것이다. 몇십년 후일지 몇백년 후일지는 모르겠다.
(유니코드 인코딩과 타 인코딩 간의 관계)

그럼 유니코드 관련 주요 인코딩의 특징 및 관계, 사연에 대해서 알아보자. 지금은 거의 쓰지 않는 인코딩은 알 필요가 없다.

1. MBCS (Multi Byte Character Set)

유니코드 인코딩이 아니며 ASCII에서 표현하지 못하는 각국의 문자를 표현하기 위해서 사용하는 수많은 문자세트와 인코딩이다. 보통 1,2바이트를 사용하며 ASCII를 포함한다. 나라별로 로케일별로 제각각 이어서 서로 호환이 안 된다. 아직도 수많은 Application이 유니코드 기반이 아니고 MBCS 기반이며 소프트웨어 국제화의 발목을 잡고 있다. Application이 MBCS 기반이어도 Windows는 내부적으로 유니코드(UCS-2)를 쓰기 때문에 Application과 OS를 오가며 문자열들은 계속 변환이 일어나고 이런 과정에서 예상치 못하게 깨지는 문자들이 나오게 된다. 최근의 새로운 개발 환경이나 플랫폼들은 아예 MBCS를 지원하지 않고 유니코드 Application만 제작할 수 있는 경우도 많다. 이런 환경에서만 개발하는 개발자는 MBCS가 뭔지도 모르고 자연스럽게 유니코드 Application을 만들 것이다.

2. UCS-2 (Unicode Character Set-2)

유니코드 문자세트이며 인코딩이다. Windows는 내부에서 UCS-2를 사용한다. C언어에서는 wchar_t(유니코드문자 자료형)로 표현한다. Unix나 Linux에서는 wchar_t는 UCS-4이다. Unix나 Linux에서는 한 글자에 4바이트이니 주의를 하자. 모든 문자가 깔끔하게 2바이트라서 연산이 매우 빠르다. 100바이트에는 딱 50글자가 들어간다. 하지만 ASCII 문자를 표현하기 위해서 NULL문자가 포함되어 있어서 기존의 char * 자료형에는 담을 수가 없다. 또한 문자열 관련된 수많은 함수를 다시 만들어야 했다. 파일에 그대로 기록할 경우 NULL문자로 인해서 기존의 Application이 텍스트 파일이 아닌 바이너리 파일로 인식하는 문제가 있다. 주로 Application 내부 데이터 용도로 사용한다고 보면 된다.

3. UTF-8 (Unicode Transformation Format-8)

유니코드 중에서 가장 널리 쓰이는 인코딩이다. 1~6바이트를 사용한다. BMP만 표현한다면 4바이트까지만 사용하면 된다. 특히 파일저장, 데이터 전송 등에 많이 사용한다. UTF-8의 가장 큰 장점은 위 그림에서도 보이듯이 ASCII, ISO8859-1과 호환된다는 것이다. 즉, UTF-8 문서를 처리하는 소프트웨어는 ASCII와 ISO-8859 문서를 변환 없이 그대로 처리할 수 있다. 반대로 UTF-8 인코딩으로 저장한 문서가 과거 미국이나 독일 Application에서 읽힌다. 물론 지원하지 않는 문자는 깨져서 보일 것이지만 열리기는 할 것이다. 미국과 유럽, 특히 영어권에 매우 유리한 인코딩이다. 이는 UTF-8이 가장 널리 쓰이는 이유이기도 하다.

영어로 된 문서를 저장할 때 용량이 매우 작다. 하지만 한국어(한글)는 한 글자당 3바이트로 용량이 낭비된다. 이또한 UTF-8이 인기있는 이유중 하나다.



그 외에도 UTF-8은 여러 장점이 있다. 데이터를 중간부터 보더라도 몇 바이트만 보면 UTF-8 인코딩인지 알 수 있고 데이터 손실도 거의 없다. UTF-8 데이터 구조는 처음부터 이런 장점을 가지도록 설계가 되어 있는데 이를 설계한 사람의 천재성을 엿볼 수 있다.

또 하나의 장점은 문자열 중간에 NULL(0x00)문자가 나오지 않는다는 것이다. 따라서 파일에 저장해도 바이너리 파일로 인식하는 오류가 없고, C언어와 같이 Null terminated string의 자료형에 담을 수가 있다. 이는 하위호환성에서는 큰 장점이 된다.

단점으로는 가변 바이트를 사용하여 글자 개수를 세려면 한 글자씩 개수를 세야 하는 등의 연산 오버헤드가 있다.

4. UTF-16 (Unicode Transformation Format-16)

유니코드 컨소시엄에서 제안한 인코딩이다. UCS-2와 거의 동일하기 때문에 혼동해서 사용하는 경우가 많다. 하지만 UTF-16은 가변 길이이기 때문에 2바이트 고정 길이를 지칭한 경우라면 UCS-2를 말하는 것을 UTF-16이라고 잘못 부르는 것이다. 최초의 UTF-16은 UCS-2와 동일 했기 때문에 그 잔재가 아직도 남아 있는 것 같다.
Java에서는 문자열의 인코딩에 기본적으로 UTF-16(BE)를 사용한다. Java로 개발을 할 때도 문자 인코딩을 제대로 이해하고 있어야 깨지지 않고 제대로 처리를 할 수 있다.

5. UTF-32 (Unicode Transformation Format-32)

4바이트로 고정된 UCS-4와 동일한 인코딩이다. (현재까지는) 모든 문자들이 UTF-32로 통일된다면 인코딩 변환이 필요 없는 세상이 올 것이다. 하지만 저장 공간 낭비가 심해서 인류가 저장 용량에서 완전히 해방되는 날이 되기 전까지는 유일한 표준이 되기는 어려울 것이다. 또한 UTF-8과는 다르게 데이터를 중간부터 보면 어디가 글자의 시작인지 알 수가 없는 단점이 있다.

이 외에도 수많은 유니코드 인코딩이 생겨났지만 거의 사장되다시피 했다.

Byte Order와 BOM 이슈는 나중에 다루도록 하겠다. 

국제화를 이해하기 위한 기초를 다루다 보니 여러 회가 지났는데 다음부터는 본격적인 내용과 기초를 교대로 다뤄볼까 한다.

이 글은 네이버 포스트에 게재할 입니다.