이 블로그는 방문자 통계를 위해 티스토리 기본 기능과 Woopra를 사용합니다. 원하지 않으신다면 사용하시는 웹 브라우저에 내장된 DNT 헤더를 켜고, JavaScript를 끄셔도 무방합니다.
이 블로그 방문자의 약 60%는 네이버 검색을 사용하십니다. 을 이용하시면 더 유용한 정보를 쉽게 얻게 되실 수도 있습니다. [mediatoday]
« 2018/07 »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
블로그 이미지
제가 주제인 블로그... 그냥 주제 없는 블로그입니다. 전공 분야나 예전 관심 분야 등등에 관한 글이 우선입니다만, 두어 문단을 넘길 만한 글이라면 대강 정리해 기록합니다. 학부생입니다. 트위터에서 볼 수 있습니다. http://aurynj.net/ 어­리


자바(Java)로 객체지향을 처음 접하는 사람이 꽤 흔하다. 이들이 가장 많이 하는 실수이면서 잘 풀리지 않는 의문 중 하나가 stackoverflow에 차고 넘치는 'abstract static'이다. abstract static으로 규격을 만들어 놓으면 인스턴스화할 필요는 없으면서 상속될 수는 있으므로, 프로그램 내에서 문맥의 영향을 받지 않는 메서드를 모아 쓰기 좋은 구조일 것이다. 이런 패턴은 상당히 많은 프로그램에 적용될 수 있을 것이다. 그런데 왜 그런 좋은 게 금지되는 걸까?

답은 먼 곳에 있지 않다. 자바는 객체지향 언어라는 것. 그리고 그 중에서도 클래스-인스턴스 관계를 매우 엄밀하게 따지는 언어라는 것이다. 일견 자바의 static은 C++의 static과도, Python의 classmethod와도 닮아 보이지만, Python을 배운 사람이라면 staticmethod와 classmethod가 어떻게 다른지 찾을 수 있을 것이다. 자바에서도 클래스 그 자체가 객체로 취급되기는 한다. 하지만 그것이 우리가 알고 있는 역할을 하는 객체, 즉 인스턴스처럼 능동적으로 움직이는 것은 아니다. 그렇기 때문에 static 메서드가 상속되어서는 안 되는 것이다. 비록 하위 클래스가 상위 클래스의 static 내부 메서드의 이름을 가져올 수는 있지만, 이것은 오버라이딩이 아니다. 클래스는 인스턴스를 위한 생성구조(construct)일 뿐이며, 클래스가 인스턴스의 다형성을 지원한다. 이것이 전통적인 클래스-인스턴스 관계이다.

다 끝난 얘기지만, 좀 더 자바 자체를 두고 살펴 보자. 자바에는 자기 반영성(reflective)의 기반이 되는 java.reflect 패키지가 있다. 이 패키지에는 클래스를 나타내는 java.lang.Class에서 꺼내지는 java.reflect.Field와 java.reflect.method가 있고, 이들은 임의의 인스턴스 객체를 가리키는 java.lang.Object를 자동으로 상속하는 클래스이지만, java.lang.Class는 임의의 클래스를 그 이름만으로 받지 않는다. 이를테면 이런 식이다.

private Class<? extends EntryActivity> mHostActivityClass = MainActivity.class;

여기서 MainActivity는 EntryActivity를 상속한다고 가정하자. 우리는 Class<? extends EntryActivity> 타입의 값이 MainActivity라는 이름이 아니라 MainActivity.class로 표현된다는 것에 주목할 필요가 있다. Class 타입은 그 값의 클래스적 특징만을 보여주며, 클래스가 할 수 있는 일들만을 제공한다. 따라서 mHostedActivityClass는 우리가 알고 있는 객체로서의 클래스와는 본질적으로 다르다. mHostedActivityClass는 오직 EntryActivity 클래스를 상속한 어떤 클래스 자체이며, 인스턴스를 만들고, 하위 클래스를 생성구조로 하는 인스턴스의 필드를 읽고 쓰거나 메서드를 발현시키는(invoke) 기능만을 갖고 있다. 이는 만약 EntryActivity에 어떤 static 메서드가 있더라도 마찬가지인데, java.reflect.Method에는 invoke(obj, ... args) 메서드가 있어서 static 메서드의 경우 인스턴스를 나타내는 첫 인자를 무시하는 방식이 된다. 이는 JVM 위에 올려졌을 때 완전히 동등한 바이트코드로 해석된다.

중요한 것은 Class<? extends EntryActivity> 타입의 값이 MainActivity가 아니라 MainActivity.class라는 점에서, mHostActivityClass 자체에서 어떤 static 클래스를 꺼내서 쓸 방법이 없다는 것이다. EntryActivity.DoStaticWork();이지, EntryActivity.class.DoStaticWork();가 아니지 않은가? 이는 mHostActivityClass가 하나의 오브젝트같지만, 실제로는 JVM이 객체지향을 구현하는 고전적인 방식에 완전히 의존적인 일종의 데이터 구조일 뿐이며, 이는 메서드를 가져다 쓸 때에도 같은 방식을 취해야 한다는 것을 의미한다. 따라서 static 메서드는 오버라이드될 수 없다. 오버라이드는 인스턴스가 자기 자신을 알고 있을 때 다형성을 만드는 방법이기 때문이다. MainActivity.class가 자기 자신을 알 리가 없지 않은가! 그렇다고 Class<? extends EntryActivity>같은 무언가가 MainActivity의 이름을 받게 언어를 구현하는 것이 가능할까? 적어도 자바에서는 그럴 리가 없다.

내가 말하고자 하는 것은 자바의 방식이 전적으로 맞다는 것이 아니다. 세상은 디자인 패턴으로 이루어져 있지 않다. 실제로 일을 나눌 때에는 static과 non-static을 구분할 때 자바의 고전적인 객체지향 논리를 적용해야 한다는 것이 정말로 방해가 된다. 그나마 그것을 헤쳐나가는 방식이 수많은 패턴으로 굳혀져 왔고, 자바는 지금 패턴을 바탕으로 업계에서 가장 선호되는 언어 중 하나의 입지를 놓치지 않고 있다. 그러나 패턴을 어떻게 썼느냐로 코드를 판단할 수 있는 만큼, 그 패턴들이 실제로 적용된 곳은 이래도 그만 저래도 그만인 억지가 있는 것도 사실이다. 중요한 것은 컴퓨터에게 일을 어떻게 시키느냐와, 사람이 그것을 언제 어떻게 다루느냐에 있다.

하지만 abstract와 static은 모두 객체지향의 키워드인 만큼 abstract static은 금지되는 것이 맞다. 자기 반영성이 훨씬 유연하고 견고한 언어인 C#에서도, 자바와 같은 원리가 작용해서 abstract static은 불가능한 것이 된다. 또한 자바에서 invoke()를 남용하면 큰 코 다칠 수 있다는 사실도 유념해야 할 것이다. 사실 이 문제는 객체지향에서만 발생한 것은 아니지만, 잘 보면 C에서는 대충 void *로 다루고 예의 상 규약을 지켜 줬기 때문에 어떻게든 설계가 가능했다. 어딜 가나 type system이 문제가 되는 것이다. 만약 함수형 언어가 지금처럼 상승세를 타면서, F#이나 하스켈(Haskell)같은 언어가 미래에 지금의 naive한 함수론을 버린다면, 언젠가는 프로그래밍 언어에서 고계(high-order) 함수와 고계 컴파일러 매크로를 포함하는 견고한 현대적 타입 시스템의 입지를 구축할 수 있지 않을까. 물론 먼 미래의 이야기다. abstract static으로 일을 나누는 것도 그만큼이나 먼 꿈이다.

수제 핸즈프리 케이블. 구조 자체는 쉽기 그지없지만 처음으로 실용적인 것을 바닥부터 만들다 보니 희한하게도 만드는 데 공이 상당히 들었다. 고등학교 때 쓰던 알량한 핸즈프리 케이블이 박살났는데 비싼 가격에 같은 것을 사기 싫다는 단순한 이유로 만들게 되었다. 고등학교 졸업할 때쯤부터 안 쓰게 되어 집구석에 박아 두고 잊었던 것을 엊그제 재발견했는데, 때가 무지막지하게 껴 있더라. 마침 주변에 있던 물티슈로 박박 닦아서 거진 새 케이블을 만들었다. 사실 처음 재료를 구입할 때부터 케이블과 플러그 팁에 때가 상당했는데, 헝겊과 휴지로 닦으려 해 봐도 영 안 되길래 그냥 불량한 물건이려니 했던 것이 오래도록 때가 축적된 것이다. (...) 덕분에 물티슈의 위력을 절감했다.

이 케이블은 큐리텔 U-5000 휴대폰의 핸즈프리 케이블을 본딴 것으로, 흔히 핸즈프리 케이블이 그렇듯 4극 TRRS 오디오 플러그와 볼륨 부속, 모노 마이크로폰, 3극 TRS 오디오 리셉터클만을 포함한다. 다만 웃기는 게 있다. 휴대폰 쪽 4극이 지름 2.5mm이다... 아마 아무 3.5mm 이어폰이나 넣지 말라는 뜻인 것 같은데, 3.5mm 4극 핸즈프리 리셉터클에 3극 이어폰을 문제 없이 쓰도록 설계하기는 일도 아니기 때문에 사실상 의미가 없다. 당장 오늘날의 수많은 안드로이드 폰과 아이폰이 그렇지 않은가.

즉 문제가 하나 남았는데, 내게 2.5mm 4극의 의미는 모조품을 만들기 어렵다는 사실과도 같았다는 것. 이 지름 2.5mm 4극 플러그와 적당한 케이블을 구하는 어려움이 아주 고약했다. 3.5mm 4극도, 2.5mm 3극도 3.5mm 3극만큼이나 흔했지만 2.5mm 4극만은 그렇지 않았다. 게다가 전주에 살다 보니 희귀한 부속을 구하기가 어려웠을 뿐더러 인터넷 부품 쇼핑몰에서도 2.5mm 4극은 거의 파는 곳이 없었다. 최근에 용산 쪽에서 큰 가게들을 돌며 시험 삼아 찾아 봤는데도 못 찾았으니 말 다 했다. 만들 당시에는 결국 오디오 케이블 전문점에 가서 공장에 주문해 재고를 가져 왔다. 그리고 무경험자로서 이 6개월 간의 사투에 질려 더 이상 무언가를 만들지 않았다.

덧붙여, 아는 사람은 알겠지만, 위 사진은 바로 그 U-5000이다. 국내 유통 휴대폰의 경쟁력을 퍽 잘생긴 갈라파고스로 만들어 버린, 해묵은 위피(WIPI) 의무 탑재 제한이 풀리면서 나온 휴대폰 모델이다. 이 기종은 무선 인터넷 기능이 완전히 없었던 덕에 초기 호황을 누렸다가 바로 효도폰과 학생폰으로 둔갑했던 슬픈 이력이 있다. 왜 위피가 없다고 게임뿐만 아니라 인터넷이 안 돼야 했는지는 아직도 의문이지만, 아마 통신사들이 아직도 붙잡고 있는 자체 생태계 싸움과 연관이 있었겠지 하는 추측이 가능할 뿐이다. 그런데 난 그 때부터 웹 브라우저 켜서 구글이나 트위터 접속하고 웹툰도 봤는데... 자기들이 MS 정도의 파급력이라도 있으면 모를까, 심지어 MSN도 실패했는데 지금처럼 되는 건 당연했다. 그렇다고 지금이 썩 좋은 생태계도 아니라 요모조모 따지자면 우리나라 소비자들만 불쌍하다.

이 기기의 장점이 몇 가지 있었는데 나는 USB 전화 모뎀 기능이 그 중 최고였다고 생각한다. 요즘은 안드로이드 폰에 USB 테더링과 와이파이 테더링이 빠지는 건 말도 안 되지만, 망 중립성같은 건 잘 알려지지도 않았을 때라 데이터 통신과 전화와 SMS가 같은 망을 쓴다는 개념은 대중적으로 생소했을 뿐이다. 하지만 이 모델에서는 폰에서 모바일 네트워크를 못 쓰니 사실상의 보완 기능으로 전화모뎀을 쓰게 되었던 것이다. 그 밖에도 이 모델에는 MP3 파일을 제조사/통신사 변환 없이 넣어도 된다거나, 테스트 모드의 자유도가 높다거나, USB 스토리지 기능이 충실하다거나 등등의 장점이 수두룩했다. 그러나 결국 이 못생긴 모델은 흥행에 실패하고 그냥 상징적인 과거로 남았다. 물론 위피와 비-위피 간의 싸움에는 패자만이 남았을 뿐이다.

지금까지 10년을 못 가는 국책 연구에 몸바친 연구원 분들이 떠오르면 두말할 것 없이 마음이 아프다. 하지만 이 지겨운 이야기의 방향성을 조금 달리하기로 한다. 나는 우리나라에서 연구 자금이나 산출물이라는 게 흘러 가는 흔한 모양새에 비추어 보아, 국가 주도 연구 프로젝트의 상징적 장점을 그럭저럭 높게 사는 편이다. 그럼에도 불구하고 위피나 공인인증서처럼 한국형으로 시작해서 한국형으로 결론지어지는 프로젝트에는 아주 신물이 난다. 사실 무엇을 연구하든 국제 표준보다는 앞장서려고 하기 마련인데, 이런 연구의 최종 산출물은 보통 패션 센스가 모자라거니와, 나름 잘 만든 것마저 정치적인 이유로 세계에 공유되지도 않으니 학계는 뭔가 꼬리가 보일 때 나라를 위해 뒤쫓아 가는 셈이다. 이따위로 하려면 위대한 령도자 수령님의 계시로 만든 우수한 아무개와 다를 게 뭔가? 사실 이런 비판을 하찮은 내가 뱉을 수 있지는 잘 모르겠지만, 그냥 그렇다고.

'System Idle Talks > 흔한 자산 목록' 카테고리의 다른 글

만화로 본 발명·특허 이야기 (2001)  (0) 2013.10.22
수제 핸즈프리 케이블  (0) 2013.07.29
2004년 전북교육청 수월성교육 교재  (0) 2013.07.20
이상한 입체퍼즐  (2) 2013.07.20
화학 사전들  (0) 2013.07.14
첫 디카의 흔적  (0) 2013.07.13

안드로이드 SDK에서 l10n은 strings.xml을 이용해 이루어진다. 해외 법인이 따로 있는 회사에서 앱을 개발하는 경우에는 아예 소스 코드를 넘겨서 별도의 앱을 만들어 버리는 경우가 많지만, 규모가 그렇게 크지도 않고 문자열 외의 대대적인 로컬화가 필요가 없다면 res/values locale postfix modifier 사용은 필수적이다. 이럴 경우 트리 메인테이너에게는 언어별 strings.xml을 관리해야 한다는 부담이 작용한다. 개발이 항상 선형으로 이루어질 수는 없기에, 로컬화 담당자가 strings.xml을 들고 왔을 때 무엇이 누락되었을지 찾기 어렵기 때문이다. 주로 이런 문제는 담당 로케일의 QA가 운좋게 찾아내는 게 고작이다.

오픈소스의 경우 얼마나 많은 타깃 언어가 만들어질 것인지 아무도 모르기 때문에 분위기가 다소 자유롭지만, 강제적인 분위기일 뿐 문제는 더욱 귀찮다. 이를테면, 주 언어에서 제거된 항목은 다른 모든 언어에서도 제거되어야 한다. 주 언어에서 새로 만들어진 항목은 다른 언어에도 반영되어야 한다. K-9 Mail의 경우 전자는 perl을 사용해 메인테이너가 관리하고 그 다음에는 번역자가 협조하도록 되어 있으나, 후자에 대해서는 사실상 대책이 없다. 새로 항목을 만들면서 다른 언어마다 주석을 만들어 주는 것도 한계가 있는 것이다.

아래는 K-9 Mail에서 항목을 모두 삭제할 때 사용하는 스크립트이다.

find . -name strings.xml | xargs perl -pi -e's!^\s+<string name="string_name_here".+?</string>\s*\z!!'

다음은 내가 쓰는 스크립트이다. 언어마다 어떤 항목이 들어있는지 수동으로 점검하는 방법이다.

ls res | grep values | while read -d $'\n' fn; do echo $fn; echo `cat res/$fn/strings.xml | grep string_name_here`; done

그러나 이것도 결국 문제가 한두 개일 때 얘기이고, 결국 주 언어와 대상 언어 간에 직접 비교를 할 수밖에 없다. 그래서 찾아낸 방법이다.

  1. 자바스크립트 콘솔을 지원하는 웹브라우저로 두 XML 파일을 연다. 나는 크롬을 사용한다.
  2. var s = document.getElementsByTagName('string')
  3. var a = []; for (var i = 0; i < s.length; i++) a[i] = s[i].getAttribute('name');
  4. a = a.sort().join('\n');
  5. 결과를 텍스트 파일로 저장해서 diff를 돌린다.

결국 name의 정렬된 목록을 뽑아 내서 비교하는 것이다. diff만 가능하다면 자바스크립트만으로 해결하는 방법이 있을 텐데, 없어서 약간 번거로울 수 있다. node를 사용하면 완전 자동화가 될지도 모르겠다.