스토리 홈

인터뷰

피드

뉴스

조회수 1051

우리 제품의 USP를 정의하는 방법

안녕하세요. 오피노입니다. 저희는 데이터 분석을 기반으로 한 성과 최적화를 대행하는 퍼포먼스 마케팅 에이전시입니다. 저희가 진행하는 모든 마케팅 의사결정의 근거는 [데이터]입니다. 데이터는 거짓말을 하지 않고 확실하고 냉정하게 숫자로 성과를 표현해주기 때문이지요. 그래서 그 어떤 근거들 보다 정확합니다. 하지만 최근 제가 데이터를 기반으로 대행을 하다가 잃어버린 것들이 있습니다. 문제는 바로, "숫자에만 치중" 한다는 것이지요. 숫자에만 치중하게 되면, 어느 날 "우리 브랜드가 어떤 방향으로 가고 있나?", "KPI가 매출이라고 해서, 매출액을 올리는 데에만 급급하지 않은가" 고민하게 됩니다. 데이터를 기반으로 매체를 다루다가 , 어떤 한계에 봉착하는 느낌을 받았습니다. 더 이상 일정 부분에서 ROAS가 오르지 않는 한계점 같은 것이지요.네, 숫자에만 치중하게 되면 무언가 잘못되어가고 있다는 느낌을 지우지 못합니다. 어느새 이 제품의 브랜드는 사라지고, 이 브랜드가 고객들에게 다가가는 메시지는 할인 프로모션, 특가와 같은 '매출을 끌어올리기 위한 수단으로써의 메시지'만 있게 되는 것 같았습니다. 숫자는 반드시 마케팅 의사결정에 필요하지만, 과학의 시대인 21세기에도 [종교]라는 개념이 있는 걸 보면, 데이터와 숫자가 비즈니스의 전부가 아니란 생각도 자연스레 떠올릴 수 있습니다. 제품이 나아가야 할 방향, 즉 , USP(Unique Selling Point)의 부재에서 저는 비어있는 공간을 느꼈던 것이지요.그렇다면 마케팅원론에서 그렇게 질리도록 배운 USP는 도대체 무엇이고, 이 USP는 어떻게 정의해야 할까요? 잠깐 이 질문에 대답을 해봅시다 : "비슷한 제품들이 즐비하는 시장에서 당신의 제품을 어떻게 차별화되는가?어려운 질문이지요. 여러분과 저는 아마도, 여러 가지 생각들이 들 수 있을 것입니다. 예를 들면, [사용하기 쉽다거나, 내구성이 좋다거나, 다른 경쟁사 제품보다 훨씬 더 품질이 좋다거나]와 같은 것들을 떠올릴 수가 있습니다.하지만 지금 직관적으로 떠오르는 이 여러 가지 특장점들은 그저, 제품의 기술적인 특징에 지나지 않습니다. 대신에, 우리들만이 판매할 수 있는 고유한 특장점(Unique Selling Point : USP) 이 있을까요? USP는 간결하게 말해, 1) 우리의 제품이나 서비스가 고객들에게 감정적으로 제공할 수 있는 것(가치), 2) 사용자들의 불편함을 처리해주는 방법론(솔루션) 3) 시장에서의 다른 제품들과 더 나은 이유(차이점) 이 모든 3가지 조건을 충족시키는 한 가지의 특징입니다. 예를 들면, 경쟁사보다 조금 더 우위에 있는 우리 제품이 잠재고객들이 겪는 문제점을  해결하는데 중요한 영향을 미치는지 까지 파악할 필요가 있다는 것이지요. 모든 기술적으로 뛰어나거나 품질적으로 뛰어나거나 하는 등의 조건들을 배제하고, 위에서 말씀드린 이 3가지를 모두 만족시키는 하나의 USP를 만들어낸다면, 우리는 좀 더 체계적인 마케팅 전략과 세일링 프로세스를 구축할 수 있습니다.그래서, 어떻게 USP를 정의할까?제품의 특성을 정의하는 것을 정확히 찾아내는 것은 때로는, 아들 딸 중에서도 어떤 자녀를 더 좋아하는지를 선택하는 것과 비슷합니다. 아마도 우리는 우리 제품의 모든 면을 사랑하고, 그 제품이 가지고 있는 잠재성과 개개의 기능들도 좋아할 것입니다. 하지만, 고객들은 다릅니다. 제품을 보는 고객은 제품의 전체 모습을 좋아하는 것이 아니라 그 제품에서 자신의 문제를 해결해줄 수 있는 단 한 가지 면만 보고 구매 의사결정을 내립니다. 경쟁사가 많은 복잡한 시장에서 고객들은 한 제품의 모든 면을 볼 수 있도록 주의를 기울이지 않기 때문이지요. 고객들이 순간적으로 제품을 스쳐 지나가 볼 때, 그들의 문제를 해결할 수 있을만한 방안 또는 그들이 얻을 수 있는 가치를 얻지 못하면, 그들은 그대로 떠나버리기 마련입니다.우리 모두는 우리 브랜드에 대해서 색안경을 벗어야 할 필요가 있습니다. 여러분 제품의 강점과 약점을 철저하게 분석하고, 우리 제품의 강점이 우리 제품의 약점을 커버할 수 있을 만한지도 되물어보셔야 합니다. 마케팅 원론에서 그렇게나 배우던 SWOT 분석이 중요한 이유도 다 여기에 있지요. 우리 제품을 분석했다면, 경쟁사 제품 역시 분석이 필요합니다. 고객의 입장에서 느낄 수 있는 경쟁사 제품의 USP는 무엇이고 이들은 어떻게 마케팅을 하고 있는지 반드시 알아야만 하는 것입니다. 그런 다음, 추가적으로 우리 제품을 구매하는 사람들을 이해하기 위해, 우리 자신에게 질문을 던집니다.- 우리 고객들은 언제 우리 제품을 사용할까?- 우리 고객들은 우리 제품을 통해 어떤 경험을 할까? - 우리 고객들은 왜 다른 경쟁상품 대신 우리 것을 선택하였을까?- 그들의 구매 결정에 시발점이 되었던 메시지는 무엇이었을까? 저희 오피노가 Google Analytics나 Google Optimize와 같은 화려한 도구들로 도출해내는 데이터는 위에 열거한 4가지 질문에 대답을 얻기 위해서였습니다. 그렇습니다. 데이터는 대답을 얻기 위한 수단일 뿐, 그 데이터는 USP라는 거대한 요리 속에서 하나의 조미료에 불과합니다. USP는 이 데이터들에 [인간]이라는 거대한 재료를 첨가하고 나서야 비로소 완성될 수 있습니다.Revlon의 창업자는 이런 유명한 말을 남겼었죠. "공장에서, 우리는 립스틱을 만들고 있습니다. 하지만 광고에서, 우리는 희망을 팔고 있습니다."  제품 그 자체 기능에 집중을 하는 것이 아니라, 제품이 최종 구매자에게 제공해주는 가치에 집중을 하는 것이지요. 네 그렇습니다. 사실 우리는 우리 고객들이 제품을 구매하면서 느끼는 휴머니즘적인 가치, 그들의 감성에 대한 공감을 기반으로 마케팅 전략을 구축해야 합니다. 저는 고객들이 느끼는 이 추상적인 감성을 데이터화 시키는 것을 잘할 수 있던 것이지, 데이터 그 자체를 맹목적으로 쫓는 게 정답이 아니었단 걸 깨달았습니다.우리 제품의 기능적 우위 + 휴머니즘적 공감을 함께 결합하여 최종 USP를 만드는 것이지요.  기억해주세요. 데이터는 본질을 가져와주는 도구일 뿐, 그 자체가 목적이 되면 안 됩니다. 이상 센치해진 저만의 반성문이었습니다 :) 영감이 될 수 있는 글이 되었으면 좋겠습니다.퍼포먼스 마케팅 에이전시, 오피노 바로가기
조회수 1690

브랜딩을 망쳐보자(말 한마디로)

사실 브랜딩을 이렇게 해라, 저게 맞다 백날 말하는 건 별 의미가 없습니다. 항상 옳은 방법은 원론적이고 방대하고 추상적입니다. 파괴하는 건 그저 말 한마디, 종이 한 장이면 충분하죠. 오우? 설마 한 번 브랜딩이 되면 쉽사리 바뀌지 않는다는 관성력을 언급하시려구요!? 물론 그렇습니다. 브랜딩은 '의도적 선입견' 을 만드는 과정입니다. 하지만 당신이 코브가 아닌 이상 남의 꿈 속으로 들어가 금고를 열게 아니라면 그것은 당신의 몫이 아닙니다. 우린 우리의 일을 할 뿐, 선입견을 만드는 건 고객들의 몫이죠. 그러니 뭔 전략을 통해서 브랜딩을 한다는 것은 어불성설입니다. 브랜딩 전략이란 말 자체가 어폐가 있다는 겁니다. 브랜딩의 전략은 이미 달성되었습니다. '회사'를 만들었잖아요!우리는 외부의 자극을 줄 뿐, 무엇을 떠올릴 지는 그들의 선택입니다.회사를 잘 운영하기 위해서 브랜딩전략을 짠다구요??? 그건 이상한 말입니다. 당신은 브랜드를 먼저 만들었고 그걸 달성할 수단으로 '회사'를 운영하는 것 뿐입니다. 그러니 이제는 운영단의 문제만 하나하나 해결해 나가면 되는 겁니다. 많은 대표님들이 오해는 이겁니다.하아, 우리 회사는 브랜딩이 안되서 매출이 안나와.뭐라는...방구같은 소리지요. 브랜딩이 안된 게 아니라 그냥 운영이 개판인 겁니다. 직원들은 시무룩하고, 다들 회사에서 뭐하는 지 별 관심이 없습니다. 의욕도 없습니다. 방향은 이랬다저랬다를 반복하고, 가져다 쓰기나 베끼기식의 컨텐츠가 가득합니다. 3일 전에 한 얘기가 오늘 또 바뀌고, 회의만 계속되는데 말하는 사람은 없습니다. 당장 써야할 제안서가 너무 많으니 전체 회식은 다음 주로 미루기로 합니다. 미뤄서 회식을 했는데 결국 또 직원들은 그냥 하하호호 고기만 먹다가 집에 갑니다. 맡긴 일은 자꾸 늦어지거나 내 맘에 안듭니다. 질책합니다. 의욕이 떨어집니다. 대표도 직원도. 하지만 아이디어는 많습니다. 실행할 사람이 없죠.이건 브랜딩의 문제가 아니라, 운영과 소통의 문제입니다. 생각을 하나 해보고 넘어갈께요.브랜딩은 고객과 회사만의 문제인가요??브랜딩은 고객과 회사만의 문제인가요??제가 앞선 글에서 얘기했던 내용이 있습니다. 브랜드는 직원과 회사 자체가 지니고 있는 성격, 그 기질이 자연스레 드러나는 것입니다. 정돈된 하나의 비쥬얼과 멘트, 일관된 행동과 철학을 통해서 말이죠. 결국 브랜딩은 사내문화에서부터 기인합니다. 뭐 복지데이 어쩌고 해서 금요일은 5시퇴근이 사내문화가 아닙니다. 아주 사소한 것부터 생각해봅시다. 서로 인사는 하나요? 손님이 들어오면 어떻게 응대하죠? 미팅은 어떤 식으로 해요? 호칭은요? 일이 끝나면 제깍제깍 보고 하던가요? 아니면 가지고 오라고 해야 가지고 오던가요?결정권자와 실무자간의 커뮤니케이션을 보면 이 회사의 브랜딩이 어디를 향하고 있는지 아주 쉽게 파악할 수 있습니다. 때론 그 방향이 괌을 포위사격하는 형태를 띠며 자폭의 길을 보여주기도 하죠.그런 의미에서 오늘은 고객과 회사가 아닌, 내부의 소통에 대해 얘기해보려고 합니다. 직원들도 고개를 가로젓는 비지니스를 외부에 브랜딩하겠다는 건 말이 안되는 일이거나 그냥 사기치잔 얘기와 비슷하니까요.브랜딩을 폭망시키는 멘트들! 지금 시작합니다.1. 예를 들어~ 다시 말하면~ 이해됨? 어떤 말이냐면..말이 많다.자꾸 예를 들지 마세요. 예를 드는 것 자체가 나쁘진 않지만, 결과적으로 말을 길게 만듭니다. 회의시간을 4시간으로 만든다구요. 30분이면 끝날 일이 자꾸 예가 붙어서 지구역사만큼 길어집니다. 직원들은 스트로마톨라이트가 되버려요. 굳어진 유기생명체 말이예요. 직원들을 태곳적 존재로 만들어서 지층속에 묻을 것이 아니라면 예를 들지말고 핵심만 딱 전달해주세요.  솔루션은 실무자들이 알아서 만드는 겁니다. 자꾸 예를 들어야 할 정도로 이해력이 좋지 않은 사람이라면 그냥 다른 일을 시키는 게 낫습니다.2. 내 친구가, 내 지인이, 내 사촌이, 내 선배가....지극히 개인적인 개인들의 경험을 실행의 근거로 삼지 마세요. 자꾸 회의시간에 '제가 아는 분이' 라는 얘기가 나오곤 하는데, 아는 분에 대한 데이터를 정확히 밝히던가 아니면 그 사례가 정확한 지 분석을 하고 얘기하는 게 좋습니다. 물론 그런 개개인도 다 고객이 될 수 있으니 중요합니다. 하지만, 더 중요한 건 우리 내부에서 일단 통일되는 것이 중요하잖아요. 종이를 집어던지던 고성이 오고가던 갈등이 있고 몸의 대화가 격렬해지는 한이 있어도 우리들끼리 의견을 합치고 지지고 볶고 해야합니다. 외부사람들의 의견을 끌어들이지 마세요. 차라리 그냥 내 의견이라고 하던가.3. 알겠지? (모르겠는데요..)이 짤 이외는 설명할 도리가 없다.중간이 없는 경우. 절차나 실무는 전혀 모르겠고.'자, 우리 이번 페이스북 좋아요!...지금 심각합니다. 이 정도로는 바이럴도 뭣도 안되요. 무조건 이번에 스폰서드 태워서 좋아요 30,000찍습니다! 다음주까지!'3일 후'팀장님 이번 컨텐츠 도달율이 괜찮은데 스폰서드 태우시죠.''응? 아 그거 거기에 돈 쓰지 말라고 하셔서 그냥 없이 해요.''네?? 그럼 목표치에 다다르기 힘들 건데.''응? 그럼 안되지. 이미 목표는 보고했는데.''뭐라고.요(와씨 반말나올뻔했네)?'무조건 하라고 하지말고, 서포팅을 해줍시다. 알겠지?가 만사장땡이 아니예요. 못 알아듣는 말이나, 불가능한 것 들을 말이 되는 것처럼 자꾸 포장하거나 모호하게 말하면 안되요. 말이란 게 원래 그렇습니다. 얘기 하면 할수록 점점 말이 되는 것 같거든요. 말하는 사람 입장에선. 듣는사람은 점점 미궁속으로 빠져들고.4. 사람은 원래..공자세요?공자세요? 우린 지금 일을 하는 거지, 인간본성에 대한 철학적 고찰이나 지극히 개인적인 가치관을 듣자고 모인 것이 아닙니다. 자신이 그런 모임을 싫어한다고 해서 남들도 싫어하는 건 아니라는 걸 좀 알았으면 합니다.5. 이렇게 하라고 했잖아.(저렇게 하람서요?)본인이 한 말은 기억해야 합니다. 굳이 전략 나부랭이가 아니더라도, 자꾸 뭔갈 갈아엎거나 기억못해서 딴얘기하는 건 브랜딩뿐만 아니라 전반적인 업무를 힘들게 합니다. 이것은 신뢰와 직결되는 문제로, 브랜딩을 위한 어떤 세부전략을 짜거나 회의를 해도 다들 '어차피 바뀔 거...' 라고 고개를 가로젓게 될 거예요!!6. 결론은, 정리하자면...결론은 하나만. 이건 심지어 5번보다 더 심할 수도 있는데, 5분전에 말했던 것과 지금 말한 결론이 다를 때도 있습니다. 보통 생각하고 말하는 게 아니라, 말하면서 정리되는 타입의 사람들이 이렇게 말하는데.....결정사항을 결정할 땐 애드립대잔치말고 명확한 오더를 주도록 합시다.7. 그래, 니 말도 맞다.경청과 인정은 좋습니다. 하지만 모두의 말이 맞다고 해버리면....뭘 해야하는 지 알수가 없습니다. 경청은 하되 방향성은 잡아야 합니다. 브랜딩은 우리 비지니스와 사람들의 공통된 톤을 정하는 일입니다. 눈에 보이지도 않고 명확하지도 않은 그 '성격과 기질' 이란 것을 표현해내는 일은 아주 세부적이고 구체적이어야 합니다.  우리가 놀기 좋아하는 활발한 분위기의 색을 지니고 있다면 적어도 오프라인 이벤트를 할 때 어떤 텍스트와 어떤 드레스코드로 무장할 지..이렇게 외부로 드러나는 모든 요소들에 대한 결정을 해야하죠. 옷, 텍스트, 디자인, 제작물, 배너, 멘트, 응대방식 등 전방위적 요소에서 컨셉츄얼한 기획이 나와줘야 합니다. 브랜딩자체는 굉장히 모호한 개념이지만, 그걸 실행하는 단계는 어떤 것보다 구체적이어야 하죠. 그래야 고객이든, 내부직원이든 어떤 맥락에서 왜 이런걸 하는지 '이해' 할 수 있습니다. 두루뭉술의 덫에 빠져버리면, 공허한 말잔치만 계속됩니다.이건 사무실을 안개속으로 빠뜨리는 일이죠.  8. 근데...근데..이건 이렇잖아. 근데..이건 이럴 수 없는데? 근데....이러면 어떻해? 근데....내가 이래서 못해. 근데...근데...내 친구가..근데...(가능이 없음)9. 해봤는데..가카?...그 결과가 요즘 대한민국입니다. 지난 레퍼런스를 교훈삼는 것은 물론 좋습니다. 거듭 말하지만 그 레퍼런스의 맥락이 분명할 경우에 말이죠. 개인적인 경험도 교훈이 될 수 있습니다. 현 상황과 적절하다면 말이죠. 사이즈나, 성격이나, 상황이나, 업무적 측면에서 유사점이 많다면 리스크를 미리 방지할 수 있습니다. 하지만, '해봤는데...' 가 지니는 문제점은 이거죠.  그럼 해본 사람이 하셔야지, 한 사람은 본인이고 일은 딴 사람이 한다는 것.발언을 했다면 책임을 지고, 다른 사람들은 손놓고 있는게 아니라 서포트를 약속해주는 겁니다. 서로 무서워서 아무말 못하는 것보다 무서운 건, 내가 안 할거니까 아무말이나 내뱉는 것이죠.10. 일단은 ... 어쨌든..여튼..제가 꽤나 싫어하는 말 중 하나입니다. 앞서 말한 모든 말들을 깡그리 무시하고 맥락을 끊어버리는 말이죠. 브랜딩 뿐 아니라 마케팅, 디자인, 영업, 생산관리 뭐...어떤 파트가 되었던 이 단어는 좋지 않습니다. 맥빠지죠. 한참 열심히 회의하고 전략까지 쭉쭉 짜내고 있는데, 일단은 그거말고. 어쩃든, 여튼 해. 등...뭔가 상대가 지금까지 말했던 수많은 의견들을 단 2,3글자로 묵살시킬 수 있습니다. 무엇보다 세부적인 플랜이 나와야하는 브랜딩영역에서 이 단어는...그렇게 디테일하게 공들인 수많은 시간과 노력을 허사로 돌려버립니다. 한 번 무너진 것들은 다시 쉽게 쌓이기 힘들죠. 그렇게 브랜드는 점점 무너져 갑니다. 방향도, 의욕도 없이-------------------------------------------------------------------------------------------------------------------------------------위의 10가지 멘트는 브랜딩을 망가뜨리는 말만은 아닙니다. 전반적으로 커뮤니케이션을 망치는 화법이죠. 하지만 브랜딩을 다루면서 굳이 이 주제를 꺼낸 이유는 우리가 생각하는 브랜딩에 대한 거창함과 거품을 걷어내고 싶었기 때문입니다. 위에서 언급했듯 색깔을 드러낼 수 있는 비쥬얼적, 기획/운영적 플랜은 아주 세부적으로 나와주는 것이 맞습니다. 그러나 그 전제는 회사 내부적으로 일단 통일된 의견과 이해입니다.실제로 출퇴근을 하면서 프로젝트를 하다보면, 운영진의 회의가 끝나고 나온 후 직원들의 뒷담화를 자주 듣게 됩니다. 그 뒷담화에 편을 들어줄 생각은 없습니다. 가만 들어보면 본인들도 전혀 노력도 없이 그냥 월급이나 따박따박 받아가고 싶은 사람들도 태반이기 때문입니다. 컨텐츠를 만들려면 당연히 뛰어다녀야 합니다. 그건 기본중에 기본입니다. 자료조사를 해도 끊임없이 인터넷을 뒤져야 하고, 사람도 만나야 하고, 인터뷰, 콘티작성, 일정조율 등...모르면 공부해야하고, 안되도 되게 해야하는 경우가 많습니다. 그걸 그냥 주저앉아서 '난 그런거 못하는데 왜 나한테 시키고 지랄이야' 하면서 불평이나 하고 있는 사원들의 모습은 좋아보이지 않습니다.(많이 순화함)하지만 여기서 잘잘못을 따지잔게 아닙니다. 그럼에도 불구하고 제대로 우리 회사의 색을 만들고, 또 살리고 싶다면...  대표님이 그토록 원하는 브랜딩을 성공으로 이끌기 위해선 어쨌든 이 브랜딩 액션을 수행해낼 수 있는 온전한 집단이 필요합니다. 대기업은 BX팀이 있으니 굳이 전 사원이 막 회의에 참여하고 이럴 필요가 없다고 칩시다.. 스타트업이나 중소기업에선...전 사원이 달려들어서 움직여야 하는 것이 맞습니다. 그 와중에 서로를 피곤하고 지치게 만드는 말들은 최소화시키는 것이 효율적으로도, 심리적으로도 좋지 않겠습니까.자칫 우리의 피곤한 표정과 서로를 등진 얼굴이...우리의 브랜드가 될 테니까요.#애프터모멘트크리에이티브랩 #브랜드 #브랜딩 #디자인 #디자이너 #팀문화 #협업 #팀스피릿 #인사이트 #꿀팁 #조언
조회수 2085

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

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팀 #팀원인터뷰 #팀원소개 #팀원자랑 #원격근무 #리모트 #디지털노마드 #재택근무
조회수 1202

14년 만의 외도 혹은 도전 @Finda

“남들이 부러워하는 직장을 왜 그만둬?”제가 가족, 친구들에게 이직을 생각하고 있다고 얘기했을 때 제일 많이 들은 말이었습니다.저는 2003년부터 최근까지 짧지 않은 기간을 모건 스탠리(Morgan Stanley), UBS 홍콩과 서울에서 Research Analyst로 근무했습니다. Analyst는 담당 산업 전문가로 자신의 이름을 건 리포트를 발간하고, 전 세계에 있는 이른바 ‘큰손’ 기관투자자들에게 발표하는 직업입니다. 애널리스트라는 직업에는 여러 가지 매력이 있습니다. 명예와 금전적인 부분들도 있지만 가장 매력적인 부분은 훌륭한 사람들을 많이 만날 수 있다는 점입니다. 저도 좋은 회사 동료들과, 유수의 기업의 최고경영자들을 포함한 많은 분들, 국내외 기관투자자들, 그 외에도 담당 산업의 최고의 전문가들을 만날 수 있는 기회가 많이 있었습니다.애널리스트로 근무하던 시절 받은 트로피들UBS 아시아태평양 지역 본부인 홍콩에서 근무하던 시절대학교를 졸업하고 첫 직업으로 analyst가 될 수 있었다는 것은 참으로 감사한 일이었습니다. 그런데 왜 이직을 했냐고요?Analyst라는 직업은 기본적으로 외부 투자자의 입장에서 담당 산업과 이에 속한 기업들을 분석하여 주식의 가치를 평가하는 것입니다. 다른 사람들의 경영의 결과를 외부에서 평가하는 일이라고 볼 수 있습니다. 저는 이러한 업무를 하면서 '내가 직접 경영에 참여해보고 싶다'는 생각을 하게 되었습니다.외부의 시선을 넘어 내부에서 직접 실행하기로 기존의 익숙한 영역에서 벗어나 새로운 사업에 뛰어들기 위해서 필요한 도전의식, 기획력, 실행력은 제가 이제껏 동경하였으나 경험해보지 못한 부분들이었습니다. 이제 30대 후반의 나이, 더 늦기 전에 저도 그런 부분들에 도전해보고 싶었습니다.또 analyst라는 직업은 많은 것이 주어지는 만큼 계속 결과물을 내놓아야 하는 직업이기도 합니다. 출장도 잦고 사무실에서 긴 시간을 보내야 하는 날들도 많았습니다. 결혼하고 아이가 생기면서 이런 부분에 대한 고민도 생겼습니다.그런 고민들을 하던 중에 핀다를 만났습니다. 대기업 전략팀에서 근무하다가 연쇄 창업가로 성공한 이혜민 대표와 미국에서 대학원을 졸업하고 유수의 금융사에서 근무하던 박홍민 대표가 함께 금융상품의 아마존을 만들겠다는 얘기를 들었을 때 무척이나 매력적인 아이템이라고 생각했습니다. (핀다의 공동대표인 박홍민 대표는 고등학교 그리고  대학교에서 같은 반이었던 오랜 친구이기도 합니다. 참 질긴 인연이죠.)국내 온라인 쇼핑은 이미 전체 소매 시장의 20% 차지하고 있는 데에 반해, 은행들과 보험사들의 경우 온라인의 비중이 아직 5%에도 못 미친다는 점 그리고 금융기관들이 지출하는 중개 비용이 굉장히 큰 규모라는 점을 고려했을 때 좋은 사업 기회가 될 것이라고 생각했습니다.애널리스트였을 때 저의 역할은 사업 아이템 얘기를 들었을 때 ‘좋은 기회 같아 보이네’라고 평가를 하고 기업이 그 아이템을 실제 사업으로 잘 현실화하는지 외부에서 지켜보는 것이었습니다. 하지만 이제는 핀다의 CFO로 그 사업을 현실화시키기 위해서 핀다의 모든 팀원들과 함께 노력해야 합니다.Startup CFO:  from Finance to BusinessCFO로서의 기본적인 역할은 재무적인 부문을 관리하는 것이지만 스타트업 회사의 특성상 여러 가지 업무를 같이 합니다. 이혜민 대표, 박홍민 대표와 핀다의 전략과 목표들을 설정하고, 금융기관들을 만나서 핀다와 협력하자고 설득하고, 계약서를 작성하고, 어떻게 많은 이용자들이 핀다를 찾게 할지 또 어떻게 해야 핀다를 찾은 이용자들이 편리하게 자신에게 딱 맞는 금융상품을 찾을 수 있을까 고민합니다.핀다에서 저는 두 공동대표 외에도 (이전 직장에서는 접할 기회가 없었던 분야인) 마케팅, 개발, 디자인 전문가분들과도 함께 일하고 있습니다. 젊고 활기찬 분위기에 함께 회사를 만들어가는 생동감 넘치는 에너지가 느껴져서 설렙니다. 제가 이전 직장에서 일하면서 얻은 분석력과 재무 지식, 금융시장에 대한 이해를 활용하고, 아울러 다른 팀원들에게 새로운 분야를 배워나가면서 함께 최고의 핀테크 회사를 만들고 싶습니다.2017년 5월 핀다에서 첫 발걸음을 내딛으며Finda's CFO 배정훈 드림Josh Junghoon Bae #핀다 #스타트업CFO #스타트업일상 #조직문화 #기업문화
조회수 1230

스위처 고객 문의 담당자의 일기

하루 중 자는 시간 빼고 고객분들과 함께하는데요. 오늘 재밌는 고객 문의가 있어서 이 글을 일기 삼아 하루를 마무리할 거예요.심쿵이 아니라 심쾅..수많은 고객 문의 중 사용조차 못하고 반납해야 하는 경우엔 눈에서 눙물 같은 닭똥이 나와요ㅠㅠ하지만, 일말의 희망을 잡아보고자 스위치 사진을 요청을 해요. (물론, 다음 개선될 제품의 R&D를 위해서 수집하는 목적도 있습니다.)오 이건 가능하지!대화 속 오른쪽 사진은 고객님과 저만의 비밀..(하트)(이때, 설명충이 등장한다.)스위처는 일반적으로 다음과 같은 모양의 스위치에 사용이 가능합니다.버튼 좌우에 밸크로를 붙여 고정을 하는 방식이기 때문이지요.아래 사진과 같이 옆에 장애물이 있으면 밸크로를 붙이지 못해 사용이 어렵습니다.우린 답을 찾을 것이다. 늘 그랬듯이하지만, 밸크로를 떼어서 위로 붙인다면?(그렇게 시간이 흐른 후)공대틱 성공적그리고 계속된 고객분의 인증샤샤샤ㅑㅑㅑ천재이시다. 내가 의미한 바를 100% 이해하셨다.  센스있게, 정면과 측면샷도 보내주셨어요.덕담과 함께 의견도 주셨어요.네! 부착법 얼렁 만들게요!그리고 찾아온 마술 같은 순간너는 장애물이 왼쪽에 있구나?나는 응용의 동물재미있다.두 번째 문의를 주신 분의 경우 밸크로가 떨어져 내일 드리기로 하였어요! 내일부터 잘 쓰실 수 있을 거예요.(그리고 이번엔, 진지충이 등장한다.)벌써 4천 명이 넘는 분들이 스위처 M을 사용해주셨고 많은 의견을 주셨어요. 그동안 주셨던 관심과 사랑 덕분에 항상 어제보다 나아진 모습으로 새로운 고객분들께 인사드리고 있네요. 저희는 살아있는 유기체와 같아서 계속 성장하고 개선되고 있습니다.내년에는 wifi를 위한 허브와 3구 출시를 목표하고 있어요. 그러면 더 많은 의견과 관심이 필요할 것 같네요ㅠㅠ 아무리 작은 의견이라도 귀 기울여 반영하는 자세 잃지 않을 테니 앞으로도 잘 부탁드려요 =)*고객문의 방법은 2가지1. 카카오톡으로 얘기하기 (친구추가하기)2. 네이버 카페에 문의하기 (카페보러가기)#스위처 #Switcher #CS대응 #CS업무 #마케터 #마케팅 #기획 #운영
조회수 578

Live 설명회 비하인드

안녕하세요~오늘은 4월 2일, 3일에 진행되었던Live 설명회 비하인드를 풀어보려고 합니다!!ㅎㅎ    먼저, Live 설명회가 무엇이냐? 하면,11번가 2019 상반기 인턴 채용을 맞이하여좀 더 지원자들에게 친숙하게 다가가고~편하게 정보를 제공해 드리기 위해 기획한! 온라인 채용 설명회 입니다!        Live 설명회의 현장! 궁금하지 않으신가요? Go Go!!        입장 통제를 넘어 들어가면..! 두구두구        짜잔! Live 설명회 현장입니다!방송 시작 전 음향 및 장비를 체크하는 리허설 모습입니다.        실무자 분들을 기다리고 있는 빈 자리들!!ㅎㅎ        자리가 채워지고 방송 시작을 기다리고 있는 모습입니다!많이 떨리고 긴장되던 순간이죠 ㅎㅎ            16:00 맞춰서 시작된 Live 설명회!사전녹화 영상이 나가는 동안저희는 실시간 채팅을 살피며Q&A시간을 대비하고 있었답니다! ^^            실제 방송 모습과 현장 모습의 갭차이!!다들 너무 긴장하셨어요 ㅠㅠ (ㅎㅎ)    사진에는 없지만,MD와 서비스 기획 직군 실무자 분들께서도많은 수고를 해주셨답니다!(짱짱!)다음에 기회가 된다면, 좀 더 좋은 화질과 음향으로! ㅎㅎ좀 더 능숙하게 11번가의 자연스러운 모습을 보여드릴 수 있도록 노력하겠습니다!    실제 방송 모습은 11번가 채용의Youtube 채널에서 다시 보실 수 있습니다!!        1시간 동안 사전 질문 및 실시간 채팅 질문들을 모두 대응해드리고 싶었는데,답변 드리지 못한 질문이 많은 것 같아 죄송할 따름입니다 ㅠㅠ    생각보다 많은 분들께서 참여해 주셨고, 시청해 주셨습니다!정말 감사드립니다~! ♡_♡더 도움이 되는 정보를 얻어가실 수 있도록 노력하겠습니다!!    마지막은 자축하는 고기!!!!ㅎㅎ
조회수 1600

React 공식 튜토리얼 한글 번역

<button type="button">메뉴</button>* 오역 및 오탈자가 있을 수 있습니다. 발견하시면 댓글로 제보해주세요!** 브런치 에디터의 한계로 마크다운 적용이 되지 않아 가독성이 떨어지고 복사 기능이 지원되지 않습니다. 이왕이면 이곳에서 보시기를 권장합니다. >> 가독성 좋은 문서로 보기React 공식 튜토리얼 바로가기시작하기 전에무엇을 구현할 것인가대화형 틱택토 게임을 구현하려고 합니다.원한다면 최종 결과물을 여기에서 확인할 수 있습니다. 아직 코드가 이해되지 않거나 문법이 낯설어도 걱정하지 마세요. 튜토리얼에서 차근차근 틱택토 게임을 구현하는 방법을 배울테니까요.게임을 플레이해보세요. 이동 리스트에 있는 버튼을 클릭하여 클릭한 때로 돌아가고, 그 때로 돌아간 후 보드가 어떻게 보이는지 확인할 수 있습니다.게임에 익숙해지셨다면 탭을 닫으세요. 다음 섹션에서 간단한 템플릿을 가지고 시작할 것입니다.사전 준비HTML과 JavaScript에 익숙할 것으로 생각합니다. 하지만 HTML과 JavaScript를 사용해본 적이 없더라도 튜토리얼을 따를 수 있어야 합니다.JavaScript를 다시 봐야한다면 이 가이드를 추천합니다. 튜토리얼에서 JavaScript의 최신 버전인 ES6의 몇 가지 특징들인 화살표 함수, 클래스, let, const를 사용할 것입니다. Babel REPL을 사용하여 ES6 코드가 어떻게 컴파일되는지 확인해볼 수 있습니다.튜토리얼을 공부하는 방법튜토리얼을 공부하기 위한 두 가지 방법이 있습니다. 브라우저에서 코드를 작성하거나 컴퓨터의 로컬 개발 환경을 설치할 수 있습니다. 편한 방법을 선택하여 공부하시면 됩니다.브라우저에서 코드를 작성하기 원한다면가장 빨리 시작할 수 있습니다!새로운 탭에서 시작 코드를 여세요. 빈 틱택토 필드를 볼 수 있습니다. 튜토리얼에서는 이 코드를 수정하여 진행합니다.다음 섹션인 로컬 개발 환경 설정을 스킵할 수 있습니다. 바로 개요 섹션으로 넘어가세요.사용하던 에디터에서 코드를 작성하기 원한다면다른 방법으로 사용하는 컴퓨터에 프로젝트를 설치할 수 있습니다.이 방법은 필수가 아닌 선택 사항입니다!더 많은 준비 작업이 필요하지만 에디터의 편리함을 누리며 공부할 수 있습니다.만약 이 방법으로 공부하기를 원한다면 필요한 단계들이 있습니다.  1. 설치된 Node.js가 최신 버전인지 확인해보세요.2. 새로운 프로젝트를 생성하기 위해 설치 방법을 따르세요.$ npm install -g create-react-app$ create-react-app my-app3. 새 프로젝트의 src/ 폴더에 있는 모든 파일들을 삭제해주세요. (폴더 안의 내용만 삭제하되 폴더는 삭제하지 마세요)$ cd my-app$ rm -f src/*4. 이 CSS 코드를 src/ 폴더에 index.css 파일로 추가해주세요.5. 이 JS 코드를 src/ 폴더에 index.js 파일로 추가해주세요.6. src/ 폴더에 있는 index.js의 최상단에 아래 세 줄을 추가해주세요.import React from 'react';import ReactDOM from 'react-dom';import './index.css';이제 프로젝트 폴더에서 npm start 명령어를 실행하고 브라우저에서 http://localhost:3000 를 여세요. 빈 틱택토 필드를 볼 수 있습니다.에디터에서 문법 하이라이팅 설정을 하고 싶다면 이 문서를 따르세요.도와주세요! 막히는 부분이 있어요!막히는 부분이 생겼다면 지원하는 커뮤니티를 확인해보세요. 특히 Reactiflux chat은 빠르게 도움을 받을 수 있는 좋은 방법입니다. 어떤 커뮤니티에서도 필요한 대답을 듣지 못했다면 이슈를 제출하세요. 우리가 도와드립니다.다 끝났으면 시작해봅시다!개요React란 무엇인가요?React는 유저 인터페이스 구현을 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리입니다.React는 여러 종류의 컴포넌트들을 가지고 있지만 우리는 React.Component의 서브클래스를 사용하여 시작할 것입니다.  class ShoppingList extends React.Component {  render() {    return (            Shopping List for {this.props.name}                Instagram         WhatsApp         Oculus                );  }}// Example usage:XML과 비슷한 재밌는 태그들을 사용할 것입니다. 작성한 컴포넌트는 React에게 무엇을 랜더링하고 싶은지 알려줍니다. 그러면 React는 데이터가 변경될 때 올바른 컴포넌트들을 업데이트하고 랜더링합니다.여기에서 ShoppingList는 React 컴포넌트 클래스 혹은 React 컴포넌트 타입입니다. 하나의 컴포넌트는 props라 불리는 파라미터를 사용하고, render 메서드를 통해 표시할 뷰 계층 구조를 반환합니다.render 메서드는 랜더링하길 원하는 내용을 반환하면 React는 그 내용을 가져와 스크린에 랜더링합니다. 특히 render는 랜더링할 간단한 내용인 React 엘리먼트를 반환합니다. 대부분의 React 개발자들은 이 구조를 더 쉽게 작성할 수 있게 해주는 JSX라는 특별한 문법을 사용합니다.라 쓰면 빌드 시 React.createElement('div')로 변환됩니다. 위의 코드는 아래의 코드와 동일합니다.  return React.createElement('div', {className: 'shopping-list'},  React.createElement('h1', /* ... h1 children ... */),  React.createElement('ul', /* ... ul children ... */));전체 코드는 여기에서 볼 수 있습니다.createdElement()에 대해 더 많은 내용이 궁금하다면 API reference 에 자세한 설명이 있습니다. 튜토리얼에서는 createdElement()를 직접적으로 사용하지 않습니다. 대신 JSX를 사용할 것입니다.JSX에서는 중괄호 안에 JavaScript 문법을 사용할 수 있습니다. 각 React 엘리먼트는 변수에 저장하거나 프로그램에 여기저기에 전달할 수 있는 실제 JavaScript 객체입니다.ShoppingList 컴포넌트는 내장된 DOM 컴포넌트만 랜더링하지만  코드를 작성하여 커스텀 React 컴포넌트를 쉽게 구성할 수 있습니다. 각 컴포넌트는 캡슐화되어 독립적으로 동작할 수 있습니다. 이때문에 간단한 컴포넌트들로 복잡한 UI를 구현할 수 있습니다.시작하기시작 코드를 가지고 시작해봅시다.이 코드는 우리가 구현할 틱택토 게임의 틀을 가지고 있습니다. 필요한 스타일들을 준비해두었기 때문에 JavaScript만 신경쓰면 됩니다.세 가지 컴포넌트로 구성되어 있습니다.- Square- Board- GameSquare 컴포넌트는 하나의 <button>을 랜더링합니다. Board 컴포넌트는 9개의 사각형을 랜더링합니다. Game 컴포넌트는 나중에 우리가 채워 넣어야 할 공백이 있는 하나의 보드를 랜더링합니다. 지금 이 컴포넌트들은 아무런 동작도 하지 않습니다.props를 통해 데이터 전달하기본격적으로 시작하기 위해 Board 컴포넌트에서 Square 컴포넌트로 데이터를 전달해봅시다.Board의 renderSquare 메서드에서 Square 컴포넌트 prop에 value 값을 전달하도록 코드를 변경해주세요.  class Board extends React.Component {  renderSquare(i) {    return ;  }value 값을 보여주기 위해 Square 컴포넌트의 render 메서드 안의 코드 {/* TODO */}를 {this.props.value}로 변경해주세요.  class Square extends React.Component {  render() {    return (      <button className="square">        {this.props.value}      </button>    );  }}변경 전:변경 후: 랜더링된 결과에서는 각 사각형 안에 숫자가 위치합니다.지금까지의 코드는 이곳에서 볼 수 있습니다.대화형 컴포넌트클릭 시 "X"로 채워지는 Square 컴포넌트를 만들어봅시다. Square의 render() 함수에서 반환된 버튼 태그를 다음과 같이 변경해주세요.  class Square extends React.Component {  render() {    return (      <button className="square" onClick={() => alert('click')}>        {this.props.value}      </button>    );  }}이제 사각형을 클릭하면 브라우저에서 알럿창이 뜨는걸 확인할 수 있습니다.새로운 JavaScript 문법인 화살표 함수를 사용하였습니다. onClick prop에 함수를 전달하였습니다. onClick={alert('click')} 코드를 작성하고 버튼을 클릭하면 알럿창 대신 경고가 뜨게됩니다.React 컴포넌트는 생성자에서 this.state를 설정하여 상태를 가질 수 있습니다. 상태는 각 컴포넌트마다 가지고 있습니다. 사각형의 현재 value 값을 상태에 저장하고 클릭할 때 바뀌도록 만들어봅시다.먼저 상태를 초기화하기 위해 클래스에 생성자를 추가해주세요.  class Square extends React.Component {  constructor(props) {    super(props);    this.state = {      value: null,    };  }  render() {    return (      <button className="square" onClick={() => alert('click')}>        {this.props.value}      </button>    );  }}JavaScript 클래스에서 서브클래스의 생성자를 정의할 때 super(); 메서드를 명시적으로 호출해줘야 합니다.Square의 render 메서드에서 현재 상태의 value 값을 표시하고 클릭할 때 바뀌도록 수정해주세요.- <button> 태그 안의 this.props.value 를 this.state.value로 변경해주세요.- () => alert() 이벤트 핸들러를 () => this.setState({value: 'X'})로 변경해주세요.<button> 태그는 다음과 같습니다.  class Square extends React.Component {  constructor(props) {    super(props);    this.state = {      value: null,    };  }  render() {    return (      <button className="square" onClick={() => this.setState({value: 'X'})}>        {this.state.value}      </button>    );  }}this.setState가 호출될 때마다 컴포넌트가 업데이트되므로 업데이트된 상태가 전달되어 React가 이를 병합하고 하위 컴포넌트와 함께 다시 랜더링합니다. 컴포넌트가 랜더링될 때 this.state.value는 'X'가 되어 그리드 안에 X가 보이게 됩니다.이제 사각형을 클릭하면 그 안에 X가 표시됩니다.지금까지의 코드는 이곳에서 볼 수 있습니다.개발자 도구크롬과 파이어폭스의 React 개발자 도구 확장 프로그램은 React 컴포넌트 트리를 브라우저의 개발자 도구 안에서 검사할 수 있게 해줍니다.트리 안의 컴포넌트들의 props와 상태를 검사할 수 있습니다.설치 후 페이지에서 검사하길 원하는 컴포넌트를 오른쪽 클릭하고 "Inspect"를 클릭하여 개발자 도구를 열면 오른쪽 마지막 탭에 React 탭이 보입니다.CodePen을 사용하여 이 확장 프로그램을 동작시키고 싶다면 추가적으로 필요한 작업들이 있습니다.1. 로그인 혹은 회원가입을 하고 이메일을 인증받으세요.2. "Fork" 버튼을 클릭하세요.3. "Change View"를 클릭하고 "Debug mode"를 선택하세요.4. 새롭게 열린 탭에서 React 탭이 있는 개발자 도구를 볼 수 있습니다.상태 들어올리기이제 틱택토 게임을 위한 기본 블록들이 있습니다. 하지만 아직 각 Square 컴포넌트 안에 상태들이 캡슐화되어 있습니다. 더 원활하게 동작하는 게임을 만들기 위해 한 플레이어가 게임에서 이겼는지를 확인하고 사각형 안에 X와 O를 번갈아 표시해야 합니다. 누가 게임에서 이겼는지 확인하기 위해 Square 컴포넌트들을 쪼개지 않고 한 장소에서 9개의 사각형의 value 값을 모두 가지고 있어야 합니다.Board가 각 Square의 현재 상태가 무엇인지만 확인해야 한다고 생각할 수도 있습니다. 이 방법은 기술적으로 React에서 가능하기는 하나 코드를 이해하기 어렵고 불안정하고 리팩토링하기 힘들게 만듭니다.각 Square에 상태를 저장하는 대신에 Board 컴포넌트에 이 상태를 저장하는 것이 가장 좋은 방법입니다. 이 Board 컴포넌트는 이전에 각 사각형에 인덱스를 표시한 방법과 동일한 방법으로 무엇을 표시할지 각 Square에게 알릴 수 있습니다.여러 하위 컴포넌트로부터 데이터를 모으거나 두 개의 하위 컴포넌트들이 서로 통신하기를 원한다면 상위 컴포넌트 안으로 상태를 이동시키세요. 상위 컴포넌트는 props를 통해 하위 컴포넌트로 상태를 전달해줄 수 있습니다. 그러면 하위 컴포넌트들은 항상 하위 컴포넌트나 상위 컴포넌트와 동기할 수 있습니다.이와 같이 상태를 상위 컴포넌트로 들어올리는 것은 React 컴포넌트들을 리팩토링할 때 가장 많이 사용하는 방법입니다. 이 기회를 통해 연습해봅시다. Board에 생성자를 추가하고 9개의 사각형과 일치하는 9개의 null을 가진 배열을 포함한 상태로 초기화하세요.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),    };  }  renderSquare(i) {    return ;  }  render() {    const status = 'Next player: X';    return (             {status}                 {this.renderSquare(0)}         {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}나중에 이것을 다음과 같이 생긴 보드로 채울 예정입니다.  [  'O', null, 'X',  'X', 'X', 'O',  'O', null, null,]현재 Board의 renderSquare 메서드는 다음과 같습니다.    renderSquare(i) {    return ;  }Square에 value prop를 전달하도록 수정하세요.    renderSquare(i) {    return ;  }지금까지의 코드는 이곳에서 볼 수 있습니다.이제 우리는 사각형이 클릭되면 발생할 변경 사항을 구현해야 합니다. Board 컴포넌트는 어떤 사각형이 채워졌는지 저장하고 있습니다. 그렇기 때문에 Square가 Board가 가지고 있는 상태로 업데이트할 방법이 필요합니다. 사각형의 컴포넌트 상태가 각자 정의되고 있기 때문에 Board가 Square의 상태를 가지고올 수 없습니다.보통의 패턴은 사각형이 클릭될 때 호출되는 함수를 Board로부터 Square에 전달하는 것입니다. Board 안의 renderSquare를 다시 변경해봅시다.    renderSquare(i) {    return (              value={this.state.squares[i]}        onClick={() => this.handleClick(i)}      />    );  }가독성을 위해 리턴 안의 요소들을 여러 줄로 나누고, 괄호를 추가하여 JavaScript가 세미콜론 없이 코드를 마무리하도록 했습니다.Board에서 Square로 value와 onClick 두 개의 props를 전달합니다. onClick Square의 rennder에 있는 this.state.value 를 this.props.value로 변경하세요.- Square의 rennder에 있는 this.state.value 를 this.props.value로 변경하세요.- Square의 rennder에 있는 this.setState() 를 this.props.onClick()로 변경하세요.- 더이상 각 Square가 상태를 가지지 않도록 Square에 정의한 constructor를 삭제하세요.모든 변경 사항을 구현한 Square 컴포넌트는 다음과 같습니다.  class Square extends React.Component {  render() {    return (      <button className="square" onClick={() => this.props.onClick()}>        {this.props.value}      </button>    );  }}이제 사각형이 클릭될 때 Board로부터 전달되는 onClick 함수를 호출합니다. 어떤 일이 일어나는지 되짚어봅시다.1. 내장된 DOM <button> 컴포넌트의 onClick prop는 React에게 클릭 이벤트 리스너를 설정하라고 알립니다.2. 버튼이 클릭될 때 React는 Square의 render() 메서드 안에 정의된 onClick 이벤트 핸들러를 호출합니다.3. 이 이벤트 핸들러는 this.props.onClick()을 호출합니다. Square의 props는 Board에서 명시한 것입니다.4. Board는 onClick={() => this.handleClick(i)}을 Square에 전달하고, 호출될 때 Board의 this.handleClick(i)가 동작합니다.5. Board에 있는 handleClick() 메서드는 아직 정의되지 않았으므로 코드는 오류가 발생합니다DOM <button> 엘리멘트의 onClick 속성이 React와는 다른 의미를 가집니다. Square의 onClick prop나 Board의 handleClick 메서드와는 다릅니다. React 애플리케이션에서는 속성에 on* 이름을 사용하고 핸들러 메서드에 handle* 을 사용하여 처리하는 것이 일반적입니다.사각형을 클릭해봅시다. handleClick을 아직 정의하지 않았으로 에러가 발생합니다. Board 클래스에handleClick 메서드를 추가해봅시다.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),    };  }  handleClick(i) {    const squares = this.state.squares.slice();    squares[i] = 'X';    this.setState({squares: squares});  }  renderSquare(i) {    return (              value={this.state.squares[i]}        onClick={() => this.handleClick(i)}      />    );  }  render() {    const status = 'Next player: X';    return (             {status}                 {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}지금까지의 코드는 이곳에서 볼 수 있습니다.이미 있는 배열을 수정하는 대신 squares 배열을 복사하기 위해 .slick()를 호출합니다. 왜 immutability이 중요한지 알고 싶다면 이 섹션으로 이동해주세요.이제 사각형을 클릭하여 다시 사각형을 채울 수 있어야 하지만 상태가 각 Square가 아닌 Board 컴포넌트에 저장되어 있어 게임을 계속 구현해나가야 합니다. Board의 상태가 변경될 때마다 Square 컴포넌트들은 자동으로 다시 랜더링됩니다.Square은 더 이상 각 상태를 유지하지 않습니다. 이들은 상위 Board 컴포넌트로부터 데이터를 전달받고, 클릭될 때 알립니다. 우리는 이 제어된 컴포넌트 같은 컴포넌트들을 호출합니다.왜 immutability가 중요할까전의 예제 코드에서 이미 존재하는 배열을 수정하지 않고 변경 사항을 반영하기 위해 squares 배열을 .slice()연산자를 사용하여 복사하였습니다. 이는 무엇을 의미하며 왜 이 컨셉이 중요할까요.mutation을 사용한 데이터 변경  var player = {score: 1, name: 'Jeff'};player.score = 2;// Now player is {score: 2, name: 'Jeff'}mutation을 사용하지 않은 데이터 변경  var player = {score: 1, name: 'Jeff'};var newPlayer = Object.assign({}, player, {score: 2});// Now player is unchanged, but newPlayer is {score: 2, name: 'Jeff'}// Or if you are using object spread syntax proposal, you can write:// var newPlayer = {...player, score: 2};mutation을 사용하지 않더라도(기본 데이터를 변경하여도) 결과적으로는 다를게 없습니다. 하지만 컴포넌트와 전체 애플리케이션의 성능을 향상시키는 장점이 있습니다.쉽게 Undo/Redo와 시간 여행하기immutability는 이 복잡한 기능들을 훨씬 더 쉽게 구현할 수 있게 해줍니다. 예를 들어 이 튜토리얼에서 우리는 게임의 다른 단계들 사이에 시간 여행을 구현할 것입니다. 데이터 변경을 피하면 우리가 이전 버전의 데이터를 계속 참조할 수 있게 해주고 원할 때 변경할 수 있게 해줍니다.변경 사항 트래킹하기변경되는 객체가 변경 사항이 있는지 아는 방법은 변경 사항이 객체로 만들어지기 때문에 복잡합니다. 그러면 이전 버전을 복사하기 위해 전체의 객체 트리를 현재 버전과 비교하고 각 변수와 값들을 비교해야 합니다. 이 과정은 갈수록 복잡해집니다.immutable 객체가 변경 사항이 있는지 아는 방법은 쉬워집니다. 만약 참조되고 있는 객체가 이전과 다르다면 이 객체는 변경된 것입니다. 이게 끝입니다.React에서 언제 다시 랜더링할지 결정하기React에서 immutability의 가장 큰 장점은 간단한 순수 컴포넌트들이 다시 랜더링될 때를 결정하기 쉽다는 점입니다.shouldComponentUpdate()에 대해 더 배우고 싶고 어떻게 순수 컴포넌트들을 성능 최적화 할 수 있는지 알고 싶다면 이 글을 보세요.함수 컴포넌트우리는 생성자를 지웠습니다. 사실 React는 render 메서드만으로 구성된 Square와 같은 컴포넌트 타입을 위해 함수 컴포넌트라 불리는 간단한 문법을 지원합니다. React.Component를 확장한 클래스를 정의하는 것보다 간단하게 props를 가져오고 랜더링 해야할 것을 반환하는 함수를 작성하는 것이 좋습니다.다음과 같은 함수를 사용해 Square 클래스를 변경하세요.  function Square(props) {  return (    <button className="square" onClick={props.onClick}>      {props.value}    </button>  );}여기서는 this.props를 둘 다 props로 바꿔야 합니다. 애플리케이션에 있는 여러 컴포넌트들은 함수 컴포넌트로 구현할 수 있습니다. 함수 컴포넌트는 더 쉽게 작성할 수 있고 React가 더 효율적으로 최적화할 수 있습니다.코드를 깔끔하게 만들면서 onClick={() => props.onClick()}을 onClick={props.onClick}으로 바꿨습니다. 함수를 전달하는 것은 이 코드만으로 분합니다. onClick={props.onClick()}는props.onClick을 호출하기 때문에 동작하지 않습니다.지금까지의 코드는 이곳에서 보실 수 있습니다.변화 가져오기지금 우리의 게임의 단점은 오로지 X만 플레이할 수 있다는 점입니다. 고쳐봅시다.기본적으로 첫 이동을 'X'가 되도록 설정해봅시다. Board 생성자에서 초기 상태를 수정해주세요.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),      xIsNext: true,    };  }이동할 때마다 xIsNext의 불린 값은 바뀌면서 상태에 저장되어야 합니다. Board의 handleClick 함수를xIsNext 값이 바뀔 수 있도록 수정해봅시다.    handleClick(i) {    const squares = this.state.squares.slice();    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      squares: squares,      xIsNext: !this.state.xIsNext,    });  }이제 X와 O가 순서대로 번갈아 나타납니다. 다음에 무엇이 표시될 때 보여주기 위해 Board의 render에서 "status" 텍스트를 바꿔봅시다.    render() {    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    return (      // the rest has not changed변경 사항을 적용한 Board 컴포넌트는 다음과 같습니다.  class Board extends React.Component {  constructor(props) {    super(props);    this.state = {      squares: Array(9).fill(null),      xIsNext: true,    };  }  handleClick(i) {    const squares = this.state.squares.slice();    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      squares: squares,      xIsNext: !this.state.xIsNext,    });  }  renderSquare(i) {    return (              value={this.state.squares[i]}        onClick={() => this.handleClick(i)}      />    );  }  render() {    const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    return (             {status}                 {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}지금까지의 코드는 이곳에서 볼 수 있습니다.승자 알려주기언제 게임에서 이기는지 표시해봅시다. 파일 맨 하단에 헬퍼 함수를 추가해주세요.  function calculateWinner(squares) {  const lines = [    [0, 1, 2],    [3, 4, 5],    [6, 7, 8],    [0, 3, 6],    [1, 4, 7],    [2, 5, 8],    [0, 4, 8],    [2, 4, 6],  ];  for (let i = 0; i < lines>    const [a, b, c] = lines[i];    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {      return squares[a];    }  }  return null;}Board의 render 함수에서 누가 게임에서 이겼는지 확인할 수 있도록 호출할 수 있습니다. 또 누군가 이겼을 떄 "Winner: [X/O]" 상태 텍스트를 표시할 수 있습니다.Board의 render에서 status를 선언을 수정해주세요.    render() {    const winner = calculateWinner(this.state.squares);    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (      // the rest has not changedBoard에서 handleClick을 일찍 반환하여 이미 누군가 이긴 게임에서 클릭하거나 이미 칠해진 사각형을 클릭하는 경우 무시하도록 변경할 수 있습니다.축하합니다! 틱택토 게임을 완성하셨습니다! 이제 React의 기초를 알았습니다. 여기서 진짜 승자는 여러분입니다.지금까지의 코드는 이곳에서 볼 수 있습니다.히스토리 저장하기보드의 이전 상태로 되돌려 이전 상태가 표시되도록 만들어봅시다. 이동이 있을때마다 새 squares 배열을 만들었습니다. 덕분에 이전 상태의 보드를 쉽게 저장할 수 있습니다.상태에 이와 같은 객체를 저장해봅시다.  history = [  {    squares: [      null, null, null,      null, null, null,      null, null, null,    ]  },  {    squares: [      null, null, null,      null, 'X', null,      null, null, null,    ]  },  // ...]우리는 이동 리스트를 표시하여 응답할 수 있는 더 수준 높은 Game 컴포넌트를 만들고 싶습니다. 그래서 Square 상태를 Board로 들어올린 것처럼 Board의 상태를 Game으로 들어올려 최 상위 레벨에서 필요한 모든 정보를 저장해봅시다.먼저 생성자를 추가해 Game의 초기 상태를 설정해주세요.  class Game extends React.Component {  constructor(props) {    super(props);    this.state = {      history: [{        squares: Array(9).fill(null),      }],      xIsNext: true,    };  }  render() {    return (                                              {/* status */}         {/* TODO */}                );  }}그 다음 Board를 수정하여 props를 거쳐 squares를 가져오고 이전에 Square에서 했던 것처럼 Game에서 지정한 onClick prop를 만들어줍시다. 각 사각형의 위치를 클릭 핸들러로 전달하여 어떤 사각형이 클릭되었는지 알 수 있습니다. 필요한 변경 사항은 다음과 같습니다.- Board의 constructor를 삭제하세요.- Board의 renderSquare에 있는 this.state.squares[i]를 this.props.sqaures[i]로 대체하세요.- Board의 renderSquare에 있는 this.handleClick(i)를 this.props.onClick(i)로 대체하세요.변경 사항을 반영한 Board 컴포넌트는 다음과 같습니다.  class Board extends React.Component {  handleClick(i) {    const squares = this.state.squares.slice();    if (calculateWinner(squares) || squares[i]) {      return;    }    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      squares: squares,      xIsNext: !this.state.xIsNext,    });  }  renderSquare(i) {    return (              value={this.props.squares[i]}        onClick={() => this.props.onClick(i)}      />    );  }  render() {    const winner = calculateWinner(this.state.squares);    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (             {status}                 {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }}Game의 render는 히스토리 전체를 보고 게임 상태를 계산하여 가져올 수 있어야 합니다.  render() {    const history = this.state.history;    const current = history[history.length - 1];    const winner = calculateWinner(current.squares);    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (                                  squares={current.squares}            onClick={(i) => this.handleClick(i)}          />                        {status}         {/* TODO */}                );  }Game에 상태를 랜더링하고 있기 때문에{status}를 지우고 Board의 render 함수로부터 상태를 계산하는 코드를 지울 수 있습니다.  render() {    return (                      {this.renderSquare(0)}          {this.renderSquare(1)}          {this.renderSquare(2)}                        {this.renderSquare(3)}          {this.renderSquare(4)}          {this.renderSquare(5)}                        {this.renderSquare(6)}          {this.renderSquare(7)}          {this.renderSquare(8)}                );  }그 다음 Board에서 Game으로 handleClick 메서드를 옮겨야 합니다. Board 클래스에서 잘라내기를 하고 Game 클래스로 붙여넣을 수 있습니다.Game 상태는 다르기 때문에 수정해야 할 것이 조금 있습니다. Game의 handleClick은 히스토리 항목을 연결하여 새로운 배열을 만들어 스택에 푸시해야 합니다.  handleClick(i) {    const history = this.state.history;    const current = history[history.length - 1];    const squares = current.squares.slice();    if (calculateWinner(squares) || squares[i]) {      return;    }    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      history: history.concat([{        squares: squares,      }]),      xIsNext: !this.state.xIsNext,    });  }여기에서 Board는 renderSquare와 render만 필요합니다. 상태 초기화와 클릭 핸들러는 둘 다 Game에서 동작합니다.지금까지의 코드는 이곳에서 보실 수 있습니다.이동 표시하기지금까지 게임에서 진행된 이동을 표시해봅시다. 이전에 React 컴포넌트가 클래스로 JS 객체이고 그 덕에 데이터를 저장하고 전달할 수 있다고 배웠습니다. React에서 여러 아이템들을 랜더링하기 위해 React 요소의 배열을 전달했습니다. 배열을 빌드하는 가장 흔한 방법은 데이터 배열에서 map을 이용하는 것입니다. Game의 render 메서드에서 해봅시다.    render() {    const history = this.state.history;    const current = history[history.length - 1];    const winner = calculateWinner(current.squares);    const moves = history.map((step, move) => {      const desc = move ?        'Go to move #' + move :        'Go to game start';      return (                 <button onClick={() => this.jumpTo(move)}>{desc}</button>             );    });    let status;    if (winner) {      status = 'Winner: ' + winner;    } else {      status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');    }    return (                                 squares={current.squares}            onClick={(i) => this.handleClick(i)}          />                        {status}         {moves}                );  }지금까지의 코드는 이곳에서 볼 수 있습니다.히스토리의 각 단계에서 <button>이 있는 리스트 아이템을 만들었습니다. 이 리스트 아이템은 우리가 곧 구현할 클릭 핸들러를 가지고 있습니다. 코드에서 다음과 같은 경고 메시지와 함께 게임에서 만들어지는 이동 목록을 볼 수 있습니다.Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of “Game”.경고: 배열이나 이터레이터에 있는 각 자식은 유니크 "key" prop을 가져야한다. "Game"의 render 메서드를 확인해보세요.이 경고의 의미가 무엇인지 얘기해봅시다.Keys아이템 리스트를 랜더링할때 React는 항상 리스트에 있는 각 아이템에 대한 정보를 저장합니다. 만약 상태를 가진 컴포넌트를 랜더링한다면 컴포넌트가 어떻게 실행되는지와 관계없이 상태는 저장 되어야 하고 React는 네이티브 뷰의 뒤에 참고할 것을 저장한다.리스트를 업데이트할 때 React는 무엇을 바꿀지 결정해야 합니다. 리스트에 아이템들을 추가하고, 지우고, 재배열하고, 수정할 수 있습니다.이 코드가 아래의 코드로 변경된다고 상상해봅시다.  Alexa: 7 tasks leftBen: 5 tasks leftBen: 9 tasks leftClaudia: 8 tasks leftAlexa: 5 tasks left사람의 눈에는 Alexa와 Ben의 자리가 바뀌고 Claudia가 추가된 것처럼 보인다. 하지만 React는 단순한 컴퓨터 프로그램이므로 여러분의 의도를 알지 못합니다. React는 리스트의 각 요소에서 key 속성을 지정해달라고 요청합니다. 문자열은 형제로부터 각 컴포넌트들을 구분합니다. 이 경우에 alexa, ben, claudia는 구분할 수 있는 키가 됩니다. 만약 아이템들이 데이터베이스의 객체와 일치시켜야 한다면 데이터베이스 ID을 사용하세요.{user.name}: {user.taskCount} tasks leftkey는 React에서 제공되는 특별한 속성입니다(ref에서 더 확장된 기능). 엘리먼트가 만들어질때 React는 key 속성을 가져오고 반환된 엘리먼트에 직접적으로 key를 저장합니다. key가 props의 한 부분으로 보일지라도 이것은 this.props.key로 참조할 수 없습니다. React는 어떤 하위 엘리먼트가 수정될지 결정하는 동안 알아서 key를 사용합니다. 컴포넌트가 자신의 키를 알 수 있는 방법은 없습니다.리스트가 랜더링될 때 React는 새로운 버전의 각 엘리먼트를 가져오고 이전 리스트에서 매칭되는 키를 가진 것을 찾습니다. key가 세트에 추가될 때 컴포넌트는 만들어집니다. 키가 삭제될 때 컴포넌트는 소멸됩니다. 키들은 React가 각 요소를 구별할 수 있도록하여 다시 랜더링하는 것을 무시하고 상태를 유지할 수 있게 합니다. 만약 컴포넌트의 키를 바꾼다면 완전히 지운 후 새롭게 생성됩니다.동적으로 리스트를 빌드할 때마다 적당한 키를 할당할 것을 강력 추천합니다. 만약 적당한 키를 가지지 못한다면 이를 위해 데이터를 재구성하여야 할지도 모릅니다.특정한 키를 구분하지 못한다면 React는 경고를 주고 배열 인덱스를 키로 사용합니다. 이는 올바른 선택이 아닙니다. 만약 리스트에 있는 엘리먼트들을 정렬하거나 리스트에 있는 버튼을 통해 지우거나 추가하면 명시적으로 key={i}를 전달하는 방법을 사용한다면 경고를 표시하지는 않지만 동일한 문제를 발생시키므로 대부분의 경우에 추천하지 않습니다.컴포넌트의 키가 전부 다를 필요는 없지만 관련있는 형제들 사이에서는 유니크해야 합니다.시간 여행 실행하기이동 리스트를 위해 우리는 각 단계에서 유니크 ID를 가졌습니다. Game의 render 메서드에서 키는로 추가하면 경고는 표시되지 않습니다.    const moves = history.map((step, move) => {      const desc = move ?        'Go to move #' + move :        'Go to game start';      return (                 <button onClick={() => this.jumpTo(move)}>{desc}</button>             );    });지금까지의 코드는 이곳에서 보실 수 있습니다.아직 junmTo가 정의되지 않았기 때문에 이동 버튼을 클릭하면 에러가 발생합니다. 지금 표시된 단계가 무엇인지 알기 위해 Game 상태에 새로운 키를 추가해봅시다.먼저Game의 constructor에  stepNumber: 0를 추가해주세요.class Game extends React.Component {  constructor(props) {    super(props);    this.state = {      history: [{        squares: Array(9).fill(null),      }],      stepNumber: 0,      xIsNext: true,    };  }그 다음 각 상태를 업데이트하기 위해 Game의 jumpTo 메서드를 정의해봅시다. 이 메서드에서는 xIsNext를 업데이트하고, 이동의 인덱스가 짝수라면 xIsNext를 true로 설정합니다.Game 클래스에jumpTo 메서드를 추가해주세요.handleClick(i) {    // this method has not changed  }  jumpTo(step) {    this.setState({     stepNumber: step,      xIsNext: (step % 2) === 0,    });  }  render() {    // this method has not changed  }Game handleClick에 상태를 업데이트 하기위해 stempNumber:history.length를 추가하여 새로운 이동이 있을 때마다  stepNumber를 업데이트 합니다. 현재 보드의 상태를 읽을 때 handleClick이 stepNumber라고 보고 클릭하는 시간대로 상태를 되돌릴 수 있습니다.  handleClick(i) {    const history = this.state.history.slice(0, this.state.stepNumber + 1);    const current = history[history.length - 1];    const squares = current.squares.slice();    if (calculateWinner(squares) || squares[i]) {      return;    }    squares[i] = this.state.xIsNext ? 'X' : 'O';    this.setState({      history: history.concat([{        squares: squares      }]),      stepNumber: history.length,      xIsNext: !this.state.xIsNext,    });  }이제 히스토리의 각 단계를 알기 위해 Game의 render를 수정할 수 있습니다.  render() {    const history = this.state.history;    const current = history[this.state.stepNumber];    const winner = calculateWinner(current.squares);    // the rest has not changed지금까지의 코드는 이곳에서 보실 수 있습니다.이제 이동 버튼을 클릭하면 보드는 즉시 그때 표시된 게임으로 변경됩니다.마무리틱택토 게임을 플레이 해보세요.- 틱택토 게임을 플레이 해보세요.- 한 명의 플레이어가 게임에서 이길 때를 이를 알려줍니다.- 게임이 진행되는 동안 이동 기록이 저장됩니다.- 게임 보드의 에전 버전을 표시하기 위해 시간을 되돌릴 수 있습니다.잘 동작하네요! React가 어떻게 동작하는지 잘 아셨기를 바랍니다.최종 결과물은 여기에서 확인하세요.시간이 더 있거나 새로운 스킬들을 연습해보고 싶다면 해볼 수 있는 몇 가지 아이디어가 있습니다. 점점 더 어려운 순으로 배치해두었습니다.1. 움직임 리스트에서 (col, row) 형태에 각 움직임 위치를 표시하세요.2. 움직임 리스트의 선택된 아이템을 볼드처리하세요.3. 하드 코딩한 것들 대신 사각형을 두 개의 루프를 사용하여 Board를 다시 작성하세요.4. 오름차순 혹은 내림차순 뭐든지 움직임을 정렬하는 버튼을 추가해보세요.5. 누군가 이겼을 때 무엇 때문에 이겼는지 세 개의 사각형을 하이라이트하세요.튜토리얼이 진행되는 동안 우리는 엘리먼트, 컴포넌트, props, 상태를 포함한 React의 수많은 컨셉들을 다뤘습니다. 각 주제에 대한 깊은 설명을 원한다면 남은 문서를 확인하세요. 컴포넌트 정의에 대해 더 많이 배우고 싶다면 이 문서를 확인하세요.#트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유 #React #리액트 #리액트가이드 #한글 #번역 #문서번역
조회수 1521

AWS 이사하는 날

오늘 8퍼센트의 AWS 인프라를 일본에서 한국으로 옮겼다. 기술적인 내용은 이사를 리드하신 세바님이 다뤄 주시기로 하셨고, 나는 그냥 오늘을 남겨두려고 한다.(세바님이 8퍼센트 서울살자에 기술적인 내용들을 다뤄주셨다.)올해 초 AWS 서울 리전이 열리면서 도쿄에서 옮겨 가야겠다 라고 생각한 것이 벌써 수개월이 지났다. 작업을 시작하면 얼마나 걸릴지 예상이 잘 되지도 않고, 인프라 전체를 예쁘게 정리해야 한다는 부담 때문에 쉽게 손이 가지 않았다. 하지만 새로 조인하신 세바님이 AWS 이사를 가지 못해 여러 가지 제약이 생기는 답답한 상황을 참지 못하시고 총대를 메셨다. 아마 나보다 좀 더 답답하셨나 보다. :)17시에 다 함께 모여 현재 작업 진행상황과 오늘 이전 계획을 검토한 후 바로 퇴근을 했다. 긴 밤이 되리라 생각해서 조금이라도 잠을 자려고 노력은 했지만 두 아이가 있는 집에서 쉬는 것은 역시 쉽지 않더라. 지하철을 타고 23시 30분에 이모작 근무를 위해 다시 회사에 들어섰다. 이미 몇몇 분이 모여서 오늘 작업에 대한 이야기를 나누고 있었다. 아침에 만나는 것보다 왠지 반갑다. 모두 모여서 파이팅을 외치고 기념사진을 하나 찍고 작업을 시작했다.(웃으면서 시작한 작업을 웃으면서 마칠 수 있을 것인가?)일단 서버 작업 공지를 띄우고 작업을 시작한다. 지난 회사에서는 모든 서비스가 24시간 운영되었어야 했기 때문에 서버 점검 시간을 따로 갖지 못해다. 그래서 큰 서버 업데이트 작업을 할 때마다 시간에 쫓기고, 장애 발생을 실시간으로 해결해 가며 작업을 했었다. 하지만 이번에는 시간을 확보해두고 작업을 하는 것이라 그래도 마음에 좀 여유가 있었다.이전 작업을 하기 위해 각 파트를 담당하는 시니어 개발자들만 있어도 충분한데, 서버 이전을 하는 것이 흔치 않은 경험이기 때문에 주니어들도 가능하면 참여를 요청했다.(꼬꼬마들이 세바님 뒤에 쪼르르 모여서 설명을 듣고 있다)코드를 이해하기에 가장 좋은 방법은 코드를 함께 짜면서 설명을 하는 것이고, 인프라를 이해하기에 가장 좋은 방법은 인프라를 설치하는 과정을 함께 하는 것이다. 하나하나 작업을 해가면서 이런저런 이야기들을 나누었다. 이전을 하는 서버의 역할, 더 나은 아키텍처,  AWS의 역사,  AWS의 여러 가지 서비스의 세부적인 옵션에 대해서도 이야기를 나누었다.  세바님이 꼼꼼하게 준비를 해주신 덕분에 1시 30분이 되니 기본적인 이전 작업이 끝났다. 야식을 먹고 맥주를 한잔 마시고 각각의 기능들에 대한 본격적인 테스트를 시작했다.(야식은 12시 전에는 치킨을 시켜야 하고 12시 후에는 족발을 시켜야 한다)드디어 세바님을 제외한 다른 잉여 인력들이 할 일이 생겼다. 체크리스트에 있는 항목들을 하나씩 테스트 하기 시작한다. 꼼꼼하게 준비를 했지만 역시나 예상하지 못했던 문제들이 드러난다. 다행히 이전 작업을 되돌려야 할 만큼 큰 문제는 아니었기에 적절히 대응을 하고 계속 테스트를 진행했다.어느덧 시간이 흘러 3시가 되었다. http://8percent.kr 의 도메인을 도쿄에 있는 서버에서 서울에 있는 서버로 변경했다. 이제 내부 시스템들을 추가적으로 점검해야 한다. 가능하면 끝까지 확인을 하고 자리를 뜨고 싶었지만 내일 오후 사무실을 지키면서 혹시 모를 장애에 대응을 해 줄 사람이 필요할 것 같아 먼저 퇴근을 했다.집에 돌아오면서 생각해 보니 오늘 내가 한 일이 거의 없었다. 기뻤다. 서버 이전 작업을 내가 해야만 하는 일로 생각하며 계속 들고 있었는데 세바님이 먼저 나서서 이 일을 진행해 주셨다. 중요한 작업 중에 자리를 뜨는데도 전혀 불안함 마음이 들지 않았다.아침에 일어나서 슬랙을 확인했다. 슬랙에 별다른 멘트가 없는 것을 보니 큰 문제는 없나 보다. 야호! 이전된 서버가 정상적으로 운영되고 있는지 확인을 위해 심사팀도 일찍부터 출근해서 테스트를 진행하고 있었다. 회사에 도착해 보니, 나를 제외하고는 모두 밤을 새워 일을 하고 계셨다. 다들 몽롱한 표정이다. 고맙다.하루에도 8시간씩 같이 일하는 동료들이지만 왠지 이렇게 같이 밤을 지새워서 작업을 하고 나면 동지애가 생긴다. 긴 밤을 고생해준 개발팀 멤버들에게 다시 한번 고마운 마음을 전한다. 앞으로도 잘 부탁드려요!(제가 따로 드릴것은 없어서 박수를!)#8퍼센트 #에잇퍼센트 #AWS #서버 #서버이전 #인프라 #개발팀 #팀워크 #조직문화
조회수 1490

앱 어트리뷰션 가이드 - 인스톨 어트리뷰션 방법

트래킹 URL을 활용한 데이터 수집에 대해 알아본 지난 글에서 이어지는 내용입니다. 유저가 광고를 클릭하면 트래킹 URL로 연결되고, 트래커는 이 URL을 이용해 어떤 유저가 어떤 매체의 광고를 언제 클릭했는지 알게 된다는 것으로 지난 글을 요약할 수 있겠네요.또한 단말기로 다운로드된 앱이 실행될 때, 앱 안에 있는 분석 SDK는 어떤 유저가 어떤 매체의 광고를 통해 언제 앱을 실행 했는지에 대한 데이터를 수집하여 트래커로 전송한다는 것도 빼놓을 수 없습니다. 이렇게 되면 트래커는 두 가지 데이터 – 광고 클릭에서 발생한 데이터와 앱 실행에서 발생한 데이터 – 를 대조하여 광고 클릭으로부터 연결된 정상적인 앱 설치를 찾아내고 해당 설치를 광고에 의한 것으로 인정하는데 이것을 인스톨 어트리뷰션이라고 부릅니다.데이터 대조를 통한 인스톨 어트리뷰션에는 크게 네 가지 방법이 활용됩니다. 각 방법마다 대조하는 데이터가 다른데요, 이 글에서는 각 방법의 적용 우선순위와 특징을 자세하게 설명하려고 합니다.네 가지 방법거의 대부분의 앱 어트리뷰션 툴이 동일한 방법을 활용합니다. 단지 적용 우선순위에서 일부 차이가 있을 뿐입니다. 각각의 인스톨 어트리뷰션 방법을 와이즈트래커가 적용하는 순서로 나열하면 다음과 같습니다.Identifier MatchingClick ID MatchingGoogle Play Install ReferrerFingerprint Matching각 방법의 세부 내용을 살펴 볼까요?1. Identifier Matching정의: 스마트폰에 부여된 고유 광고 식별자(Identifier)를 기준으로 어트리뷰션 하는 방식입니다.설명: 유저가 트래킹 URL을 클릭하면 해당 유저의 광고 식별자가 파라미터에 추가됩니다. 트래커는 파라미터에 담긴 유저의 광고 식별자를 수집하고 유저를 최종 목적지로 리다이렉트 합니다. 유저가 최종 목적지에서 다운로드한 앱을 실행하면, 앱 안에 있던 분석 SDK가 광고 식별자를 수집하여 트래커로 전송하고, 트래커는 광고 클릭 시점에 수집한 식별자와 앱 실행 시점에 수집한 식별자를 대조하여 어트리뷰션 합니다.특징: 광고 식별자는 각 단말기마다 부여된 고유 식별자 중에서도 마케팅 목적으로 활용하도록 고안된 값입니다. Android 단말기의 식별자는 ADID(Advertising ID) iOS 단말기의 식별자는 IDFA(Identifier for Advertisers)라고 부릅니다. 이 식별자들은 유저의 선택에 따라 비활성화 또는 리셋이 가능하며 개인정보도 아니기 때문에 마케팅에 활용할 수 있습니다. 각 단말기마다 고유한 값이라는 점, 비활성화 또는 리셋을 하는 유저가 극히 드물다는 점에서 어트리뷰션 정확도가 매우 높습니다.한계: 식별자를 수집할 수 없는 환경을 통해 유입된 유저에 대해서는 어트리뷰션 할 수 없습니다. 식별자를 비활성화 또는 리셋하는 경우, 그리고 웹 환경에서 발생한 광고 클릭, 다시 말해 웹 브라우저(크롬, 사파리, 기본 브라우저 등) 상에서 발생한 광고 클릭이 식별자 수집이 불가능한 환경에 해당합니다. 2. Click ID Matching정의: 각 광고 클릭에 부여하는 고유한 Click ID를 기준으로 어트리뷰션 하는 방식입니다.설명: 유저에 의해 광고 클릭이 발생하면 해당 클릭의 고유한 Click ID가 파라미터에 추가됩니다. 트래커는 파라미터애 담긴 Click ID를 수집하고 유저를 최종 목적지로 리다이렉트 합니다. 만약 유저가 플레이 스토어를 통해 다운로드한 앱을 실행하면 앱은 구글로부터 리퍼러를 받아오게 되는데, 분석 SDK는 이 리퍼러 정보를 검출하여 트래커로 전송합니다. 검출된 리퍼러에는 Click ID가 포함되어 있으며, 트래커는 클릭 시점에 발생한 Click ID와 앱 실행 시점에 발생한 Click ID를 대조하여 인스톨을 어트리뷰션 합니다.특징: 많은 광고지면을 보유한 매체라면 하루에도 수억 건의 클릭을 처리하게 될 것입니다. 이렇게 되면 어떤 지면의 어떤 광고에서 언제 클릭이 발생했는지에 대한 데이터를 체계적으로 관리해야 할 필요가 있습니다. 그래서 각 클릭마다 고유한 식별자를 붙이는 방법을 활용합니다. 이런 Click ID는 고유한 값이라는 점에서 광고 식별자와 동등한 정확도를 가지면서, 웹 환경에서 발생한 클릭에 대해서도 어트리뷰션이 가능하기 때문에 광고 식별자 매칭으로는 어트리뷰션 할 수 없었던 영역을 보완한다는 장점이 있습니다.한계: 기본적으로 Install Referrer 방식의 일종이므로 해당 방법의 한계점을 모두 가지고 있습니다. 이 방법의 한계점은 아래 3번 내용에서 확인할 수 있습니다. 또 하나의 한계는 모든 매체가 Click ID를 지원하지는 않는다는 점입니다. 매체 성격에 따라 Click ID를 사용할 필요가 없을 수도 있으며 Click ID를 사용할 환경을 갖추지 못한 매체도 있을 것입니다. 이렇게 Click ID를 지원하지 않는 매체에는 Click ID 기반의 어트리뷰션을 적용할 수 없습니다. 3. Google Play Install Referrer정의: 구글의 앱 플랫폼인 플레이 스토어에서 넘겨주는 리퍼러를 기준으로 어트리뷰션 하는 방식입니다.설명: 구글로부터 받아온 리퍼러에 Click ID가 존재한다면 Click ID Matching 방식을 사용하는데, 리퍼러 안에 Click ID가 없다면 Install Referrer 방식을 적용하게 됩니다. Click ID Matching 방식은 리퍼러 전체에서 Click ID 값만 뽑아서 대조하는 것이며, Install Referrer 방식은 리퍼러 전체를 대조한다는 것이 두 방식의 차이점입니다.Install Referrer 방식은 앞에서 설명했던 Click ID Matching과 거의 유사한 프로세스로 진행됩니다. 광고 클릭 시 수집한 정보들을 리퍼러 파라미터에 붙여서 유저를 플레이 스토어로 보내고, 유저가 이 곳에서 다운로드한 앱을 실행하면 분석 SDK는 이 리퍼러를 검출하여 트래커로 전송합니다. 트래커는 광고 클릭에서 발생한 리퍼러와 앱 실행에서 발생한 리퍼러를 대조하여 어트리뷰션 합니다.특징: 구글을 통해서 리퍼러를 재확인 받는다는 점에서 광고주와 매체 모두가 어트리뷰션 결과를 신뢰하게 됩니다. 또한 정상적인 환경에서는 리퍼러가 유실될 우려가 없기 때문에 어트리뷰션 정확도가 매우 높습니다.한계: 리퍼러 확인은 플레이 스토어 앱을 통해 앱이 다운로드되는 경우에만 사용할 수 있는 방법입니다. 다시 말해 iOS 플랫폼에는 적용할 수 없고, 안드로이드라고 하더라도 플레이 스토어 웹사이트(https://play.google.com)를 통한 다운로드에도 적용할 수 없습니다. 4. Fingerprint Matching정의: 유저 단말기의 다양한 정보를 조합해서 생성한 핑거프린트를 기준으로 어트리뷰션 하는 방식입니다.설명: 핑거프린트가 무엇인지에 대한 쉬운 설명으로 시작 해야 할 것 같습니다. 어떤 사고가 발생했는데 범인은 현장에서 도주했고 몇몇 목격자가 있다고 가정해 보겠습니다. 경찰은 목격자들에게 범인이 누구인지를 알고 있냐고 먼저 물어봅니다. 만약 누구든지 범인의 이름, 주민등록번호, 전화번호를 알고 있다면 쉽게 범인을 찾아낼 수 있을 것입니다.하지만 범인의 정확한 인적사항에 대해 알고있는 목격자가 없으면, 경찰은 범인에 대한 다른 정보들을 탐문합니다. 성별, 키, 신체적 특징, 입었던 옷, 목격한 지점 등을 확인하게 되겠지요. 확인한 정보로 몽타주를 만들고 용의자를 특정합니다. 이렇게 되면 사고현장 근처를 지나갔으며 알리바이가 없었던 수많은 불특정 다수가 용의자에서 제외됩니다.이름, 주민등록번호, 전화번호 등의 고유값으로 범인을 찾아내는 것이 Install Referrer나 Identifier Matching에 해당하며, 다수의 특정 조건을 만족하는 용의자를 찾아내는 것은 Fingerprint Matching이 됩니다. 인스톨 어트리뷰션을 위한 핑거프린트는 IP주소, 브라우저 정보, 통신사, 단말기 모델, OS 버전, 국가 및 언어 설정 등 다수의 조건으로 구성됩니다.특징: 앞의 두 가지 방법인 리퍼러와 광고 식별자를 모두 적용할 수 없는 상황에 한해 적용합니다. 어떠한 기술적 환경에도 적용할 수 있으며 고유값이 없는 상황에서는 가장 정확한 방법입니다. 핑거프린트 생성 후 24시간 이내에는 통계적으로 약 90%의 정확도를 보입니다.한계: 리퍼러나 식별자 매칭과는 달리 핑거프린트 매칭은 추정적(Heuristic) 방법입니다. 고유한 값이 아닌 다수의 가변적인 값으로 결과를 판단하기 때문입니다. 예를 들어 데이터를 사용하다가 와이파이에 연결하면 IP 주소가 달라지며, 해외에서 SIM 카드를 교체하면 통신사도 변경됩니다. 이로 인해 시간이 지남에 따라 클릭 시점의 핑거프린트와 앱 실행 시점의 핑거프린트가 달라질 가능성이 있기 때문에 100%의 정확도를 보장할 수는 없습니다.앱 설치가 어떤 광고에 의한 것인지를 어트리뷰션 하는데 사용하는 네 가지 방법을 살펴 보았습니다. 이렇게 측정한 데이터는 툴 사용자뿐만 아니라 광고를 노출한 매체사들에게도 전달 되는데요, 애드테크 업계에서는 이것을 포스트백이라고 부릅니다. 포스트백에 대한 정확한 개념과 구체적인 방법은 다음 글에서 다루겠습니다.
조회수 1473

스타트업의 디자인 철학

제목이 좀 거창하다. 디자인 철학!! 철학이라는 단어와는 그다지 친하지는 않은데.. 그래도 저렇게 쓰고 싶었다. 파펨은 나름의 철학이 있다고 생각하니까.. 철학이 꼭 멋있을 필요는 없는 것이 아닐까?라는 생각에 글을 시작하고자 한다. 우선 제목이 던진 질문에 대해서 먼저 답변을 하자면...파펨의 디자인 철학은 "Industrial"이다.Industrial이라는 말이 의미하는 것이 무엇이 있을까?영어사전 industrial 미국·영국 [ɪn|dʌstriəl] 1. 산업의   2. 공업용의   3. 산업 시설이 많은산업용, 공업용이라는 의미가 있는데.. 파펨의 디자인이 산업용, 공업용 목적이라는 의미는 아니고, 산업용 제품들의 특징을 살펴보면, 사용자를 위해 멋지게 꾸미는 디자인이라기보다는 사용자의 편의와 기능이라는 핵심에 focus를 둔 것들이 많다. 파펨의 디자인도 향수/향기라는 "본질"에 보다 focus 하고, 불필요한 치장을 줄여가자 라는 것이 핵심이다.최근 출시된 30ml 제품은 일단 외관에서도 industrial이라는 느낌 (뭔가 투박하고, 금속적인 느낌?)을 잘 반영하고 있지만, 패키지의 소재에서도 그 특징을 반영하고자 고민을 하였다. bottle은 알루미늄으로 만들어졌는데, 이 알루미늄 bottle은 원래 향기를 제작하는 회사에서 fragrance oil(100% 순도의 향기)을 담는 목적으로 사용되던 것이다. 이 원래의 목적을 잘 적용하는 것이 파펨의 bottle에도 잘 어울릴 것이라는 생각에서, 일반적으로 사용되는 유리병들보다 7~8배는 비싸지만 이 aluminum bottle을 사용하게 되었다. 게다가 이 bottle은 독일에서 생산되어, 한국까지 도착하는데 배로 1.5 개월이라는 시간까지 걸린다는 단점이 있는데, 이는 operation에서의 risk로 존재하기도 하니.. 쉬운 접근은 아니었다. OTL그리고, 외관의 패키지는 골판지를 이용하였는데.. 이 또한 흔히 제품의 외관 혹은 배달용(delivery) box에는 골판지를 사용한다는 점에서 이 모티브를 가져오게 되었다. 단, 파펨의 철학 중 하나인.. 남들 다하는 것은 하지 않는다는 점에서, 우리는 박스 안에 그냥 넣기 보다는 우리의 제품이 그 사이에 위치한다(?) 라는 아이디어를 적용해 보았다.이렇게 패키지 디자인에 적용된 industrial이라는 느낌도 있지만, 그것보다 먼저.. 파펨이라는 서비스의 시작이 industrial이라는 keyword와 닿아있다. 파펨의 EDP 향수 제품은 새로운 향기를 조향하여 만드는 것이 아니라, 드롬(DROM)이라는 100년이 넘은 독일 fragrance 회사가 이미 만들어 놓은 샘플 들 중에 큐레이션을 통해 만들어지는 것이다.이미 완성도를 가진 제품들이 sample용 선반에 숫자화 된 code(e.g. 86245689)로 존재할 때, 파펨은 그 제품에 생명력을 불어넣어 제품으로 만드는 작업을 하는 것이다. 파펨의 시작부터 이러하였고, 그렇기 때문에 향기를 잘 살려내는 그 본질에 focus 하자는 것이 우리의 목표다.이러한 아이디어가 서비스의 출발점이다 보니.. 5ml 제품의 package 또한 향수라는 제품이 일반적으로 가지고 있는 향수 병의 선입견 (e.g. 아름다운 병 모양) 과는 다르게, 무광 검정 bottle에 묘하게 디자인된 category 분류용 기호만이 덩그렇게 달려있다. 파펨이 고객과의 대화 혹은 survey를 진행하게 되면, 왜 검정 바틀인가요? 혹은 투명한 것으로 바꿔주세요.. (물론 좋다고 말씀해 주시는 분들도 꽤 있다.. ^^;;)라는 의견들이 제법 있는데..  사실 파펨의 indentity라는 부분을 쉽사리 바꿀 수는 없는 것이라, 오히려 왜 우리가 그렇게 디자인하였는지를 설명해드리는 편이다.한 가지 추가 설명을 하자면, 위의 5ml bottle용 포장은 industrial 키워드가 조금 다르게 해석되어 있는데, 꼭 필요한 정보의 전달을 한다는 차원에서 카테고리의 로고(e.g. 1. F/F)가 표현되었고.. 또한 파펨이라는 제품의 특징 중 하나인 Subscription, 즉 매달 새롭게 출시되는 ART라는 기본 컨셉을 표현하여야 하였다.그래서 우선 향수가 가진 후각적인 ART, 그리고 향기를 표현하는 이미지의 visual art, 게다가 BGM의 청각적인 ART까지.. 그래서 이렇게 매달 제품을 만들어 출시하는 것이 마치 앨범 하나를 만들어 내는 것이라는 생각을 하게 되었고, 그래서 CD 케이스의 모양을 만들고 싶다는 욕심을 부리게 되었다. 마치 월간 윤종신과 같이... online 서비스를 제공하는 서비스는 물론이고, 특히나 제품을 생산하는 "Startup"에게 디자인 철학을 갖는다는 것은 쉽지 않은 이야기일 수도 있다. 그렇다고 파펨이 우월하다?라는 말을 하는 것 이기보다는..파펨은 industrial이라는 명확한 디자인 방향성이 있다 보니, 이렇게 저렇게 흔들리지 않고 계속해서 나아갈 지향점이 있다는 점이 중요한 point#파펨 #스타트업 #창업가 #창업자 #마인드셋 #인사이트 #디자인 #철학

기업문화 엿볼 때, 더팀스

로그인

/