스토리 홈

인터뷰

피드

뉴스

조회수 3175

eventlet을 활용한 비동기 I/O 프로그래밍

안녕하세요. 스포카 크리에이터팀 문성원입니다. 현대적인 프로그래밍 환경에서 네트워크는 더는 특정 직군의 개발자만 접하는 분야가 아닙니다. 그런 만큼 대량의 요청을 네트워크를 통해 송수신하는 프로그램이 생각보다 성능이 나오지 않는 경우를 경험하신 분들도 많으실 겁니다. 물론 스포카 개발팀도 예외는 아니었습니다. 그래서 오늘은 저희의 이러한 경험과 그 해결책-eventlet을 통한 비동기 I/O(Asynchronous I/O)-에 대해 소개합니다.Why우선 스포카 개발팀에서 겪었던 문제부터 시작하죠. 얼마 전 페이스북(facebook)의 FQL(Facebook Query Language)를 통해 정보를 수집해서 이를 활용하는 기능을 작성해야 했습니다. 기존의 함수들은 필요할 때마다 FQL을 요청하는 방식이었고 당연히 이건 너무 느렸죠. 그래서 생각한 것이 “하루의 일정 시간마다 대량의 FQL 요청을 보내서 필요한 정보를 미리 갱신시켜놓자.”였습니다. 여기까진 좋았죠. 이때 제가 작성한 코드의 얼개를 살펴보면 대강 이렇습니다.# 페이스북 계정들을 가져와서 반복하면서for account in FacebookAccount.query:    account.update() #FQL을 보내자.view rawgistfile1.py hosted with ❤ by GitHub그런데 문제가 있었습니다. 기존의 FQL을 보내는 FacebookAccount.update()는 FQL요청이 완료될때까지 멈추고 기다립니다. 대부분의 FQL요청이 2, 3초 정도 걸린다고 했을 때 이러한 지연은 매우 치명적입니다. 대안이 필요했고 자연스레 떠오른 것이 서두에 소개한 비동기 I/O(Asynchronous I/O)였습니다.Asynchronous과거 일부 고급 서버 개발자만 알고 있는(혹은 알아야 하는) 기술로 치부되던 ‘비동기(Asynchronous)’란 개념은 2000년대 들어 등장한 Ajax(Asynchronous JavaScript and XML)의 성공 이후 많은 개발자에게 강한 인상을 줬습니다. 사용자는 HTTP 요청이 끝날 때까지 멈추어 있는 하얀 화면으로부터 해방되었고, 다양하고 많은 요청과 응답들이 자연스럽게 서버로 흘러들어 가서 나왔습니다. 개발자들의 이러한 경험과 통찰은 이후 node.js와 같은 플랫폼의 등장에도 많은 영향을 끼쳤습니다.다시 문제로 돌아가죠. 그렇다면 이러한 비동기에 관한 개념은 위의 상황을 어떻게 해결할 수 있을까요? 문제의 원인부터 다시 살펴봅시다. 2, 3초 정도씩 걸리는 FQL 요청이 문제일까요? 물론 요청이 매우 빨리 처리된다면 별도의 처리 없이도 저 코드는 문제없이 동작합니다. 하지만 현실적으로 이런 I/O의 속도를 빠르게 하는데에는 물리적으로 한계가 있습니다. 오히려 여기에서 주목해야 할 점은 ‘2, 3초’ 보다 ‘기다린다’라는 점입니다. FacebookAccount.update() 같은 경우, I/O가 처리되는 동안 CPU는 하던 일을 멈추고 문자 그대로 기다리게 됩니다. 만약 CPU가 멈추지 않고 다른 요청을 보낸다면 어떨까요? 이렇게 말이죠.비동기만으로는 부족하다?이러한 아이디어는 그동안 많은 개발자가 대량의 I/O를 다루는 올바른 방식으로 여겨왔습니다. 하지만 보통 이러한 비동기 I/O를 통한 구현은 동기식 I/O와는 좀 다른 형태를 띠게 됩니다. 이렇게 말이죠.# http://docs.python.org/library/asyncore.html#asyncore-example-basic-http-clientimport asyncore, socketclass HTTPClient(asyncore.dispatcher):    def __init__(self, host, path):        asyncore.dispatcher.__init__(self)        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)        self.connect( (host, 80) )        self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path    def handle_connect(self):        pass    def handle_close(self):        self.close()    def handle_read(self):        print self.recv(8192)    def writable(self):        return (len(self.buffer) > 0)    def handle_write(self):        sent = self.send(self.buffer)        self.buffer = self.buffer[sent:]client = HTTPClient('www.python.org', '/')asyncore.loop()view rawgistfile1.py hosted with ❤ by GitHub불행하게도, 이 경우 기존에 사용하던 urllib2대신 HTTP 요청을 처리하는 핸들러를 이처럼 재작성 해야합니다. 거기에 FacebookAccount.update()의 호출 방식마저 바뀔 수 있죠. 더군다나 콜백(Callback) 투성이의 코드는 유지보수가 쉬어 보이지도 않습니다. 여러모로 손이 많이 가는 상황이죠.결국, 기존 코드를 최대한 수정하지 않으면서도, 어느 정도 성능은 보장되는 그런 해결책이 필요했습니다. 그런 해결책이 있을까요? 다행히도 그렇습니다.What저희가 해결책으로 택한 eventlet은 Python(정확히는 CPython)에서 코루틴(Coroutine)을 지원하기 위해 만들어진 greenlet을 이용해 작성된 네트워크 관련 라이브러리입니다. 생소한 용어가 갑자기 튀어나와서 놀라셨을지도 모르니 우선 eventlet에 대해 설명하기 전에 앞에 나온 용어들을 찬찬히 한번 살펴보죠.코루틴과 greenlet먼저 코루틴(Coroutine)부터 살펴보죠. 전산학도라면 누구나 그 이름을 한번은 들어봤을 도널드 카누쓰(Donald Knuth)는 자신의 저서 The Art of Computer Programming에서 코루틴을 다음과 같이 설명합니다.Subroutines are special cases of more general program components, called “coroutines.” In contrast to the unsymmetric relationship between a main routine and a subroutine, there is complete symmetry between coroutines, which call on each other.코루틴은 우리가 잘 알고 있는 서브루틴(Subroutine)과 달리 진입점(Entry Point)이 여러 개일 수 있습니다. 쉽게 이야기하면 실행을 멈췄다가(Suspend) 재개(Resume)할 수 있다는 점인데요. 이 특성을 살리면 우리가 익히 아는 스레드(Thread)처럼 쓸 수 있게 됩니다. 다만 스레드와 달리 코루틴은 비선점적(Non-Preemptive)이기때문에 코드의 흐름을 전적으로 사용자가 제어할 수 있습니다.하지만 불행히도 모든 언어에서 이런 코루틴이 지원되진 않습니다. greenlet은 이런 코루틴을 CPython에서 지원하기 위해 작성된 라이브러리입니다.eventlet코루틴을 통해 스레드를 대체할 수 있다는 점에 주목한 사람들은 greenlet을 통해 유용한 네트워크 라이브러리를 만들어냈습니다. eventlet도 그 중 하나죠. 잠시 eventlet의 소갯글을 봅시다.Eventlet is a concurrent networking library for Python that allows you to change how you run your code, not how you write it.위에서 볼 수 있듯이 eventlet은 사용성에 중점을 두었습니다. 기존의 블로킹 I/O 스타일의 프로그래밍에 익숙한 개발자들도 쉽게 비동기 I/O의 장점을 얻을 수 있게끔 하는 게 목적이죠.특히 저희가 주목한 점은 eventlet의 멍키패치 기능입니다. 멍키패치는 본래 동적 언어에서 런타임에 코드를 고쳐서 별도의 파일 변경 없이 본래 소스의 기능을 변경하는 것을 말합니다. eventlet은 eventlet.monkey_patch 메서드를 통해 표준 라이브러리의 I/O 라이브러리를 논블러킹으로 동작하게끔 변경해서 코루틴에 적합하게 만듭니다.How앞서 소개한 eventlet.monkey_patch를 이용하면 실제로 고칠 부분은 정말로 적어집니다. 다음 코드가 eventlet을 이용해 변경한 전부입니다.import eventleteventlet.monkey_patch() #표준 라이브러리를 변환# 여러가지 import를 하고...pool = eventlet.GreenPool()# 페이스북 계정들을 가져와서 반복하면서for account in FacebookAccount.query:    # 코루틴들에게 떠넘기자.    pool.spawn_n(FacebookAccount.update, account)        pool.waitall()view rawgistfile1.py hosted with ❤ by GitHub정말 적죠? 조금만 구체적으로 살펴보죠. 우선 eventlet.monkey_patch는 socket이나 select등의 Python 표준 라이브러리를 eventlet.green 패키지안에 정의된 코루틴 친화적인 모듈들로 바꿔치기 합니다.# from eventlet/pathcer.pydef monkey_patch(**on):    """Globally patches certain system modules to be greenthread-friendly.    The keyword arguments afford some control over which modules are patched.    If no keyword arguments are supplied, all possible modules are patched.    If keywords are set to True, only the specified modules are patched.  E.g.,    ``monkey_patch(socket=True, select=True)`` patches only the select and     socket modules.  Most arguments patch the single module of the same name     (os, time, select).  The exceptions are socket, which also patches the ssl     module if present; and thread, which patches thread, threading, and Queue.    It's safe to call monkey_patch multiple times.    """        accepted_args = set(('os', 'select', 'socket',                          'thread', 'time', 'psycopg', 'MySQLdb'))    default_on = on.pop("all",None)    for k in on.iterkeys():        if k not in accepted_args:            raise TypeError("monkey_patch() got an unexpected "\                                "keyword argument %r" % k)    if default_on is None:        default_on = not (True in on.values())    for modname in accepted_args:        if modname == 'MySQLdb':            # MySQLdb is only on when explicitly patched for the moment            on.setdefault(modname, False)        on.setdefault(modname, default_on)            modules_to_patch = []    patched_thread = False    if on['os'] and not already_patched.get('os'):        modules_to_patch += _green_os_modules()        already_patched['os'] = True    if on['select'] and not already_patched.get('select'):        modules_to_patch += _green_select_modules()        already_patched['select'] = True    if on['socket'] and not already_patched.get('socket'):        modules_to_patch += _green_socket_modules()        already_patched['socket'] = True    if on['thread'] and not already_patched.get('thread'):        patched_thread = True        modules_to_patch += _green_thread_modules()        already_patched['thread'] = True    if on['time'] and not already_patched.get('time'):        modules_to_patch += _green_time_modules()        already_patched['time'] = True    if on.get('MySQLdb') and not already_patched.get('MySQLdb'):        modules_to_patch += _green_MySQLdb()        already_patched['MySQLdb'] = True    if on['psycopg'] and not already_patched.get('psycopg'):        try:            from eventlet.support import psycopg2_patcher            psycopg2_patcher.make_psycopg_green()            already_patched['psycopg'] = True        except ImportError:            # note that if we get an importerror from trying to            # monkeypatch psycopg, we will continually retry it            # whenever monkey_patch is called; this should not be a            # performance problem but it allows is_monkey_patched to            # tell us whether or not we succeeded            pass    imp.acquire_lock()    try:        for name, mod in modules_to_patch:            orig_mod = sys.modules.get(name)            if orig_mod is None:                orig_mod = __import__(name)            for attr_name in mod.__patched__:                patched_attr = getattr(mod, attr_name, None)                if patched_attr is not None:                    setattr(orig_mod, attr_name, patched_attr)        # hacks ahead; this is necessary to prevent a KeyError on program exit        if patched_thread:            _patch_main_thread(sys.modules['threading'])    finally:        imp.release_lock()view rawgistfile1.py hosted with ❤ by GitHub이렇게 바꿔치기된 eventlet.green안의 모듈들은 I/O에 의해 블럭되는 경우 다른 코루틴에 제어권을 넘기는 식으로 지연을 방지합니다.다른 대안들사실 이러한 목적으로 사용되는 라이브러리는 eventlet만 있는 것은 아닙니다. gevent는 eventlet에서 영향을 받았지만, libevent를 기반으로 하여 더욱 나은 성능과 성숙한 인터페이스를 갖추고 있습니다. 저희처럼 libevent의 설치에 제한이 있는 환경이 아니라면 이쪽을 살펴보셔도 좋습니다.만약 이벤트 주도적 프로그래밍(Event-Driven Programming)에 흥미가 있으신 분은 Twisted역시 좋은 대안이 될 수 있습니다.#스포카 #개발 #개발자 #인사이트 #꿀팁
조회수 394

[마케터 K의 시선] B.A.T의 흔한 월요일 아침

지금 당장 누군가를 우울하게 만드는 건 쉽다.단 두 단어를 내뱉는 것만으로도 가능하다. 월요일 그리고 아침.그렇다. 이번엔 이 극악무도한, 월요일 아침이란 녀석에 대한 이야기다.[B.A.T의 소파는 마약 소파 같다. 등을 대고 눕기만 하면 잠이 스르르..]고백하건대, 나는 자정이 가까울수록 눈이 말똥해지고 에너지가 솟아나는 전형적인 저녁형 인간이다. 어릴 적부터 그랬다. 분명 나의 아침은 11시부터 시작되는 것 같은데, 왜 대다수의 사람들의 기상 시간에 맞춰 7시 즈음이란 시간이 아침이 되어야 하는가에 대한 의문을 가지고 있었다. 괜히 나와 다른 아침이란 잣대에 심술이 나 부지런하게 맞이하는 아침을 거부하곤 했었다. 대학생이 되고 나서, 1교시만은 마치 원래부터 아무것도 없었던 것처럼 비워놓는 여백의(?) 시간표를 짤 수 있었던 게 얼마나 신났던지!대학생활이 끝나고 직장인이 되는 순간, 자율성의 유효기간은 단호하게 끝나버렸다.'전 저녁형 인간이라, 늦게 출근하고 늦게 퇴근하는 게 더 효율적입니다'라는 주장이 씨알도 먹히지 않을 거라는 걸 진작에 깨닫고, 매일 아침과의 사투를 시작했다. 특히, 피곤과 졸림의 끝판왕은 주말과의 시차적응(!)이 채 끝나지 않은 월요일 아침이었다. 월요일 아침만 생각하면 일요일 오후부터 가슴이 답답하고 우울해지는 증상이 현대인의 병이라는 말이 괜히 있는 게 아니다.새로운 사실도 알게 되었다. 출근 시간 몇 분을 앞두고 엘리베이터를 탄 사람들의 얼굴을 보니 나와 다르지 않게 아직 꿈나라에 있다는 것을. 세상에는 나 같은 사람이 생각보다 많구나! 하며 스스로를 위로하곤 했다. 대체 어떤 이기적인 아침형 인간이 일찍 일어나고 일찍 자는 것이 인간에게 이로운 것이라는 설을 퍼뜨렸는지 모르겠지만, 이로 인해 많은 사람들이 고통받고 있다는 것은 분명하다.다양성이 존중되고 있는 요즘은, 자율 출퇴근 제도를 도입하는 회사가 많이 늘어나고 있다. 우리 회사 역시 그중 하나다. 10시부터 19시까지가 기본적인 근무시간이지만, 코어 근무시간인 11시~17시를 제외하고는 각자 스타일에 맞게 업무 시간을 조정할 수 있다. 그렇다 보니 종종 이런 풍경을 마주하기도 한다.[몰래 B.A.T의 아침 풍경을 촬영하려 했는데 아무도 없다. 그냥 몰래 온 손님이 되어 버렸다.]다만, 매주 월요일은 예외다. 이날은 10시부터 주간 회의가 있어 시간에 맞추어 출근해야 한다. 평소 출퇴근 시간을 신경 쓰는 데에 익숙하지 않은 우리지만, B.A.T 주간회의는 벌써 몇 주째 한 명의 지각자도 없이 원활하게 진행되고 있다. 지각자는 벌칙으로 사무실을 혼자 청소해야 한다는 규칙이 새로 생겼기 때문이다. 지각자가 없는 것은 좋지만, 기껏 만들어 놓은 벌칙을 아무도 받지 않는 건 좀 김빠지는 일이다.그런데 주간회의가 있는 오늘, 갑자기 설레기 시작했다.현재 시각은 9시 58분이고, 아직 도착하지 않은 사람이 한 명 있기 때문이다. 드디어 첫 번째 지각자 발생![뻘쭘해하며 들어온 지각자 디자이너 J님. "다음주 청소인거 아시죠?" "아, 네...."]["지각자는 자리에 앉을 자격이 없습니다." "아, 네....." 결국 소파에 걸터 앉은 지각자의 최후]주간 회의에서는 경영진의 중요 이슈 공유, 각 프로젝트 담당자의 진행 상황 공유, 업무 효율성 증진을 위한 논의 등 세 가지 사안에 대한 이야기를 나눈다. 단어들이 몹시 오피셜해 보이지만, 전반적으로 편안한 분위기에서 회의가 진행된다. 밤새워 예쁘게 PPT를 만들어 온 사람도 없다.대놓고 자랑을 하자면, 우리 회사는 원래 쓸데없는 데에는 힘을 쏟지 않는다.우연히도 이 글을 준비하고 있는 오늘, 주간 회의 막바지에 특별 안건이 올라왔다.이 괴로운 월요일 아침을 어떻게 하면 더 활기차게 만들 수 있을 것인가?여러 의견 중 당장 실현 가능한 것을 추린 결과, 다음주부터 만화 주제가를 틀어놓기로 했다. 선곡 담당은 회사에서 분위기 메이커를 담당하고 있는 영상 디자이너 N님. 가장 먼저 틀 곡은 활기참의 대명사로 불리는, 불후의 명곡 쾌걸근육맨의 '질풍가도' 라고...물론, 아무리 아침에 신나는 이벤트가 있다 해도 난 여전히 아침보다 저녁이 좋다. 월요일마다 엄청나게 비싼 레스토랑에서 대표님이 밥을 사준다고 해도 금요일이 200배는 더 좋다. 하지만 싫은 것을 더 낫게 만들기 위해 고민하는 건 나쁘지 않다. 싫은 것, 힘든 것, 어쩔 수 없는 것을 바꿔볼 수 있지 않을까 생각하는 것 만으로도, 싫은 마음이 조금은 사라지니까.-
조회수 2015

다양한 형태를 지원하는 리스트 UI, 잘 그리고 계신가요?

대략 1년 반 전, 5.0 롤리팝과 함께 나타난 RecyclerView. ListView 를 이용할 때 아주 기초적이고 정석적인 개념으로 사용되던 ViewHolder pattern 을 반 강제화? 하면서 동시에 성능까지 개선한 ListView 의 개량버전.앱 시장이 활성화되면서 한 가지 타입의 뷰만 반복적으로 보여주는 단순한 구성보다는 다양한 타입의 뷰를 보여주는 앱들이 많아지고 보편화 된 시점에 이것을 구현하기 위한 Adapter.getView 메소드는 혼돈.chaos 가 되었지요. 가독성을 높일만한 나름대로의 시도를 해보고 있을 때, RecyclerView 가 갑툭튀 했고 이걸 이용하면 원하는 만큼의 많은 타입의 뷰를 “가독성 좋게 만들어 볼 수 있겠다” 라는 생각이 들었습니다.그래서 RecyclerView.Adapter 를 상속 받아 다양한 타입의 뷰를 바인딩 할 수 있게 도와주는 헬퍼 클래스, MultiItemAdapter 라는 것을 만들어 보게 됐습니다. 구 회사 프로덕트에 적용해보기도 하고, 개인 프로젝트에 넣어보기도 하고, 토스랩에서 서비스하고 있는 “잔디”에 녹여내보기도 했는데 나쁘지 않은 느낌이들어 그 과정을 공유하고 많은 분들께 피드백도 받고 싶습니다. 또, 어떻게 더 잘 활용하고 계신지 여쭙고 싶습니다.RecyclerView.Adapter 의 이해를 위해 단순단순하게 만들어보자public class BasicAdapter extends RecyclerView.Adapter { private List mItems = new ArrayList<>(); @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(android.R.layout.simple_list_item_1, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.mTextView.setText(mItems.get(position)); } class MyViewHolder extends RecyclerView.ViewHolder { private TextView mTextView; public MyViewHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(android.R.id.text1); } } ... 이런 식으로 구현하면 되는군, 하지만 내가 최종적으로 원하는 건 다양한 ViewHolder 를 다뤄야 되는 건데 ViewHolder 가 많아지는 경우 inner class 는 쓰면 안되겠다! ViewHolder 들은 따로 패키지 만들어서 관리하자. 음 근데 ViewHolder 를 구성하고 난 다음 어떻게 그려지는 지에 대해 궁금하면 다시 어댑터를 찾아가야 되고, 반대로 어댑터에서 ViewHolder 내 구성요소가 어떻게 생겼는지 궁금하면 다시 ViewHolder 찾아가서 뒤져봐야되는 군. 이건 비효율 적인 것 같다. ViewHolder에 뷰를 그리는 메소드를 하나 만들자. 아 기왕이면 추상화된 클래스를 만들어 돌려돌려 쓰자. 하나 더 Generic 을 사용하자.public abstract class BaseViewHolder extends RecyclerView.ViewHolder { public BaseViewHolder(View itemView) { super(itemView); } public abstract void onBindView(ITEM item); } 뷰를 그리는데 쓰이는 객체는 Generic 을 이용하면 ViewHolder 안에서 그리는 작업 또한 해결이 가능하겠군! 이걸 이용해서 다시 만들어보자.public class MyViewHolder extends BaseViewHolder { private TextView mTextView; public MyViewHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(android.R.id.text1); } @Override public void onBindView(String item) { mTextView.setText(item); } } ... public class BaseAdapter extends RecyclerView.Adapter { private List mItems = new ArrayList<>(); @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(android.R.layout.simple_list_item_1, parent, false); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.onBindView(mItems.get(position)); } public void setItems(List items) { mItems.clear(); mItems.addAll(items); } @Override public int getItemCount() { return mItems.size(); } } 음 원하는 모양새다. 근데 이제 Adapter 에선 ViewHolder 에 들어갈 layout 이 어떤 건지 관심꺼도 되겠네. 게다가 ViewHolder 에서 layout 궁금하면 다시 또 찾아와야 되는게 문제다. 좀 더 명시적인 방법으로 Factory method 로 생성자를 제한해보자. RecyclerView.ViewHolder 는 View 를 가지는 생성자가 강제되니 이렇게 바꾸자.public static MyViewHolder newInstance(ViewGroup parent) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(android.R.layout.simple_list_item_1, parent, false); return new MyViewHolder(itemView); } private MyViewHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(android.R.id.text1); } 이렇게 하면 어떤 layout 을 다루고 있는지도 금방 알 수 있겠다. 이 정도만 되도 구색을 다 갖춘듯하니 이 느낌으로 다양한 타입의 뷰들을 다뤄보자.public class BasicMultiTypeAdapter extends RecyclerView.Adapter { public static final int VIEW_TYPE_A = 0; public static final int VIEW_TYPE_B = 1; private List mItems = new ArrayList<>(); @Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_A) { return AViewHolder.newInstance(parent); } else { return BViewHolder.newInstance(parent); } } @Override public void onBindViewHolder(BaseViewHolder holder, int position) { holder.onBindView(mItems.get(position)); } public void setItems(List items) { mItems.clear(); mItems.addAll(items); } @Override public int getItemCount() { return mItems.size(); } @Override public int getItemViewType(int position) { if (position % 2 == 0) { return VIEW_TYPE_A; } else { return VIEW_TYPE_B; } } } 음 깔끔하긴 하다. 근데 getItemViewType 이 스크롤 할 때마다 불릴 텐데, 분기도 많고 연산이 생겼을 때 스크롤 속도에 괜한 영향을 줄 듯? view type 을 차라리 미리 가지고 있게 만들자. 또! 가만보니 한 타입의 객체를 이용해서 다른 스타일로 뷰를 보여줄 뿐이었네. 이것도 여러가지 객체를 담을 수 있게 만들어야지.뷰를 그릴 대상이 될 객체랑 타입을 가지는 Wrapper class 를 만들어서 해결하자. 이러면 Adapter.onBindViewHolder 랑 Adapter.getItemViewType 도 해결이 되겠군.public abstract class MultiItemAdapter extends RecyclerView.Adapter { private List mRows = new ArrayList<>(); @SuppressWarnings("unchecked") @Override public void onBindViewHolder(BaseViewHolder holder, int position) { holder.onBindView(getItem(position)); } @SuppressWarnings("unchecked") public ITEM getItem(int position) { return (ITEM) mRows.get(position).getItem(); } public void setRows(List mRows) { mRows.clear(); mRows.addAll(mRows); } @Override public int getItemCount() { return mRows.size(); } @Override public int getItemViewType(int position) { return mRows.get(position).getItemViewType(); } public static class Row { private ITEM item; private int itemViewType; private Row(ITEM item, int itemViewType) { this.item = item; this.itemViewType = itemViewType; } public static Row create(T item, int itemViewType) { return new Row<>(item, itemViewType); } public ITEM getItem() { return item; } public int getItemViewType() { return itemViewType; } } } MultiItemAdapter 완성.네, 저는 이렇게 만들어서 1년 반 정도 필요한 부분(복잡해 질만한 부분)에 이 클래스를 상속받아 구현했습니다. 사용방법을 예로들어 데이터베이스나 서버로부터 긁어온 아이템들을 타입에 따라 A, B로 나눠서 보워줘야 한다면,// MutiItemAdapter 구현 public class AdvancedItemAdapter extends MultiItemAdapter { public static final int VIEW_TYPE_A = 0; public static final int VIEW_TYPE_B = 1; @Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_A) { return AViewHolder.newInstance(parent); } else { return BViewHolder.newInstance(parent); } } } // Activity 나 Fragment 등 view 요소에서 ListAdapter item setting. public void setItems(List items) { List rows = new ArrayList<>(); for (int i = 0; i < items xss=removed>이렇게 해주면 됩니다. 그런데 위 사용방법을 보면 추가적인 새로운 타입(Row)의 List 와 반복문을 돌려야 된다는 것이 단점으로 보이는데요. 그럼 이 클래스를 사용하지 않고 직접 구현한 결과를 좀 볼까요?public class NormalItemAdapter extends RecyclerView.Adapter { public static final int VIEW_TYPE_A = 0; public static final int VIEW_TYPE_B = 1; private List mItems = new ArrayList<>(); @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == VIEW_TYPE_A) { View itemView = LayoutInflater.from(parent.getContext()) .inflate(android.R.layout.simple_list_item_1, parent, false); return new AViewHolder(itemView); } else { View itemView = LayoutInflater.from(parent.getContext()) .inflate(android.R.layout.simple_list_item_1, parent, false); return new BViewHolder(itemView); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (holder instanceof AViewHolder) { Item item = getItem(position); ((AViewHolder) holder).getTextView().setText(item.getName()); } else { ((BViewHolder) holder).getTextView().setText("I am B."); } } private Item getItem(int position) { return mItems.get(position); } public void setItems(List items) { mItems.clear(); mItems.addAll(items); } @Override public int getItemViewType(int position) { if (getItem(position).getType().equals(Item.ITEM_TYPE_A)) { return VIEW_TYPE_A; } else { return VIEW_TYPE_B; } } @Override public int getItemCount() { return mItems.size(); } } 뭐, 나쁘진 않습니다. 이 정도 수준으로 개발이 끝나도 되고 추가적인 확장이 필요하지 않아보인다면 굳이 MultiItemAdapter 를 쓸 필요가 없습니다.중요성을 가지는 리스트 위주의 화면에서 위와 같이 개발된다면 당장 보이는 제 불만은 onCreateViewHolder, onBindViewHolder 계속해서 분기가 들어가게 되고 getItemViewType 에서는 계속 해서 List 데이터에 접근해야 한다는 것입니다. 접근 자체가 큰 문제, 큰 영향을 끼치지 않을 정도 규모의 자료구조라면 논외로 치더라도, 뷰 타입이 조금만 늘어나도 onCreateViewHolder, onBindViewHolder 의 덩치는 엄청 커질 겁니다.예를들면 맨 마지막 아이템 타입이 B 이고 현재 추가 될 아이템 타입이 A인 경우에는 다른 형태의 디바이더를 넣어야 한다던지 하는 추가적인 확장이 이루어져야 한다면 골치가 꽤 아플겁니다. 특히 저는 위 예와 비슷하게 뷰 타입에 따라 각기 다른 아래 위 마진값을 요구받을 때, ViewHolder 마다 이전 데이터를 참고하게 만들고 동적으로 Visibility 처리를 하거나 MarginLayoutParams 를 고치는 것이 비효율적으로 느껴져서 height를 주입받는 DividerViewHolder 를 하나 만들어 사용하곤 했습니다. 이렇게 하니 각각의 ViewHolder 들이 데이터들에 의존적이지 않게 코딩이 가능했었습니다. 한 가지 더 예를들어 리스트 중간 중간 광고가 보여지게 되고 이 광고 클래스는 완전히 다른 객체로부터 보여줘야 한다 라고 했을 때 MultiItemAdapter 를 이용하면 쉽게 해결이 가능합니다.정작 근 1년간 “잔디”를 만들면서는 자주 쓰진 않았는데, 작년부터 각광받기 시작한 MVP 패턴을 사용할 때 View 에서의 로직을 최소화 하려고 한다면 써먹을 수 있는 모델로 적합하지 않나 생각이 들면서 다시 사용하기 시작했습니다. Presenter 에서 Row 를 만들어 던져주면 View 는 그것을 그대로 사용하게 만들 수 있다는 생각이 들었거든요.(아직까지는 비교적 크지 않은 부분에서만 사용하게 되서 View(MainThread)에서 Row 를 만들게 코딩해 놓은 컴퍼넌트가 더 많네요 흑흑) 더 복잡한 구조를 갖는 컴퍼넌트를 만들어야 할 때는 비동기 스레드에서 Row 까지 만들어 내보내는 것도 해볼까 하는 생각도 듭니다.제 눈에만 괜찮은 구조인지, 생각지도 못한 치명적인 단점이 있진 않은지, 구조나 설계 측면에서 안 좋은 점은 있지 않은지, 논리없이 Generic 으로 “퉁” 치고 있는 코드는 아닌지, 여러가지가 많이 궁금합니다 ^^ MultiItemAdapter 를 쓴 것과 안 쓴것의 정말 심플한 비교 소스를 열어놓았습니다 MultiItemAdapter 또, 여러분들은 어떻게 구현하고 계신지요? 여러분의 관심이 필요합니다 ! :)#토스랩 #잔디 #JANDI #개발 #개발자 #인사이트 #경험공유
조회수 1326

위펀딩 투자자 인터뷰 #2 | 증권업종 기획부서 근무 30대 여성

위펀딩은 고객과 더 소통하기 위해 투자자분과 직접 대면하여 인터뷰를 진행합니다.모든 투자자분들을 찾아뵙는 그날까지 인터뷰는 계속됩니다.그 두번째 인터뷰는 증권업 기획부서에 종사하는 30대 여성 (새댁!)입니다.그럼 인터뷰 내용 시작하겠습니다.1. 위펀딩을 선택한 이유는? 얼마 전에 결혼한 새댁이에요. 아무래도 싱글 시절보다는 위치가 달라졌으니, 돈 관리에 더 많이 신경이 쓰이는 게 현실인데, 적절한 재테크 대상을 찾는 중에 위펀딩을 알게 됐죠. 물론 평소에 부동산 투자에 대해서는 관심이 많았어요. 그래도 목돈이 필요하다는 점, 권리 분석 등에 대한 부담이 있어서 멀게 느껴졌는데 위펀딩의 부동산 크라우드 펀딩 플랫폼을  만나 가능해졌죠. 소액 투자도 가능하고 부동산인데 단기 투자 회수가 가능하다는 점이 끌렸죠. 물론, 가장 중요한 것은 담보가 확보된다는 투자. 이거죠. 투자에서 가장 중요한 것은 담보가 확보된다는 투자. 이거죠.2. 본인만의 재테크나 자산관리 방법은?증권업계에 있다 보니 자연스럽게 다양한 투자 상품을 접하는 편이에요. 그래서 재테크에 자연스럽게 시간을 많이 할애하는 편이에요. 공부도 평소에 꼼꼼하게 해서 포트폴리오 이론에 따라 분산해서 하는 편이에요. 예금, 저축, 펀드 등 다양한 금융상품을 관리하고 있어요. 나가는 돈을 먼저 관리하는 게 재테크의 출발이라 생각해서 세제혜택에 대해서도 꼼꼼하게 챙기는 편입니다. 아 참, P2P 투자의 세금에 대해서 정부 차원에서 개선이 되었으면 좋겠어요.공부도 평소에 꼼꼼하게 해서 포트폴리오 이론에 따라 분산해서 하는 편이에요.3. 저희 위펀딩에 바라는 점이 있다면?자동 적립식 투자 기능이 있으면 좋겠어요. 돈을 쓰기 전에 미리 꾸준하게 일정 금액을 투자하고 싶은데, 제가 신경 쓰지 않아도 자동으로 투자가 가능하게요. 제가 신경 쓰지 않아도 자동으로 저의 재산을 운용해주는 플랫폼이 되었으면 좋겠습니다. 위펀딩에서 운용하는 부동산 담보 채권 투자는 믿고 맡길 수 있을 것 같아요.(이건 투자 상품 알림이 가는 것으로 설명드렸습니다^^. 자동 적립식 투자도 될 수 있도록 개선하겠습니다)자동 적립식 투자 기능이 있으면 좋겠어요.위펀딩 투자자 인터뷰 2편은 여기서 마칩니다.안정적인 부동산 투자를 쉽게! 위펀딩의 다음 인터뷰도 기대해 주세요.감사합니다.#위펀딩 #투자자인터뷰 #서비스소개
조회수 830

실무자에게 권한을 주라구! (서로 힘든 계단타기에 대해

담당자 : "아!! 맞다 그 자료 곧 넘겨 드릴게요."담당자의 황급함이 카톡과 라이언의 땀방울로 전해졌습니다. 요즘은 효율적인 업무용 이모티콘이 많아서 매우 다양한 감정표현을 섬세하게 할 수 있는 것 같아요. 15분 뒤 담당자에게서 연락이 왔습니다담당자 : "제가 잘못 알았나 봐요. 그 자료는 제 쪽이 아니라 다른 쪽 담당자가 담당이라서 그쪽에서 드릴 거예요."디자이너 : "그분과 직접 컨택할 순 없나요? 어떤 채널로 주시는 거예요?”담당자 :  "잠시만요!”잠시라고 한 잠시가 흐르고 다시 연락이 왔습니다. 보통 잠시라고 하면 우린 그 동안 다른 일을 하기가 참 힘들잖아요? 예를 들면 배가 아파도 화장실에 가기도 뭐하고..밥을 먹으러 카페에서 나가기도 뭐합니다. 심지어 담배 한 대 피러나가는 것도 좀 애매하죠. 그냥 잠시동안 네이버뿜이나 보면서 기다리고 있어야 하는거죠. 근데 그 잠시가 좀 길어지면 초조해지기 시작합니다.초조해..초조하다고...담당자 : "메일로 보내 드렸다고 하네요! 혹시 받으셨나요?"디자이너 : "네네, 메일로 오긴 왔는데 그럼 이 건은 이분께 드려야 하나요?"담당자 :  "아니요, 그냥 저에게 주시면 돼요!"디자이너 : "그럼 수정 피드백이나 추가 자료 요청은 어떻게 해요?"담당자 :  "아… 음 그건 그분께 받아야 하는데… 그럼 잠시만요!”마찬가지로 잠시가 흐른 뒤 재차 받은 연락은 이러했습니다.담당자 : "그럼 필요한 자료 말해 주시면 제가 요청해서 보내라고 할게요!"디자이너 : "아니, 그러지 말고 그냥 담당자님이 한 번에 해 주시면 안돼요?"담당자 :  "아, 그럴까요?"받은 프로젝트는 사용 설명서와 홍보용 브로슈어에 대한 건이었습니다. 그런데 설명서와 브로슈어의 담당자가 달랐던 것이죠. 일단 담당자 중 누가 선배고 기가 더 센지 알 순 없지만, 작업 시간 중 45분이 '잠시만'을 기다리다가 사라진 것은 명백했습니다. 아마도 다른 담당자에게 자꾸 물어보는 걸로 봐선 그 분에게 약점이 잡혔거나 빚을 졌다거나, 이것도 저것도 아니면 둘이 참..별로 안친한가보다..는 사실을 잘 알겠더라구요.다..담당자님께..여..쭤보고...중요한 건 이런 거예요.누가 전달하고 누가 컨펌하는가 다음 사례도 한 번 볼까용. 어느 중소기업의 회사 소개서와 로고 리뉴얼 건이었는데, 아무래도 담당자가 육두품 신입이고 팀장님은 성골 귀족 정도 되었던 것 같습니다. 수화기 너머 담당자가 긁적이며(보이진 않았지만 분명 긁적였을 것이다) 입을 열었어요.담당자 : "아, 보내 주신 콘셉트 시안은 잘 받았고요. 이제 팀장님께 보고해서 결정한 뒤에 알려 드릴게요."디자이너 : "그럼 콘택트 포인트는 어디로 정리할까요?"담당자 : "일단 저에게 연락주시면 제가 팀장님한테 연락드리도록 할게요."디자이너 : "네(일단 뭐…) 알겠습니다."이렇게 마무리한 뒤 하루가 지났다. 아니 팀장님이면 아무리 멀어도 지척에 있을 텐데, ‘혹시 어디 출장을 가신 건가’ 싶어 재차 연락을 했지요. 급하다고 했던 건이라서 저도 조급하긴 마찬가지니까요.디자이너 : "어제 말씀드린 콘셉트 시안은 어떻게 결정되었나요?"담당자 :  "아, 그게 팀장님께는 보고가 올라갔는데 일단 세 개 중에 하나로 말씀은 하셨거든요. 근데 이사님께도 보고를 드려야 하는데 지금 잠시 자리를 비우셔서 돌아오시는 대로 확인해서 알려 드릴게요!"팀장님과 이사님 등장새로운 미션의 등장. 이.사.님. 그렇게 하루가 또 지났습니다.. 보통 "잠시 자리를 비우셔서"에서 '잠시'는 열두 시간 정도를 의미하는 것 같아요. 어쩌면 우린 슈뢰딩거의 야옹이마냥 평행우주에서 서로 다른 시간을 보내고 있는 지도 모르겠습니다. "어떻게 진행하는 게 좋을까요?"라고 이번에는 이모티콘 없이 보내 보았습니다. 사실상 소심한 투정을 부린 것이지요. 마침표는 너무 심할 것 같아서 그래도 물음표로 마무리 지어보았습니다. 효과는 미미했습니다. 한참 뒤 담당자에게서 메시지가 왔습니다.담당자 : "아…, 이사님께서 확인은 하셨는데, 대표님과 확인해서 피드백 주신다고 하네요."끝판왕 등장끝판왕 등장. 대.표.님. 대표님까지 올라갔으니 하루가 더 넘어가겠구나 생각하며 닭볶음탕에 소주를 한잔하고 있었습니다. 어차피 오늘은 글렀으니 오늘의 술은 오늘 마시는 것이 좋을 듯 했죠."대표님께서 내일 중으로 바로 알려 주시겠다고 하네요!!"라고 밤 12시에 온 카톡을 보니 마음이 짠해지고 애틋해지면서 뭔가 뜨거운 것이 뭉클하니 올라오는 듯한 기분이었는데, 닭볶음탕이 매워서 그랬나 봅니다. 예상대로 다음 날이 되어서야 답변이 오긴 왔습니다.담당자 : "일단 모든 콘셉트를 확인은 했는데, 혹시 좀 더 다른 형태의 시안 한 개만 더 보면 좋을 것 같다는 의견이 나와서요! 세 번째 콘셉트에서 조금 심플한 느낌으로 하나만 더 부탁드려도 될까요?"디자이너 : "대표님과 이사님 쪽에서 나온 피드백인가요?"담당자 : "네네."그렇게 하나의 시안을 더 만들어 보내 준 뒤 다시 처음으로 되돌아가 팀장, 이사, 대표(역시나 이사님은 자리를 비우셨고, 대표님은 밤 12시에 피드백을 주신 모양)를 거쳐 실무자에게 되돌아왔습니다. 정식 시안은 시작도 못한 채 컨셉 정하는데만 정확히 8일이 걸렸습니다. 결과는 어떻게 되었을까용?네…. 결국 3일 만에 회사 소개서를 만들어야 했고 로고는 만들지 않는 걸로 했어요. 내 500만원 어디감.... 음. 서두가 길었지만 본론은 간단합니다. 디자인 의뢰하기 전에 미리 컨셉회의랑 제작부수, 페이지구성 등등은 미리 끝내놓도록 합시다. 그 후에 디자이너 찾아도 늦지 않습니다. 디자이너는 미팅 후 아무리 늦어도 2,3일 내로 바로 작업에 착수할 수 있어요. 그런데 구우우욷이.... 미리 계약맺어놓고 한도끝도없이 대기만 타게 하고있으면 서로 긴장하고 피곤해지기 시작하거든요.언제까지요?..그리고 핵심은 실무자에게 권한을 주세요. 위에서 회의와 구성을 어느정도 가닥 잡았으면 이제부턴 니가 알아서 해라..라고 어느정도 맡겨야해요. 자꾸 세세한 것, 토시 하나, 컬러 하나까지 대표님까지 보고가 올라가면 그 시안은 억겁의 세월이 흘러 역사속으로 사라지고 말거예요. 만약 그 실무자를 못믿겠으면 본인이 직접 커뮤니케이션 하세요. 그 불안불안함을 안고 그 미더운 분에게 맡기곤 자꾸 본인에게 가져와서 확인 맡으라고 하면..결국 본인의 일만 늘어나는 거거든요.서로 힘든 일이 아닐 수 없습니다. 1. 몇 일까지 시안3개로 추려서 가꼬와.2. 그 중 가장 괜찮다고 생각되는 거 1,2,3순위 잡아줘.3. 그 이유를 써줘.하고 그냥 맡기는 게 짱입니다. 이게 자꾸 안되는 이유는 3가지가 있더라구요.1. 윗사람이 굉장히 자기의견 반영을 좋아하시는 분이다.(뭐라도 한 마디 꼭 하고싶으신 분)2. 회장님의 심기를 건드리는 어떤 것을 잘못넣으면 진짜 큰일나는 회사 (생각보다 많습니다. 특히 대기업 대상 행사나 디자인할 때는 회장님의 언어, 그 분의 말, 가치를 표현하는 데에 있어 괴이이이잉....장히 신중해야 합니다. 띄어쓰기도 틀리면 안되거든요.회장님 타노스인줄)3. 실무자가 진짜 일을 못하는 경우거의 과반수 이상의 경우는 1번 케이스가 많았습니다. 뭔가 팀장님이 시각디자인과 출신이라던가... 미술가 집안 분이시라거나, 또는 대표님이 유독 디자인에 덕력이 있다거나..아니면 디자인과 상관없이 뭐라도 한 마디 해야 직성이 풀리시는 스타일이라던가. 이런 식이죠.음 이건 딱 잘라서 간단하게 말씀드릴 수 있을 것 같습니다. 디렉션 방식이 이렇게 오르락내리락 계단타기만 하고 있으면 잘나올 디자인도 망합니다. 이건 명백한 사실이예요. 수많은 사람들을 거치며 말이 더해지고 그 말이 오르내릴 때마다 조금씩 바뀌거든요. 디자인은 길을 잃고 쑥대머리가 됩니다. 그리고 결국 비싼 돈 들여서 이상한 시안을 받을 거고시간은 시간대로 썼을거고실무자는 지쳐버렸을 거고디자이너는 고개를 가로 저으며 떠날겁니다.이런 대우주적 비극을 막기 위해.....우리 모두 실천해봐요. 1프로젝트 1담당자 1컨택포인트 니 선에서 정리하기, 정리된 것만 나에게 보고!
조회수 875

[Buzzvil Design] Design System at Buzzvil

~ 8min. read 버즈빌이 완전히 새로운 ‘디자인 시스템’을 갖추고 있습니다. 와우~ 새로운 시스템을 만드는건 정말 시간이 많이 드는 일입니다. 그런데 과연 그럴만한 가치가 있는 일일까요? 버즈빌과 같은 스타트업이 굳이 디자인 시스템을 만들어야 하는 이유가 있을까요? 네. 당연히 그렇습니다. 그 이유에 대해서 이해하기 위해서는 버즈빌이 디자인적으로 어떤 문제를 가지고 있고 이러한 문제들을 디자인 시스템을 통해 어떻게 풀어내려고 했는지를 좀더 면밀히 살펴볼 필요가 있습니다. 버즈빌은 수많은 (지금도 그 수가 늘어나고 있는) 파트너들과 함께 서비스를 운영하고 있습니다. 그렇기 때문에 버즈빌은 스타트업치고는 매우 많고 복잡한 Product들을 만들어내야만 하고 계속해서 그것들을 관리하고 운영해 나가야 합니다. 이러한 버즈빌의 비즈니스적인 필요를 충족시키는 것이 디자인 시스템을 통해 해결 하려고 했던 첫번째이자 가장 중요한 문제였습니다.   디자인 팀은 그렇게 크지 않은데 비해 디자인업무는 계속해서 늘어나고 다양해집니다. 이러한 상황을 어떻게 극복할 수 있을까요?  버즈빌의 Product line을 살펴보면 다음과 같습니다. 먼저 자체적으로 2개의 앱을 운영하고 있고, 파트너들의 앱에 통합될 여러가지 SDK와 API들 그리고 곧 추가될 각 파트너별 White-label 앱까지… 각각의 앱과 연관된 마케팅 관련 작업이나 대쉬보드 작업을 제외한다고 해도 버즈빌이 다루고 있는 Product들은 너무나도 많습니다. 시스템이란 각각의 요소들을 함께 연결하는 것이며, 이러한 ‘요소들’이 다른 곳에서 재사용될 수 있게 만드는 것입니다. 그래서 버즈빌 디자인 팀에서는 디자인 asset들을 분리하여 재사용할 수 있는 component로 만드는 일들을 해왔습니다. 식은죽 먹기죠! 주의할 점은 technical dept의 문제가 디자인에도 똑같이 적용된다는 점입니다. 즉, 잘못된 점을 초기단계에 바로 잡는 것은 간단하지만 일이 진행되면 진행될 수록 단순한 오류를 수정하기도 어려워진다는 것이지요. 그래서 디자인 팀에서는 하나의 일을 오랜기간 고민하면서 진행함으로써 잘못 진행된 일 때문에 두 번 일해야 하는 불필요한 시간의 낭비를 방지하고 있습니다. 이것이 바로 디자인 시스템을 만들게된 출발점이자 시스템을 만들면서 해결하려고하는 가장 큰 문제입니다. 디자인 팀에서는 이 일을 8개월 전부터 해왔고 그 과정 가운데서 다양한 문제들에 대한 답을 시스템을 통해 찾았습니다. 버즈빌은 작은 회사지만 전세계를 향해 사업을 확장해 나가려는 큰 비전을 가지고 있습니다. 글로벌 기업이 되는 것은 분명히 멋진 일이지만 디자인의 관점에서 그것은 수많은 다른 문화적 배경을 가진 사람들, 다른 기술적, 미적 이해수준을 가진 사람들이 이해할 수 있는 디자인을 만들어야한다는 것을 의미합니다. 다행히도 이런 고민을 한 디자이너 들이 많이 있었고, 그들은 어떤 product에도 적용할 수 있는 아주 유연한 표준을 세웠습니다. 바로 그 기준들이 흔히 ‘시스템’ 이라고 불리는 것들입니다. 이런 상황에서 우리의 두번째 문제가 생겨납니다. ‘모든 사람이 이해할 수 있어야 한다’는 것입니다.  우리는 어떻게 다양한 스펙트럼의 사람들에게 적합한 세계적인 Product를 디자인 할 수 있을까요? 복잡한 문제들은 보통 간단한 방법으로 해결되는 경우가 많은데요.  모순적으로, 디자인을 조금이라도 해봤다면 어떤 것을 단순하게 만드는것이 말처럼 쉬운 일이 아니라는 것을 이해하실 수 있을 것입니다. 앱을 디자인 하는 경우도 크게 다르지 않습니다. 앱을 디자인 한다는 것은 시각적 의미(Visual semantic)를 앱안에 표현하는 일입니다. 다시말해서, 그 앱을 사용하는 사람들이 이해할 수 있는 일종의 메시지를 시각적요소를 통해 담아내는 것이라고 할 수 있는데요.  이렇게 담아내는 메시지의 복잡성을 줄임으로서 그 메시지를 더 많은 사람들이 이해 할 수 있게 만들 수 있습니다. 디자인에 있어서 미니멀리즘은 바로 이런 생각에서 부터 시작됩니다. 단순성이 핵심입니다. 단순함을 유지함으로써 우리는 비로소 많은 사람들이 이해할 수 있는 디자인을 할 수 있습니다. 그렇다면 이를 위해서 시스템적으로 접근하기 위해 무엇을 해야할까요? 바로 원칙들을 세우는 일입니다. 시스템적으로 일한다는 것은 원칙과 과정음 세움으로써 우리가 공유하는 가치들을 바탕으로 우리만의 고유한 일하는 방식을 만드는 것을 의미합니다. 버즈빌의 경우, ‘미니멀리즘’이라는 원칙은 작은 디자인팀을 운영하면서 동시에 버즈빌이 진출해있는 다양한 시장에 맞는 디자인을 하기 위한 핵심적인 원칙 중에 하나입니다. 디자인 팀에서는 Google Material에서 부터 시작해서 버즈빌만의 컴포넌트와 원칙들을 정해 나갔습니다. Google Material을 선택한데에는 몇 가지 이유가 있는데요. 첫 번째로 끊임없이 진화해 나가는 놀라운 디자인 언어이기 때문이고 두 번째로는 이미 안드로이드 OS와 많은 주요 앱들에서 사용되어 왔기 때문에 널리 퍼져있고 많은 유저들이 이에 굉장히 익숙하기 때문입니다.. 버즈빌은 자체적으로 허니스크린과 슬라이드조이를 운영하고 있습니다. 뿐만아니라 BuzzScreen, BuzzStore, BuzzOfferwall등을 포함한 Buzz Product라고 불리는 product line도 관리하고 있습니다. 이  product line들은 앞서 언급했듯이 white labelled app이나 SDK와 API를 통해서 파트너 앱에 통합되기 때문에 그 자체로는 브랜드가 없습니다. 반면에 각각의 파트너들은 당연히 그들의 서비스를 통해 표현하고자하는 고유의 브랜드를 가지고 있습니다. 여기서 세번째 문제가 나오는 데요. 바로 ‘다양한 브랜드의 필요를 충족시켜야 한다’는 점입니다.  어떻게 버즈빌의 다양한 서비스들에서 수많은 브랜드를 다룰 수 있을까요? 공통점이 많지 않은 다양한 브랜드를 다루는 것은 그 자체만으로 어려운 일입니다. 게다가 버즈빌의 경우는 파트너의 요구를 충족시키기 위해서 파트너의 브랜드와도 잘 통합 될 수 있어야 합니다. IT회사들 사이에서 디자인 시스템으로서 사용되고 있는 수많은 방법들이 있지만 그중에서  다니엘 에덴의 방법이 버즈빌의 상황과 가장 맞는다고 판단하였습니다. 에덴은 디자인 시스템을 조직하고 구조화하는 매우 흥미로운 방법을 가지고 있는데요. 에덴은 모든 디자인 asset을 2개의 레이어로 나누는 것부터 시작합니다. 바로 패턴과 표현입니다 :   패턴 레이어  패턴은 디자인 에셋들의 레이아웃입니다. 패턴은 각각의 컴포넌트들이 어떻게 배치되어야 하는지에 대한 정확한 기준을 제시 합니다. 패턴에서 다루는 컴포넌트들은 더 작은 컴포넌트들도 쪼개질 수 있으며 어떠한 메시지나 표현을 담고 있지 않습니다. 또한 서비스 안에서 혹은 서비스 밖에서도 재사용이 가능합니다. 훌륭한 시스템을 만들기 위해서는 겹치는 부분을을 제거하여 각각의 컴포넌트들을 최적의 컴포넌트로 만드는 과정이 필요합니다. 이러한 과정을 통해 점점 더 간결함을 추구 할 수 있습니다.   표현 레이어  표현레이어는 메시지를 다루는 부분이자 컴포넌트의 가장 기본이 되는 레이어입니다. 표현레이어는 고유의 브랜드와 색상이 담기는 곳이며 더 작은 컴포넌트들로 나누어지지 않습니다. 그래서 일반적으로 표현레이어의 구성요소들을 ‘Atoms’ 라고 부릅니다. 표현 레이어는 색상, 텍스트, 아이콘, 일러스트레이션, 사진과 비디오등으로 구성되고 각각 메시지를 담고 있으며 우리의 UI구조와 관계없이 전체적인 브랜드 가이드라인에 따라 결정됩니다. 이제 이 접근방식이 얼마나 확장가능한지 어느정도 감을 잡으실 수 있으실 겁니다. 각각의 레이어의 모든 컴포넌트들을 잘 정의해두었다면 Sketch와 같은 유용한 툴을 이용해서 손쉽게 두 레이어를 연결할 수 있습니다. 이에 대해서는 추후에 다른 포스트를 통해서 소개하도록 하겠습니다. 시스템의 약점은 얼마나 유연하게 적용될 수 있는 것인지에 있습니다. 아주 엄격한 구조와 너무 유연한 구조 사이에서 균형을 잡는건 쉽지 않은 일인데요. 이것을 위에서 살펴본 두 가지 레이어로 나누어보면 좀 더 쉽게 접근할 수 있게 됩니다. 먼저 블록을 만드는 것, 즉 컴포넌트를 배치하는 패턴 레이어에 대한 부분은 대부분 개발자에 의해서 구현 됩니다. 마찬가지로 표현 레이어는 브랜드의 이미지를 세팅하는 마케터에 의해서 다루어져야합니다. 디자이너 입장에서 개발자와 마케터에게 서로 다른 메시지를 통해 이야기 하는 것처럼 이 두 가지 측면은 다른 방법으로 소통 되어야 합니다. 뛰어난 디자인을 만들기 어려운 이유는 하나의 명확한 디자인 컨셉을 통해 다른 모든 stakeholder들을 연결해야 하기 때문입니다. 패턴레이어는 개발팀의 가이드라인을 잘 따라야 하고 표현레이어는 브랜드가 확실하게 전해지면서 컨텐트가 적절히 보여질 수 있도록 충분히 유연해야 합니다. 그리고 디자이너의 역할은 이 모든 것들의 중간에서 각각의 요소들을 연결하여 최종적으로 우리의 서비스를 사용하는 사람들이 우리가 전하고자 하는 모든 것을 인지할 수 있게 만드는 것입니다. 위에서 언급한 다니엘 에덴의 기사를 읽어봤다면 또 다른 레이어인 컨셉 레이어에 대해 알고 계실것입니다. 컨셉 레이어는 디자이너가 다른 모든 stakeholder들과 어떻게 소통할 것인지에 관한 것이고 우리의 디자인이 얼마나 좋은지를 평가하는 기준이 되는 레이어입니다. 이것은 ‘우리의 디자인의 필요를 충족 시키는 것’이라는 네번째이자 마지막 문제로 이어집니다:  우리는 어떻게 우리의 디자인 컨셉에 대해 소통하고 평가 해야할까요? 컨셉은 스토리를 의미합니다. 컨셉은 가능한 모든 수단들을 통해서 추상적인 생각을 전달하는 것입니다. 그리고 바로 이 부분에서 디자이너가 차이를 만들 수 있습니다. 이것은 사실 부분적으로는 우리가 시스템으로 일하는 이유이기도 합니다. 시스템을 통해 다른 레이어를 구성하는데 드는 시간을 절약하는 이유는  궁극적으로 컨셉 레이어 맞는 아이디어들을 시각화하고 구현하는데 충분한 시간을 쓰기 위해서 입니다. 다니엘 에덴의 말을 빌리자면, 표현 레이어가 우리의 알파벳이라면 패턴 레이어는 우리의 단어와 문장이다. 그리고 컨셉 레이어는 우리의 이야기입니다. 알파벳과 사전을 만드는 이유는 사람들에게 들려줄 이야기를 쓰기 위해서입니다. 이것이 가장 중요합니다. 디자인 적인 기반이 탄탄하지 못하면 메시지가 명확하게 전달 되지 못할 수도 있지만 디자인에 담겨있는 스토리가 탄탄하지 못하면 우리의 Product의 경험 전반에 부정적인 영향을 미치게 됩니다. 그렇다면 우리는 어떻게 좋은 디자인을 평가할 수 있을까요? 컨셉은 이론적이고 실재적인 연구에 기반한 가정들로 이루어져 있습니다. 따라서 디자인에 담으려고 하는 이야기가 우리가 원래 의도했던 답을 주면서 이용자들과 공감대를 형성하는지를 알아보기 위해서는 그것을 실제로 테스트 해보는 것이 가장 좋은 방법입니다.  버즈빌의 디자인 시스템은 각각의 컨셉별로 우리의 가정이 옳았는지를 판단할 수 있는 Metric들을 다룹니다. 전체적인 시스템과 마찬가지로 Metric은 불변하는 것이 아니라 테스트를 진행해 가면서 계속해서 수정되고 보완되는 것입니다. 만약 몇번의 루프 후에도 결과가 기대와 다르게 나온다면 컨셉 레이어가 잘못되었고 어딘가 수정해야할 부분이 필요하다는 것을 의미합니다.  수정하는 과정에서의 원칙은 핵심적인 컴포넌트들 자체가 문제를 일으킨다고 보는 것이 아니라 그것들을 어떻게 조합하는지가 문제를 일으킬 수 있다고 생각하는 것입니다.  다시말해 각각의 단어들은 올바르지만 문장 구성이 잘못되었고 이를 보완하기 위해서는 단어의 순서를 바꿔서 문법이 잘 지켜지게끔 해야하는 것과 같습니다. 만약에 그것 마저 소용이 없다면, 처음부터 전하려고 했던 이야기 자체가 정말 사람들의 기대를 충족시킬 수 있는지의 관점에서 다시 생각해볼 필요가 있습니다.  Conclusion 디자인 시스템은 디자인을 하는데 도움을 주는 좋은 방법입니다.  디자인 시스템을 통해서 모든 사람이 디자이너처럼 생각하고 일할 수 있기를 기대하는 것은 아니지만 이를 활용하여 정보를 잘 전달하고 모두가 디자인 리소스에 접근하게 함으로써 결과적으로 누구나 미리 만들어진 블럭을 활용하여 대략적인 컨셉을 만들어낼 수 있기를 기대합니다.  좋은 아이디어는 모두가 생각 해낼 수 있지만 디자이너들은 다른 팀원들에게는 없을 수도 있는 다양한 도구들을 통해 생각을 더 잘 표현 할 수 있습니다. 디자이너의 asset을 공유하는 것은 우리의 다른 팀원을 돕는 것뿐만아니라 아이디어에 대해서 의사소통하는 데에 도움을 주는 방법입니다. 그리고 디자인의 초기 컨셉과 원칙 그리고 디자인 문화를 공유하는 것은 모두가 디자이너가 일하는 방식을 이해하는 데 도움을 줍니다. 뿐만아니라 그렇게 공유된 컨셉,원칙,문화들은 전혀 쓸모없는 것이 아닙니다. 다른 누군가에게 새로운 생각을 할 수 있게 도울 수도 있기 때문입니다.
조회수 2446

A/B Testing 도구인 Optimizely 사용법

웹 서비스를 운영하다 보면 준비하는 과정에서 정말 많은 고민이 오갑니다. 컨텐츠의 배치, 헤드 카피, 인터랙티브.. 하지만 어떤 요소가 조금 더 사용자의 반응을 이끌어내는지 정확히 알 수 없습니다. 이런 부분들을 ‘직감’이나 ‘경험’으로 막연하게 자기 자신과 타인에게 주장하고 있지는 않나요?그렇다면 두 가지 혹은 그 이상의 시안들을 직접 시험대에 올려 각각 더 좋은 것을 선택하는 것은 어떨까요?A/B 테스팅에 관련한 유명한 일화가 하나 있습니다. 1497년, Vasco da gama는 최초로 유럽에서 아프리카 남부를 거쳐 인도까지 항해한 인물입니다. 그가 인도를 발견하고 귀항했을 때 160명의 원정대원 중 100명이 괴혈병으로 사망하는 사건이 있었습니다. 그만큼 괴혈병은 항해하는 선원들의 공포 대상이었죠. 그로부터 약 300년 뒤, 영국의 의사인 James Lind는 괴혈병의 치료법을 알기 위해 실험군을 나누어 각각 다른 음식으로 실험을 진행했습니다.실험은 다음과 같습니다. 괴혈병에 걸린 12명의 선원을 선정하여 그 중 10명에게는 보통 음식을 주고, 두 사람에게는 매일 라임 과즙을 마시게 하였습니다. 6일 후 라임 과즙을 마신 선원 두 명만이 괴혈병에 완벽히 치료된 모습을 보였습니다. James Lind가 실험하기 전에는 단순히 ‘감귤류 과일이 괴혈병에 좋다.’, ‘괴혈병으로 죽어가는 찰나에 잡초를 먹고 다시 살아났다.’ 라는 이야기만이 난무했었고 직접적인 치료법을 제시한 사람은 James Lind가 최초였습니다. 비타민C가 발견된 것이 1928년임을 고려하면, 이 당시에는 비타민C 이라는 개념이 없었기 때문에 James Lind의 실험은 후에 많은 선원의 목숨을 괴혈병으로부터 지켜주는 사례가 됩니다.괴혈병이 해적보다 더 무서웠던 대항해시대에 보통 음식(A)과 라임(B)을 이용해 선원들을 모두 구했던 영국 해군의 현명한 대처법에서 우리의 웹 서비스를 더욱 더 활성화 시키는 지혜를 얻어야 합니다.Optimizely?Optimizely는 웹서비스를 운영하면서 A/B Testing 수행을 원하시는 분들에게 적합한 서비스입니다. Optimizely를 사용하기 전에 A/B 테스팅에 대한 정보가 필요하다면 A/B 테스팅에 관련한 JC Kim님의 글( A/B Testing에 대한 기초적인 정보들 )을 먼저 읽어보시는 것을 추천합니다.Optimizely는 단순히 A/B 테스트의 진행과 그 통계 결과만 제공하는 것이 아니라, 테스트를 진행하는 동안의 모든 준비 과정에서 사용자들에게 도움을 주고 있습니다. 오늘은 그 Optimizely의 핵심 기능 및 활용법에 대하여 알아보겠습니다. Optimizely는 유료 서비스이지만 30일 동안의 Free Trial을 제공해주므로 그 기간 동안 충분히 이 서비스의 모든 것을 체험할 수 있습니다.Optimizely는 세계적인 대형 기업들이 이용하는 서비스로, 이들은 이미 Optimizely를 통해 각각 컨텐츠들에 대한 사이트 접속자들의 반응을 체크하고 있습니다. 대표적인 회사로 Starbucks, Salesforce, MTV, The Walt Disney Company, ABC 등이 있습니다.그렇다면 왜 많은 기업들이 A/B Testing에 집중하고 있고, Optimizely를 이용하는 걸까요?더 정확한 데이터를 추출하려는 노력.메일링 리스트를 수집하는 등의 폼 입력/전송을 하는 비율을 구하는 경우, 혹은 메인 페이지에서 다른 세부페이지로 이동하는 이용자 비율을 나타내기 위해 목표(Goal)을 나타냅니다. 목표한 골에 A 버전(기존안/Original) 이용자가 더 많이 들어갔는지, B 버전(새로 작성한 안/Variation)이 효과적이었는지를 테스트 할 수 있습니다.이처럼 Goal에 도달하는 행위를 ‘Conversion’이라 표현합니다. 방문자 수 대비 Conversions 수치를 비교한 Conversion rate를 비교하면 A/B 시안 중에 더 효과적인 결과를 수치와 그래프, 특히 “기준을 이길 수 있는 확률”(Chance to beat baseline)을 철저하게 계산해 결과를 명확하게 진단할 수 있습니다. 말 그대로 Goal과 Conversion Rate 수치로 사용자가 승자를 판단하는 것이 아니라, 수치공식을 통해 B 버전이 기존안(A버전)을 확실하게 이겼는지 아닌지를 파악해줍니다.더 자세히 알고싶은 부분은 해당 값을 구하는 통계공식이 있는 링크를 참고해주세요.정말 쉬운 실험요소 변경.Optimizely를 이용하면 여러분이 복잡한 CSS나 Javascript 기술이 없어도 쉽게 A/B 테스팅을 진행할 수 있습니다. Optimizely에서는 실험군의 요소를 마우스 클릭 몇 번으로 손쉽게 바꿀 수 있습니다. 가령 B 버전에 A 버전과 다른 문서 배치를 하거나 배경화면, 이미지, 폰트, 버튼 속의 문구 등도 별도의 코딩 절차 없이 Optimizely 실험페이지 내에서 변경할 수 있다는 말이죠. 또한 실시간으로 CSS를 변경하여 적용하거나 Javascript도 적용할 수 있습니다. 마치 ‘나모 웹 에디터’ 나 ‘드림위버’ 같은 인터페이스로 파워포인트 내의 요소를 다루듯 쉽게 바꿀 수 있습니다.위치와 크기를 Drag & Drop 으로 쉽게 움직이게 할 수 있습니다.웹사이트에 적용된 이미지 또한 로컬에 있는 파일 혹은 웹에 있는 이미지로 대체할 수 있습니다.텍스트도 곧바로 변경할 수 있고 HTML을 직접 대체해서 끼워 넣을 수 있습니다.참 쉽죠?간단한 설치위처럼 변경했던 시험요소들을 저장하려면 복잡하고 긴 코드를 다시 원래 파일에 붙여 넣어야 할까요? 그렇지 않습니다. Optimizely는 변경한 컨텐츠 정보를 간단한 자바스크립트 코드로 ‘Optimize’ 해 주기 때문에 단 몇줄만 추가해주면 원하는 결과가 나옵니다.확장성유명한 아티스트 두 명이 콜라보레이션 하는 상상을 해보죠. 각자의 개성을 살려 새로운 결과물들을 창조해내지요. 물론 그들의 궁합이 잘 맞아야 한다는 전제가 있습니다. 하지만 다행히도 Optimizely와 연동되는 서비스들은 궁합이 잘 맞는 편입니다. Optimizely는 A/B 테스팅에 관한 자료에 집중하고 있기 때문에, 조금 더 디테일한 자료(Analytics, Heatmap)는 욕심내지 않고 기타 많은 서비스와 연동합니다.Optimizely와 연동되는 서비스는 다음과 같습니다.AnalyticsGoogle AnalyticsKISSmetricsMixpanelOmniture SiteCatalystHeatmapClickTaleCrazyegg위 서비스 중 하나라도 이용 중이시라면, Optimizely와 어떤 부분이 연동이 되는 지 살펴보세요.마치며페이지 두 개를 접속자들에게 무작위로 나누어 배포해서 반응을 트래킹하는 기술은 흔할지도 모릅니다. 하지만 Optimizely를, 그리고 연동되는 다양한 서비스들을 이용하면 조금 더 세밀하고 확실한 데이터를 얻을 수 있습니다. 정말로 나의 웹 서비스에 필요한 것이 ‘잡초’인지 ‘레몬’인지 알고 싶다면 지금 당장 시작해보세요.#스포카 #기획 #A/B테스트 #A/BTest #꿀팁 #인사이트 #조언
조회수 1252

영어공부 꾸준히 하는 법

파파고나 구글 번역기와 같은 통번역 기기가 속속 등장하고 있다.기술이 발전하면 외국어가 더 필요 없어질 것이라고 생각했는데, 영어는 일상생활에 더욱 깊숙이 파고든다.출장과 해외여행이 점차 늘고, 길에서 마주치는 외국인도 많아졌다.업무에서도 영어자료를 쓸 일이 점점 늘어난다. 영어에 대한 문턱이 낮아진만큼 기대는 높아졌다. 번역기가 나오면 천국일 줄만 알았는데, 번역기 덕에 외려 부담감이 더 늘어가는 느낌이다. 기기에 의존하든 스스로의 능력에 기대든, 어쨋든 영어는 점점 생활의 일부가 되어가고 있다.나는 중학교때부터 영어를 끊임없이 배워왔다. 고등학교까지 나는 대학에 들어가기 위해 영어를 공부했고, 대학에서는 취직을 위해 영어를 배웠다. 그리고 지금은 세상에 뒤쳐지지 않고, 더 나은 정보를 얻기 위해 영어를 읽는다. 하지만 여전히 영어는 울렁울렁 거린다.나도 영어 잘하고 싶다..직장에 들어와 영어 공부를 한다고 학원도 다녀보고, 영어 신문도 보고 영어 잡지도 봤지만 결국은 오래가지 못했다. 시작할 때는 그 의지가 제법 호기로웠지만, 작심삼일이라는 단어는 꽤나 무거웠다. 하지만 그렇다고 영어공부를 포기할 수는 없다. 타의든 자의든 영어를 해야만 더 많은 기회에 노출되는 것이 현실이기 때문이다.마주친 현실과 꿈쩍않고 낮은 의지 사이에서 많은 고민을 했다.도대체 어떻게 해야 영어를 꾸준히 공부할 수 있을까 나란 놈에게 맞는 영어공부법을 찾기 위해 지난 실패들을 한번 돌아봤다. 학원은 공부하는 것보다 가는 게 힘들었다. 직장 동료와 술자리도 가져야 하고 친구도 만나야 하는 일정속에서, 학원갈 시간이 살아남을 틈은 없었다. 영어 신문과 잡지는 솔직히 어려웠다. 트럼프나 시진핑의 대화는 그렇게 까지 관심이 가진 않았다. 내 친구도 이야기도 아닌데. 그러다보니 자연스럽게 공부와 멀어졌다. 하지만 영어는 꾸준히 공부해야 했다.경제학과 세상에 대해 공부하기에 참 좋은 잡지지만, 어렵다인터넷을 뒤지다 마지못해 재미있는 영어공부법이라 하는 미드보기를 뒤늦게 시작했다. 남들은 10년전에 이미 경험했던 그 미드보기다. 나이 서른에 뒤늦게 24시도 보고, 프리즌 브레이커도 봤다. 영어 공부한다는 핑계로 한글 자막을 틀어놓고 매일을 킬킬거렸다. 영어공부가 이렇게 재미있을 수가. 영어가 느는지 알 수는 없었지만 아무튼 하루에 1-2시간씩 영어발음을 꾸준히 들었다. 당연히 크게 효과는 없었다. 두달 가량을 거의 매일 미드를 봤으니 못해도 50시간은 공부했을 텐데, 영어 말하기는 제자리였다. 드라마에서 수도 없이 나왔던 "범인이 아직 잡히지 않았어" 라는 문장을 나는 두달후에도 여전히 "he is still our there" 로 표현했다. (이 표현도 틀린건 아니지만, 드라마에서 계속 나왔던 표현은 The criminal is still at large 라는 표현이었다) 대통령 케빈스페이시와 영부인 로빈라이트의 영어발음은 정말 좋다 ⓒ Netflix Original House of Cards재미로 미드보기의 효과없음을 여실히 느끼고 나는 그날부터 영어자막을 틀어놨다. 그제서야 영어공부를 하는 것 같은 느낌을 조금 받을 수 있었다. 잘 모르겠는데 중요한 표현 (중요한 표현 같다는 느낌이 있다!) 은 뭐라고 한건지 다시한번 돌려도 보고, 이해가 안되면 이해가 되는 장면으로 되돌려 보기도 했다. 그렇게 직장인이 되고 처음으로 작심삼일의 엄벌을 피해 영어 공부를 꾸준히 하기 시작했다. yay!하지만 어디 완벽한 공부법이란게 있을까. 영어자막 미드보기는 영어 듣기랑 빠른 독해에 큰 도움이 됐지만, 영어로 말하기에는 별반 차이를 주지 못했다. 외국 바이어와 드라마 이야기를 하면서 친해지는 데에는 큰 기여를 했다. 그래서 두달 전부터는 미드에서 나오는 표현들을 하루에 두세개씩 노트에 적기 시작했다. 어떤 날은 한문장을 적기도 하고, 간혹 느낌이 충만한 날에는 10문장을 쓰기도 했다. 하지만 10문장을 쓴 다음날은 어쩐지 한문장도 쓰기가 싫어졌다. 그렇게 한달이 지난 시점부터는 하루에 3문장을 꾸준히 쓰고 있다. 부담이 되면 또다시 작심삼일의 엄벌에 처해질 것을 알기에, 지금은 욕심을 더 내지 않고 딱 하루 3문장만 쓰고 있다.노트에 계속 적으면 복습에도 도움이 된다그럼 이제 사람들이 가장 궁금해 할 3문장 쓰기의 효과에 대해 이야기를 해보겠다.무엇이 달라졌을까? 혹시 엄청난 기대를 하고 있다면 정신을 바로 차려야 한다. 애초에 기대가 너무 크면 안된다. 고작 하루 5분의 시간을 투자했을 뿐이다. 하지만 5분의 시간을 투자한 것 치고는 대단한 변화가 있었으니, 1. 우선 생활영어 표현이 엄청나게 많이 늘었다. 똑같은 표현은 훨씬 자연스러워졌다. 바이어를 만나면 취미가뭐냐고 물을 때 이제는 "What's your hobby?" 대신 "what do you do for fun?" 을 쓴다. (우리도 시간날 때 뭐하냐고 묻지 취미가 뭔지 묻지를 않는다!) 2. 자연스러운 표현을 쓴다는 자신감을 얻으니, 외국인과의 대화를 더 많이 시도하게 됐다. 초면인 사람을 만나면 뭐라고 말할지 몇번을 미리 연습했고, 영어 실력때문에 ice-braking 을 포기했었던 나지만, 지금은 나름 몇마디를 할 수 있다. 자연스레 영어가 늘어가는 재미도 느꼈다. 새로 배운 표현을 외국인이 바로 알아들었을 때의 그 쾌감은 정말 이루말할 수가 없다. 3. 끝으로 한가지를 덧붙이자면, (개인적으로는 이게 가장 만족스럽다) 무엇보다 드디어 영어를 꾸준히 공부하는 방법을 찾았다는 것이다. 영어 실력을 늘려야하고, 정해진 공부법은 맞지 않아 꾸준히 할 수 없어 답답했던 마음이 지금은 완전히 사라졌다. 어쩌면 무언가를 해야한다는 강박을 해결했기 때문일 수도 있다. 하지만 나는 이제 영어를 꾸준히 공부하고 있고, 그 성과도 매일매일의 업무에서 확인하고 있다.하루 3문장 영어쓰기는 이제 고작 2달이 지났다. 문득 내가 이 습관을 계속 유지할 수 있을까 걱정이 되기도 한다. 하지만 또 어느순간 포기하면 어떠랴. 새로운 방법을 다시 찾으면 된다. 우선은 지금의 공부법을 할 수 있는 한 유지해보려고 한다. 당장은 미드를 보고 있지만, 나중에는 그동안 실패했던 영어 신문과 영어 잡지도 똑같은 방법으로 공부를 해볼 수 있지 않을까 생각하고 있다. 6개월에 되는 시점에 다시한번 글을 써보겠다. 부디 그때까지 꾸준히 이 습관을 계속 유지할 수 있기를!끝으로, '파파고와 구글 번역기가 더 발전해서 영어능력이 정말 필요없어지면 어쩌지' 라는 쓸데 없는 걱정도 해본다. (가진자의 걱정이 이런거구나 싶다)by 아직도 영어가 고픈 30대 직장인챌린저스, 확실한 목표달성 꾸준한 습관형성 앱www.chlngers.com
조회수 1745

TDD(파이썬) : 테스트 잘하고 계신가요?

Overview반복적인 테스트에 지쳐가고 있던 무렵, TDD방법론을 접하게 되었습니다. TDD(Test Driven Development)는 테스트 주도적인 개발로 소스코드 작업 전에 테스트 코드를 먼저 작성해 소스수정에 대한 부담을 덜고 디버깅 시간을 줄일 수 있습니다. TDD 장점소스코드의 품질이 높다.재설계 및 디버깅 시간이 절감된다.TDD 단점단기적 코드일 경우 생산성이 떨어진다.실제 코드보다 테스트 케이스가 더 커질 수 있다.파이썬에서 TDD가 필요한 이유1) 파이썬에는 정적 타입 검사 기능이 없다. (Python 3.6 에서는 정적 타입 선언 가능)2) 동적언어이기 때문에 TDD를 하기에 적합하다.3) 파이썬은 간결성과 단순함으로 생산성이 높은 반면 런타임 오류가 발생할 수도 있다.4) 파이썬을 신뢰할 수 있는 유일한 방법은 테스트를 하는 것이다.파이썬 테스트 모듈 unittest이번 글에서는 unittest를 사용해 단위 테스트를 해보겠습니다. unittest는 이미 내장되어 있어 따로 설치하지 않아도 되는 표준 라이브러리입니다. 사용방법1) import unittest 2) unittest.TestCase 상속받는 하위 클래스 생성3) TestCase.assert 메소드를 사용하여 테스트 코드를 간략화4) unittest.main() 실행그럼 간단한 예제로 단위 테스트를 해보겠습니다.1.사칙연산 함수를 추가합니다.def add(a, b):     return a + b   def substract(a, b):     return a - b   def division(a, b):     return a / b   def multiply(a, b):     return a * b 2. unittest.TestCase 상속받아 테스트 클래스를 생성합니다. 아래는 각각의 함수 결과값을 비교해 텍스트를 출력하는 코드입니다.import unittest class TddTest(unittest.TestCase): def testAdd(self):         result = lib_calc.add(10, 20)         if result == 30:             print('testAdd OK')      def testSubstract(self):         result = lib_calc.substract(20, 30)          if result > 0:             boolval = True         else:             boolval = False if boolval == False:             print('testSubstract Error')      def testDivision(self):         try:             lib_calc.division(4, 0)         except Exception as e:             print(e)      def testMultiply(self):         result = lib_calc.multiply(10, 9)          if result < 100>             print('testMultiply Error') if __name__ == '__main__':     unittest.main() 3.결과: 해당 조건에 만족해 작성한 텍스트가 출력됩니다.이번에는 unittest에서 지원하는 TestCase.assert 메소드를 사용해 간략하게 소스를 수정해보겠습니다.TestCase.assert 메소드1) assertEqual(A, B, Msg) - A, B가 같은지 테스트2) assertNotEqual(A, B, Msg) - A, B가 다른지 테스트3) assertTrue(A, Msg) - A가 True인지 테스트4) assertFalse(A, Msg) - A가 False인지 테스트5) assertIs(A, B, Msg) - A, B가 동일한 객체인지 테스트6) assertIsNot(A, B, Msg) - A, B가 동일하지 않는 객체인지 테스트7) assertIsNone(A, Msg) - A가 None인지 테스트8) assertIsNotNone(A, Msg) - A가 Not None인지 테스트9) assertRaises(ZeroDivisionError, myCalc.add, 4, 0) - 특정 에러 확인1. TestCase.assert 메소드 사용TestCase.assert 메소드를 사용하여 에러를 발생시켜 보겠습니다.import unittest class TddTest(unittest.TestCase): def testAdd(self):         result = lib_calc.add(10, 20)          # 결과 값이 일치 여부 확인         self.assertEqual(result, 31)      def testSubstract(self):         result = lib_calc.substract(20, 10)          if result > 10:             boolval = True         else:             boolval = False # 결과 값이 True 여부 확인         self.assertTrue(boolval)      def testDivision(self):         # 결과 값이 ZeroDivisionError 예외 발생 여부 확인         self.assertRaises(ZeroDivisionError, lib_calc.division, 4, 1)      def testMultiply(self):         nonechk = True result = lib_calc.multiply(10, 9)          if result > 100:             nonechk = None # 결과 값이 None 여부 확인         self.assertIsNone(nonechk) if __name__ == '__main__':     unittest.main() 2. 결과1) 테스트가 실패해도 다른 테스트에 영향을 미치지 않음2) 실패한 위치와 이유를 알 수 있음다음으로 setUp(), tearDown() 메소드를 사용하여 반복적인 테스트 메소드 실행 전, 실행 후의 동작을 처리해보겠습니다.TestCase 메소드1) setUp() - TestCase클래스의 매 테스트 메소드가 실행 전 동작2) tearDown() - 매 테스트 메소드가 실행 후 동작 1. setUp(), tearDown() 메소드 사용- setUp() 메소드로 전역 변수에 값을 지정- tearDown() 메소드로 “ 결과 값 : ” 텍스트 출력import unittest class TddTest(unittest.TestCase): aa = 0     bb = 0     result = 0 # 매 테스트 메소드 실행 전 동작     def setUp(self):        self.aa = 10        self.bb = 20 def testAdd(self):         self.result = lib_calc.add(self.aa, self.bb)          # 결과 값이 일치 여부 확인         self.assertEqual(self.result, 31)      def testSubstract(self):         self.result = lib_calc.substract(self.aa, self.bb)          if self.result > 10:             boolval = True         else:             boolval = False # 결과 값이 True 여부 확인         self.assertTrue(boolval)      def testDivision(self):         # 결과 값이 ZeroDivisionError 예외 발생 여부 확인         self.assertRaises(ZeroDivisionError, lib_calc.division, 4, 1)      def testMultiply(self):         nonechk = True self.result = lib_calc.multiply(10, 9)          if self.result > 100:             nonechk = None # 결과 값이 None 여부 확인         self.assertIsNone(nonechk)      # 매 테스트 메소드 실행 후 동작     def tearDown(self):         print(' 결과 값 : ' + str(self.result))   if __name__ == '__main__':     unittest.main() 2. 결과- setUp() 메소드로 지정한 값으로 테스트를 수행 - tearDown() 메소드로 각각의 테스트 메소드 마다 “ 결과 값 : ” 텍스트 출력실행 명령어 여러 옵션을 사용하여 실행 결과를 출력해보겠습니다.실행 명령어python -m unittest discover [option]1. -v : 상세 결과 2. -f : 첫 번째 실패 또는 오류시 중단3. -s : 시작할 디렉토리4. -p : 테스트 파일과 일치하는 패턴5. -t : 프로젝트의 최상위 디렉토리1. 상세 결과테스트 메소드명 및 해당 클래스명 출력 2. 첫 번째 실패 또는 오류시 중단첫 번째 테스트에서 오류 발생하여 중단3. 여러 옵션 실행현재경로 디렉토리 안에 tdd_test*.py 패턴에 속하는 모든 파일의 상세 결과Conclusion지금까지 파이썬에서 unittest 모듈을 이용한 테스트 코드를 작성했습니다. 처음에는 귀찮고 번거롭지만 테스트 코드를 먼저 작성하는 습관을 길러보세요. 분명 높은 품질의 소스코드를 만들 수 있을 겁니다!참고Python 테스트 시작하기파이썬 TDD 101글곽정섭 과장 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발자 #개발팀 #인사이트 #경험공유 #파이썬 #Python
조회수 1009

Node.js 이해하기

Understanding node.js 글을 번역한 글입니다. 부족한 영어 실력이지만 공부를 위해 번역하여 틀린 내용이 있을 수 있습니다. 이런 부분이 있을 경우 댓글로 알려주시면 감사하겠습니다!! 글이 문답형으로 진행되니 감안하시고 읽어주세요!Node.js(이후 '노드'로 통칭)를 소개했을 때 사람들은 일반적으로 두 가지 반응을 보인다. 바로 알았다고 하는 반응 혹은 매우 혼란스러워 하는 반응이다.만약 너가 후자의 경우라면 노드를 설명하기 위한 내 시도가 있다.노드는 command line tool이다. 너는 파일을 다운로드하고 컴파일하고 소스를 설치한다.노드는 JavaScript(이후 '자바스크립트'로 통칭) 프로그램들을 터미널에 'node my_app.js'를 입력함으로써 실행하게 한다.자바스크립트는 V8 자바스크립트 엔진으로 실행된다. (구글 크롬을 빠르게 만드는 것이다.)노드는 네트워크와 파일 시스템에 접근하기 위한 자바스크립트 API를 제공한다.나는 내가 필요한 모든 것을 Ruby, Python, PHP, Java에서 구현할 수 있어!너의 말이 맞다! 미안하게도 노드는 너를 위해 오고 너의 일을 하는 별난 유니콘이 아니다. 이것은 단지 툴이고 적어도 지금은 너가 보통 사용하는 완벽한 툴들을 대체하지 않을 것이다.요점을 알려줘!ㅇㅋ. 기본적으로 노드는 같은 시간에 여러 가지의 일들을 해야할 때 매우 좋다. 코드를 작성하고 "나는 이것들이 동시에 작동했으면 좋겠어"라고 말해본 적 있니? 노드에서는 너의 코드를 제외한 모든 것들이 동시에 작동한다.엥??정말이다. 너의 코드를 제외한 모든 것들이 동시에 작동한다. 이것을 이해하기 위해 너의 코드는 왕이고 노드는 왕의 하인들이라고 상상해보자.한 하인이 왕을 깨워 왕이 필요한 것들이 있는지 물어보는 것으로 하루가 시작된다. 왕은 하인들에게 해야할 일 목록을 주고 다시 오랫동안 자러 간다. 하인은 이 할 일들을 동료들에게 나눠주고 그들은 일을 시작한다.하인이 일을 끝내면 그는 왕의 쿼터 밖으로 보고서를 나열한다. 왕은 한 하인씩 따로따로 들여보내고 그들의 보고서를 듣는다. 때때로 왕은 나가는 길에 하인에게 더 많은 일을 준다.인생은 좋다. 왕의 하인들이 동시에 왕의 모든 일들을 수행하는 동안 왕은 하나의 결과가 있는 보고서에만 따로따로 집중할 수 있다.짱이다! 하지만 그 어리석은 비유를 그만두고 컴퓨터적으로 말해줄 수 있니?ㅇㅋ. 간단한 노드 프로그램은 아래와 같을 것이다:너의 코드는 노드에게 파일을 읽고 쓰는 두가지 일을 주고 자러 간다. 노드가 일을 완료했을 때 이것을 위한 콜백이 실행된다. 하지만 그들은 동시에 실행되는 콜백이 될뿐이다. 콜백이 실행을 완료하는 동안까지 다른 모든 콜백들은 라인에서 멈춰있어야 한다. 게다가 그 콜백들이 실행될 것이라는 보장도 없다.그래서 나는 동시에 같은 데이터 구조에 접근하는 코드에 관해 걱정할 필요가 없지않아?맞다! 그것이 자바스크립트의 싱글 쓰레드와 이벤트 루프 디자인의 아름다움이다. 좋긴 하지만 내가 왜 노드를 써야해?한 가지 이유는 효율성이다. 웹 어플리케이션에서 너의 메인 응답 시간 비용은 대개 너의 모든 데이터베이스 쿼리들이 실행하는데 전력하는 시간들의 합이다. 노드에서는 제일 느린 쿼리를 실행하는 동안 응답시간을 줄이기 위해 너의 모든 쿼리를 즉시 실행한다.또 다른 이유는 자바스크립트다. 너는 노드를 브라우저와 백엔드 사이에서 코드를 공유하기 위해 사용할 수 잇다. 자바스크립트는 정말 다방면성의 언어다. 너가 과거에Python, Ruby, Java, PHP를 써왔다하더라도 아마도 어떤 자바스크립트를 선택해왔을 것이다.마지막 이유는 로우 스피드다. V8은 계속해서 행성에서 가장 빠른 동적 언어 인터프리터의 하나로 경계를 밀고 있다. 나는 자바스크립트만큼 적극적으로 속도를 위해 푸시되는 다른 언어를 생각할 수 없다. 게다가 노드의 I/O 설비는 정말 가볍고 너의 시스템의 가능한 많은 I/O 능력을 활용하게 다가가는 것이다.그러면 너는 내가 당장 내 모든 앱을 노드에서 구현하라고 말하는거야?그렇기도 하고 아니기도 하다. 너가 노드 망치를 휘두르기 시작하면 모든것들은 분명 손톱처럼 보이기 시작할 것이다. 하지만 만약 너가 데드라인이 있는 일을 한다면 너는 아래의 사항들을 기초하여 결정하고 싶을 수도 있다.- 적은 응답 시간과 높은 동시성이 중요한가? 노드는 이것에 정말 좋다.- 프로젝트가 얼마나 큰가? 작은 프로젝트는 괜찮다. 큰 프로젝트는 아마 신중하게 평가해야 한다. (이용가능한 라이브러리, 버그를 고치기 위한 리소스들, 투 업스트림 등)윈도우에서 노드가 실행되니?안된다. 만약 너가 윈도우라면 너는 리눅스와 함께 버츄얼 머신을 실행해야 한다. (VirtualBox를 추천한다.) 윈도우는 노드를 지원하는 계획이 있지만 그 포트와 함께 도와주기를 원하지 않는다면 앞으로 몇 달 동안 뜸들이지 마라.노드에서 DOM에 접근할 수 있니?좋은 질문이다! 접근할 수 없다. DOM는 물질적인 브라우저고 노드의 자바스크립트 엔진(V8)은 감사하게도 그 복잡한 모든것들과 분리했다. 그러나 사람들은 노드 모듈로써 DOM를 실행하여 일한다. 이것은 클라이언트 사이드 코드 유닛 테스트와 같은 매우 놀라온 가능성을 열어줄 것 같다. 이벤트 드리븐 프로그래밍은 어렵지 않니?그것은 너에게 달렸다. 만약 너가 juggle AJAX를 호출하는 방법과 브라우저에서 유저 이벤트들에 대해 이미 배웠다면 노드 사용 방법을 배우는게 큰 문제 아닐 것이다.그렇지 않다면 너가 유지 보수 디자인을 마련하는데 도움을 줄 수 있는 드리븐 개발을 테스트해라.노드는 누가 사용하고 있니?node wiki에 작고 불안정한 리스트가 있다. 야후는 YUI를 위해 노드를 경험중이고 Plurk는 거대한 comet을 위해 사용중고 Paul Bakaus(jQuery UI fame)은 노드 백엔드를 가지는 mind-blowing game engine을 빌드 중이다. Joyent는 노드 창시자인 Ryan Dahi를 고용하여 개발에 막대한 지원을 해주고 있다.아 그리고 Heroku는 실험적으로 hosting support for node.js를 발표했다.어디서 더 배울수 있니?Tim Caswell는 훌륭한 How To Node 블로그를 운영중이다. 트위터에서 #nodejs를 팔로우해라. 메일링 리스트를 구독해라. 그리고 IRC 채널 #node.js에서 시간을 보내라. 우리는 곧 200 lurker-mark에 도달해 간다. 또한 나는 계속 http://debuggable.com/에 글을 쓰고 있다. #트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유
조회수 2072

스켈티인터뷰 / 스켈터랩스의 열정리크루터 최고 님을 만나보세요:)

Editor. 스켈터랩스에서는 배경이 모두 다른 다양한 멤버들이 함께 모여 최고의 머신 인텔리전스 개발을 향해 힘껏 나아가고 있습니다. 스켈터랩스의 식구들, Skeltie를 소개하는 시간을 통해 우리의 일상과 혁신을 만들어가는 과정을 들어보세요! 스켈터랩스의 열정리크루터 최고 님을 만나보세요:)사진1. 스켈터랩스의 열정 리크루터, 최고 님Q. 자기소개를 부탁한다.A. 스켈터랩스에서 최고의 HR매니저가 되기를 꿈꾸는 최고다.Q. 이름이 정말 인상 깊다. ‘최고' 라는 이름은 어떻게 지어졌나.A. 출생지가 독일이다. 아버지께서 외국사람들이  발음하기 쉽고, 기억하기 쉬운 이름을 짓고 싶어하셔서 외국어로도 발음하기 쉬운 ‘고(Go)’라는 이름을 갖게되었다. 아마 숨은 뜻은 항상 ‘최고’의 사람이 되고, 어떤 분야에서건 ‘The Best’로 성장하라는 의미로 지어주셨을거라고 생각한다.Q. 스켈터랩스에서 어떤 업무를 맡고 있는가. 항상 전화를 자주 하고 있는 것으로 보인다.A. 맞다. 주요 업무가 리크루팅이기 때문에 잠재적 지원자들과 연락을 하느라 통화가 잦은 편이다. 나는 스켈터랩스의 모든 인사 업무를 담당하고 있는데, 특히 스켈터랩스와 동반 성장할 수 있는 지원자를 선별하기 위해 노력하고 있다.Q. 리크루터로서 많은 지원자를 만나보았을 것 같다. 기억에 남는 지원자가 있나.A. 기억력이 좋은 편이라 내가 뽑은 지원자는 모두 기억하고 있다. 스켈터랩스 입사 이전에 헤드헌터로 일했는데, 참 다양한 사람을 만날 수 있는 경험이었다. 헤드헌터로서 고객사에 3명의 지원자를 추천한 적이 있다. 그런데 고객사와의 면접이 있는 자리에, 한 지원자가 A4용지 50장이 넘는 분량으로 형광펜 자국이 잔뜩 남아있는 서류 뭉치를 가져왔었다. 자세히 들여다보니 지원한 고객사에 대한 다양한 매체의 정보를 모아서 정리한 일종의 자료집이었다. 많은 지원자들이 면접 전에 회사에 대한 공부를 할테지만, 그토록 완벽하게 준비해 온 지원자는 처음이었다. 그렇게 노력한 분이 당연하게도 최종 입사자로 결정이 났었다.스켈터랩스의 경우, 각자의 배경과 관련 없이 알고리즘 해결 능력과 코딩 능력 등의 실무 기준을 중심으로 판단하는 편이다. 상대적으로 외국인 비중도 높다. 그 중 한 분은 러시아 국적의 지원자였는데, 유학생 신분이였던 탓에 핸드폰이 없었다. 전화 인터뷰가 불가하여서, 의사소통 할 수 있는 수단은 이메일이 전부였다. 무엇보다 그 지원자의 태도가 기억에 남는데, 회사의 세밀한 부분이며 면접과 업무에 대한 이야기까지 하나씩 꼼꼼하게 물어보고 준비하는 모습을 보였다. 덕분에 그 분과의 이메일만 입사 전에 20통 넘게 오고 갔던 것 같다.Q. 좋은 인재를 뽑는 나름의 노하우가 있다면.A. ‘노하우'라고 표현하기는 어렵지만, 지원자의 서류만 보고 판단하기 보다는 전화 인터뷰나 대면 면접을 통해 최대한 여러 지원자를 만나보고 그들과 눈을 맞추고 대화 하다보면 우리 회사에 대한 입사 의지 혹은 열정을 어느 정도 확인 할 수 있다. 스켈터랩스는 말 그대로 ‘최고의 인공지능 기술 회사’를 추구하기 때문에, 그만큼 알고리즘에 대한 이해도, 코딩 능력이 중요하다. 그러나 그런 부분은 이미 두 차례 이상의 실무 면접을 통해 꼼꼼하게 검증되는 부분이다. 나는 실무 면접 단계 이전에 지원자가 우리 조직과 융화될 수 있는지, 입사에 대해 진지한 태도를 가지고 있는지를 살피려고 한다. 뻔한 질문인 ‘지원 동기' 등을 묻기 보다는 편한 분위기에서 예상치 못한, 혹은 일상적인 질문을 던지고 그 대답을 준비하는 자세나 태도를 보는 편이다.Q. 다른 회사의 인재 영입 방식과 스켈터랩스의 차별점을 무엇이라고 생각하는가.A. 많은 기업들 특히 스타트업 기업들은 성장기에 들어섰을 때 단시간 내 많은 인원을 모집하는 대규모 채용(Mass Recruitment) 방식을 사용한다. 그러나 스켈터랩스는 공격적으로 여러 명을 뽑기보다 아주 잘 다듬어진 소수의 채용을 추구하고 있다. 오죽하면 스켈터랩스의 문화에도 ‘같은 목표를 가진 똑똑한 소수의 구성원들과 함께 성장하는 것이 평범한 사람들과 일하는 것보다 훨씬 재미있습니다’라고 명시했겠나. 그만큼 면접이 쉽지 않다. 기술 면접은 국내 최고의 IT 기업으로 꼽히는 여타 기업들과 수준이 비슷하거나 혹은 그 이상이다. 이렇듯 지원자에 대한 기준이 높기 때문에, 지원자 한 명 마다 깊게 들여다보려고 한다. 스켈터랩스가 요구하는 인재의 수준이 높은 만큼, 최고의 인재 영입을 위해 리크루터로서 발로 뛰어야한다는 사명감을 가지고 임하고 있다.  Q. 스켈터랩스에서 일을 하며 가장 어렵거나 힘든 점이 있다면.A. 스켈터랩스는 아직 B2C 시장에 본격적인 진출을 하지도 않았고, 규모도 스타트업인 만큼 작은 편이다. 물론 현재는 70여명의 구성원과 함께하기에 작다고만 말할 수는 없지만 말이다. 스켈터랩스를 아직 모르는 사람들이 많은 만큼, 잠재적인 지원자에게 어필하는 부분도 약하다. 그래서 마케팅 팀과의 협업을 통해 스켈터랩스 브랜딩을 적극적으로 펼칠 계획이다. 우리가 어떤 회사이고 얼마나 기술력이 있는지, 문화는 어떠한지 구체적으로 소개하고 알리는 방법을 모색하고 있다. 블로그를 통한 이런 인터뷰도 그 노력의 일환이라고 생각한다. 이렇게 회사를 알린다면 인재 영입도 수월해지고 지원자도 많이 늘어나지 않겠나. 정말 과장 하나 없이 수평적인 문화에서 즐겁게 일할 수 있는 회사라는 점을 이 글을 읽는 모든 이들에게 말해주고 싶다.Q. 스켈터랩스의 문화 중 가장 좋아하는 문화는 무엇인가.A. 나는 그냥 지금 스켈터랩스 자체가 좋다. 감히 사랑한다고도 말할 수 있을 정도다. 스켈터랩스는 구성원들이 자유롭게 의견을 제시할 수 있고, 창의성을 우선시되고, 수평적인 커뮤니케이션이 이루어지는 문화를 갖추고있다. 출신에 구애받지 않고 다양한 인재들이 아이디어를 필터링 없이 선보여 구현할 수 있는 환경이다. 많은 스타트업들이 이 문화를 표방하고 자신들이 정말 실천하고 있다고 말하지만 진짜로 이렇게 이루어지는 곳은 찾기 힘들다. 특히 국내에서는 일종의 직급에 따라 일종의 계급이 존재하는 경우가 많지 않나. 물론 스켈터랩스에서도 Senior / Junior 라는 존재하지만 이는 의사소통을 원활히 하기 위한 역할일뿐 참여도나, 의사결정 과정에 있어서는 모두 동등한 위치에 있다. 자율적인 출퇴근, 심지어는 집에서 원격으로 업무를 처리해도 아무도 눈치 주지 않는 문화, 수평적인 의사결정과 조직 체계, 일일이 보고를 하거나 받지도 않고 자신의 업무에 집중할 수 있게 해주는 분위기 등이 스켈터랩스의 성장 원동력이라고 생각한다. 무엇보다 C-Level(관리자 직급)에 있는 분들이 회사의 규모가 아무리 커지더라도 문화를 온전히 지키기 위해 하는 노력들을 보며 감탄할 때가 많다.사진2. 스켈터랩스의 컬쳐 커미티(Culture Committee)Q. C-Level 분들의 문화를 위한 노력에 대해 구체적으로 듣고 싶다. A. 최근에 스켈터랩스의 문화와 관련된 익명 설문조사를 실시했다. 이러한 서베이는 컬쳐 커미티(Culture Committee, 스켈터랩스의 문화를 만들고 개선시키기 위한 자율 조직)가 정기적으로 실행하고 있다. 서베이의 결과에 대해서 한 사람이 맡아 보고서를 만들다기 보다는, C-Level 분들까지 함께 모여서 하나씩 응답을 살피고 있다. 달면 삼키고 쓰면 뱉는다는 말처럼 흔히 긍정적인 피드백에 집중하게 되는데, 스켈터랩스는 반대다. 부정적인 피드백을 오히려 꼼꼼히 살피려고 한다.서베이 답변 중에 하나가 '외국인과 한국인 사이의 언어 장벽때문에 커뮤니케이션과 소통이 아쉽다'는 것이었다. 그래서 이 문제를 해결하기 위한 방안으로 회사 차원에서 사내 영어 교육을 검토를 하고 있다. 외국인 비율이 더 높아지면 한국어 교육을 실시할 지도 모르겠다. 사소한 예로는 간식 얘기를 하고 싶다. 스켈터랩스의 키친에는 입이 심심할 때 간편하게 먹을 수 있는 각종 간식과 음료가 구비되어 있다. 감자칩이나 초콜렛, 사탕, 소시지 등이 주를 이루었는데 응답 중에 ‘건강한 간식'을 먹고 싶다는 피드백을 받았다. COO를 맡고 있는 안현덕님은 이를 보자마자 바로 간식 재구매부터 실시했다. 덕분에 요즘 스켈터랩스는 사과와 체리, 포도, 바나나 등의 각종 과일로 채워져있다. 아주 사소하지만 이렇게 한 사람, 한 사람의 목소리를 주의 깊게 듣고 바로 개선하려는 노력들이 관리자 급에서부터 주도적으로 이루어지고 있다.Q. 기술 회사에서 리크루터로 일하며, 일종의 기술에 대한 이해 등이 어려움으로 다가오지는 않는지.A. 나는 어디까지나 리크루터지 않나. 좋은 인재를 모아서 소개하는 역할을 담당하는 셈이다. 앞서 말했듯 기술 면접은 실무진들이 직접 진행하고 있고, 채용 포지션에 대한 JD(Job Description)는 CTO인 조성진 님과 함께 구체화 시킨다. 하지만 엔지니어들의 이력서 검토를 위해서 우리 회사의 JD와 Project 그리고 Product들에 대한 이해가 필요한 것도 사실이다. 기술적으로 궁금한 부분이 있으면 사내 면접관님들과 수시로 커뮤니케이션을 진행하며 틈틈히 공부하고 있다. 또한 사내에서 열리는 Tech-Talk와 같은 세미나를 통해 자연스럽게 최신 기술에 대해 들여다보려고 한다. 어려움이라기 보다는, 리크루터로서 쉽게 접할 수 없는 지식이기 때문에 오히려 즐기며 임하고 있다.Q. 최근 가장 뿌듯한 순간은?A. 어느 조직에 있더라도 가장 뿌듯한 순간은 내가 스카우트한 사람이 회사와 동반 성장하는 것을 지켜보는 일인 것 같다. 회사가 성장하거나, 혹은 구성원 한 사람만 성장하는 것은 쉽다. 그러나 회사와 구성원이 절묘하게 싱크가 맞아 떨어져 회사와 구성원이 서로에게 시너지가 나는 것은 흔치 않은 일이다. 다행스럽게도 스켈터랩스에서는 엄격한 채용 기준과 자율적인 문화 덕분인지 이런 동반 성장의 모습을 종종 목격할 수 있다. 최근에도 인턴으로 입사한 분이 회사에 대해 애정을 가지고 정직원으로의 입사를 희망하며, 사내 동아리 활동도 활발하게 이어가고 업무를 수행하는 모습을 볼 수 있었다. 이런 모습을 볼 때 일종의 뿌듯함이랄까, 리크루터로서의 보람을 느낀다.Q.  신규 지원자들을 위한 입사 꿀팁을 공유해달라.사실 꿀팁이랄 것 까지는 없지만, 지원자들에게 ‘미리 걱정하지 마라'라는 얘기를 가장 해주고싶다. 지원자들에게 가장 많이 받는 질문 중 하나가 ‘인공지능 관련 경험이 없는데, 지원해도 될까요?’다. JD를 읽은 분들은 그 중 하나라도 자신이 충족하지 못하면 자격조건이 없다고 생각하는 경우도 있더라. 그러나 우리 회사는 기본적으로 코딩 능력과 알고리즘에 대한 이해만 있다면 누구나 지원할 수 있다. 포지션에 따라 다르지만, 소프트웨어 엔지니어는 정말 항상 채용을 진행하고있다. 그러니 이미 입사자를 뽑은 것은 아닌지, 자신의 경력 분야와 달라서 면접에서 떨어지는 것은 아닌지 등의 앞선 지레짐작을 할 필요는 없다. 다만 상대적으로 손코딩 면접에서 어려움을 겪는 지원자가 많았기 때문에, 면접 전 코딩에 대해 다시 한번 들여다보고 공부하는 것을 추천한다.그리고 자신의 개성을 마음껏 드러내라고 말하고 싶다. 우리는 다양한 사람들이 모여 서로 다른 생각을 나눌 때 창의성이나 영감 등이 피어날 수 있다고 믿는다. 업무 스타일도 주어진 업무를 수행하는 전통적인 방식이 아니다. 자발적으로 아이디어를 내고 업무의 방향에 대해 주도적으로 결정하는 경우가 많다. 때문에 자기주도적이고 개성있는 모습을 드러내는 것이 좋지 않을까.Q.  리크루터가 된 계기가 궁금하다. 고등학교 시절부터 주식을 분석하는 금융인에 대한 로망을 가지고 있었는데, 그 꿈을 실현하기 위해 대학을 뉴욕으로 진학하였다. 대학 졸업 후 운 좋게도 모든 금융인들의 메카인 월스트리트에서 일했지만, 서브프라임 모기지 사태가 터진 후 세계 경제가 급격히 하락세에 접어들었다. 이후, 한국으로 돌아와 헤드헌터가 되었다. 우연으로 시작한 일이지만 막상 하고 나서 보니 내 적성에 딱 맞더라. 독일, 캐나다, 미국, 한국을 오가며 살면서 다양한 배경을 가진 사람들과 만나고 대화하는 것을 즐겨했다. 새로운 사람을 만나는 것에 대한 두려움도 없는 편이고, 호기심도 많다. 친구들 모임도 언제나 주도해서 만드는 스타일이랄까. 그런 성향을 가진 내가 잠재적인 지원자와 커뮤니케이션하고 설득하는 리크루터를 맡으니, 일하는 것이 너무 즐겁더라. 우연한 기회가 천직을 찾아주었다고 생각한다.사진3. 최고 님의 인스타그램에서 그의 일상을 살필 수 있다.Q. 스켈터랩스에서 가장 많은 팔로워를 보유한 인스타그래머로 알고있다. 인기 인스타그래머가 될 수 있는 자신만의 비법이 있는지.A. 나는 SNS도 일종의 브랜딩이라고 생각한다. 스켈터랩스의 브랜딩은 아니지만, ‘나’라는 사람을 앞으로 내세워 채용에 관련된 소식을 더 많은 이들에게 알릴 수 있지 않나. 그래서 SNS를 열심히 하는 편이긴 하다. 비법이랄 것은 없다. 그냥 내 일상 속에서 사람들이 좋아할 만한 부분을 잘 담아내려고 한다. 여심저격 카페 혹은 맛집, 강아지, 운동하는 남자, 분위기깡패 등의 해시태그(#)를 사용하여 컨텐츠를 업로드한다.Q. 취미는 무엇인가, 슬쩍 인스타그램을 살펴봤더니 운동하는 사진이 많았다.A. 맞다, 운동을 좋아한다. 기본적으로 운동은 모두 좋아하는 편인데 하루에 한 번 빠짐없이 헬스장에 운동을 하고 사내의 축구동아리인 FC Skelter와 농구동아리, Skeldunk에서 모두 활동하고 있다. 강아지를 키우고 있어서 운동이라고는 할 수 없지만 강아지와 함께 동네 산책도 많이 한다. 몸을 쓰고 땀 흘리는 것을 즐기는 편이다.Q. 최고 님의 꿈은?A. 커리어적으로는 리크루팅 분야의 스폐셜리스트(Specialist)보다, HR 전반에 관련된 제너럴리스트(Generalist)가 되고 싶다. 그런 의미에서 스켈터랩스가 나에게는 좋은 기회였다. 입사 이후 리크루팅 뿐만 아니라 전문연구요원과 같은 정부 지원 관련 인사 업무에 대해서도 익힐 수 있었고, 인재 개발 분야에 대한 업무도 진행하고 있다. 나의 업무 영역을 더 넓히고 있고, 더욱 넓혀 나갈 계획이다.개인적인 꿈은 소박하다면 소박할 수 있겠다. 15년 쯤 후엔 고향으로 돌아가 단란한 가정을 꾸리고 싶다. 고향인 독일은 한국보다 훨씬 조용하고, 초록이 많다. 아직 미혼이지만 아내와 아이가 생긴다면 언젠가 독일로 가고싶기도 하다. 물론 결혼을 하는 것이 1차 목표지만 말이다.#스켈터랩스 #사무실풍경 #업무환경 #사내복지 #기업문화 #HR팀 #팀원인터뷰 #팀원소개 #팀원자랑 #원격근무 #리모트 #디지털노마드 #재택근무
조회수 849

스타트업을 시작하시려는 분들에게

 첫 글에서 제가 스타트업을 깟음(?)에도 불구하고, 스타트업을 하기로 결심! 을 하셨다면 그만큼 큰 결정을 내리신 거라 생각하고, 저도 지금부터 아는 바에 대하여 알려드리기 위해서 최선을 다하겠습니다. 막상 "나 이제 할 거야!!"라고는 하셨는데, 처음부터 엄청나게 막막하실 겁니다. 그쵸? 뭘 어떻게 시작해야 할지, 뭐부터 해야 할지, 나는 누구인지, 여긴 어디인지 그럴 겁니다. 저도 그랬어요 :). 그래서 이번엔 어떤 것들을 가지고 시작해야 하는지 간단히 설명해 드리며 시작하려고 합니다.많은 분이 이야기하시는 스타트업을 시작하기 위해서는 주요 3요소를 알아야 하는데 이는, 1. 서비스를 개발하고, 어느 정도 성장할 때까지 필요한 자본력(Capital). 2. 같이 고생할 파티원들 (Teammates). 3. 이거 하나면 페이스북도 정복할 수 있을 것 같은 아이디어! (Idea).라고 들 합니다. 가장 기본적인 3요소라고 하죠. 그리고 많은 분이 저것 중 2가지 요소만 가지고 있어도 충분히 시작할 수 있는 원동력이 있다고 합니다. 이에 대하여는 저도 적극적으로 동의하는바 입니다. 정말 3개 중 하나도 없으면 시작할 이유가 없거든요… 그래서 일단 저 3요소들이 되었다 싶은 젊은 청년 창업가 분들은 “우리는 이제 다 됐다! 두려워질게  없다!!!”라고 생각하시는 분들이 있어서 바로 이 장을 마련한 겁니다! ㅎㅎ그럼 3가지가 다 있다! 고 생각하신 분들은 이런 생각은 해보신 적이 있나요?  “혼자서 이것저것 다할 수 있을까?, 팀원을 모으면 같이 하는 사람들의 월급/ 지분관리는 어떻게?”  “개인사업자 등록? 법인 설립은 어떻게?”  “개발자/기획자/디자이너랑 다들 친하게 잘 지낼 수 있을까?”  “하면서 돈 다 떨어지면 어떻게 하지?”  “사무실은 어디에다 둘 거야? 아니 사무실이 필요할까?”  “나는 그러면 개발(또는 디자인 또는 기획 또는 경영)만 하면 되는 건가?”   등등 기타 무궁무진한 질문들이 많을  것이라고 예상이 됩니다…일단은 다음장에서 하나하나 제 경험과 견해를 하나하나  말씀드리도록 하겠습니다!#코인원 #블록체인 #기술기업 #암호화폐 #스타트업인사이트

기업문화 엿볼 때, 더팀스

로그인

/