스토리 홈

인터뷰

피드

뉴스

조회수 889

크로키닷컴을 소개합니다 #6

크로키닷컴 6번째 인터뷰!오늘은 지그재그 앱 개발을 담당해주시는 앱 개발자 연미님, 하경님과 함께 현재 채용 중인 [안드로이드/iOS 개발자] 포지션 및 지그재그 앱에 관해 소개해보도록 하겠습니다. :-)Chapter 1. 저를 소개합니다!Q. 연미님, 하경님 안녕하세요! 이번 인터뷰이로 선정되신 소감과 간단한 자기소개 부탁드립니다.하경 저는 입사하기 전에 저희 파트 형준님 인터뷰(3편)를 봤었는데 되게 인상 깊었거든요. 그래서 제가 면접을 볼 때도 그 얘기를 했던 게 엊그제 같은데.. 제가 어느새 인터뷰를 하고 있는 모습을 보니 신기하고 뿌듯하네요. (웃음)연미 저는 앱 개발뿐만 아니라 다른 포지션에 대해 도움이 되는 정보를 드릴 수 있게 돼서 감사하게 생각하고 있습니다. 초대해주셔서 감사해요!하경 저는 iOS 개발을 주로 하고 있는 김하경이라고 합니다. iOS를 하다가 최근에는 조그만 것들부터 안드로이드도 시도해보고 있어요. 이번에 저희 지그재그 결제 서비스인 Z결제 소속으로 들어가게 되었는데요, 커머스 앱의 가장 중요한 기능 중 하나인 리뷰 서비스 개발을 맡게 되었어요. 한동안 굉장히 바쁘게 일하지 않을까 생각합니다.연미 저도 하경님과 같이 앱 개발을 하고 있는 손연미입니다. 저는 지그재그 서비스의 초반부터 안드로이드/iOS 개발을 모두 해왔는데, 요즘은 안드로이드 쪽에 좀 더 집중해서 하고 있어요. 이번에 저는 지그재그 서비스를 폭풍 성장시킬 그로쓰팀의 일원으로 들어가게 되었는데요, 유저의 사용성뿐만 아니라 또 다른 시야에서 서비스의 성장을 같이 일궈낼 예정입니다. (앗, 그러면 하경님이 입사 8개월 만에 연미님의 품을 벗어나게 된 건가요?)하경 네 맞아요. 저희가 이번에 처음으로 떨어지게 되었는데 약간은 두렵네요.연미 혹시 괴롭히는 사람 있으면 말하세요. 제가 혼내줄게요!오늘도 평화로운 지그재그 팀Q. 하경님은 지그재그로 이직을 하고 싶으셨던 이유나 계기가 따로 있으셨나요?하경 저는 예전에 정말 소규모의 스타트업에서 처음 iOS 개발을 시작했었어요. 심지어 같이 일하는 동료도 iOS 개발이 처음이었다 보니 같이 배워가는 입장이라 어려운 점이 많았고, 항상 부족하다는 느낌을 받았어요. 계속 검색에 의존하여 개발을 하다 보니, '더 좋은 코드를 짤 순 없을까?' 하는 욕구가 있었는데 우연히 지그재그의 글을 보게 되었어요. 지그재그 기술 블로그나 브런치 글들을 보면 아시겠지만, 성장이라는 문구가 되게 많이 나오거든요! 그게 저에게 굉장히 매력적이었어요. 채용 공고에도 같이 성장하고 싶은 팀원을 구한다고 적혀있길래 망설임 없이 지원했습니다. 저는 그때도 그렇고 지금도 그렇고 항상 성장하고 싶어요.(실제로 입사하시고 나서 겪어본 지그재그는 어떤가요?)하경 전 직장과 비교했을 때 저에게는 지그재그가 큰 회사로 느껴졌어요. 그러다 보니 왠지 이미 굳어진 정책 안에서 정해져 있는 일만 해야 하지 않을까 하는 개인적인 생각이 있었는데, 실제로 겪어보니 전혀 아니었어요. 매우 빠르게 성장하며 끊임 없이 변하고 있고, 이것 저것 새로운 걸 많이 시도해보는 게 신기했어요. 코드 리뷰도 처음 입사했을 당시에는 모든 코드에 대해 서로 꼼꼼히 리뷰한 후에 배포가 가능해서 그 과정에서 정말 많이 배울 수 있어서 너무 좋았는데, 최근에는 작은 부분에 대해서는 코드 리뷰를 거치지 않고 담당자가 빠르게 배포할 수 있도록 자율성을 부여하고 있어요. 이렇게 자율적으로 맡게 되니, 코드에 대한 책임감이 더 커지고 공부도 더 열심히 하게 되는 것 같아요.Q. 연미님은 지그재그 서비스 초기에 합류해서 지금까지 함께하고 계신데요! 처음 합류하시게 된 계기가 궁금합니다.연미 이전 회사에서는 주로 파견을 나가서 근무한 적이 많았어요. 그러다보니 제가 애정을 담아 지속적으로 키워나갈 수 있는 서비스가 없는 점이 항상 아쉬웠어요. 그래서 그런 서비스를 만들어나가는 사람들을 만나 함께 발전시켜나가고 싶다는 생각을 늘 했었는데, 운이 좋게도 지그재그 팀을 만나게 되었네요.(합류를 결정하실 때, CTO 상민님과 함께 일을 해보고 싶었던 마음도 한 몫(?) 했다고 들었어요!)연미 제 주변에서 상민님은 천재 개발자라고 불렸어요. 그러다 보니 저도 상민님과 함께 일하면서 배우고 싶었고, 같이 파이팅해보고 싶다는 의지가 매우 강했어요. 그렇게 합류한 이후 지금까지 상민님과 함께 개발을 하면서 정말 많이 배웠어요. 그리고 상민님과 저의 커뮤니케이션 스타일이 비슷한 부분이 있는데, 그래서 더 편하게 좋은 팀워크를 유지해올 수 있었던 것 같아요. 둘 다 말수가 적은 편이라 업무에 필요한 말만 간단히 주고 받는 편인데, 그게 오히려 서로에게 편하게 느껴진다는 것? (웃음)천재 개발자 상민님의 기술 블로그 놀러 오세요! https://devblog.croquis.com(하경님께서 이전에 코드 리뷰에 대해 언급해주셨는데요, 연미님은 개발팀의 문화로 자리 잡은 코드 리뷰에 대해 어떻게 생각하시나요?)연미 개발자들에게 코드 리뷰는 책임감이고, 방향성이고, 하나의 커뮤니케이션 방식이라고 생각해요. '우리는 코드로 얘기한다.'고 생각하는 개발자들이 모여있기 때문에, 서로가 어떤 생각을 가지고 코드를 짰는지 공유하고, 미처 발견하지 못한 문제점이 있다면 같이 해결해보고 하는 거죠. 같이 sync를 맞추고 신뢰를 쌓아가는 과정이랄까요? 다른 인터뷰에서도 다른 분들이 말씀하시긴 했지만, 우리 지그재그 팀은 다들 서비스에 대한 애정을 바탕으로 더 나은 서비스를 만들고 싶어하는 사람들이라서 이런 문화를 매우 선호하고 환영합니다!Chapter 2. 업무와 일하는 방식Q. 오늘은 지그재그 팀이 목적 조직으로 대대적인 조직개편을 한 그 첫 날인데요, 이 변화에 대해 두 분이 기대하는 점 또는 걱정되는 점이 있다면요?연미 작년에 인원이 많이 늘었고 회사도 커지면서 각자의 R&R이 명확해지다 보니 '함께 한다'는 느낌이 조금 약하게 느껴졌었어요. 그런데 목적 조직은 모두 하나의 목적을 가지고 으쌰 으쌰 하기 때문에 그렇게 생각을 덜 하게 되지 않을까 싶어요. 다만 조직개편 후에 놓치지 않도록 신경써야 되겠다고 생각하는 부분은 각 프로젝트팀의 목적 범위 외의 부분인 것 같아요. 주요 프로젝트 목적에 너무 치중하다보면 다른 부분에서 소홀해질 수 있기 때문에, 그런 경우가 생기지 않도록 어떻게 대비할 것인지 생각해두면 좋을 것 같아요.하경 우선 업무에 보다 집중을 할 수 있는 환경이 된 것 같아 기대가 됩니다. 그리고 개발자는 개발팀, 디자이너는 디자인팀 이렇게 각자의 팀에서 협업을 진행했었는데, 이제는 같이 한 팀이 되어 하나의 목적을 달성해나가게 되었으니 커뮤니케이션이 훨씬 더 수월해진 것 같기도 하고요. 그리고 그럴 일은 절대 없게끔 할테지만, 각 팀의 목적이 훨씬 뚜렷해진 만큼 만에 하나라도 목적을 달성하지 못하게 된다면 제가 너무 우울해지지 않을까 하는 생각이 들기도 해요. 반드시 달성하고 싶어요!Q. 지그재그 앱의 역사를 함께 해오셨는데, 새로운 기술이 도입될 때마다 적용시키는 과정이 궁금해요. 연미 인원이 늘기 전까지는 새로운 기술들에 대해서 시도를 많이 못해봤던 것 같아요. 1년 전부터 팀원이 늘어나게 되면서는 레거시를 바꾸는 작업을 계속 진행하고 있어요. 서비스 초기에 적용된 기존의 기술들을 계속 사용하기보다는 새로 합류하는 팀원들의 의견을 많이 들어보고 적용하려고 하고요. 예전 버전에서 지속적으로 업그레이드를 하고 있다고 생각해주시면 될 것 같아요.하경 우리 지그재그 팀원분들은 기술 그 자체이신 분들이 굉장히 많거든요. 다른 팀원들이 새로운 기술을 접목해서 우리 서비스에 득이 된다면 새로운 기술은 언제든 배우고 싶어요. 물론 서비스의 안정성이 가장 중요하기 때문에, 접목해보기 전 새로운 기술에 대한 충분한 공부가 선행되어야 하는 건 필수라고 생각해요!(새로운 걸 배운다는 게 쉬운 일은 아닌데요..!)하경 어렵지 않아요. 되려 재밌어요! 새로운 걸 배운다는 게 제가 개발을 하는 데 있어서 활력을 불어넣어주는 거라고 생각하거든요. 기존에 하던 대로만 개발을 해오다 보면 지루해져서, 새로운 걸 시도해보곤 한답니다.연미 학습 속도가 정말 빨라요. 뚝딱 하고 만들어내시거든요.하경 아니에요..(부끄) 재밌어서 하다 보니 그렇게 되었네요.빛의 속도 하경님!Q. 서비스 초기부터 앱 파트를 이끌어오신 연미님! 성과를 이루기 위해 해왔던 업무 방식이나 프로세스가 있다면 어떤 것인가요?연미 저는 업무에 대한 흥미도가 성과 향상에 많은 영향을 미친다고 생각해요. 그래서 태스크의 담당자를 정할 때에는 그 태스크에 관심이 있고 하고 싶어하는 팀원이 있는지를 우선적으로 고려하는 편이에요. 만약 지원자가 없는 업무라면 그땐 팀원들과 이야기를 나누며 팀의 상황에 따라 분배를 하긴 하지만요. (웃음)그리고 2019년 초반에는 앱 파트 팀원이 총 3명이었는데요, 그땐 집중 프로젝트라는 것을 적용해봤어요. 각자 하나의 프로젝트를 맡아서 본인이 주도적으로 타 팀과 커뮤니케이션을 하면서 프로젝트를 완료할 수 있게끔 해본 거였는데, 결론적으로는 팀원 분들이 좋아하셨어요! 책임감도 키울 수 있었고, 비즈니스적인 측면에서도 성장할 수 있었다고 생각해요. 내 프로젝트다!라는 생각이 있다 보니까 아이디어도 내보고, 의견 전달도 많이 하면서 진행될 수 있어 좋았습니다 .하경 집중 프로젝트를 통해 미리 경험을 했어서 그런지 이번에 목적 조직에 들어간 게 어색하지 않은 것 같아요. 이미 그 방식에 적응되어 있는 것 같거든요.Q. 하경님은 작년 제1회 지그재그 배 해커톤에서 1등을 하셨어요! 그때 만드신 프로젝트 '휴가봇'이 회사에서 실제로 활용되고 있는데, 기분이 어떠세요?하경 작년 추석쯤에 해커톤을 했었는데요, 뭘 만들어볼까 하다가 회사 생활하면서 작은 부분이지만 불편하게 느껴졌던 부분을 개선해보고 싶어서 만들게 되었어요. 이름은 달비 휴가봇이고 같은 Dev.팀의 우식님과 만들었는데, 이게 이렇게 큰 영향을 끼치게 될 줄 몰랐어요! 사실 지금도 계속 사용 중이니 계속 기능 업데이트를 위해 공부를 하게 되고요. 그래서 신기하기도 하고 뿌듯하기도 하답니다. 원래 쓰던 기술이 아닌 새로운 기술을 배우면서 만들어봤던 프로젝트라서 더 재밌었고, 만약 개발에 지루함을 느끼신 분이라면 이런 프로젝트도 시도해보시면 좋을 것 같아요.연미 새로운 기술을 쓰다 보면 시야가 넓어지거든요. 정말 추천드리고 싶습니다. 하경 그리고 달비 휴가봇을 같이 만들어주신 우식님께도 감사인사를 전하고 싶습니다. 지금까지도 얼떨결에 같이 개발을 하고 계시거든요..(웃음)Chapter 3. 저희 공고를 소개합니다!Q. 현재 채용 중인 [안드로이드/iOS 개발자] 얘기를 해볼까 해요. 어떤 앱 개발자를 찾고 있는지 말씀해주시겠어요?연미 일단 현재는 경력이 3년 정도는 있으신 분을 찾고 있어요. 아무래도 계속 새로운 작업들이 진행되고 있기도 하고 새로운 기술을 계속 적용하며 함께 업데이트를 해나가실 분을 모시고 있기 때문에 어느 정도의 경력은 있으시면 좋을 것 같아요.그래서 Dev.팀이 이번에 인턴쉽을 진행했을 때에도 앱 개발 분야는 별도로 접수를 받지는 않았는데요, 나중에 기회가 된다면 저희도 인턴이나 신입 개발자분들도 모시고 싶어요!하경 개인적으로 바라는 부분이기도 하지만 서비스에 대한 애정이나 관심이 있으신 분이면 좋겠어요. 저랑 같이 텅장을 만드실 분이랄까요.. (웃음) 장난이고요, 애정과 그 서비스에 쏟는 열정은 비례하다고 생각하거든요.연미 사용자가 지그재그 앱을 어떻게 더 편하게 사용할 수 있을지 같이 고민해주실 분을 찾고 있어요. 그리고 채용 공고 내에 우대사항으로 적힌 부분은 정말 우대사항이기 때문에 해당 기술을 알고 계시면 더욱 좋겠지만 모르셔도 괜찮아요. 들어오셔서 같이 배워나가시는 분들도 많이 계시고, 또 필요하면 팀 내에서 개발 스터디를 직접 오픈하실 수도 있고 이미 활발하게 진행중인 스터디에 참여하실 수도 있으니까요.하경 아! 그리고 서로에게 동기부여가 될 수 있는 팀원분을 모시고 싶어요. 앞에서 말씀 드렸던 코드 리뷰 같은 경우에도 서로 코드를 공유 하면서 모르는 기술도 알 수 있게 되고, 몰랐던 문제점을 알아갈 수 있으니 각자의 발전에도 도움이 되거든요! 저희 팀에서 자체적으로 OOP스터디도 진행했었는데, 같이 배우고 공유하면서 회사와 개인의 발전에 목표를 가지실 분이었으면 좋겠어요!Q. 지그재그의 예비 지원자에게 전하는 메시지!연미 지그재그 팀은 사업적인 측면에서도 그렇고, 기술적인 측면에서도 성장 가능성이 무한해요. 아무래도 새로운 시도를 할 때 오픈 마인드이기 때문이지 않을까 싶은데요, 팀원의 성장을 위해서 적극적으로 지원을 해주기 때문에 회사와 함께 발전하고 싶으신 분들은 맘껏 지원해주셨으면 좋겠어요.하경 주저하지 마시고 지원해주셨으면 좋겠어요! 전 개인적으로 빠르게 성장하고 있는 회사에서 일하다 보니 제 자신이 능동적이고 주도적으로 변해가는 걸 느꼈는데요, 그게 되게 회사에서 재밌게 일할 수 있게 해주는 것 같아요.Chapter 4. 마무리Q. 2020년 두 분의 목표가 있으신가요?하경 2019년에는 개인적으로 이사를 하게 되면서 인테리어에도 관심을 갖게 되었고, 특히 제가 청소에 빠지게 됐어요. 부모님이 보시면 어이없어하시겠지만. (웃음) 다른 것에 좀 더 포커싱이 가있었다 보니 개발 공부를 많이 못했던 것 같아요. 그래서 올해엔 공부를 열심히 할 생각이고, 마인드도 바꿔보려고요! 더 긍정적인 마인드!연미 저도 비슷한데, 개발자로서의 역할을 더 강화하고 싶어요. 저의 테크니컬한 부분을 더 발전시켜보려고요. 개인적인 목표는.. 내 집 장만입니다.하경 그거.. 작년 목표 아니었나요..?(웃음) 저는 건강관리도! 점심마다 샐러드를 먹고 있는데요, 공개적으로 말해두면 더 열심히 지키지 않을까 싶어 추가로 말씀드려봅니다.Q. 다음으로 인터뷰를 진행했으면 하는 팀이 계신가요? 궁금한 팀이 있으면 말씀해주세요!하경 저는 Relations팀이요. 전반적인 채용을 담당해주셔서 그 과정도 궁금하고, 이번에 팀 인원도 두 분 더 합류하셨는데 어떤 변화들이 생겼는지 궁금해요.연미 저는 3PL분들이요! 각자 프로젝트마다 어떤 방식으로 업무를 진행해가실 예정인지, 주간 미팅 때 공유해주시긴 했지만 좀 더 구체적인 가이드라인과 비전이 궁금해요. 이 인터뷰가 성사되고 많은 분들께 읽혀진다면, 각 팀의 목표에 공감하시는 좋은 분들이 많이 합류해주시지 않을까요! (*3PL은 지그재그의 새로운 프로젝트팀 리더 세 분을 의미합니다!)지그재그에서는 안드로이드/iOS 개발자를 포함하여 활발하게 채용을 진행하고 있습니다. 지그재그 팀과 함께, 수면 아래 숨겨진 가치를 찾아내는 경험에 동참할 팀원을 꼭 모시고 싶습니다 :-) 궁금하신 점은 언제나 [email protected] 또는 http://facebook.com/zigzagcareer로 연락 주세요!지그재그 [안드로이드/iOS 개발자] 포지션을 소개합니다!이런 일을 합니다.안드로이드, iOS이런 분을 모십니다.안드로이드iOS이 중 하나라도 가능하시다면 더더욱 좋아요 :)공통안드로이드iOS지원 방법채용 절차혜택과 복지   더 많은 공고는 채용 사이트에서 확인 가능합니다! >>> 채용 사이트 바로가기
조회수 1771

Genius? Jininus!

나는 인생을 살면서 많은 "천재"들을 만났다. 스타트업에 있다보면 더더욱 "영재""천재"로 불리는 수 많은 사람들을 보게 된다. 그들은 학문적으로 놀라운 성과와 스펙을 보유하고 있었다. 아마 당신이 한 회사를 운영하는 사람이거나 인사 담당자라면 분명 혹할 것이다. 하지만 정작 나는 같이 일하고 싶었던 사람이 단 한 명도 없었다. 주변에서는 천재들과 같이 일하면 성공할 것이라고 생각하지만, 사업적 결과물과 두뇌는 별개의 문제라고 나는 생각한다. 대단한 능력을 가지고도 빛 없이 사라진 사람들을 얼마나 많이 보았는가. 물론 나도 대단한 사람과 일하고 싶다. 그러나 그 기준을 "영특함"에 국한시키고 싶지는 않다. 사업적으로 혹은 사회적으로 더 나은 미래를 후손에 물려주기 위해서는 그 이상의 "무언가"가 필요하다. 지금부터 나에게 그 "무언가"를 가르쳐 준 "진짜 천재"에 대한 이야기를 하고자 한다. 그에 대한 이야기를 하기 전에 나에 대한 이야기를 가볍게 하고자 한다. 5년 전만 해도 나는 비전과 목표가 없었다. 어려서 부터 돈 욕심만 많았다. 대학교를 다니면서도 돈을 벌 수 있는 방법이면 수단과 방법을 가리지 않았다. 한 일화로 당시에 학원 강사 아르바이트를 하고 있었는데 도매시장에서 트렌디한 문구류를 사와 수업을 가르쳤던 중/고등학생에게 팔았다. 시간과 행동에 제약이 있는 학생들은 수업 시간에 벌어지는 소소한 쇼핑에 돈을 지불했다. 그러나 끝이 좋지 않았다. 학생의 부모님에게 알려져 결국 학원에서 해고 조치 되었다. 지금의 내가 돌이켜보면 엄청나게 창피한 일이다. 학생들에게 단순한 편리와 재미를 줄 순 있었지만, 돈 말고는 남는게 없었다.20대의 대부분은 가치 없는 돈벌이의 연속이었다. 혹자는 말한다. 우선 돈 벌고 가치 있는 곳에 쓰면 된다고. 그러나 이런 식의 무의미한 접근은 내가 가야할 길이 아니라고 느꼈다. 인생에서 가치 있는 일을 찾아야 했다. 그때 발견했다. 혁신, 도전, 열정이 정말 실천되고 있는 세계가 있다는 것을. 스타트업이라는 단어조차 생소했던 시기였다. 심지어 IT라는 분야를 그 전까지 제대로 공부해 본 적도 없었다. 스타트업의 "ㅅ"도 모르던 내가 이 세계에 적응할 수 있는 방법은 뛰어난 사람들과 함께 시작하는 것 뿐 이었다. 온갖 미사여구로 괜찮은 연봉과 복지를 내세우는 기업도 꽤 있었다. 그러나 나에게 가장 중요한 건 "내가 성장할 수 있는지"와 “구성원”이였다. 꽤나 당연한 조건으로 기업을 찾았음에도 불구하고 찾을 수가 없었다. 그러다가 첫 스타트업으로 선택한 게 라우드소싱 이라는 작은 팀이었다. (찾게 된 과정에 대해서는 다른 글을 통해 소개하겠다) 안정적인 연봉도 없고, 확실한 미래도 없었지만 내가 이 팀과 같이 해야겠다 결정한 건 "권진" 이라는 단 한 사람 때문이었다. 모든 기업이 그렇지만 누구나 회사에 합류하면 3개월간의 수습기간을 거친다. 스타트업이라고 예외는 아니다. 오히려 더 냉정하게 자신을 되돌아 보는 시간을 가져야 한다. 나는 내 스스로를 입증하고 싶었다. “제가 3달 안에 이 회사가 성장할 수 있는 계약들을 가져오겠습니다. 그 정도 능력도 발휘 못한다면 제 발로 나가겠습니다” 3달 동안 권진은 일에 대해서 전혀 간섭하지 않았다 . 팀워크에 있어서 가장 중요한 부분은 신뢰라고 생각한다. 하지만 신뢰라는 부분이 친하다고 해서 혹은 비전과 목표가 같다고 해서 생기는 것이 아니다. 각자의 위치에서 최고의 성과를 목표로 내고, 한계를 뛰어넘어 성장하는 모습을 보여줄 때 강력한 신뢰가 생긴다. 서로가 같이 일하고 싶은 마음을 만들어 주는 것.이게 팀워크의 핵심이다. 나는 나대로 권진은 권진대로 각자가 맡은 일들을 완벽하게 수행했고, 우리는 그 일들을 하나의 사업으로 만들어 갔다. 그는 나에게 따로 주저리 주저리 피드백을 하지 않았다. 하지만 행동으로 결과물의 중요성을 보여주었고, 나는 3달동안 7건의 B2B 계약을 성사시켰다.애초에 같이 할 사람을 정할 때는 모든 부분을 면밀히 살피고 고민해야 하지만, 내가 같이 하기로 결정 했다면 상대가 최고의 결과물을 낼 수 있도록 믿어주는 것. 내가 배운 첫번째 교훈이었다.실력을 보여주었다고 환상적인 Fit일까? 누구든 본인이 만들어 내는 결과물을 혼자만의 능력이라고 오판하기 쉽다. 내가 영업처를 설득하고, 계약서를 체결해 왔기 때문에 내가 없었으면 이 계약도 없었을 것이다. 감각적이고 환상적인 디자인을 뽑아냈는데 이건 순전히 나의 재능에 의한 것이다. 팀원들이 이런 생각들을 하기 시작한다면 그 팀은 단시간 내에 모래성처럼 무너질 것이다. 권진은 개인이 만들어 내는 결과물도 팀원들이 각자의 분야에서 해 온 노력들의 최종산출물이라고 생각한다.영업처를 설득할 수 있었던 건, 우리 팀이 환상적인 서비스를 만들어 주었기 때문이다.나의 디자인은 기획팀과 마케팅팀의 노력을 하나로 담은 것 뿐이다.톱니바퀴처럼 팀원들이 맞물려 돌아가며 서로의 존재에 대해 감사함을 느낄 때 놀라운 일이 벌어진다. 내가 배운 두번째 교훈이다.권진이 지켜온 2가지 요건이 계속 좋은 사람을 팀으로 영입할 수 있었던 강력한 요소였다고 생각한다. 나의 실력을 우리 팀에 입증하는 것. 나의 결과물은 우리 팀 노력의 산물 이라는 것.권진과 함께 일하며 느낀 그의 주요한 능력은 개발도 디자인도 아니었다. (물론 이 2가지도 잘한다)팀 내의 균형을 맞추고 팀원들이 끊임없이 성장하게 도와주는데 있다. 개성 넘치는 팀원들을 하나의 비전으로 묶어서 성장할 수 있게 하는 사람을 나는 살면서 권진 이외에는 아직 본 적이 없다. 장담컨데, 만약 현재 더팀스 대표가 권진이 아니라 다른 사람으로 바뀐다면 팀원들은 전부 팀을 나갈 것이다. (연봉이 대폭 인상된다 할지라도)그래서 나는 이걸 Jin in Us 라고 명칭했다. 권진이라는 확실한 구심점 안에 개성넘치는 팀원들이 한 몸처럼 목표로 향해가는. 나는 앞으로 대표라는 역할을 할 생각이 없다. 권진 이라는 사람보다 대표의 역할을 충실히 수행할 자신이 없어졌기 때문이다.리더십이라는 분야가 있다면 그는 천재가 아닐까?내가 우리 팀에 합류시키고 싶은 사람이 있을 때면 하는 단골멘트로 이 글의 마무리를 짓는다.“우리 팀의 권진을 만나보세요. 분명히 함께 하고 싶을 겁니다”#더팀스 #THETEAMS #천재디자이너 #풀스택개발자 #CEO #리더십 #경험공유 #팀원자랑 #팀원소개 #회사의자랑
조회수 2592

8퍼센트 Test case 작성 가이드

8퍼센트에서 Python Django 코드에 대한 Test case 작성시 사용하는 가이드를 공유해보려고 합니다.클래스명일반적으로 TestCase 를 상속 받는 클래스일 경우 class 명의 마지막에 TestCase 를 붙입니다.예제: SimpleTestCase(TestCase)함수명테스트 함수명의 경우 test_ 로만 시작하면 동작하는데 문제가 없고 테스트 코드에까지 주석을 다는 것은 번거로우므로 함수명의 test_ 뒷부분을 한글로 하여 설명을 대신하도록 합니다.class IUPaginationMethodTestCase(TestCase): @classmethod def setUpTestData(cls): cls.request = Mock() cls.request.GET = {'page': 1, 'items_per_page': 1} cls.pagination = IUPagination(cls.request) def test_page_url_기본(self): expected = '?{}=1'.format(self.pagination.page_key) self.assertEqual(self.pagination.page_url(), expected) def test_page_url_쿼리스트링_없는경우_물음표_붙인다(self): expected = '/?{}=1'.format(self.pagination.page_key) self.pagination.url_prefix = '/' self.assertEqual(self.pagination.page_url(), expected) def test_page_url_쿼리스트링_있는경우_엠퍼센드로_붙인다(self): expected = '{}&{}=1'.format( self.pagination.url_prefix, self.pagination.page_key )) self.pagination.url_prefix = '?utm=source' self.assertEqual(self.pagination.page_url(), expected) factory_boyfixture 를 대신해서 가급적 factory_boy 를 사용합니다.signals 끄기factory boy로 모델 객체 생성시 signal 이 호출되는데 signal에 대한 테스트가 아니라면 대부분 실행할 필요가 없습니다.이 때 factory.django.mute_signals를 사용해서 끄면 됩니다.decorator, context manager 둘 다 사용 가능합니다.decorator@mute_signals(signals.post_save) def test_some_code(self): some = SomeFactory() context managerwith mute_signals(signals.post_save): some = SomeFactory() 참고 링크factory_boyDisabling signalssetUpTestData vs setUpfixture를 사용하면 fixture로 정의한 모델 객체가 모든 테스트 시작 전에 생성이 되는데 유사하게 setUp 에서 factory 생성을 하게 되면 매번 객체 생성을 하게 되므로 느립니다.테스트에서 read only 로만 사용하는 객체의 경우 class method인 setUpTestData 에서 생성하면 1번만 생성이 되므로 빨라집니다.가급적 setUp 에서 매번 객체를 생성하는 것을 지양하고 테스트 함수 내에서 필요한 객체만 생성하는 것이 효율적이고 빠릅니다.method mock메소드를 mock 하는 경우 unittest.mock.patch() 를 사용합니다.decorator보통 테스트 메소드에 대한 decorator 로 사용합니다.직접 호출class 내의 여러 테스트 메소드 혹은 모든 테스트 메소드에서 동일한 함수를 mock 하는 경우에는 start, stop 을 활용하면 편합니다.예제 코드from unittest import mock class MyTest(TestCase): def setUp(self): self.mock_method1 = mock.patch('package.module.method1').start() self.mock_method1 = mock.patch('package.module.method2').start() def tearDown(self): mock.patch.stopall() def test_something(self): something() self.assertTrue(self.mock_method1.called) 참고 링크: patch methods start and stoptimezonedatetime.datetime.now() datetime.datetime.strptime() 등을 사용해서 naive datetime 객체를 django 모델의 DateTimeField 에 할당할 필요가 있는 경우 반드시 django.utils.timezone.make_aware() 를 사용해서 time-zone-aware datetime 객체로 변환한 후에 합니다.참고 링크: Django timezone 문제 파헤치기freezegun특정 시점에서의 테스트가 필요한 경우 freezegun 을 사용해서 현재 시간값을 고정합니다.가급적 decorator 나 context manager 를 사용해서 특정 클래스나 메소드, 혹은 코드 블럭에만 적용하도록 하는 것이 좋습니다.decorator 예제from freezegun import freeze_time import datetime import unittest @freeze_time("2012-01-14") def test(): assert datetime.datetime.now() == datetime.datetime(2012, 1, 14) context manager 예제from freezegun import freeze_time def test(): assert datetime.datetime.now() != datetime.datetime(2012, 1, 14) with freeze_time("2012-01-14"): assert datetime.datetime.now() == datetime.datetime(2012, 1, 14) assert datetime.datetime.now() != datetime.datetime(2012, 1, 14) 특정 테스트 케이스 전체에 적용을 하기 위해 start(), stop() 메소드를 사용하기도 하는데 이 경우 반드시 stop() 을 해주어야 다른 테스트 케이스의 시간 값에 영향을 주지 않습니다.예제from django.test import TestCase from freezegun import freeze_time class SomeTestCase(TestCase): def setUp(self): self.freezer = freeze_time("2016-01-05 00:00:00") self.freezer.start() def tearDown(self): self.freezer.stop() 참고 링크: freezegun맺음말Python Django 개발시 Test case 작성을 잘 하기 위한 8퍼센트 개발팀의 가이드를 공유해 보았습니다. Python Django 개발자들이 Test case 작성을 효율적으로 잘 해서 서비스의 안정성을 높이는데 도움이 되기를 기대해 봅니다.#8퍼센트 #에잇퍼센트 #Django #Python #장고 #파이썬 #개발 #개발자 #가이드 #꿀팁 #인사이트
조회수 1325

A씨의 일주일

스포카 개발팀 문성원입니다. 오늘은 스포카 개발팀의 가공의 개발자 A씨의 일주일을 통해, 스포카 개발팀에서는 일주일간의 개발 일정을 어떻게 진행하는지 살펴보겠습니다. 평소 스타트업(Startup) 개발팀의 문화에 관심이 있으셨던 분들에게 도움이 되길 바랍니다.월요일오전 10시, A씨는 평소보다 조금 일찍 사무실에 도착했습니다. 매주 월요일은 스포카 전체 미팅이 있는 날이기 때문이죠. 한 주간 각자 진행한 것을 토대로 이번 주에 진행할 일을 대외적으로 공표하는 이 회의에 앞서, 스포카 개발팀은 따로 미팅을 잠깐 가집니다. 그동안 지난 주 개발 사항, 이번 주 구현 목록 등을 트렐로(Trello)를 통해 정리한 뒤, 이를 전체 미팅에서 공유합니다. A씨는 지난 주에 미쳐 다 구현하지 못했던 서버의 몇 가지 기능과 클라이언트 신버전 배포 준비를 하게 되었습니다.정신없이 회의 하고 났더니 벌써 점심시간입니다. 늘 가던 근처 식당에서 즐겁게 점심을 먹고 사무실로 올라온 A씨는 막간을 이용해 간밤에 올라온 스포카 개발 블로그의 원고를 검토합니다. 몇 가지 오탈자와 맞춤법을 지적한 뒤 모두가 지루해할 월요일 오후 1~2시경에 공개하는 것이 목표입니다.올라간 블로그 글을 확인한 뒤에, A씨는 구현해야 할 서버 기능을 살펴보기로 했습니다. 생각보단 많긴 하지만 일주일 안에는 어떻게든 끝낼 수도 있을 것 같은 분량이네요. 우선 트렐로에 올라온 카드의 명세를 토대로 작업해야 할 내용을 체크리스트(Check List)로 정리합니다. 그다음은 모두가 짐작하시듯이 열심히 일합니다. A씨는 프로니까요.어느덧 저녁 시간이 다 되었습니다. 특별히 일이 없는 이상 야근은 하지 않는 주의인 A씨지만, 오늘만큼은 저녁을 먹고 조금 더 남아있기로 합니다. 팀 내에서 진행하고 있는 스터디 때문이죠. 혼자서 읽기는 까다로웠던 책을 다 같이 읽어보니 조금은 이해가 더 되는 느낌이 드네요.화요일A씨는 오전에 작업하던 중 이상한 점을 발견합니다. 구현하기로 한 기능이 기존 기능과 모순이 되기 때문이죠. 이걸 어떻게 해결할까 고민하던 A씨는 다행히 사무실에 남아있던 엔에이블러(Enabler)팀원들과 간단하게 미팅을 합니다. 문제를 설명하고 명세를 다시 확인한 A씨는 작성한 회의록과 함께 배포합니다. 트렐로의 해당 카드에도 첨부하여 나중에 다시 볼 수 있게 하는 것은 기본입니다.뜻하지 않은 문제 때문에 오전을 날려서 기분이 나빠진 A씨지만, 다행히 좋아하는 스파게티를 먹고 기운을 내기로 했습니다. 사무실에 올라와 인터넷 뉴스와 페이스북을 잠시 보던 A씨는 암묵적으로만 정해진 점심시간이 끝나자 바로 작업을 시작합니다. A씨는 프로니까요.그런데 문제가 있습니다. 오전에 배포한 회의록을 읽어 본 다른 팀원들이 이건 다른 문제의 원인이 될 수 있다고 합니다. A씨는 새 기능 추가가 단순히 로직이 아니라 클라이언트 UI를 포함한 대규모 변경이 필요하다는 것을 깨닫습니다. A씨는 새 기능에 대한 대략적인 스케치를 발사믹 목업(Balsamiq Mockup)으로 마친 뒤 이를 다시 배포합니다. 또한, 관련된 카드에 설명도 잊지 않습니다.수요일매주 수요일 오전에 스포카 개발팀은 짧은 미팅을 합니다. 금주의 진행사항 중 변경사항이나 도움이 필요한 내용을 공유하는 자리인데요. 여기서 A씨는 어제 일을 다시 정리해서 이야기하고, 일정이 지연될 수 있음을 전달합니다. A씨에게 할당된 카드 일부를 다음 주로 미루거나, 좀 한가한 사람에게 나눠주는 형식으로 짐을 던 A씨였지만, 여전히 큰일이 되어버린 기능 변경은 무거운 짐입니다.이런 대량의 작업 때문에 고민하던 A씨에게 같은 팀 B씨가 어떤 라이브러리를 소개해줍니다. A씨는 처음 보는 라이브러리인지라 B씨가 전담해서 가르쳐주는 모양이 되었지만, 생각보다 문제 해결에 큰 도움이 될 것 같습니다. 마침 다음 주에 개발 블로그에 글을 써야 할 당번이 된 A씨는 그 라이브러리에 대해 좀 더 공부해서 쓰기로 정합니다.B씨의 도움 덕에 진행 속도가 붙은 A씨는, 금주 업무 중 하나였던 클라이언트 새 버전의 테스트를 내일부터 진행하기 위해 페이스북이나 인터넷 뉴스도 보지 않은 채 열심히 일합니다. 이런 A씨의 프로다운 모습에 하늘도 감탄했는지, 퇴근 시간인 7시 전에 작업을 끝낸 A씨는 구현된 기능들을 테스트 해 보고 팀의 다른 개발자와 공유하기 위해 github에 만들어진 스포카 서버 코드 저장소에 푸시(Push)합니다.목요일구글 플레이(Google Play)는 하루 정도면 배포가 되니 다행이지만 애플 앱스토어(Apple App Store)는 일주일 정도의 심사기간이 있기 때문에 거절(Reject)당하지 않게 철저히 준비해야 합니다. 그래서 어느 때보다 A씨는 날카로운 눈매로 클라이언트를 점검합니다. 아니나 다를까 메뉴를 이동하다 보니 화면 구성이 흐트러지는 버그가 발견되었습니다. 하지만 프로답게 A씨는 당황하지 않고 재현 조건을 확인한 뒤, 클라이언트 담당자인 C씨에게 알려줍니다. “클라이언트 관련한 버그를 찾았는데, 트렐로를 확인해보세요.”라구요. QA(Quality Assurance) 업무 역시 스포카 개발팀은 직접 처리합니다.밖에 비가 오는지라 피자를 시켜먹은 뒤, 자리에 앉아 잠깐 쉬고 있던 A씨에게 D씨가 다가와서는, “어제 푸시한 소스를 내려받다(Pull)가 충돌(Conflict)이 났는데, 어떻게 병합(Merge)해야 할지 모르겠네요.” 라며 묻습니다. A씨는 D씨와 충돌이 난 소스를 함께 검토하고 문제가 발생하지 않게끔 조정한 뒤 이를 다시 푸시해서 상황을 종료합니다.이러는 사이 C씨가 A씨가 말한 버그를 고쳤다며 다시 확인해보라고 트렐로의 관련 카드를 “테스트” 리스트로 옮깁니다. A씨는 재현된 상황에서 문제 없이 동작하는 것을 확인하고 카드를 “완료” 리스트로 다시 옮깁니다. 이제 클라이언트 앱을 심의 신청하고 어제 구현한 서버 쪽 코드의 개선사항이 있는지 살펴봅니다. 서버는 클라이언트가 앱스토어나 플레이에 준비되는 것을 확인한 뒤, DotCloud에서 제공하는 배포 스크립트를 통해 손쉽게 버전업할 수 있기 때문에 시간이 좀 남아 있습니다. 현재로선 특별히 더 손댈 부분이 없다는 걸 확인한 A씨는 오늘도 즐겁게 퇴근합니다.금요일월요일과 마찬가지로 오늘도 A씨는 평소보다 조금 서둘러서 사무실에 도착했습니다. 오늘은 사내 전체적으로 한 주간 있었던 업무 내용을 간략하게 보고하는 자리가 있습니다. A씨는 이번 주에 맡은 서버 개발이 이러저러해서 이렇고 저렇게 바뀌었다고 설명한 뒤, 앱스토어에 신청되었다는 사실을 공지합니다. 전체 보고가 끝난 뒤엔 개발팀은 따로 남아서 약간 자세하게 금주 작업을 공유하면서 트렐로의 “완료” 상태에 있는 카드들을 정리하는 시간을 갖습니다.점심을 먹고 나서, 이번 주에 더는 급한 일정이 없다는 것을 확인한 A씨는 개발 블로그에 쓸 글을 정리하기 시작합니다. 수요일에 B씨가 알려 준 라이브러리의 사용 방법은 대강 배웠지만, 그것을 남에게 설명할 수 있을 만큼 자세히 알지는 못했기 때문에 A씨는 한동안 공식 문서와 예제 코드들과 씨름합니다. 그래도 어느새 옆에서 거들기 시작한 B씨 덕에 글은 생각보다 순조롭게 마무리되었습니다. 이제 다음 주 월요일까지 퇴고해서 블로그에 공개하기만 하면 되죠.생각보다 오늘 업무를 끝낸 A씨는 친구들과 약속이 있는 홍대로 가기 위해, 7시 정시에 사무실을 떠납니다.#스포카 #개발 #개발자 #개발팀 #개발자의일주일 #개발자의일상 #인사이트 #경험공유
조회수 864

2017 NDC 리뷰) 몬스터 슈퍼리그 리텐션 프로젝트

 2017년 4월 25일부터 27일까지 진행된 Nexon Developer Conference 에 나녀온 후기입니다.제가 들었던 재밌는 세션들 하나하나 올릴 테니 기대해 주세요! :)2017 NDC 재밌었다능!!!몬스터 슈퍼리그 리텐션 15% 개선 리포트 - 숫자보다 매력적인 감성 테라피"몬스터 슈퍼리그"의 게임 리텐션 개선 리포트였는데요, 기본적인 서비스의 소비자를 향한 어프로치인"당신은 똑똑한 유저!"라는 인식 심기(쉬운 접근성/ 심도 깊은 진행 유도)"빠른 어필"(이벤트에 대한 빠른 피드백)"축복받은 계정" (다양하고 많은 초반 보상)이라는 인식과,"주어지면 알아서 하겠지""보상이 있으면 알아서 하겠지"라는 생각을 지양하고, "의도하지 않았지만 수행을 할 수 있도록" 하는 넛지(Nudge) 효과를 일으킬 수 있는 무의식을 자극하는 재밌는 전략에 대해서 흥미를 느꼈습니다. 그리고 이후 리텐션 강화를 위한 프로젝트로 가장 중요한 건,단지 "무슨 기능을 만들 것이냐?"가 아닌, "어떤 부분에서 유저가 이탈"하게 되고,이탈한 유저들 중 "우리가 진짜 챙겨야 할 유저"가 어떤 유저들인가에 집중한다.라는 부분에서 항상 우선돼야 하지만, 되지 못한 부분들을 생각하게 되었던 것 같아요. 그래서 이를 통해스스로 모험 입장을 몇 번 한 유저: 원하는 것에 접근하지 못한 유저에 대한 파악 후 개선다음 지역에 접근 한 유저: 지속 플레이 의향 있으나, 니치를 못 찾은 유저들의 의도 파악 후 개선이라는 개선에 필요한 정확한 목표를 가지고, 어떤 방식으로 접근해야 하는지에 대한 고민을 보는 것에 정말 재미를 느꼈고요, 이에 대한 진행방향을 듣는 것도 정말 값진 경험이었습니다.개선 시퀀스1차 개선 (-)보상 10배 상향에도 불구, 큰 성장 없음.>"가치비교가 익숙하지 않은 유저들에게 보상의 절대적 수치 증가는 큰 감흥이 없다."라는 점을 파악하고, 유저에 "감정"을 터치하는 방법을 고안.2차 개선 (+ & -)조사 결과, 첫 패배 지점에서 유저들의 높은 이탈률을 파악> 패배 지점을 인위적으로 미루지 않되, 패배에 신경 쓰지 않도록 다른 부분들에 대한 장치를 추가.> "도전 가능한 포인트를 생성하는 것은 유효하다."는 부분을 Metric으로 확인했으나, 타깃 유저 범위를 정확하게 파악하지 않고  Metric만 집중해서 정확한 범위 파악을 놓침.3차 개선 (+ & +)유저가 얻을 수 있는 보상의 기회를 꼭 찾아가도록 유도옵션 1. 텍스트 강조? 텍스트는 망각의 영역 (X)옵션 2. 강제 터치? 이미 자유 플레이가 된 유저에게 부자연스러운 접근 (X)옵션 3. 얻고 싶은 보상이 있다면, 어떨까? 그리고 보상 등에 대해서 스토리 텔링이 될 수 있다면?  (O)  - 부정 경험 개선을 통한 리텐션 향상 효과  - 초반에 한 일을 다시 하게 하는 것은 큰 부정 경험을 초래  - 강제적 이동보다는 원하는 보상을 통해 부여4차 개선 (+)스토리 텔링 요소 추가  1. 일러스트  2. 캐릭터에 대한 스토리 추가글로벌 원빌드로서 북미권 영역에서  특히 추가결과개선 프로세스 이후,"무언가가 무조건 있다."라는 이야기 보단, "기대치 않은 행동에 대해서 얻는 보상의 획득"으로 유저의 감성을 자극하는 스토리 텔링의 중요성 확인.교훈보상도 주지만, "보상을 준다"라는 이벤트를 행하는 것 만으로 서비스 제공자는 끝내선 안된다. 보다 감성적인 접근을 통해 유저의 감정을 이해하는 것이 더 중요하다. 첫날 첫 번째 세션이었는데요, 아침부터 정말 보람찬 세션 들을 수 있어서 정말로 감사했습니다. 사실, 게임이건, 모바일 서비스건, 웹 서비스건 "소비자를 이해한다."라는 부분은 언제 어디서나 필요한 부분이지만, 결과적으로 서비스 제공자들은 "보상을 제공했다."로 서비스 제공을 스스로 끝을 내버리는 순간들을 더 많이 마주하기 때문에, 다양한 분야들에서 생각해 볼만한 이야기라고 생각합니다. 한줄평: 중요한 건, "내가 이런 걸 줬다!"보다는 "이런 걸 줘서 고마워"라는 것을 느낄 수 있도록 소비자의 마음에 초점을 맞추는 서비스 제공이 맞다!#코인원 #블록체인 #기술기업 #암호화폐 #스타트업인사이트
조회수 2602

React + Decorator + HOC = Fantastic!!

React + Decorator + HOC = Fantastic!!지난 포스팅에서는 ES7의 Decorator 문법을 이용해 선언된 클래스와 그 프로퍼티들을 디자인 시간에 변경하는 법을 알아보았습니다. 그렇다면 리액트 컴포넌트와 Decorator가 만나면 어떤 시너지가 발생할까요?만약 ES7의 Decorator에 대해 모르신다면 지난 포스팅을 읽고 오시는 걸 권장합니다. 이 포스팅은 독자들이 Decorator에 대해 이미 알고 있다고 가정하고 작성됐습니다.Higher Order Component리액트 공식 문서를 보면 Higher Order Component(이하 HOC)를 다음과 같이 설명하고 있습니다.리액트 컴포넌트 로직을 재활용할 수 있는 고급 기법리액트에서 공식적으로 제공하는 API가 아니라 단순히 아키텍쳐이 설명으로는 HOC가 어떤 역할을 하는지 이해하기는 역부족이기 때문에 간단한 예제를 통해 HOC를 어떻게 작성하는지 알아보겠습니다.function withSay(WrappedComponent) {     return class extends React.Component {     say() {       return 'hello'     } render() {       return (                   {...this.props}           say={this.say} />       )     }   } } withSay 함수는 WrappedComponent를 인자로 받아 원하는 속성들을 결합해 새로운 컴포넌트를 반환합니다. 이렇게 만들어진 withSay 함수는 아래와 같이 사용 가능합니다.@withSay class withOutSay extends React.Component {     render() {     return (               {this.props.say()}           )   } } withOutSay 컴포넌트는 say 메소드를 가지고 있지 않습니다. 하지만 withSay 함수를 사용하니 say 메소드를 사용할 수 있게 됐습니다. 이처럼 컴포넌트를 인자로 받아 입맛에 맞게 바꾼 뒤 새로운 컴포넌트로 반환하는 기법을 HOC라고 부릅니다.그렇다면 HOC는 리액트에서 어떻게 사용을 해야 효율적일까요?Cross Cutting Concerns개발을 하다 보면 다음과 같은 상황에 직면하는 경우가 종종 있습니다.개발 전반에 걸쳐 반복해서 등장하는 로직그럼에도 불구하고 모듈화가 쉽지 않은 로직예를 들어 방명록 작성, 게시글 작성, 게시글 스크랩을 하는 컴포넌트들에서 유저 인증과 에러 처리의 과정이 필요하다고 했을 때 어떻게 코드를 디자인해야 할까요? 컴포넌트와 직접적으로 연관이 없는 기능들이 컴포넌트와의 결합이 너무 강해 쉽게 모듈화를 시키지 못합니다.그림 1. Cross Cutting Concerns의 예시이렇듯 코드 디자인적인 측면에서 공통적으로 발생하지만 쉽게 분리를 시키지 못하는 문제를 Cross Cutting Concerns라고 합니다. 이 문제를 끌어안고 가면 프로젝트의 코드는 쉽게 스파게티가 되고 나중에는 유지 보수를 하기 힘들어집니다.하지만 우리게에는 HOC와 Decorator가 있고 이를 이용해 이 문제를 쉽게 해결할 수 있습니다.유저 인증 문제를 HOC로 해결아래는 인증이 안된 유저에게 다른 페이지를 보여주는 코드입니다.class TeamChat extends React.Component {     constructor() {     super()     this.state = {       unAuthenticated: false     }   } componentWillMount() {     if (!this.props.user) {       this.setState({ unAuthenticated: true })     }   } render() {     if (this.state.unAuthenticated) {       return     }     return I'm TeamChat   } } 유저 인증을 전통적인 if-else 구문으로 구현했습니다. 당장 이 컴포넌트를 본다면 문제가 없어 보입니다. 어떻게 보면 정답처럼 보이기도 합니다. 하지만 유저 인증이 필요한 컴포넌트가 많아지면 상황이 달라집니다.100개의 컴포넌트에서 위와 같은 방식으로 유저 인증을 하고 있는데 유저 인증을 하는 로직이 변경된 상황을 생각해 봅시다. 100개의 컴포넌트 모두 유저 인증 코드를 바꿔야 하는 상황에 직면하게 됩니다. 전부 다 바꾸는 것도 일이지만 실수로 몇 개의 컴포넌트를 수정하지 않을 확률이 농후합니다. 당장에는 간단하지만 잠재적 위험을 안고 있는 위 코드는 아래와 같이 수정되어야 합니다.function mustToAuthenticated(WrappedComponent) {     return class extends React.Component {     constructor() {       super()       this.state = {         unAuthenticated: false       }      } componentWillMount() {       if (!this.props.user) {         this.setState({ unAuthenticated: true })       }     } render() {       if (this.state.unAuthenticated) {         return       }       return     }    } } HOC를 이용해 확장이 용이한 유저 인증 로직이 탄생했습니다!! 이렇게 만들어진 HOC는 아래와 같이 적용이 가능합니다.@mustToAuthenticated class TeamChat extends React.Component {     render() {     return I'm TeamChat   } } @mustToAuthenticated class UserChat extends React.Component {     render() {     return I'm UserChat   } } 기존의 코드와 비교했을 때 코드가 훨씬 간단해진 것을 확인할 수 있습니다. 비단 코드만 간단해진 것뿐만 아니라 아래와 같은 추가 효과를 기대할 수 있습니다.유저 인증 로직이 컴포넌트와 분리가 되어 자신이 맡은 역할에만 집중할 수 있습니다.유저 인증 로직이 바뀌어도 코드를 수정해야 할 곳은 하나의 컴포넌트뿐입니다.예시로 작성한 HOC는 최소한의 코드로만 작성된 예시입니다. 실제 제품에서 사용되기 위해서는 몇 가지 고려해야 할 사항이 있는데 이는 리액트 공식 문서를 참고해주세요.i18n 컴포넌트를 HOC로 작성채널 서비스는 한국어, 영어, 일본어를 지원하기 때문에 번역 기능이 필요했습니다. 초기에는 번역 서비스를 아래와 같이 구현했습니다.@connect(state => ({   locale: getLocale(state) }) class Channel extends React.Component {     render() {     const local = this.props.locale     const translate = TranslateService.get(locale)     return (               {translate.title}         {translate.description}           )   } } 처음에는 위와 같은 방식으로 번역 서비스를 구현하는 것이 괜찮았습니다. 하지만 번역을 제공해야 하는 컴포넌트가 많아지면 많아질수록 중복되는 코드가 많아지는 것을 보고 아래과 같이 HOC를 이용해 코드의 중복을 제거했습니다.function withTranslate(WrappedComponent) { @connect(state => ({     locale: getLocale(state)   }))   class DecoratedComponent extends React.Component {     render() {       const locale = this.props.locale       const translate = TranslateService.get(locale) return (                   {...this.props}           translate={translate} />       )    }   } } 이렇게 작성된 HOC는 아래와 같이 사용이 가능합니다.@withTranslate class Channel extends React.Component {     render() {     const translate = this.props.translate     return (               {translate.title}         {translate.description}           )   } } HOC의 작성 방법은 예시로 작성한 두 개의 HOC에서 크게 벗어나지 않습니다. 이를 응용해 자신의 프로젝트에 맞는 코드를 작성해보세요.중첩 가능한 HOCHOC는 여러 개를 중첩해서 사용할 수 있습니다.. 예를 들어 유저 인증과 i18n 서비스를 동시에 제공하고 싶을 때 두 HOC를 중첩해서 사용하면 됩니다.@mustToAuthenticated @withTranslate class Channel extends React.Component {     render() {     return (               {`Hello!! ${this.props.user.name}`         {translate.title}         {translate.description}           )   } } 마무리이상으로 리액트에서 HOC를 사용할 수 있는 상황과 작성 방법을 알아보았습니다. 본 포스팅에서 다루지는 않았지만 만능처럼 소개한 HOC에도 몇 가지 단점은 존재합니다.Component Unit Test를 할 때 문제가 있을 수 있습니다.HOC를 몇 개 중첩하면 디버깅이 힘들 수 있습니다.WrappedComponent에 직접적으로 ref를 달 수 없어 우회 방법을 사용해야 합니다.비동기 작업과 같이 사용하다 보면 예상치 못한 결과를 만날 수 있습니다.하지만 이러한 단점에도 불구하고 상속을 제공하지 않은 리액트에서 HOC는 많은 문제를 효율적으로 해결해주는 단비와 같은 존재입니다. 유명한 리액트 라이브러리들(react-redux, redux-form 등)은 이미 예전부터 HOC를 사용해 사용자들에게 편의를 제공해 왔습니다. 이러한 라이브러리들과 자신의 프로젝트가 직면하고 있는 문제에 맞는 HOC를 작성해 같이 사용한다면 우아하고 아름다운 설계에 한층 더 다가간 프로젝트를 발견할 수 있습니다.마지막으로 한 문장을 남기고 본 포스팅을 마치도록 하겠습니다.React + Decorator + HOC = Fantastic!!본 포스팅은 2017 리액트 서울에서 발표한 내용입니다. 발표 자료와 발표 영상을 확인해보세요.#조이코퍼레이션 #개발자 #개발팀 #인사이트 #경험공유 #일지
조회수 1460

확률론적 프로그래밍 언어는 왜 필요 할까요?

AI•머신러닝은 모든 분야에서 거론되며 이를 적용해볼 수 있는 다양한 AI•머신러닝 툴들이 쏟아져 나오고 있습니다. 기본적인 머신러닝 기법들을 담고 있는 scikit-learn을 시작으로 deep learning이 화두가 되며 구글에서 내놓은 tensorflow까지 다양한 회사, 연구원이 오픈소스 트렌드에 맞춰 수많은 머신러닝 라이브러리를 공개하고 있습니다. 이러한 라이브러리들은 기존의 프로그래밍 언어를 이용하여 효율적으로 계산될 수 있도록 개발, 패키징 되어 보다 손쉽게 머신러닝을 체험해볼 수 있습니다. 최근에는 기존 프로그래밍 언어로 개발된 머신러닝 라이브러리를 넘어서 머신러닝 기법에 특화된 확률론적 프로그래밍 언어(Probabilistic Programming)들이 개발되고 있습니다. 이는 기존 하드웨어에서 머신러닝 계산에 적합한 GPU 하드웨어의 폭발적인 인기를 넘어서 인공지능에 최적화된 하드웨어(Google Tensor Processing Unit) 개발 시도가 소프트웨어에서도 일어나고 있다고 생각합니다. 백문이 불여일견이니만큼 엘리스에서 간략한 소개 튜토리얼을 해보실 수 있습니다.구글 Tensor Processing Unit (TPU)확률론적 프로그래밍 언어란?확률론적 프로그래밍 언어는 머신러닝 분야, 확률과 통계 분야, 그리고 프로그래밍 언어 분야, 총 세 분야를 아울러 만들어진 새로운 프로그래밍 언어입니다. 기존의 전산학(Computer Science)은 주어진 변수/파라미터가 있고, 이를 프로그램 및 계산하여 결과 값을 얻습니다. 머신러닝 내에서 주로 쓰이는 방법은 추론인데 이는 관측되는 결과 값 들이 있고, 이를 다양한 수학적 방식으로 추론하여 변수/파라미터값들을 구합니다. 따라서 확률 통계의 수학적 계산법을 직관적으로 프로그래밍 할 수 있기 위해선 기존의 전산학 방식이 아닌 새로운 방식의 프로그래밍 언어가 필요하고, 확률론적 프로그래밍 언어는 이러한 패러다임에 맞춘 시도라고 볼 수 있습니다. 이렇게 개발된 언어는 복잡한 머신러닝 기법도 간략한 코드로 개발할 수 있게 하는 목표를 가지고 있습니다.확률론적 프로그래밍 언어란? (NIPS Tutorial 2015)확률론적 프로그래밍 언어 리스트 (Wikipedia)우리에게 아직은 생소해 보이는 확률론적 프로그래밍 언어는 현재 활발히 연구되고 있으며, 그 종류도 30가지가 넘습니다. 각 확률론적 언어는 기존의 다양한 프로그래밍 언어에서 파생 되었는데요, 엘리스에서 사용하는 주 언어 중 하나인 Python을 기반으로 한 PyMC3을 기반으로 튜토리얼을 만들었습니다.그 외 실제 실험에서 적용된 Picture라는 확률론적 프로그래밍 언어는 2D 얼굴 사진을 토대로 3D 얼굴을 모델하는 프로그램을 단 코드 50줄로 만들어 2015년에 공개되었습니다. 이를 보통 프로그래밍 언어로 개발했다면, 몇 천줄로 개발되어야 했다고 합니다.마치며이번 글에서는 간략하게 확률적인 프로그래밍 언어를 소개했습니다. 아직은 생소할 수 있지만, 점점 다양한 분야에서 머신러닝이 사용 될 수록 이에 적합한 확률론적 프로그래밍 언어의 연구, 개발은 활발해 질 것으로 예상됩니다. 지금 엘리스에 로그인 하셔서 확률론적 프로그래밍 언어 실습 예제를 실행해보세요!엘리스에 올려진 실습문제를 실행하면 책에서만 보던 이런 그래프들이 무슨 의미인지 이해하고 실제로 그려볼 수 있습니다!글쓴이김재원: The Lead, Elice김수인: KAIST 전산학부 박사과정박정국: KAIST 전산학부 박사과정#엘리스 #코딩교육 #교육기업 #기업문화 #조직문화 #서비스소개
조회수 3113

ReactorKit 시작하기

ReactorKit 시작하기오늘은 StyleShare에서 ReactorKit을 사용한지 딱 1년이 되는 날입니다. ReactorKit은 반응형 단방향 앱을 위한 프레임워크로, StyleShare와 Kakao를 비롯한 여러 기업에서 사용하고 있는 기술입니다.StyleShare의 iOS 프로젝트 첫 커밋은 2011년 8월 23일입니다. 그 뒤로 약 7년간 크고 작은 기능을 추가하며 굉장히 큰 코드베이스를 가지게 되었습니다. 특히 2015년에는 스토어 기능을 런칭하면서 기존 서비스 만큼이나 많은 코드를 작성했습니다. 서비스 복잡도는 점점 높아졌고, 지속 가능한 코드베이스를 위해 많은 개선이 필요했습니다.ReactorKit은 많은 부분에 있어서 StyleShare가 가진 고민을 해결해주었습니다. Flux와 Reactive Programming의 개념을 결합하여 만들어진 ReactorKit에서는 사용자 인터랙션과 뷰 상태가 관찰 가능한 스트림을 통해 단방향으로 전달됩니다. 뷰와 비즈니스 로직을 분리할 수 있게 되면서 모듈간 결합도가 낮아지고 테스트하기 쉬워졌습니다. 또한, 자칫 복잡해질 수 있는 비동기 코드를 일관되게 작성할 수 있게 되었습니다.이 글에서는 ReactorKit의 기본 개념과 테스트를 위한 기법을 소개 합니다.데이터 흐름ReactorKit에는 뷰(View)와 리액터(Reactor)라는 개념이 존재합니다. 뷰는 상태를 표현합니다. 뷰 컨트롤러나 셀도 모두 뷰에 해당합니다. 뷰는 사용자 인터랙션을 추상화하여 리액터에 전달하고, 리액터에서 전달받은 상태를 각각의 뷰 컴포넌트에 바인드합니다. 뷰는 비즈니스 로직을 수행하지 않습니다.반대로, 리액터는 뷰의 상태를 관리합니다. 뷰에서 액션을 전달받으면 비즈니스 로직을 수행한 뒤 상태를 변경하여 다시 뷰에 전달합니다. 리액터는 UI 레이어에서 독립적이기 때문에 비교적 테스트하기 쉽습니다.ViewView 프로토콜을 적용하면 뷰를 정의할 수 있습니다. DisposeBag 속성과 bind(reactor:) 메서드를 필수로 정의해야 합니다.import ReactorKit import RxSwift class UserViewController: UIViewController, View { var disposeBag = DisposeBag() func bind(reactor: UserViewReactor) { } }<iframe width="700" height="250" data-src="/media/78a16e327ba4eb073cc5bdbb703c81f9?postId=c7b52fbb131a" data-media-id="78a16e327ba4eb073cc5bdbb703c81f9" data-thumbnail="https://i.embed.ly/1/image?url=https://avatars2.githubusercontent.com/u/931655?s=400&v=4&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://medium.com/media/78a16e327ba4eb073cc5bdbb703c81f9?postId=c7b52fbb131a" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 236.984px;">이 프로토콜을 정의하면 reactor 속성이 자동으로 생성됩니다. 이 속성에 새로운 값이 지정되면 bind(reactor:) 메서드가 자동으로 호출됩니다. 이곳에는 사용자 인터랙션을 리액터에 바인드하거나, 리액터의 상태를 각각의 뷰 컴포넌트에 바인드하는 코드를 작성합니다.func bind(reactor: UserViewReactor) { // Action self.followButton.rx.tap .map { Reactor.Action.follow } .bind(to: reactor.action) .disposed(by: self.disposeBag) // State reactor.state.map { $0.isFollowing } .distinctUntilChanged() .bind(to: self.followButton.rx.isSelected) .disposed(by: self.disposeBag) }<iframe width="700" height="250" data-src="/media/6a6d5aa66b156cae7d4475f6ed13efb0?postId=c7b52fbb131a" data-media-id="6a6d5aa66b156cae7d4475f6ed13efb0" data-thumbnail="https://i.embed.ly/1/image?url=https://avatars2.githubusercontent.com/u/931655?s=400&v=4&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://medium.com/media/6a6d5aa66b156cae7d4475f6ed13efb0?postId=c7b52fbb131a" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 325px;">Reactor리액터를 정의하기 위해서는 Reactor 프로토콜을 사용합니다. 사용자 인터랙션을 표현하는 Action과 뷰의 상태를 표현하는 State, 그리고 상태를 변경하는 가장 작은 단위인 Mutation을 클래스 내부에 필수로 정의해야 합니다. 또한 가장 첫 상태를 나타내는 initialState가 필요합니다.import ReactorKit import RxSwift final class UserViewReactor: Reactor { enum Action { case follow } enum Mutation { case setFollowing(Bool) } enum State { var isFollowing: Bool } let initialState: State = State(isFollowing: false) }<iframe width="700" height="250" data-src="/media/572f53fb442c67060d2a69f90a42a07b?postId=c7b52fbb131a" data-media-id="572f53fb442c67060d2a69f90a42a07b" data-thumbnail="https://i.embed.ly/1/image?url=https://avatars2.githubusercontent.com/u/931655?s=400&v=4&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://medium.com/media/572f53fb442c67060d2a69f90a42a07b?postId=c7b52fbb131a" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 435px;">Action이나 State와 달리 Mutation은 리액터 클래스 밖으로 노출되지 않습니다. 대신, 클래스 내부에서 Action과 State를 연결하는 역할을 수행합니다. Action이 리액터에 전달되면 두 단계를 거쳐서 뷰의 상태를 변경합니다.mutate() 함수에서는 Action 스트림을 Mutation 스트림으로 변환하는 역할을 합니다. 이곳에서 네트워킹이나 비동기로직 등의 사이드 이펙트를 처리합니다. 그 결과로 Mutation을 방출하면 그 값이 reduce() 함수로 전달됩니다. reduce() 함수는 이전 상태와 Mutation을 받아서 다음 상태를 반환합니다.func mutate(action: Action) -> Observable { switch action { case .follow: return UserService.follow() .map { Mutation.setFollowing(true) } .catchErrorJustReturn(Mutation.setFollowing(false)) case .unfollow: return UserService.unfollow() .map { Mutation.setFollowing(false) } .catchErrorJustReturn(Mutation.setFollowing(true)) } } func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { case let setFollowing(isFollowing): newState.isFollowing = isFollowing } return newState }<iframe width="700" height="250" data-src="/media/dc8fbdce8314a7eba99be944241c5432?postId=c7b52fbb131a" data-media-id="dc8fbdce8314a7eba99be944241c5432" data-thumbnail="https://i.embed.ly/1/image?url=https://avatars2.githubusercontent.com/u/931655?s=400&v=4&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://medium.com/media/dc8fbdce8314a7eba99be944241c5432?postId=c7b52fbb131a" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 522.984px;">테스팅테스트를 위해 가장 먼저 고민하게 되는 것은 ‘무엇을 테스트할 것인가’에 대한 것입니다. ReactorKit을 사용하면 뷰와 로직이 분리되어 상대적으로 쉽게 해답을 얻을 수 있습니다.View사용자 인터랙션이 발생했을 때 Action이 리액터로 잘 전달되는지리액터의 상태가 바뀌었을 때 뷰의 컴포넌트 속성이 잘 변경되는지ReactorAction을 받았을 때 원하는 State로 잘 변경되는지뷰 테스팅리액터의 stub 기능을 이용하면 뷰를 쉽게 테스트할 수 있습니다. stub 기능을 활성화하면 리액터가 받은 Action을 모두 기록하고, mutate()와 reduce()를 실행하는 대신 외부에서 상태를 설정할 수 있게 됩니다.func testAction_refresh() { // 1. Stub 리액터를 준비합니다. let reactor = MyReactor() reactor.stub.isEnabled = true // 2. Stub된 리액터를 주입한 뷰를 준비합니다. let view = MyView() view.reactor = reactor // 3. 사용자 인터랙션을 발생시킵니다. view.refreshControl.sendActions(for: .valueChanged) // 4. Reactor에 액션이 잘 전달되었는지를 검증합니다. XCTAssertEqual(reactor.stub.actions.last, .refresh) } func testState_isLoading() { // 1. Stub 리액터를 준비합니다. let reactor = MyReactor() reactor.stub.isEnabled = true // 2. Stub된 리액터를 주입한 뷰를 준비합니다. let view = MyView() view.reactor = reactor // 3. 리액터의 상태를 임의로 설정합니다. reactor.stub.state.value = MyReactor.State(isLoading: true) // 4. 그 때 뷰 컴포넌트의 속성이 잘 변하는지를 검증합니다. XCTAssertEqual(view.activityIndicator.isAnimating, true) }<iframe width="700" height="250" data-src="/media/9e5e0349766c69076a5081cbd680645b?postId=c7b52fbb131a" data-media-id="9e5e0349766c69076a5081cbd680645b" data-thumbnail="https://i.embed.ly/1/image?url=https://avatars2.githubusercontent.com/u/931655?s=400&v=4&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://medium.com/media/9e5e0349766c69076a5081cbd680645b?postId=c7b52fbb131a" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 721px;">리액터 테스팅리액터는 뷰에 비해서 상대적으로 테스트하기 쉽습니다. Action이 전달되었을 때 비즈니스 로직을 수행하여 State가 바뀌는지를 확인하면 됩니다.func testBookmark() { // 1. 리액터를 준비합니다. let reactor = MyReactor() // 2. 리액터에 액션을 전달합니다. reactor.action.onNext(.toggleBookmarked) // 3. 리액터의 상태가 변경되는지를 검증합니다. XCTAssertEqual(reactor.currentState.isBookmarked, true) } func testUnbookmark() { // 1. 리액터를 준비합니다. 액션을 미리 한 번 전달해서 테스트 환경을 만들어둡니다. let reactor = MyReactor() reactor.action.onNext(.toggleBookmarked) // 2. 리액터에 액션을 한 번 더 전달합니다. reactor.action.onNext(.toggleBookmarked) // 3. 리액터의 상태가 변경되는지를 검증합니다. XCTAssertEqual(reactor.currentState.isBookmarked, false) }<iframe width="700" height="250" data-src="/media/32af3eac8c1c9646bf95ea1442ad8ff4?postId=c7b52fbb131a" data-media-id="32af3eac8c1c9646bf95ea1442ad8ff4" data-thumbnail="https://i.embed.ly/1/image?url=https://avatars2.githubusercontent.com/u/931655?s=400&v=4&key=a19fcc184b9711e1b4764040d3dc5c07" class="progressiveMedia-iframe js-progressiveMedia-iframe" allowfullscreen="" frameborder="0" src="https://medium.com/media/32af3eac8c1c9646bf95ea1442ad8ff4?postId=c7b52fbb131a" style="display: block; position: absolute; margin: auto; max-width: 100%; box-sizing: border-box; transform: translateZ(0px); top: 0px; left: 0px; width: 700px; height: 522.984px;">마치며ReactorKit은 지금까지 CocoaPods에서 약 3만 7천회 다운로드 되었고, 약 730개 앱에서 사용되고 있습니다. 최근에는 Wantedly에서 사용하며 일본에서도 많은 호응을 얻고 있습니다. 공개된지 1년밖에 되지 않았지만 굉장히 좋은 평을 받으며 성장하고 있는 프레임워크입니다. 만약 새로운 프로젝트를 시작하거나, StyleShare와 비슷한 고민을 하고 계신다면 ReactorKit을 강력하게 추천합니다.ReactorKit GitHublet’Swift 2017 ReactorKit 발표 영상let’Swift 2017 ReactorKit 발표 자료#스타일쉐어 #개발팀 #개발자 #경험공유 #인사이트
조회수 328

컴공생의 AI 스쿨 필기 노트 ⑤ 베이즈 결정이론

이번 5회차 수업에서는 베이즈 결정이론(Bayes Decision Theory)과 가우시안 혼합모형(Gaussian Mixture model)에 대해 배웠어요.1980년대 이후 세계 금융시장에서 위험관리를 계량화한 것은 확률이론, 그중에서도 ‘베이즈 정리’가 있었기에 가능했어요. 이전의 경험과 현재의 증거를 토대로 사건의 확률을 추론하는 알고리즘 덕분에 온갖 파생상품이 탄생했어요. 그런데 베이즈 정리는 오랫동안 금기시됐는데요. 주관적인 믿음을 측정하기 때문에 합리적이지 않다는 이유에서였다고 해요. 하지만 베이즈 정리의 활용도는 갈수록 커지고 있어요. 암호 해독부터 전쟁 중 의사결정, 실종된 사람이나 선박의 위치 추정, 암 발병률 예측, 스팸메일 걸러내기 등 무한대에 가깝다고 해요. 이번  필기노트에서는 베이즈 결정이론에 대해 알아볼게요.Bayes Decision Theory베이즈 결정이론은 패턴 인식을 위한 통계적 접근 방법이에요. 베이즈가 제시한 통계적 방법을 통해 의사 결정을 하는 방법이죠. 전통적 통계 방식은 통계적 추리를 할 때 표집으로 얻은 정보만 사용해요. 베이지안 확률이 전통적 통계 방식과 다른 점은 학습자가 기존에 가지고 있는 사전 정보를 활용한다는 것인데요. 불확실한 상황에서 통계적으로 얻은 정보를 가지고 의사 결정을 해야 하는 경제학, 경영학 등 여러 분야에서 많이 사용되고 있어요.베이즈 결정이론에 사용되는 베이즈 정리(Bayes rule)에 대해 간단한 예시를 들어볼게요.우리가 은행 지점장이라고 가정해봐요. 고객에게 돈을 빌려줄 수는 있지만 아무에게나 막 빌려줄 수는 없겠죠?그래서 은행 고객을 high-risk, 즉 돈을 빌려줘도 안 갚을 확률이 높은 고객과 low-risk, 즉 돈을 빌려주면 갚을 확률이 높은 고객으로 나눌 거예요.그런데 은행 고객이 돈을 갚을지 안 갚을지를 판단하는 기준이 있어야겠죠? 그래서 고객의 연봉(yearly income)과 현재 은행 계좌 보유금액(savings)을 가지고 판단할 거예요. 이렇듯 변수가 두 개만 있을 때 우리는 이항분포를 사용해서 의사를 결정해요. 위에서는 두 가지 고객이 존재하므로 이항분포를 사용해서 고객에게 돈을 빌려줄지 여부를 결정하죠. 결정을 내릴 때는 확률이 큰 쪽을 선택할 거예요. 확률이 큰 쪽을 선택하는 것은 이성적인 판단이기 때문이에요. 그래서 고객 x가 high risk일 확률(P(C=1|x)이 x가 low-risk일 확률(P(C=0|x)보다 크다면 1이라는 결정을 내리고, 작다면 0이라는 결정을 내려요.하지만 우리가 내리는 결정에도 error(=risk)가 존재하겠죠?확률의 합은 항상 1이고 결정은 항상 P(C=1|x)나 P(C=0|x) 중 확률이 큰 쪽이기 때문에 1에서 그 확률을 빼면 그 결정의 error가 돼요. 베이즈 결정이론은 이처럼 분류하고자 하는 물체들에 대해서 사전 정보가 주어지는 경우에 사용이 될 수 있는 이론이에요.Bayes’ rule베이즈 결정이론에는 베이즈 정리(Bayes’ rule)가 사용되는데요. 자세히 살펴볼게요.- P(C) : prior probability(선행 확률, 특정 사건이 일어날 것에 대한 추가 정보를 획득하지 못한 확률)로 여기서는 x가 어떤 값을 가지든 C가 1일 확률을 말해요.- p(x|C) : likelihood(우도, C가 주어졌을 때 조건부 확률) C가 주어졌을 때 x를 가지고 있을  확률을 말해요. 따라서 x값에 따라 확률이 달라져요. 예를 들어 p(x|C = 1) 은 C가 1인 즉 high risk인 고객이 x를 가지고 있을 확률을 나타내요.- p(x) : evidence(증거)는 C와 상관없이  x가 나타날 확률이에요.- p(C|x) : posterior probability(사후 확률)로 우리는 사후 확률을 기반으로 아래와 같이 decision을 내려요.위의 예시처럼 두 가지 고객만 있는 상황(이항분포)이 아니라 K명의 고객이 있는 경우(다항분포)는 어떻게 계산할까요? 이 경우에도 베이즈 정리가 적용되는데 식이 조금 달라져요.p(x) 구하는 식만 달라지고 나머지는 위에서 봤던 예시와 같아요. 그리고 이항분포의 error는 1에서 둘 중에 큰 확률을 뺐듯이 다항분포의 error도 아래와 같이 구해요.Loss and Risk위의 이항분포에서는 고객에게 돈을 빌려줌으로써 돈을 못 받는 손실(Loss)이 존재하고 돈을 못 받을 것 같은 고객에게 빌려주지 않음으로써 생기는 손실이 존재해요. 이 중 어떤 것이 더 손실이 적을지 생각해봐야겠죠?의사 결정을 하는 행동(action)을 αi라고 했을 때 αi에 대한 손실을 λik라고 정의할게요.위의 식은 예상되는 손실값이에요. 이 손실값은 실제로는 k인 상황이지만 행동 αi를 취해서 생기는 손실이에요.손실을 줄여야 하기 때문에 가장 작은 손실이 생기는 행동을 취해야 해요. 따라서 위의 식을 보면 argmin함수를 이용해서 k개의 행동 중 가장 작은 손실을 취해요.Reject 의사 결정이 어려운 경우에는 의사 결정을 피하는 것이 더 적절한 경우도 있어요. 이때는 어떠한 행동도 하지 않는 행동 αK+1을 추가해요.action αK+1을 추가하면 αK+1에 따른 손실 λik 또한 하나가 더 늘어요.위의 수식은 reject 행동을 포함했을 때 결정을 내리는 식인데 간단하게 참고하시면 될 것 같아요.이번에는 베이즈 결정이론에 대해 자세하게 다뤘는데요. 이번 수업은 교수님께서 많은 것을 가르쳐주셔서 저 같은 초보자가 듣기에 조금 힘든 점이 있었어요. 벌써 8주차 이론수업의 절반 이상이 지났는데요. 5주 동안 배운 많은 이론들을 코드로 능숙하게 표현하는 데에는 많은 노력이 필요하겠지만, 이만큼 왔다는 것만으로도 뿌듯한 기분이 들어요. 8주차부터 시작하게 될 팀 프로젝트에서 실력 발휘를 하기 위해서 더 열심히 수업에 임해야겠어요!* 이 글은 AI스쿨 - 인공지능 R&D 실무자 양성과정 5주차 수업에 대하여 수강생 최유진님이 작성하신 수업 후기입니다.
조회수 5240

AWS Instance Scheduler Bot 적용기

이 포스팅은 총 2부로 이어지며 현재는 2부입니다.1부 : AWS 비용 얼마까지 줄여봤니?2부 : AWS Instance Scheduler Bot 적용기1부에서 AWS 비용을 절감하기 위한 Instance Scheduler에 대한 소개를 하였습니다. 2부에서는 Instance Scheduler의 설정을 손쉽게 변경하기 위한 Bot을 적용한 사례에 대해서 소개합니다.Bot의 필요성Instance Scheduler의 설정을 변경하기 위해서는 정보를 담고 있는 Dynamo DB 의 데이터를 변경해야 합니다. AWS Console을 이용하여 직접 수정할 수도 있지만 여전히 불편하고 느립니다. 더군다나 이를 이용하는 사용자가 DB Table의 구조와 AWS Console 사용법을 알고 있어야 하고 비 개발자라면 더 쉽지 않은 문제입니다. 하지만 Bot을 이용하면 사용자는 어려운 DB Query나 구조를 알아야 할 필요도 없고 손쉽게 채팅 메시지를 통해 Bot에게 질의하고 처리 결과를 응답받을 수 있습니다.Outgoing WebhookJANDI에서는 Incoming Webhook과 반대되는 개념으로 Outgoing Webhook을 제공합니다. 특정 키워드로 시작하는 메시지가 있을 경우 내용을 설정된 URL Endpoint에 POST로 Webhook을 보내줍니다. Webhook을 수신한 곳에서는 일련의 처리 후 메시지 데이터 형식을 맞춰 응답하게 되면 채팅창에 메시지를 표시하게 됩니다. 이를 통해 다른 외부 시스템과 연동할 수 있습니다.POST Data예를 들어 날씨 키워드로 Outgoing Webhook을 생성했다면 /날씨 메시지가 시작될 때 다음과 같은 데이터가 Webhook으로 발송됩니다.{ "token": "YE1ronbbuoZkq7h3J5KMI4Tn", "teamName": "Toss Lab, Inc.", "roomName": "토스랩 코리아", "writerName": "Gloria", "text": "/날씨 서울", "keyword": "날씨", "createdAt": "2017-07-19T14:49:11.266Z" } token을 이용하여 요청의 유효성 체크를 할 수 있고 text를 적절히 파싱 하여 요청에 부합하는 처리를 할 수 있습니다.ResponsePOST Data를 적절히 처리 후 결과를 채팅창에 응답 메시지를 표시하고 싶다면 아래와 같은 JSON Data를 Response body에 넣어주면 됩니다.{ "body" : "서울의 현재 날씨", "connectColor" : "#FAC11B", "connectInfo" : [{ "title" : "온도", "description" : "최고:28.00, 최저:24.00, 현재: 24.30" }, { "title": "날씨", "description": "흐리고 비" }] } 이를 이용하여 Instance Scheduler에도 적용해봤습니다.Schedule BotSchedule Bot은 Instance Scheduler의 Lambda 함수에 함께 포함되어 작동하며 스케쥴 조회 / 예외 설정, 서버 강제 시작/중지, 서버 상태 조회 등의 기능을 수행합니다.API Gateway와 Lambda 함수를 연결하여 Endpoint URL을 생성하고 Outgoing Webhook URL로 설정하여 Webhook으로 Lambda 함수가 실행될 수 있도록 하였습니다. Lambda 함수는 Cloudwatch를 통해서 실행되면 Scheduler가 작동되고 API Gateway를 통해 실행되면 Schedule Bot이 작동됩니다.Schedule Bot 명령어Schedule Bot은 다음과 같은 명령어를 수행합니다./서버 help : 도움말 /서버 [스케쥴명] status : 현재 서버 상태 조회 /서버 [스케쥴명] info : 오늘의 스케쥴 조회 /서버 [스케쥴명] info [YYYY-MM-DD] : 특정일 스케쥴 조회 /서버 [스케쥴명] exception info : 오늘의 스케쥴 예외 조회 /서버 [스케쥴명] exception info [YYYY-MM-DD] : 특정일 스케쥴 예외 조회 /서버 [스케쥴명] exception set [YYYY-MM-DD] [start|stop] [h:m] : 예외 설정 /서버 [스케쥴명] exception del [YYYY-MM-DD] [start|stop] : 예외 삭제 /서버 [스케쥴명] force_start : 서버 강제 실행 /서버 [스케쥴명] force_stop : 서버 강제 중지 Schedule Bot 작동 화면Schedule Bot은 서버병이라는 컨셉으로 인격화(?)에 힘썼습니다.스케쥴 정보 조회서버 상태 조회서버 강제 시작/중지명령어 오류마무리AWS 기반의 서비스를 운영하는 스타트업이라면 더욱더 현실적으로 부딪히는 비용 문제에 대해서 저희가 고민한 내용과 솔루션에 대해서 공유하였습니다.아직 적용기간이 길지 않아 절감비용에 대해 수치적인 데이터를 언급하기는 힘들지만 많은 금액이 절감될 거라 예상하고 있습니다.저희와 같은 고민을 하고 있다면 Instance Scheduler를 적극 권장합니다.#토스랩 #잔디 #JANDI #개발 #개발자 #AWS #도입후기 #일지 #인사이트 #경험공유
조회수 1217

[인터뷰] Humans of MEME, 그 마지막 주인공을 만나다. - 긍정의 힘을 지닌 듀크의 이야기

여러분 안녕하세요.미미박서의 평범하지만 특별한 이야기를 담아왔던 모뜨입니당!오홍 벌써 프로젝트의 마지막 이야기가 다가왔네요.Humans of MEME 의 마지막 주인공은바로 Global SCM 팀의 듀크입니다 !듀크의 솔직하고 담백한 이야기를들어보실까요 ?Q. 듀크가 담당하시는 업무인 SAP는 사내에서도 어렵다고 소문이 났는데요(쥬륵). SAP를 간략하게 소개해주신다면, 무엇인가요?A. 미미박스라는 회사가 원활하게 운영될 수 있도록 도와주는 시스템이 ERP(Enterprise Resource Planning : 전사적 자원 관리)이고 그 ERP 안에 여러가지 툴 중의 하나가 SAP이에요. 또 SAP에는 많은 프로그램들이 있는데, 그 프로그램을 개발하는 것이 abap 개발을 담당하고 있어요. 저는 컴퓨터를 전공하여 대학교 때부터 계속 컴퓨터만 해왔어요. SAP는 거의 대학교 과정에 없는 내용이라, 우연찮게 첫 직장에 들어가면서 처음 접했어요. 실무를 접하게 되면서 여러가지 상황에 대응하는 능력을 배우면서 적성에도 맞고 차차 젖어든 것 같아요. 전공에 따라 직업이 선택되기도 하지만 둘 사이의 직접적인 관련보다는 직업을 선택하는 것에 있어서 여러가지 경험 중의 한 단계인 것 같아요. 저도 컴퓨터가 전공이었지만 기획하고 여러가지 활동적인 일들도 하고 싶어서 찾아보기도 했었어요. 2가지 사이의 직접적인 연관은 없지만, 전공은 직업을 선택하는 데에 있어서 토대를 마련해주는 경험의 일종이라고 생각해요.  Q. 미미박스를 어떻게 만나게 되셨나요?A. 이전 직장 동료의 추천으로 미미박스에 합류하게 되었어요. 이전 직장의 동료들이 현재 미미박스의 동료들이기도 합니다(웃음). 저는 물론 하고 있는 업무도 중요하지만 동료와의 관계가 회사 생활의 50%를 차지한다고 생각해요. 동료와의 관계가 좋아야지 같이 시너지 효과를 내면서 분명히 업무 또한 잘 할 수 있는 것 같아요. 일도 마음도 잘 맞는 동료들과 함께 일을 하다보면 즐거운 일도 같이 공유하고 속상한 일이 있어도 서로 그때그때 풀 수 있어요. Q. 삶에서 도전적인 경험을 하신 적이 있으세요?A. 저는 늘 여린 외모때문에 주변 분들에게 약해보인다, 여려보인다 등 이런 얘기를 들은 적이 많아요. 그래서 그런지 몰라도 자꾸 무모한 도전을 해보려고 했던 과거 시절이 있었어요. 그 중의 하나로 대학교를 휴학한 후 자전거로 전국 일주를 다녀왔어요. 남들이 해보지 않은 경험을 해보고 싶었고 스스로 강해지고 싶다는 욕구도 있었어요. 저를 포함해서 친구들 3명과 같이 일주를 했어요. 저는 3이라는 숫자를 좋아해요. 2명이라면 싸울 수도 있는데 3명이라면 싸워도 2:1 이 되기 때문에 늘 그 자리에서 결론이 나거든요(웃음).서울에서 출발해서 미시령을 넘고, 강원도에서 부산으로 내려와, 부산에서 배를 타고 제주도를 갔어요. 제주도 한바퀴를 돌고 다시 배를 타고 목포에 도착했어요. 그렇게 목포에서 서울로 다시 올라왔습니다. 그렇게 총 한달 정도 걸렸어요.자전거로 한달 동안 전국을 돌면서 많은 사람들도 만났고 위험한 일도 많이 겪었어요. 무모하게 시작했던 것이지만 지금 돌이켜보면 가장 기억에 남고 제 자신의 한계를 시험해볼 수 있었던 것 같아요.자전거 전국일주를 하던 2002년의 듀크(좌)! WOWOWQ. 요즘 느끼시는 소소한 행복이 있으신가요?A. 최근에 아내가 아이를 출산했어요. 태어난지 현재 4개월 째가 되었는데 아이를 보는 낙에 살아가고 있어요. 제가 눈썹만 움직여도 아이는 꺄르르 웃으며 자지러지는데, 아이가 웃으며 결국 저도 웃거든요!저는 예전에는 운동하는 것이 특기이자 취미였어요. 이전에는 다른 즐거움이 분명히 있었는데 세월이 흐르다 보면서 또다른 즐거움을 맞이하고 있어요. 아내와 아이를 보면서 살아가는 데서 행복을 느끼고 에너지를 받는 것 같아요. Q. 듀크는 스스로 어떤 사람이고 싶으세요?A. 저는 늘 마음에 품고 있는 말이 있어요. 바로 ‘긍정의 힘’ 이라는 말이에요. 상황을 부정하고 의심하기보다 어려운 상황 속에서도 긍정적인 요소를 찾아낼 수 있어야 해요.먼저 긍정적인 마인드는 스스로를 변화시킬 수 있어요. 또한 저의 긍정적인 마인드를 통해 주변 사람들 또한 변화시킬 수 있는 것 같아요. 제가 긍정적인 에너지를 줌으로써 옆에 계신분들에게도 웃음을 전달할 수 있고 기쁜 순간들을 같이 할 수 있을 때 뿌듯해요. 앞으로도 저는 스스로에게도 긍정적으로, 주변 사람들에게도 긍정의 힘을 전파할 수 있는 사람이고 싶어요.듀크가 말한 긍정적인 마인드가 자신을 변화시키고나아가 주변 사람들도 변화시킬 수 있다는 힘과짧은 시간이나마 인터뷰를 진행하며 듀크의 긍정적인 기운을 느낄 수 있었어요 :)매일 행복할 수는 없지만행복한 일은 매일 있다는 말이 있듯이 여러분도 긍정의 힘을 믿어보시는 것은 어떠세요 !?이렇게 7번째 주인공 듀크를 마지막으로Humans of MEME 프로젝트가 끝나게 되었습니다.실화인가요?실화입니다.흫 여러분들은 이야기를 보며 어떠셨나요?저 모뜨는 인터뷰를 통해개인적으로나 회사의 속한 구성원으로서나새로운 자극을 받기도 하고 많이 성장할 수 있었던 시간이였습니다!판교 미미박스 본사 10층 플레이미미Humans of MEME 프로젝트는블로그에 올라오는 이야기 뿐만 아니라 미미박스 사내의 카페테리아에 매주마다 주인공들의 포스터가 붙여졌었답니다! (매주 포스터 구경하는 재미가 쏠쏠했다구여)Humans of MEME 는미미박서분들이 가장 많이 찾는 공간인 10층 플레이미미에서서로서로를 알아갈 수 있었던좋은 커뮤니케이션의 채널로서도 자리잡았었는데요!아쉽게도 프로젝트가 끝이 나게 되지만,미미박서 FOREVER 얍얍얍 미미박스 FOREVER 얍얍얍앞으로도 더 멋진 미미박서와 미미박스의 이야기로꾸준히 찾아오도록 하겠습니다 !안녕히계세요 !
조회수 1039

애플리케이션 개발부터 배포까지, AWS CodeStar

OverviewAWS CodeStar를 이용하면 애플리케이션의 개발-빌드-배포까지 빠르게 진행할 수 있습니다. CodeStar는 몇 가지 장점을 가지고 있는데요. 오늘은 간단한 Python App Service Tutorial을 통해 CodeStar를 사용하는 방법을 알아보겠습니다. CodeStar의 장점통합된 UI로 한 번에 여러 활동 관리 가능Continuous Delivery 도구 체인을 구성해 신속한 코드 배포 가능소유자, 기여자 및 최종 사용자 추가로 안전한 협업 가능Dashboard를 사용해 전체 개발 프로세스의 진행 상황 추적 가능CodeStar 사용하기1-1. 처음 CodeStar를 실행하면 나오는 화면입니다. ‘Start a Project’를 누르면 프로젝트 템플릿을 선택할 수 있습니다. 1-2. 이것은 아직 지원되지 않는 지역(Region)에서 노출되는 화면입니다. 2-1. ‘Start a Project’를 클릭하면 프로젝트 템플릿을 선택할 수 있습니다. 2-2. Python과 AWS Lambda를 이용해 Web service를 구현해보겠습니다. 3. Project Name을 지정하고 repository를 선택합니다. 여기서는 AWS CodeCommit으로 선택하여 진행해보겠습니다. CodeCommit의 경우 Repository name을 따로 지정할 수도 있습니다. Repository name까지 지정했다면 Next를 클릭합니다. 4. 아래의 화면은 프로젝트의 흐름입니다. CodeCommit에 소스가 저장되고 AWS CodeBuild를 통해서 Build와 Test가 진행됩니다. 그리고 AWS CloudFormation을 통해서 Deploy가 진행되며 Monitoring은 Amazon Cloud Watch를 통해 진행합니다. CodeStar의 경우 IAM 사용자에 AWSCodeStarFullAccess 관리형 정책을 적용합니다.1) 5. Create Project를 클릭하면 프로젝트가 생성되고, CodeStar 유저 설정을 할 수 있습니다. 6-1. 이제 editor를 선택해봅시다. Command line tools, Eclipse, Visual Studio 등을 고를 수 있습니다. 툴은 언제든지 바꿀 수 있으니 여기서는 Eclipse를 이용하여 프로젝트를 진행하겠습니다. 6-2. See Instructions를 클릭하면 Eclipse를 다운로드 받아 설정하는 방법을 볼 수 있습니다. 6-3. 이제 Eclipse를 설치하고 AWS Toolkit for Eclipse를 설치해보겠습니다. Eclipse의 종류는 Eclipse IDE for java EE Developers 에디션을 설치하겠습니다. 다른 버전은 AWS Toolkit 설치할 때 의존성 문제가 발생할 수 있습니다. 7. Eclipse를 설치하고 Eclipse Marketplace에서 AWS Toolkit for Eclipse 2.0를 설치합니다. 8-1. import를 클릭하고 8-2. AWS -> AWS CodeStar Project를 선택합니다. 8-3. 지역(Region)을 선택하면 해당 지역의 CodeStar 프로젝트를 import 할 수 있습니다. 이 때 CodeCommit의 HTTPS Git credentials를 입력해야 합니다. 9. IAM -> Users -> 사용 계정을 선택해 HTTPS Git credentials for AWS CodeCommit에 가면 User Name과 Password를 Generate 할 수 있습니다. (아래 이미지에 민감한 정보는 삭제했습니다.) 10. CodeStar에서 Project를 Eclipse에 import한 모습입니다. buildspec.yml, index.py, README.md, template.yml이 clone 된 것을 확인할 수 있습니다. 11. 브라우저의 Eclipse 설치 설명 화면에서 back을 클릭해 에디터 선택 화면으로 돌아갑니다. 12. 도쿄 지역에 아직 출시되지 않은 Cloud9은 선택을 마치면 자동으로 셋업이 완료됩니다. 그러나 Eclipse는 Skip을 클릭해야 CodeStar Dashboard로 이동할 수 있습니다. 13. CodeStar Dashboard에 진입하였습니다. IDE는 이미 설정이 끝났으므로 I have already done this를 선택합니다. 화면 하단에 파란색 직육면체가 계속 그려지면 deploy가 완료된 상태가 아니므로 조금 기다렸다가 refresh를 해줍니다. 14-1. deploy가 완료되면 위와 같이 Team wiki tile, Application endpoints, Commit history, Continuous deployment, Application activity등이 나타납니다. 14-2. JIRA를 연동해서 사용할 수도 있는데, 그 내용은 다음에 다루겠습니다. ???? 15. 우선 첫 deploy가 완료된 것을 자축하며 Application endpoints를 클릭합니다. 개발자들에게 굉장히 익숙한 “Hello World”가 나옵니다! 간편하게 소스를 deploy 하여 AWS Api-Gateway와 연결했습니다. 이제 각 파일의 용도에 대한 설명과 새로운 method를 추가하는 작업을 진행해보겠습니다. 16. 이미지처럼 sample.py 파일을 추가하고 아래 코드를 추가합니다. import json import datetime def handler(event, context):     data = {         'output': 'Sample! pathParameters test = ' + event["pathParameters"]["test"]     }     return {'statusCode': 200,             'body': json.dumps(data),             'headers': {'Content-Type': 'application/json'}} 17. 그리고 template.yml에는 아래 내용을 추가합니다. — template.yml —  Sample:     Type: AWS::Serverless::Function     Properties:       Handler: sample.handler       Runtime: python3.6       Role:         Fn::ImportValue:           !Join ['-', [!Ref 'ProjectId', !Ref 'AWS::Region', 'LambdaTrustRole']]       Events:         GetEvent:           Type: Api           Properties:             Path: /sample/{test}             Method: get — 18-1. 이제 수정한 내용을 CodeStar에 반영해보겠습니다. 프로젝트에서 오른쪽 클릭을 해 Team -> Commit을 선택하고 Commit합니다. 18-2. 수정한 파일을 Commit하고 Push합니다. 18-3. Dashboard를 보면 Commit history에 Commit 내용이 반영되었습니다. 19-1. Dashboard에 Continuous deployment를 보면 Source -> Build -> Deploy를 통해서 수정한 내용이 반영되는 것을 실시간으로 확인할 수 있습니다. 이 작업은 생각보다 시간이 많이 소요됩니다. Deploy까지 Succeeded로 완료가 되면 새로 만들어진 URL을 클릭합니다. 19-2. 아래와 같이 pathParameters가 정상적으로 출력되는 것을 확인할 수 있습니다. 20. 이어서 새로 만든 API에 단위테스트를 추가해보겠습니다. sample_test.py라는 파일을 만들고 아래 코드를 추가합니다. — sample_test.py — from sample import handler   def test_sample_handler():         event = {         'pathParameters': {             'test': 'testMessage'         }     }         context = {}         expected = {         'body' : '{"output": "Sample! pathParameters test = testMessage"}'         ,'headers': {             'Content-Type': 'application/json'         },         'statusCode': 200     }         assert handler(event, context) == expected  — 21. 그리고 buildspec.yml 파일을 아래와 같이 수정합니다. — buildspec.yml —  version: 0.2 phases:    install:     commands:       - pip install pytest    pre_build:     commands:       - pytest    build:     commands:       - pip install --upgrade awscli       - aws cloudformation package --template template.yml --s3-bucket $S3_BUCKET --output-template template-export.yml artifacts:   type: zip   files:     - template-export.yml  — 22-1. Commit을 진행합니다. 그리고 다시 Source -> Build -> Deploy 를 거쳐서 Succeeded가 되면 Build 부분의 CodeBuild로 들어가서 Build 결과를 확인합니다. 22-2. 맨 마지막에 Build 결과를 클릭하면 Build 상세 내역을 확인하실 수 있습니다. 22-3. Build logs부분을 보면 sample_test.py를 이용한 단위테스트가 정상적으로 진행된 것을 확인할 수 있습니다. Conclusion지금까지 CodeStar를 이용한 간단한 튜토리얼을 진행했습니다. 다음 화에서는 다양한 방법으로 CodeStar를 활용할 수 있는 방법을 소개하겠습니다. CodeStar에 대한 자세한 내용은 여기를 참조하세요. 참고 1) AWS CodeStar 설정글윤석호 이사 | 브랜디 [email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유 #CTO

기업문화 엿볼 때, 더팀스

로그인

/