2015년 2월 1일 일요일

리눅스상 C/C++ 빌드툴 정리.

"빌드툴이 무엇이다" 라는 것까지는 알고 있는 사람들을 대상으로 리눅스 환경에서 고려될만한 빌드툴들을 가벼운 마음으로 경험상 비교한 것.

크게 보면 다음 세 가지 카테고리로 먼저 분류를 하게 된다.
  1. 직접 cc 명령어로 컴파일하고 링크함. 예제파일 이상의 수준에서 쓸 일 없음.
  2. Makefile을 작성하고 make 명령어로 1을 수행함.
  3. Makefile을 만들어주는 프로그램(예-autotools)을 이용해서 2→1을 수행함.
여기서 1은 대체할 툴이 어쩌고 할 여지가 없고,
2에서 make 대안으로 언급될만한 것은 ninja,
3에서 autotools의 대안으로 언급될만한 것은 cmake, 그리고 내 경우에는 gyp, gn 등


ninja와 make를 비교하면 가장 중요한 차이는 make는 스스로 빌드툴 역할을 하는 상황을 상정하지만 ninja는 하지 않는다는 것이다. make의 경우에 make 자체가 빌드 시스템의 프론트엔드인 경우, 다시 말해서 Makefile을 인간이 직접 작성하는 것을 전제하고 만들어져서 소스가 되는 파일을 눈으로 읽어가면서 작업하는 것이 그래도 할만하고 조건부 빌드같은 기능도 필요한 것으로 간주되어 있다.

반면에 ninja의 경우에는 정말로 빌드를 위한 의존관계 정리만으로 할 일을 최소화하고 있다. 다시 말해서 autotools나 cmake의 백앤드로 작동하기 위한 것이지 스스로 빌드툴 역할을 할 생각은 없다는 것이다. 컨디셔널 빌드? 각종 프로그램적 커스텀? 그건 다 cmake같은 툴에서 ninja 파일을 생성하는 시점에 할 일이다. ninja는 그저 기계적으로 빠르게 의존관계를 처리해서 빌드만 하면 된다. 그리하여 ninja의 리소스파일은 유닉스 텍스트 파일이지만 사람 눈으로 읽기에 친절하게 하려는 목표를 가지고 있지 않다. 그리고 실제로 make와 속도의 차이가 난다고 한다.


그 뒷 단계의 autotools/cmake 등을 비교하면..

autotools는 첫번째 난관은 진정 제대로 쓰려면 m4라는 언어 하나를 더 배워야 한다는 점이다. 개인적으로 "순수하게 declarative한 언어의 예시"로 언급하는 이상으로는 이 언어를 활용하지 못하고 있다.

두번째로는 autoconf-autoreconf와 automake의 이원화된 시스템이라는 점.

세 번째는 첫번째와 두번째의 시너지가 일으키는 복잡성.

마지막으로는 빌드 관리툴이라기보다는 GNU 시스템의 타볼 패키지를 생성하는 도구에 가깝다는 점이다. 다시 말해서 autotools를 기반으로 한 소스코드 디렉토리는 다양한 환경을 지원하는 프로젝트의 소스코드라기보다는 GNU운영체제의 배포용 패키지에 가깝다. GNU 운영체제가 나름 다양한 타겟 아키텍쳐를 지원하지만 근본적으로 GNU 밖에서 쓰기에 적절하지 않다. 윈도우 환경에서 빌드하는 데 악명이 높다.

cmake는 그냥 무난하고 대세스러운 느낌. 특별히 설명할 말을 모르겠다. 특히 지금처럼 잘 알지도 못하는 상황에서는.


지금 무슨 프로젝트를 시작하게 된다면 cmake/ninja가 내 선택이 될 것 같다.


가벼운 인상비평들.

2014년 12월 29일 월요일

페도라에서 pointing stick 휠 사용하기

$ cat /etc/X11/xorg.conf.d/20-thinkpad.conf Section "InputClass" Identifier "Trackpoint Wheel Emulation" MatchProduct "TPPS/2 IBM TrackPoint|DualPoint Stick|Synaptics Inc. Composite TouchPad / TrackPoint|ThinkPad USB Keyboard with TrackPoint|USB Trackpoint pointing device|Composite TouchPad / TrackPoint" MatchDevicePath "/dev/input/event*" Option "EmulateWheel" "true" Option "EmulateWheelButton" "2" Option "Emulate3Buttons" "false" Option "XAxisMapping" "6 7" Option "YAxisMapping" "4 5" EndSection

2014년 10월 8일 수요일

어째서 에러리턴을 예외처리보다 선호하는가.

대체로 아래 링크들에서 하는 이야기들의 재탕이다.
http://blogs.msdn.com/b/oldnewthing/archive/2005/01/14/352949.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2004/04/22/118161.aspx

아래 링크에서는 Joel Spolsky의 코멘트 부분 역시 근본적으로 같은 지적.
http://nedbatchelder.com/text/exceptions-vs-status.html

내 코멘트를 아주 간략하게 덧붙이자면, 예외처리는 "방금 벌어진 일이 무엇인가?"가 아니라 "무슨 종류의 문제가 생겼는가?"를 기반으로 동작하도록 되어 있는데, 이것은 declarative한 코드에 어울리지만 예외처리라는 기능이 그 자체로 코드를 declarative하게 만들 능력은 없으므로, Imperative한 언어에서는 에러리턴이 맞는 방식이라는 것이다.

일전에 트위터 타임라인에서 "어째서 exception이 더 깔끔한가?" 라는 주제로 글이 올라왔었는데, 솔직하게 말하자면 그 예제 자체가 엄밀한 예외처리가 아니었고, 어째서 내가 예외처리를 싫어하는가를 단적으로 보여주는 예였다. 글을 찾기 귀찮아서 거의 equivalent한 유사코드를 써보자면,

try {
    file_object = file_open("file_name"); // 파일을 연다. 예외 발생 가능성 있음.
    file_object.read(BUFFER_SIZE, buffer); // 파일을 읽는다. 예외 발생 가능성 있음.
    // 읽은 내용으로 뭔가를 한다.
    file_object.close();
} catch (Exception e) {
    handle_file_exception(e);
}

이와 대비되는 에러코드 리턴의 유사코드를 써보자면,

open_error = file_open("file_name", file_object_pointer);
if (open_error != NULL) {
    handle_open_error();  // 이 구문만을 보자면 별로 할 일 없음.
    return;
}

read_error = file_object_pointer->file_read(BUFFER_SIZE, buffer);
if (read_error != NULL) {
    handle_read_error();  // file_close() 동작 포함.
    return;
}

// 읽은 내용으로 뭔가를 한다.
file_object_pointer->file_close();

이런 형태가 될 것이다. 따라서 전자가 깔끔하다는 논리인데,

문제는 어차피 handle_open_error()와 handle_read_error()는 다를 수밖에 없다는 점이다. 위의 사례를 가지고 말한다면 handle_open_error()에서는 그냥 나가면 되지만 handle_read_error에서는 이미 파일은 열린 상태이므로 file_close()가 되어야 한다. 저 handle_file_exception() 내부는 에러리턴 방식의 코드보다 훨씬, 훨씬, 훨씬 더 fugly 하게 된다는 점이다.

func handle_file_exception(e) {
    if (e instanceof FileOpenExeption) {
        handle_open_error();
    } else if (e instanceof FileReadExeption) {
        handle_file_error();
    }
}

...

쳐다 보기도 싫다. -_-

...

instanceof가 둘이나 들어갔다. 그나마도 런타임에 오브젝트에 대한 힌트를 얻을 수 있는 언어라고 가정할 때 이야기다.

여전히 handle_open_error()와 handle_file_error()는 필요하다. 이것이 exception 방식의 깔끔함인가? -_-

조엘이 지적한 같은 문제의 다른 측면도 추가해보자. 예외처리 방식은 어느 라인에서 try 블럭을 빠져나오는 것인지 명시적으로 보이지 않는다. 그리고 그것은 위의 func handle_file_exception(e);의 같은 추함의 다른 측면을 가리키는 것이다.

finally 도 생각보다 무력하다. finally절에 들어갈 코드 역시 코드가 어디까지 진행되었느냐에 따라서 다르게 동작해야 한다. db를 열다가 실패했을 때, db까지는 열렸는데 쿼리를 하다가 실패했을 때, 쿠리까지는 성공했는데 다른 뭔가를 하다가 실패했을 때, 그때마다 해야 할 일이 다르다. 진짜로 무조건 실행되는 구문이라면 겨우 두세줄 이하이거나, 아니면 finally 구문 안에서 코드 진행을 유추할 수 있는 흔적들을 찾아서 if-else로 씨름할텐데 역시 추하기는 마찬가지.

한편 위의 일견 깔끔해보이는 코드를 가지고 exception을 비난하는 것은 부당하다. 예외처리 코드로서도 잘못되었기 때문이다. 잘못 짠 예외처리 구문을 가지고 예외처리를 비난하는 것은 부당하지 않은가. 자바의 경우 catch(Exception e){} 구문은 DO NOT 목록의 대표적인 아이템이다. exception의 방식으로 위의 문제를 다룬다면,

try {
    file_object = file_open("file_name"); // 파일을 연다.
    file_object.read(BUFFER_SIZE, buffer); // 파일을 읽는다.
    // 읽은 내용으로 뭔가를 한다.
    file_object.close();
} catch (FileOpenException e) {
    handle_file_open_exception(e); // 별로 하는 일 없음.
} catch (FileReadException e) {
    handle_file_read_exception(e); // 파일 클로즈 포함해야 함.
}

이 코드 자체는 맞는 코드라고 할 수 있을 것이다. 그러나 여전히 코드 진행 도중에 점프가 일어나는 문제는 그대로이다. 열기 단계에서 문제가 일어났을 때, 읽기 단계에서 문제가 일어났을 때, 코드가 위에서 아래로 라인단위로 진행되지 못하고 goto가 발생한다. 위의 코드는 코드 흐름만을 보이기 위해서, read를 버퍼 사이즈만큼 한 번만 읽고 말 정도로 각 단계가 매우 짧게 축약되어 있다는 점을 상기하자. 위로 다시 올라가지 않는 goto는 경우에 따라서 조심스럽게 쓸 수도 있다고 생각하는 쪽이지만 이 경우에 에러리턴 방식에서는 필요 없었던 점프가 등장해서 얻게 된 실익이 무엇인가? 올바른 에러처리에 오면, 이미 에러리턴 방식보다 코드가 짧다고도 할 수 없다.

상황을 좀 더 복잡하게 만들어보자. 동일한 예외가 다른 두 지점에서 발생할 수 있는 상황을 가정해보자.

  1. 파일1을 특정 지점까지 읽는다.
  2. 거기까지 읽은 내용에 기반해서 파일2를 읽는다.
  3. 파일2에 써 있는 내용을 기반으로 파일 1을 계속 읽을지 결정한다.
try {
    file_object1 = file_open("file_name1");  // 문제1
    read_count = file_object1.read(BUFFER_SIZE, buffer); // 문제2
    file_name2 = extract_filename_from_buffer(buffer, read_count);
    file_object2 = file_open(file_name2"); // 문제3
    read_count = file_object2.read(BUFFER_SIZE, buffer); // 문제4
    file_object2.close();
    if (should_read_more(buffer, read_count)) {
        read_count = file_object1.read(BUFFER_SIZE, buffer); // 문제5
        // 추가로 읽은 내용으로 뭔가를 한다.
    }
    file_object1.close();
} catch (FileOpenException e) {
    // 지점 1, 3에서 발생한 문제 해결
} catch (FileReadException e) {
    // 지점 2, 4, 5에서 발생한 문제 해결
}

위의 경우에 FileOpenException의 캐치 구문은 문제 1, 3을 해결해야 하고, FileReadException의 캐치 구문은 문제 2, 4, 5를 해결해야 한다.

문제1 발생시 - 할 일 없음.
문제 3 발생시 - 파일1을 닫아줘야 함.

문제2 발생시 - 파일1을 닫아줘야 함.
문제4 발생시 - 파일1, 2를 닫아줘야 함.
문제5 발생시 - 파일1, 2를 닫아줘야 함.

이 지경이 되면, 각각의 catch 블락은 어떤 상황인지를 파악하느라 if-else에 무슨 객체가 널인지 아닌지 등등을 체크하느라 헬이다. 그것도 문제가 일어난 코드와 점프로 멀찍이 떨어져서. 저런 문제를 피하기 위해서 당장 떠오르는 방법은.. try 구문의 중첩이나, 아니면 위의 "올바른 예외처리" 형태처럼 될 수 있도록 try 블락 하나에서 파일을 하나만 여닫는 것이다. 그러면 자주 여닫게 될 것이다. fd가 로컬 파일이 아니라 TCP 통신이거나 한 상태라면 이 옵션은 불가능할 것이다.

수미상관으로, 예외처리가 과연 유용할까? 유용할 수 있다고 생각한다. 고도로 declarative 하게 짜여진 코드라면 그럴 수 있다. 그러나 imperative한 언어에서, 예외처리로 올바르게 예외를 처리하는 것은 극히 어려우며 주로는 잘못 해결한 문제를 감추는데 훨씬, 훨씬 더 유용하다.

2014년 9월 25일 목요일

안드로이드 환경의 웹브라우저들.

대충 4.0 이후만 취급해서..


1. 웹브라우저 - 런쳐에서 아이콘 클릭해서 열리는 브라우저 얘기. 크롬. 최신 버젼 사용 가능.

2. WebView - android.webkit.WebView. 경우의 수가 나뉨.

  • Android API level 19 (킷캣) 이상에서는 최신 크롬 기반.
  • Android API level 18 이하에서는 꽤 낡은 버젼의 웹킷.
3. 시스템에 기본으로 들어 있지 않은, chromium 프로젝트에서 자기가 직접 싸제로 WebView 빌드하는 경우 - 최신 버젼의 WebView. 그러나 그래픽 가속이 불가능.

4. 시스템에 기본으로 들어 있지 않은, chromium 프로젝트에서 빌드한 최신버젼의, 그래픽 가속 되는 WebView - 저희 회사에 문의주세요. 한 번 만들어 보죠.

2014년 7월 8일 화요일

Material Design 단상

개인적으로는 언제부터인가 몇 번이나 말해온 내용을 좀 더 공식적이고 권위있는 목소리로 말해줘서 무척이나 감격스럽다.


나는 이 영상을 계기로 ux에 대해서 '직관성', '직관적인' 등의 단어가 무분별하게 남용되지 않기를 바란다. 이 영상은 애플의 스큐어몰피즘이 어째서 작동하는 매커니즘인지를 그런 모호한  단어들보다 훨씬 잘 설명한다.


영상의 최초의 키워드는 메타포(은유)이다. 메타포는 서로 공유하는 경험, 짧은 스토리를 통한 강력하게 함축된 전달이다.

안드로이드의 Material Design이 택한 재질(Material)은 종이였다. 인류가 천 년을 사용해 온 재질을 은유하되 그보다 강력한, 마음대로 늘어나고 줄어들고 붙었다 떨어지는 마법의 종이.

매터리얼 디자인은 그림자 효과와 높낮이를 활용한다. 클릭될 수 있는 것은 돌출되어서 클릭될 수 있다고 알려주어야 한다.

매터리얼 디자인은 애니메이션을 활용한다. 현실세계에 워프와 순간이동은 거의 관측되지 않는다. 사물은 과정을 통해서 변해야 한다.

이러한 디자인 '언어'는 직관을 가능하게 된다. 종이, 누를 수 있을 것 같아 보이는 돌출, 애니메이션, 만질 수 있음 (tangible) 등등은 무엇이 될 것 같은지를 알 수 있게 한다.


따라서 애플의 스큐어모피즘이 작동하는 이유를 설명할 단어가 '직관성'이겠는가 메타포(은유) 이겠는가?


뮤직플레이어에 나오는 버튼을 클릭하자 버튼이 확대되면서 플레이 제어 화면이 되는 장면은 매터리얼 디자인이 스큐어모피즘보다는 좀 더 추상적이고 고차원적인 은유임을 보여준다. 스큐어모피즘은 주로 우리가 아는 사물을 모사하는데서 직관을 발생시킨다. 진짜 오디오처럼 동작함으로써 동작을 예측 가능하게 했을 것이다.

그러나 위의 예시는 현실에서 가능한 동작이 아니다. 그것은 추상적으로, 버튼이 확장된다는 점, 바로 그 버튼이 확장된 종이 위에 UI가 펼쳐졌다는 점에서 해당 버튼과 같은 맥락 위에 있다는 것을 보여준다. 이것은 현실의 모사가 아니고, 추상적, 논리적인 컨텍스트의 발생이다.


얼마나 많은 디자이너들이 이것을 이해할까? 잘 모르겠다. 스스로를 ux 디자이너라고 주장하는 사람들은 이제 매터리얼 디자인의 빌딩블럭을 손에 쥔 이상 정말로 자신이 디자인하는 것이 단순한 겉모양이 아니라 ux임을 증명해야 할 것이다. 그들은 애니메이션을, 그 애니메이션의 컨텍스트를 디자인해야 할 것이다.

ux의 흐름 그 자체에 대해서 별다른 생각을 해보지 않은 채, 그저 iOS 디자인을 열화카피한 디자인을 안드로이드에 적용시키는 기획자와 일하는 것은 꽤나 재미 없는 작업이었다. 난 극단적으로 냉소적인 사람은 아니지만, 솔직히 말하자면 이런 명료한 프레젠테이션이 발표된 이 순간 이후에도, 여전히 그런 기획자들이 대다수를 차지할 것이라고 생각할 정도로는 냉소적이다.

2014년 5월 31일 토요일

Android 개발자로 일하기 위해서 필요한 지식들

한동안 안드로이드 앱 개발자로 일하면서 생각했던 단계별로 필요한 지식들, 안드로이드 개발을 막 시작한 부사수가 있다면 이런 과정을 거치도록 유도할 것이라고 생각했던 것들입니다.

1단계 - 아직 일 못 맡기지만 좋은 시작

2단계 - 간단한 작업은 맡겨도 됨

  • UI thread를 이해할 것
  • race condition, deadlock, visibility 등을 이해하고 위의 세 가지 문제가 없는 AsyncTask를 작성할 수 있을 것.
  • 코딩 원칙 체화 - 디자인 패턴, SOLID, 메소드 짧게 짜기, 한 메소드 내에서 각 statement 들의 추상화 정도가 비슷한 수준을 유지하게 하기 등등
  • Activity, Service의 라이프사이클을 이해하여 이 부분에서 문제가 되는 해괴한 코드를 짜지 않음. (static 변수에 의한 leakage예시가 대표적)
  • http://developer.android.com/guide/index.html 에 있는  내용을 대체로 숙지

3단계 - 같이 일하기 즐거운 동료

  • LOGCAT, adb shell top, hprof, 프로파일러 등등을 이용해서 메모리 사용, CPU-배터리 사용을 최적화할 수 있음
  • Mark and Sweep 을 이해하고, Activity 내의 static 변수가 발생시킬 수 있는 과도한 메모리 사용, 비트맵의 recycle() 이슈 등 이미 잘 알려진 메모리 관련 이슈들을 알고 있음.
  • 최신 유행하는 오픈소스 라이브러리 동향을 파악하고 있음. 이미지 로더 짜놓고 나서 volley를 발견한 멍청하고 아픈 과거가 있음.
  • jni 를 이용하여, 네이티브에서 자바 오브젝트와 메소드을 접근하여 사용할 수 있음

4단계 - 내가 배워하는 분들

  • Android 내부를 잘 이해하여 zygote, Binder 등을 막힘 없이 설명할 수 있음.
  • aidl을 활용함
  • GC의 동작 특성을 이해하여 locality를 고려한 코드를 짤 수 있음

2014년 5월 2일 금요일

emacs + Golang

go-settings.el

(add-to-list 'load-path "~/.emacs.d/go-mode" t)
(require 'go-mode-load)

(add-hook 'go-mode-hook (lambda ()
                          (local-set-key (kbd "C-c C-r") 'go-remove-unused-imports)))
(add-hook 'go-mode-hook (lambda ()
                          (local-set-key (kbd "C-c i") 'go-goto-imports)))
(add-hook 'before-save-hook 'gofmt-before-save)

(add-to-list 'load-path "~/.emacs.d/go-autocomplete" t)
(require 'go-autocomplete)

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(magit-item-highlight ((t nil)) t))

;; go-flymake & go-flycheck
(add-to-list 'load-path "~/.emacs.d/go-flymake")
(require 'go-flymake) 

 

 .emacs 에 추가될 사항

(load "~/.emacs.d/go-settings")