스토리 홈

인터뷰

피드

뉴스

조회수 1262

EOS Token 생성과 발행, 전송

이번시간에는 배포한 Contract를 통해 Token 발행과 전송을 해보겠습니다. 이를 위한 준비는 아래 2미디엄 글을 참조해주세요EOS Smart Contract 를 위한 준비EOS Smart Contract 배포먼저 저번 시간에 배포한 token 발행 abi 를 확인해 보겠습니다.$ cleos get abi hexlanthenryget abiabi를 확인하다보면 actions 라는 항목에 총 3개의 action이 있음을 확인할 수 있습니다. 이 3개의 name이 실행할 수 있는 action입니다. token발행은 create action을 통해 진행할 수 있습니다.Token 생성$ cleos push action hexlanthenry create '["hexlanthenry", "10000000000.0000 HEX"]' -p hexlanthenrycreate action 실행 결과create action 을 통해 ‘HEX’ 토큰을 100억개 생성했습니다. create 라는 action의 인자는 account_name(hexlanthenry), maximum_supply(10000000000.0000 HEX) 입니다. 즉 첫번째 인자는 토큰의 발행자를 나타내며, 두번째 인자는 토큰의 최대 수량을 나타냅니다.이 인자가 어떻게 들어가는지는 abi 의 struct 를 확인하면 알 수 있습니다.abi의 create structparameter 1 : account_name type— issuerparameter 2 : asset type — maximum_supply+ 저번 강의에서 공지한데로 다음 포스팅에서는 abi가 무엇을 뜻하는지, 이를 통해 어떻게 action을 실행할 수 있는지 알아보도록 하겠습니다.Token 발행생성과 발행 이 2개의 개념이 헷갈릴 수 있습니다. create action을 통한 생성은 최대 발행량을 결정 하는 것이며, issue action 은 토큰을 유통 시키는 것입니다.create : token 생성과 동시에 최대 발행량 결정issue : token 의 유통따라서 issue action을 통해 이전에 생성한 HEX token을 발행해보겠습니다.$ cleos push action hexlanthenry issue '["hexlanthenry", "10000.0000 HEX", "initial issue"]' -p hexlanthenryissue contract 실행 결과issue action 역시 data로 어떤 인자가 들어가는지는 abi를 통해 확인 가능합니다.abi의 issue structparameter 1 : account_name type — toparameter 2 : asset type — quantityparameter 3 : string type — memomemo 는 transfer 가 어떤 목적인지에 대해 설명해주는 인자 입니다. 생략해도 되는 값으로, 원하시면 parameter 개수를 유지하는 선에서 empty string을 넣으시면 됩니다. memo를 어떻게 쓰면 유용한지에 대해서도 다른 포스팅에 담도록 하겠습니다.issue가 잘 실행 되었는지 확인해 보겠습니다.$ cleos get currency balance hexlanthenry hexlanthenry저는 issue 를 4번 수행한 후 balance 를 체크 했기 때문에 총 40000개의 HEX token이 존재하는 것을 확인 할 수 있습니다.hexlanthenry 의 HEX token개수예외사항1create 하지 않은 token을 issue 할 경우해당 symbol 이 존재하지 않음예외사항2생성한 token 수보다 많은 양을 issue 할 경우maximum supply를 초과함Token transfer마지막으로 token을 다른 계정에 전송 해보도록 하겠습니다. 다른계정에 token을 보내야 하기 때문에 계정을 생성하거나 존재하고 있는 계정을 사용하시면 됩니다.아래 명령으로 hexlanthenry 계정이 babylion1234 계정으로 10000개의 HEX 토큰을 보냅니다.$ cleos push action hexlanthenry transfer '["hexlanthenry", "babylion1234", "10000.0000 HEX", "first"]' -p hexlanthenrytransfer 실행결과transfer 시 들어가는 data에 대해서도 abi를 확인해보겠습니다. 다른 action보다 많은 인자를 필요로 합니다. [“hexlanthenry”, “babylion1234”, “10000.0000 HEX”, “first”]abi의 transfer structparameter 1 : account_name type — fromparameter 2 : account_name type — toparameter 3 : asset type — quantityparameter 4 : string type — memo실제로 babylion1234 계정을 확인해 보면, 방금 배포한 HEX token을 보유하고있는 것을 확인할 수 있습니다.babylion1234의 HEX 보유이번 포스팅에서는 token을 생성과 발행 그리고 전송을 다뤄봤습니다. EOS는 Ethereum 과 달리 토큰 발행을 매우 쉽게 진행할 수 있습니다. 이 두 dapp의 차이에 대해서도 포스팅을 하고 싶으나 우선 다음 포스팅에서는 contract 개발의 기초를 다루도록 하겠습니다.감사합니다.#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심
조회수 1452

크몽 gulp 개선기

안녕하세요. 프론트개발자 bk입니다 :)이 글은 gulp사용법에 대한 글이 아닙니다. build 자동화 도구를 개선할까 말까 고민하는 개발자들을 위해 제 경험을 공유드리려 합니다.음... build 자동화 꼭 해야 해..?가 아닙니다. 크몽이라는 서비스가 어떤 개발 환경을 통해 만들어졌는지, 좀 더 편하고 효율적으로 개발 생활을 즐기기 위해 어떻게 개선을 했는지에 대한 개발 경험을 나눠 보려고 합니다.왜 이 작업을 하게 되었고 뭐가 그리 중요했는지, 크몽 개발 환경에서 gulp가 개선되어야 했던 이유에 초점을 맞추었습니다.시작에 앞서본격적인 gulp에 대한 이야기를 하기 전에 왜 내가 크몽에 입사하자마자 gulp를 개선해야겠다 마음먹었습니다.크몽에서의 개발크몽에 입사한 지 이제 3개월이 되었습니다. 회사의 개선사항, 변화가 필요한 것들에 대해선 반드시 말하는 스타일이라 크몽의 자유로운 분위기와 수평적 문화는 정말 만족했고 적응에 큰 어려움은 없었습니다.첫 주는 개발환경 laravel + vuejs 공부의 시간을 보냈고 둘째 주부터 이벤트 페이지를 맡아서 작업했습니다. 기존 사용하였던 개발환경과 크게 다르지 않아 크게 어려움 없이 이벤트 페이지 작업을 완료하고 배포가 되었습니다.호환성을 위한 es6 환경 필요성하지만 늘 그렇듯 다음 날 출근을 하니 버그가 날 기다리고 있었습니다. 조금 생소한 버그였습니다. script관련 오류였는데, 이전의 개발환경에선 babel, browserify가 거의 대부분의 script에 걸려있었기 때문에 습관적으로 es6로 작업했습니다. 결국은 es5스타일로 복구하여 수정했습니다. 곰곰이 생각해보니 호환성을 위해 es6를 사용하지 않는 것이 아닌 es6를 사용하도록 환경이 구성되어야 하는 게 맞는 것 아닌가 라는 생각했습니다.gulp가 개선되어야만 하는 이유크몽에 입사한 지 1주일 만에 다른 작업을 뒤로하고 회사에서 gulp만 외치고 다녔습니다. 다른 무언가 개선을 하려면 gulp가 필연적으로 개선이 되어야 했습니다. 기존의 개발환경은 파일 수정할 때마다 terminal에서 gulp를 명령어를 쳐야 했습니다. 주르륵 코드를 적고 한번 새로고침해서 짠하고 바뀌는 스타일이 아니라, 내가 짠 코드도 의심이 되어 자주 스텝별로 확인 스타일이라 이러한 현재 환경은 저와 정말 맞지 않았습니다.앞잡이(크몽의 프론트앤드 멤버) 챕터 회의 때 gulp개선을 요청하였고 모두에게 현재의 문제를 공유하고 gulp개선을 해야 하는 이유에 대해 설득 한 뒤 크몽개발팀 내 프로젝트 일정으로 잡히게 되었다.나 편하자고 시작한 작업, 내가 편한 건 팀원도 편하다우선 gulp 개선의 가장 큰 목적은 4가지였다.es6 및 최신 기술과 라이브러리를 사용하자gulp watch를 효율적으로 사용하자script태그와 style태그로 쓰는 것을 지양하자 (.js, .scss파일 많아지는 것에 대한 부담 가지지 말자)script, style, directory 구조를 기능별로 구조화시키자bk's PLANs그렇게 gulp는 이렇게 만들었습니다.기존 크몽 개발 환경에서 너무 확 바뀌진 않도록 개발자 분들과 협업하여 flow를 최대한 유지하며 작업했습니다. (공통 모듈, 유틸, 서비스 관련, 인증, 라이브러리, 이벤트, 구매 판매 트랙, sass)로 모듈을 나누어 bundle을 하였고 각각에 watch를 걸어 파일을 변경하면 자동으로 관련 모듈만 bundle이 실행되도록 작업했습니다.gulp를 위한 작업이었지만directory구조도 깔끔해지고 project도 좀 더 가벼워졌습니다. 계획은 거창했고 의욕은 앞섰지만 build 자동화 툴을 제대로 만져본 적이 경험이 없어서 (gulp, elixir, babel, browserify, stream) 작업과 공부를 병행하느라 예상보단 조금 더 시간이 걸렸지만 결과적으론 개선된 지금이 훨씬 개발하기 편해졌다.불필요한 작업이 습관이 되기 전에 개선을 실행했습니다.사실 크몽의 이전 개발환경에선 gulp가 크게 중요하지 않았지만, 크몽의 팀원이 많아지고 개발자도 많아지면 제가 아니더라도 크몽팀 누군가는 했을 작업이었습니다. 더 나은 환경이 분명히 존재하는데도 불구하고 기존의 불필요한 작업이 습관이 되어 개선을 망설이거나 하는 회사, 개발자가 많다고 생각합니다.개발자가 더 나은 환경에서 개발하는 걸 막자고 할 사람은 없을 것입니다. 그것이 입사한 지 1개월이 되든 10년이 되든 누가 말하든지 간에 말입니다. 갓 들어온 주니어 개발자의 말을 잘 들어주고 gulp 개선에 대한 필요성을 인정해주어 작업이 가능했던 것 같습니다.올바른 방향으로 문제 해결방법을 목표로 삼았습니다.앞으로의 bk의 계획이제 gulp가 완성이 됨과 동시에 directory 구조와 es6가 해결되었으니, 원래 가장 하려던 크몽의 코드 스타일, ESLint를 적용할 예정입니다. 그 후 vue2 마이그레이션 작업이 진행될 예정입니다.마무리작지만 하나하나 개선해 나가면 더 나아진 개발환경 구축이 되고 이런 작은 개선 사항들이 모여서 더 나은 크몽이 될 것이라 생각합니다.real _마무리이렇게 개발했던 경험을 블로그로 포스팅한 건 이번이 처음입니다.역시 글을 쓰는 건 어렵고 두서없었지만 build 자동화 툴에 대해 더 깊게 공부할 시간을 가지게 되어 좋은 경험이었습니다.글을 마무리 지으려니 어떻게 지어야 할지 모르겠네요.그래서 급 마무리 인사드립니다.이렇게 부족한 글 귀한 분들께서 읽어주셔서 감사합니다.다음 포스팅에는 크몽의 개발 조직문화 소개로 돌아오겠습니다.#크몽 #개발팀 #개발자 #개발문화 #경험공유 #인사이트
조회수 1480

원하는 정보를 5초 안에 인지할 수 있게 하자

우리나라에서 웹 서비스가 아이디어에서 출발해 출시되기까지 여러 단계를 거치게 되는데 크게는 기획, 디자인, 개발의 3단계를 거치게 된다고 볼 수 있다. 각 단계별로 세분화된 역할들이 있어도 결국은 각각 기획자, 디자이너, 개발자로 분류된다. 어니스트펀드에서는 그들이 제품개발팀을 이루고 있다.어니스트펀드 제품개발팀나는 그중 개발자로 속하고 퍼블리싱 & 프론트 개발을 하고 있다. 퍼블리싱은 디자이너가 그린 디자인된 화면을 웹페이지용 프로그래밍 언어라고 할 수 있는 HTML과 CSS로 웹 문서화하는 것이고, 프론트 개발은 HTML과 CSS로 만들어진 웹문서를 사용자의 의도/목적에 따라 기능이 동작하도록(주로 데이터 입출력, 예를 들자면 네이버 검색창의 자동 완성이나, 네이버 메인의 다음 뉴스 보기 등) 기능을 개발하는 것이다.어니스트펀드에서는 팀원들이 자신의 지식/경험을 공유하는 브런치 글을 돌아가면서 쓰고 있고 나도 함께하기로 결정하였다. 내가 가치 있게 공유할 수 있는 내용이 무엇인지를 고민하면서 나의 과거 경험들을 생각해보았다.나는 2002년 웹 디자인을 시작으로 퍼블리싱 업무를 겸하다 2004년부터 퍼블리싱 업무를 본격적으로 했고 2011년부터 스타트업에 합류하면서 기획 및 프론트 개발까지 제품 개발에 있어서 서버 개발을 제외한 사용자와 접하는 모든 업무를 두루 경험하였다. 보통 디자인 전공자들은 기획파트로 전업하는 경우가 많지만 나는 프로그래밍 언어로 코드를 작성하는 것이 재미있어 기회가 닿을 때마다 업무 영역을 넓혀왔다.따라서 기획과 디자인, 퍼블리싱, 프론트 개발에 이르는 사용자와 접점이 많은 다양한 업무를 해오면서 경험한 것을 바탕으로, 서비스를 구성하고 화면을 개발하는 데 있어 도움이 되는 유용한 내용을 공유하고자 한다.1. 많을 땐 나눠서 해결하자정보가 많다는 것은 정리 정돈할 물건이 많다는 것과 비슷하게 생각할 수 있다. 물건이 목적에 맞게 정리되지 않으면 찾기 어렵고 정리해놓더라도 쉽게 어질러질 수 있다. 정보도 마찬가지로 목적에 맞게 정리가 안되어 있을 때 이해가 어렵게 되고, 이해가 어려워서 이해를 돕기 위한 불필요한 설명이 덧붙여지다보면 더욱 이해하기 어려운 결과를 낳게 된다. 그렇게 되면 결국 설명하는 말만 늘어나고 고객의 이해는 저편에 남게 된다.웹페이지가 뜨는데 1초, 훑어보는데 3초, 원하는 정보를 캐치하는데 5초로 충분해야 한다. 사용자가 원하는 정보를 5초 안에 캐치하지 못할 정보의 양이라면 정보를 나누는 것이 좋다. 2. 제목을 생략하지 말자목적으로 나누어진 정보를 사용자가 빠르게 캐치할 수 있도록 돕는 가장 중요한 요소는 바로 제목이다. 제목은 본문을 다 읽지 않아도 내용을 어느 정도 짐작할 수 있게 한다. 따라서 훒어보는데 3초라는 의미는 한 페이지의 메뉴와 제목을 훑어보는데 필요한 시간이다. 이런 제목의 중요성 때문에 제목은 직관적이어야 하고 되도록 생략하지 말아야 한다. 생략을 할 때는 제목이 없어도 이해가 가능하며, 생략된 제목을 누구나 유추할 수 있을 경우가 아니면 제목의 생략을 피하도록 한다. 위 캡쳐화면은 네이버 메인 콘텐츠의 일부를 캡처한 이미지다. 네이버 메인 중 제목이 생략된 예는 왼쪽 하단 영역인 '주제형 캐스트'뿐이다. 다른 영역들은 '뉴스스탠드', '쇼핑' 등 제목을 생략하지 않고 노출시키고 있다. 메인 페이지처럼 목적이 다양한 페이지일수록 콘텐츠의 성격을 분명히 알 수 있게 하는 제목은 짧은 시간 안에 원하는 정보를 찾는데 도움을 준다.3. 한눈에 중요 정보를 읽을 수 있게 하자그다음으로는 정보의 배치이다. 해당 정보가 발생한 원인, 결과 등 고객이 인지하는 과정에 기반한 그룹으로 나누는 것이 좋다. 정보를 배치할 때는 개별 정보의 중요도 순서와 왼쪽에서 오른쪽, 위에서 아래로 흘러가는 흐름대로 배치고 중간에 역행하는 구성이 없는 것이 좋다. 국내 대형 인터넷 쇼핑몰의 상품 목록을 보면서 위 설명을 이해할 수 있다.정보 배치에 정답이 있는 것은 아니지만 마치 정답이 있는 것처럼 상품, 제목, 할인율, 가격, 현재 판매현황에 이르는 순서대로 나열하고 있다. 이는 선두업체를 따라 흉내 낸 것이 아니라 이와 같은 구성이 인지하기에 용이하기 때문에 모두 이와 같이 구성했다고 생각한다.   4. 어렵지 않게 보이도록 하자서비스에 대한 정보를 전달하고 나서 우리가 기대하는 바는 고객이 서비스를 이해하고 우리 서비스를 이용하게 하는 것이다. 쇼핑몰에서는 주문을 받는 것일 것이고, 어니스트펀드의 경우는 대출이나 투자를 신청하는 경우이다. 서비스를 이용하게 하려면 고객의 정보를 필수적으로 입력을 받아야 한다. 어니스트펀드의 경우는 대출 및 투자에 대한 금융서비스이기 때문에 더욱 많은 정보를 고객에게 요청한다. 고객의 정보를 웹 상에서 입력을 받을 때는 "폼"이라는 일종의 정형화된 웹페이지 구성항목을 이용하게 되는데 이것은 정형화되어있기 때문에 남들과는 다른 개성적인 방식을 이용하기는 어렵다. 금융서비스의 입력 폼이 아주 쉽지는 않다는 것을 고객들은 여러 다른 서비스를 이용하면서 어느 정도 알고 있다. 그러나 고객이 중간에 포기하지 않고 제대로 서비스 이용을 완료할 수 있도록 어렵지 않게 만들어야 하고, 언제나 경쟁사의 서비스를 확인하고 경쟁사보다는 어려워 보이지 않도록 만들어야 한다.5. 순서는 반드시 지키자순서는 여러 가지가 있다. 입력해야 할 항목이 무엇인지를 알려주는 입력항목 및 입력하는 창(=입력 필드), 입력하는데 필요한 도움말, 입력해야 할 항목들을 나열하고 전송/입력완료 버튼까지의 순서가 곧 정보의 순서이다. 이 중 쉽게 놓치는 부분은 첫 입력 필드에서 입력완료 버튼까지의 여정 중에 연관이 없는 링크나 버튼을 추가하는 경우이다. 이 순서는 디자인상으로는 잘 구분되지 않을 수 있지만, 웹코드 상으로는 100% 지켜져야 하는 순서이고 디자인과 웹코드의 순서가 일치하면 가장 좋은 결과이다.'다음'과 '네이버'의 로그인 영역을 비교해보자면 두 포탈 서비스 모두 메인 검색창에서 탭키로 아이디 입력 칸까지 이동할 수 있지만, 아이디 입력 후 비밀번호를 입력하고 로그인 버튼을 누르기까지의 탭키 이동 경로가 다르다. 다음 로그인 화면네이버 로그인 화면다   음 : 아이디 입력 -> 비밀번호 입력 -> 로그인 버튼 -> 로그인 상태 유지 순서로 이동한다.네이버 : 아이디 입력 -> 비밀번호 입력 -> 로그인 상태 유지 -> IP보안 선택여부 -> 로그인이다.탭키로 입력필드를 이동하는 경우가 곧 웹코드상에서의 각 입력 필드의 순서가 되는데, '다음'과 같은 경우는 아이디/비밀번호 입력 후 로그인에 대한 옵션을 키보드로 선택하기 위해서는 로그인 버튼을 지나쳐야 선택할 수 있다. 로그인에 대한 옵션은 로그인 버튼을 선택하기 전에 나오는 것이 더 자연스럽지 않을까? 눈에 보이는 순서도 중요하지만 각 입력필드의 논리적 우선순위를 지키는 것 또한 중요하다.6. 틀린 부분을 즉시 명확하게 알려주자고객이 언제나 우리가 기대한 값을 입력해주지는 않는다. 이 경우 너무너무 명확하게도 오류가 발생한 시점에 오류가 발생한 지점을 알려주는 것이 필요하다. 10개의 입력필드가 있는데 입력완료 버튼을 누르자마자 10개 항목 구구절절이 맞고 틀리고를 알려주는 것보다는, 오류가 발생한 시점에 알려주는 것이 훨씬 인지가 빠르다. 따라서 오류 항목을 보여주어야 하는 곳은 해당 입력필드의 다음이고 전송 버튼이나 후속 작업 이전이 되는 것이다. 위 캡쳐화면은 어니스트펀드에서 대출을 받고자 할 때 이름과 생년월일을 입력하는 부분이다. 필자는 생년월일 부분에 5월 32일이라고 없는 날짜 정보를 넣었고, 이와 같은 입력 실수는 사용자가 실수를 했다는 것을 시스템이 "정확한 정보를 입력해 주세요"라고 즉시 알려주고 있어 사용자가 입력을 실수하지 않도록 돕고 있다. 웹 페이지를 보는 고객들은 아무런 도움 없이 해당 서비스를 이해하고 이용할 수 있어야 한다. 똑같은 정보라고 하더라도 어떤 순서로 어떻게 보여주느냐에 따라서 인지와 인식은 크게 개선될 수 있다. 하물며 정보까지 가공을 하게 되면 더욱 큰 개선을 이끌어 낼 수 있다. 각자가 맡고 있는 서비스에서 5초 안에 고객이 원하는 정보를 웹 페이지 내에서 바로 인지할 수 있는지를 생각해보고 아니다면 테스트해보고 개선해보자.#어니스트펀드 #개발자 #개발팀 #UX개발 #철학 #인사이트
조회수 4815

신입 개발자를 위한 코드의 정석

Overview대학을 수석으로 졸업했지만, 정작 회사에서는 A부터 Z까지 제대로 할 줄 아는 게 없었습니다. 실수를 남발할 때마다 느꼈던 좌절감은 아직도 생생하지만 되돌아보면 그때의 삽질이 소중한 피와 살이 되었지요. 오늘은 헤매고 있는 신입 개발자를 위한 글을 쓰려고 합니다. 신입 개발자, 당신! 내 이야기를 편하게 듣고 가지 않으실래요? 남을 위한 코드, 클-린 코드“너랑 같이 일하는 사람은 어떻게 보라는 거야?” “한 명이 짠 코드인데, 어째 한 사람이 짠 것 같지가 않다..” “이게 네 스타일이냐?” 대학생이었을 땐, 대부분 혼자서 프로젝트를 진행했습니다. 다른 사람이 제 코드를 보는 일도 거의 없어서 띄어쓰기나 들여쓰기 등에 통일이 없었고, 함수의 네이밍도 전혀 고려하지 않았습니다. 이게 나쁜 습관이었다는 걸 입사하고 알게 되었죠. 이 습관을 고치려고 코딩 컨벤션(coding convention)을 지키는 것에 꽤 오랜 시간을 들여야만 했습니다. 우리는 협업을 하는 사람들입니다. 사람들이 더러운 방보다 깨끗한 방을 좋아하는 것처럼, 당신과 협업하는 개발자도 보기 어려운 코드보다 깨끗한 코드를 더 좋아합니다. 클린 코드를 작성하기 위한 세 가지 기본 원칙을 잠시 소개합니다. 클린 코드를 위한 세 가지 기본 원칙 코드를 최초로 작성한 사람이 끝까지 유지보수를 한다는 보장은 없다.이미 잘 정리된 코드는 효율성이 증가한다. 정리할 시간에 코드 한 줄을 더 분석할 수 있으니까!리팩토링은 미루었다가 한꺼번에 하는 것이 아니다. 코드를 작성하는 매순간 함께 하는 활동이다.작은 것 하나부터 습관을 들여보세요. 분명 깔끔하고 보기 좋은 코드를 만드실 수 있을 겁니다. 머지 않아 “남을 위한 코드는 곧 나를 위한 코드”라는 것도 알게 될 거고요. 책의 한 구절이 떠오르네요. “우리는 저자이다. 저자에게는 독자가 있다. 그리고 저자에게는 독자와 잘 소통해야할 책임이 있다.”⌈Clean Code⌋의 저자, Robert C. Martin 설마가 사람 잡는다, 철저한 검증만약 누군가 검증 단계에서 잊지 말아야할 것이 뭔지 묻는다면 이렇게 대답하고 싶습니다. “절대 사용자가 입력한 값을 신뢰하지 말라. 프론트엔드에서도, 백엔드에서도.” 모든 사용자가 각 항목에 맞게 올바른 정보만 입력해준다면 얼마나 좋을까요? 세상에는 다양한 사용자가 있습니다. 너무 바빠서 얼른 회원가입을 해야하는 사용자는 항목을 채우지도 않고 신청 버튼을 누를 수도 있습니다. 영어로 입력해야 하는 항목엔 한글을 입력한 사용자도 있을 겁니다. 이런 휴먼 에러(human error)뿐만 아니라 의도적으로 비정상적인 요청을 시도하는 사용자도 분명 있습니다. 이 때문에 개발자는 기능에 대해 항상 검증해야 합니다. 바로 이렇게요!그런데 프론트엔드에서 유효성 검사를 하면, 백엔드에는 유효한 값만 넘어온다고 보장할 수 없습니다. 자바스크립트는 브라우저 엔진에 따라 다르게 동작할 수도 있습니다. 또 자바스크립트에서 다루는 값들은 크롬의 개발자도구(option + command + i)를 이용하면 얼마든지 값을 변조하거나 검증을 회피할 수 있습니다. 또 불온한 시도가 아니더라도 다양한 예외 케이스들이 존재하기 때문에 백엔드에서도 무조건 검증해야 합니다.페이스북 페이지의 개발자 도구를 열었을 때 노출되는 화면입니다. 얼마나 나쁜 사람들이 많으면 경고화면까지 만들었을까요?누군가 질문할 수도 있겠군요. “프론트엔드의 검증이 의미가 없다면, 백엔드에서만 검증을 해도 되지 않을까요?” 네, 아닙니다.(단호) 많은 양의 일을 한꺼번에 하는게 힘든 것처럼 무분별한 요청이 서버에 쏟아지면 서버도 사람처럼 지치고 말 겁니다. 응답이 느려지는 등의 문제가 생길 수도 있고, 결국 사용자가 불편해질 것입니다. 그러므로 가장 좋은 검증 방식을 3단계로 정리하면 아래와 같습니다. 고수가 되는 검증 방식 3단계프론트엔드에서 먼저 값 검증을 하여 빠른 사용자 경험을 제공한다.백엔드에서 다시 한 번 더 검증을 거쳐 상황에 적절한 응답 코드를 내려준다.프론트엔드는 상황에 맞게 적절한 UX와 메시지를 보낸다. 동작 그만! 정리는 하고 코딩하자!예전에는 요구사항이 있으면 바로 키보드 위에 손부터 올렸습니다만, 그건 좋은 태도가 아니었습니다. 팀장님이 “이 경우엔 어떻게 하지?”라고 질문하면 아무 대답도 하지 못했기 때문이죠. 팀장님은 늘 “항상 먼저 생각하고 코딩하자!“라고 조언하십니다. 맞습니다. 최대한 모든 경우의 수를 머릿속에 정리하고 코딩을 시작해야 합니다. 시간이 없다는 핑계로 무작정 시작하면 분명 문제가 발생합니다. 또 구현할 기능만 몰두하지 말고, ‘이 기능이 다른 기능에 영향을 미칠 수 있을까?’를 고민하면 훨씬 좋은 코드를 만들 수 있을 겁니다. “이런 거 다 생각하고 짜면 도대체 코딩은 언제 하라고?” “얼른 선임 분들에게 코딩하는 내 모습을 보여줘야 하는데!” “당장 코드 안 짜고 있으면 노는 것처럼 보이지 않을까?” 혹시 이런 생각을 하고 계신가요? 나도 그런 생각을 했던 적이 있습니다. 하지만 요구사항을 확실하게 정리한 후, 코드를 짜는 게 더 효율적입니다. (그렇지 않으면.. ‘수정’이란 이름 아래 모든 것을 뒤엎고 처음부터 다시 시작해야할 수도 있습니다.) ‘더 나은 개발자가 되는 8가지 방법(8 Ways to Become a Better Coder)’이란 글에는 이런 내용이 있습니다. “동작하는 코드는 끝이 아니라 시작이다.” 신입 개발자는 종종 요구사항에 따라 동작하는 코드만 짜면 된다고 여길 때가 있습니다. 물론 사회생활에 적응하느라 정신 없는 와중에 그나마 자신의 코드가 요구사항대로 동작하면 무척 뿌듯할 겁니다. 하지만 동작만 한다고 절대 지나치지 말아주세요. 위에서 언급한 것처럼 깨끗한 코드가 되도록 리팩토링을 하고, 검증하고, 동작이 제대로 되는 것인지 의심하면서 꾸준히 노력해야 합니다. 마지막으로 항상 중요하게 생각하는 문장 하나를 남기고 글을 마치겠습니다.“진정으로 훌륭한 프로그래머는 적극적으로 어디가 잘못되었지를 찾는다. 자기가 놓친 결함은 결국 ‘사용자’가 발견하게 된다는 것을 알고 있기 때문이다.” Esther SchindlerConclusion지금까지 다룬 내용은 결국 같은 맥락입니다. 모든 개발조직은 좋은 품질의 소프트웨어를 개발할 줄 아는 개발자, 협업할 줄 아는 개발자를 원합니다. 누군가 “당신은 잘 지키고 있는가”라고 질문한다면, “저 또한 노력하고 있습니다.”라고 답변하고 싶습니다. 같은 자리에 머무르는 개발자가 될 것인지, 부족함을 알고 항상 배우고 나아가는 개발자가 될 것인지는 스스로의 몫이라고 생각합니다. 이 글을 끝까지 읽은 신입 개발자 당신! 같이 노력하지 않으실래요? :-) 참고 Robert C. Martin, 「Clean Code」, 케이엔피북스(2010)Esther Schindler, 8 Ways to Become a Better Corder, New Relic, 2018.03.02.유석문, 「프로그래머 철학을 만나다 - 소프트웨어를 사랑하는 기술」, 로드북(2014)임백준, 「읽기 좋은 코드가 좋은 코드다」, 한빛미디어(2012)팀장들이 꼽은 신입 PHP 개발자가 가급적 빨리 알았으면 하는 것들프론트에서”만” 유효성 검사가 문제인 경우남을 위한 코드 만들기 - 클린코드글김우경 대리 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 2504

JANDI 검색엔진 도입기

이번 포스트에서는 JANDI가 검색엔진을 도입하게 된 배경과 어떤 작업을 했는지 공유하려고 합니다검색엔진 도입 배경JANDI는 사용자가 입력한 메시지를 검색하고 사용자가 올린 파일의 파일명/파일 타입을 검색하는 메시지/파일 검색 기능을 제공하고 있습니다. 데이터 저장소로 MongoDB를 사용하고 있는데 검색되는 필드에 인덱스를 걸고 정규 표현식을 이용하여 DB Like 검색(“DB는 검색을 좋아한다”아니에요;;)을 하고 있습니다.초기에는 데이터가 아담했는데, 서비스가 커감에 따라 사용자 증가하면서 생성되는 데이터도 많아졌습니다. 올 초에 데이터가 많아지면서 검색이 DB에 부하를 주고, JANDI 서비스에도 영향을 주게 되었습니다. 그래서 JANDI 서비스용 MongoDB와 검색 전용 MongoDB를 분리했는데 이는 임시방편이었고 언젠가는 꼭 검색엔진을 도입하자며 마무리를 지었습니다.시간은 흘러 흘러 4월이 되었습니다. 당시 메시지 증가량을 봤을 때 올해 안에 검색엔진을 사용하지 않으면 서비스에 문제가 될 거라고 판단이 되어 도입을 진행하게 되었습니다.검색엔진 도입의 목표는 다음과 같았습니다.현재 DB Like 검색과 비슷한 검색 품질이어도 좋다. (일정때문에)검색엔진 도입을 통해 검색이 JANDI 서비스에 영향을 주지 않도록 한다.색인을 위해서 주기적으로 JANDI의 MongoDB 데이터를 가져 와야 했지만, 이 작업이 JANDI 서비스에 큰 부하를 주지 않을 거라고 생각했습니다.검색엔진 후보로는 Solr, ElasticSearch, CloudSearch, ElasticSearch Service 가 있었는데 Solr를 선택했습니다.왜냐하면제가 경험한 검색엔진이 Solr 였습니다. 더군다나 2010년 초에 접했던 Solr 비해 많이 발전한 것 같아 개발자로서의 열정과 도전 욕구가 샘솟았습니다. SolrCloud pdf, WhyNoWarAWS에서 제공하는 검색 서비스는 많은 부분을 관리해준다는 면에서 솔깃했지만, Custom Analyzer는 적용할 수 없어서 선택하지 않았습니다.ElasticSearch에 크게 흔들렸지만 경험이없다 보니 공부하면서 프로젝트를 진행한다는 부담감이 커서 다음을 기약했습니다.작업 내용1. MongoImporter, Sharding. MongoImporter 수정현재 JANDI는 MongoDB를 데이터 저장소로 사용하고 있습니다. MongoDB의 데이터를 색인하기 위해 데이터를 검색엔진으로 가져와야 하는데 Solr에서는 DataImportHandler 기능을 제공하고 있습니다. 기본 DataImportHandler로 RDB 데이터는 가져올 수 있지만 이 외 MongoDB나 Cassandra 같은 NoSQL의 데이터를 가져오기 위해서는 따로 구현이 필요합니다. 구글신에게 물어봐서 SolrMongoImporter 프로젝트를 찾았는데 문제가 있었습니다. mongo-java-driver 버전이 낮아서(2.11.1) 현재 JANDI에서 서비스 되고 있는 MongoDB(3.0.x)의 데이터를 가져올 수 없었습니다.url: Reference compatibility MongoDB Java2.11.1에서 3.2.2로 버전을 올리고 변경된 api를 적용하는 작업, 빌드 툴을 ant에서 maven으로 변경하는 작업을 하였습니다. 마음의 여유가 된다면 P/R을 할 계획입니다.여담으로 DataImportHandler 작업과 함께 검색 schema 정하는 작업을 했는데 sub-document 형식이 필요하게 되었습니다. Solr 5.3부터 nested object를 지원한다는 article을 보았는데, nested object 지원 얘기를 보니 Solr도 text search 뿐 아니라 log analysis 기능에 관심을 가지는건 아닐까 조심스레 생각해봤습니다. (역시나… 이미 banana, silk 같은 프로젝트가 있습니다. Large Scale Log Analytics with Solr 에 관련된 이야기를 합니다.). Sharding. 그리고 Document Routing대량의 데이터를 처리하기 위해 한 개 이상의 node로 구성된 데이터 베이스에 문서를 나누어 저장하는 것을 sharding이라고 합니다. SolrCloud는 shard 생성/삭제/분리할 수 있는 API가 있고, 문서를 어떻게 나눌지 정할 수 있습니다. 어떻게 나눌지는 shard 생성 시 router.name queryString에 개발한 router 이름을 적어주면 됩니다. 그렇지않으면 Solr에서 murmur Hash 기반으로 문서를 나누는 compositeId router를 사용합니다. JANDI의 검색 기능은 Team 단위로 이루어지기 때문에 TeamId를 기준으로 문서를 나누기로 하고, compositeId Router를 사용했습니다. 실제 서비스의 문서 데이터를 색인 돌려서 각 node에 저장되는 문서 개수나 메모리/디스크 사용량을 확인했는데 다행히도 큰 차이가 나지 않았습니다.하나의 문서는 TeamId와 MessageId를 조합한 “TeamId + ! + MessageId” 값을 특정 field에 저장하고 해당 필드를 uniqueKey 지정했습니다. 간단한 수정으로 문서 분배가 되는점이 좋았고, 더 좋았던건 검색시 _route_ 를 이용해서 실제 문서가 존재하는 node에서만 검색을 한다는 점이 었습니다. 4년 전 제가 마지막으로 Solr를 사용했을 때는 사용자가 직접 shards queryString에 검색할 node를 넣어주어야 했습니다..../select?q=\*:\*&shards=localhost:8983/solr/core1,localhost:8984/solr/core1SolrCloud RoutingSolrCloud Routing2Multilevel CompositeId2. analyzer, queryParser. analyzerSolr에 기본으로 있는 text_cjk analyzer를 사용하였습니다. <!-- normalize width before bigram, as e.g. half-width dakuten combine --> <!-- for any non-CJK --> text_cjk는 영어/숫자는 공백/특수기호 단위로 분리해주고 cjk는 bigram으로 분리해주는 analyzer 입니다. analyzer는 이슈 없이 완성될 거라 생각했지만 오산이었습니다. 텍스트가 들어오면 token을 만들어주는 StandardTokenizerFactory 에서 cjk와 영어/숫자가 붙어있을 때는 분리하지 못해 원하는 결과가 나오지 않았습니다. 또한 특수기호중에 ‘.’(dot), ‘_‘(underscore)가 있을 때에도 분리하지 못했습니다.nametextInputTopic검색개선_AB1021_AB제시CD.pdfStandardTokenizerFactoryTopic검색개선_AB1021_AB제시CD.pdfCJKWidthFilterFactoryTopic검색개선_AB1021_AB제시CD.pdfLowerCaseFilterFactorytopic검색개선_ab1021_ab제시cd.pdfCJKBigramFilterFactorytopic검색개선_ab1021_ab제시cd.pdf원하는 결과topic 검색개선 ab 1021 ab 제시 cd pdf그래서 색인/검색 전에 붙어있는 cjk와 영어/숫자사이에 공백을 넣어주고 ‘.’와 ‘_‘를 공백으로 치환해주는 작업을 하였습니다. 색인은 Transform에서 처리하고 검색은 다음에 알아볼 QParserPlugin에서 처리했습니다.nametextInputTopic검색개선_AB1021_AB제시CD.pdfTransform 단계Topic 검색개선 AB 1021 AB 제시 CD pdfStandardTokenizerFactoryTopic 검색개선 AB 1021 AB 제시 CD pdfCJKWidthFilterFactoryTopic 검색개선 AB 1021 AB 제시 CD pdfLowerCaseFilterFactorytopic 검색개선 ab 1021 ab 제시 cd pdfCJKBigramFilterFactorytopic 검색개선 ab 1021 ab 제시 cd pdf※ 추가 : 검색 결과를 보여줄때 어떤 키워드가 매칭되었는지 Highlight 해야했는데, 색인하기 전에 원본을 수정을 해서 Solr에서 제공하는 Highlight를 사용하지 못하게 됐습니다. 눈 앞의 문제만 바라보고 해결하기 급급했던 저를 다시금 반성하게 되었습니다.. queryParser앞에서도 언급하였지만, 색인뿐만 아니라 검색할 때도 검색어가 입력되면 검색하기 전에 붙어있는 cjk와 영어/숫자를 분리하고 ‘.’, ‘_‘를 공백으로 치환해주는 작업이 필요합니다. Solr에서 기본으로 사용하는 LuceneQueryParserPlugin 을 수정하였습니다.@Override public Query parse() throws SyntaxError { // 수정한 코드 String qstr = splitType(getString()); if (qstr == null || qstr.length() == 0) return null; String defaultField = getParam(CommonParams.DF); if (defaultField == null) { defaultField = getReq().getSchema().getDefaultSearchFieldName(); } lparser = new SolrQueryParser(this, defaultField); lparser.setDefaultOperator (QueryParsing.getQueryParserDefaultOperator(getReq().getSchema(), getParam(QueryParsing.OP))); return lparser.parse(qstr); } QParserPlugin3. DataImportHandler manageMongoImporter에서도 얘기했지만 Solr에서는 DB 데이터를 가져오는 DataImportHandler 기능을 제공 하고 있습니다. DataImportHandler Commands를 보면 총 5개의 명령을 제공하고 있는데, 그중 색인을 실행하는 명령은 full-import와 delta-import입니다. full-import 명령은 DB의 모든 데이터를 색인 하는 것을 말합니다. 색인 시작할 때의 시간을 conf/dataimport.properties에 저장하고 이때 저장한 시간은 delta-import 할때 사용됩니다. 전체 색인한다고 말합니다. delta-import 명령은 특정 시간 이후로 생성/삭제된 데이터를 색인 하는 것을 말합니다. 특정 시간이란 full-import 시작한 시간, delta-import가 최근 종료한 시간을 말합니다. full-import와는 다르게 delta-import가 종료된 시간을 conf/dataimport.properties에 저장합니다. 증분 색인 혹은 동적 색인이라고 하는데 여기서는 증분 색인이라고 얘기하겠습니다. 두 명령을 이용하여 JANDI의 메시지/파일을 색인 하기 위한 삽질 경험을 적었습니다.. 첫 번째 삽질full-import는 현재 active인 데이터를 가져올 수 있도록 query attribute에 mongo query를 작성하고, delta-import 는 특정 시간 이후에 생성된 데이터를 가져올 수 있도록 deltaQuery attribute에 mongo query를 작성합니다. 또한 deltaQuery로 가져온 id의 문서를 가져올 수 있도록 deltaImportQuery attribute에 mongo query를 작성하고, 특정 시간 이후에 삭제된 데이터를 가져올 수 있도록 deletedPkQuery 에도 mongo query를 작성합니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> 정상적으로 동작은 했지만, 색인 속도가 실제 서비스에 적용하기 힘들 정도였습니다. 실행되는 mongo query를 확인했는데 다음과 같이 동작하였습니다.특정 시간 이후에 생성된 데이터를 색인하기 위해 약 (새로 생성된 문서개수 + 1) 번의 mongo query가 실행되었습니다. (batch size와 문서 갯수에 따라 늘어날 수도 있습니다.) 메신저 서비스 특성상 각각의 문서 크기는 작지만 증가량이 빠르므로 위 방식으로는 운영 할 수 없었습니다. 그래서 delta-import using full-import 를 참고해서 두 번째 삽질을 시작 하였습니다.. 두 번째 삽질full-imoprt 명령을 실행할 때 clean=false queryString을 추가하고 data-config.xml query attribute를 수정하는 방법으로 증분 색인 하도록 수정했습니다. 특정 시간 이후 생성된 문서를 가져오는 attribute인 deltaQuery와 deltaImportQuery 는 필요가 없어 지웠습니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> <!-- if query="" then it imports everything --> 전체 색인은 /dataimport?command=full-import&clean=true 로 실행하고, 증분 색인은 /dataimport?command=full-import&clean=false(생성된 문서)와 …/dataimport?command=delta-import&commit=true(삭제된 문서)로 실행하도록 했습니다.정상적인 것 같았지만, 문제가 있었습니다.full-import, delta-import 명령을 실행하면 conf/dataimport.properties 파일에 전체 색인이 실행한 시작 시각 혹은 증분 색인이 최근 종료한 시간이 “last_index_time” key로 저장됩니다. 첫 번째 삽질에서 증분 색인시 delta-import 명령 한 번으로 생성된 문서와 삭제된 문서를 처리했지만, full-import와 delta-import 두개의 명령으로 증분 색인이 동작하면서 생성된 문서를 처리할 때도 last_index_time이 갱신되고 삭제된 문서를 처리할 때도 last_index_time이 갱신되었습니다.예를 들면증분색인 동작이 1분마다 삭제된 문서를 처리하고, 5분마다 생성된 문서를 처리 한다고 가정해보겠습니다. 3시 13분 14초에 delta-import가 완료되어 last_index_time에 저장되고, 다음 delta-import가 실행되기 전 3시 13분 50초에 full-import가 완료되어 last_index_time이 갱신되었다면, 3시 13분 14초부터 3시 13분 50초 사이에 삭제된 문서는 처리를 못 하는 경우가 발생합니다.Solr에서 dataimport.properties에 기록하는 부분을 수정하는 방법과 전체/증분 색인을 동작시키는 Solr 외부에서 특정 색인 시간을 관리하는 방법이 있었는데 Solr를 수정하는 건 생각보다 큰 작업이라 판단되어 외부에서 관리하는 방법으로 세 번째 삽질을 시작하였습니다.. 세 번째 삽질전체/증분 색인을 주기적으로 동작 시키는 곳에서 full-import&clean=false(생성된 문서) 처리할 때 필요한 마지막으로 색인 된 문서 id와 delta-import(삭제된 문서) 처리할 때 필요한 마지막으로 색인 된 시간을 관리하도록 개발하였습니다. 증분 색인 시 full-import&clean=false를 실행하기 전에 현재 색인 된 마지막 id 조회 후 해당 id보다 큰 데이터를 처리하도록 하였고, delta-import를 마지막으로 마친 시간을 따로 저장하다가 delta-import 실행 시 해당 시간을 전달하는 방법으로 수정하였습니다.<!-- data-config.xml --> <?xml version="1.0" encoding="UTF-8" ?> 마치며튜닝의 끝은 순정이라는 말이 있는데 IT 기술은 예외인 것 같습니다. 현재는 Solr의 기본 기능만으로 구성했지만, 고객에게 더 나은 서비스를 제공할 수 있는 시작점으로 생각하고, JANDI 서비스에 맞게 끊임없이 발전해나가겠습니다.감사합니다.참고Getting Started with SolrApache Solr 5.5.0 Reference Guide PDFApache Solr 6.1 - Analyzers, Tokenizers and FiltersRebalance API for SolrCloud issueYonik Blog#토스랩 #잔디 #JANDI #개발자 #개발팀 #개발후기 #인사이트
조회수 1300

Android Wear 개발하기 - VCNC Engineering Blog

비트윈 팀은 지난달 비트윈에 Android Wear 앱 기능을 릴리즈했습니다. 즐거운 개발 경험이었지만, 힘들었던 점도 많았습니다. 어떤 과정을 통해서 개발하게 되었고, 내부 구조는 어떻게 되어 있는지, 신경 쓰거나 조심해야 할 점은 어떤 것들이 있는지 저희의 경험을 공유해보려고 합니다. 이 글을 통해 Android Wear 앱 제작을 고민하는 개발자나 팀이 더 나은 선택을 하는 데 도움이 되고자 합니다.Android Wear에 대해Android Wear는 최근 발표된 구글의 새 웨어러블 플랫폼입니다. 공개된 지 얼마 되지 않았음에도 불구하고 완성도 있는 디바이스들이 출시된 상태이며, 기존의 웨어러블 기기보다 기능과 가격이 매력 있다는 평가를 받고 있습니다. 또한, 2014 Google I/O에서 크게 소개되고 시계를 참가자들에게 나눠주는 등, 구글에서 강하게 밀어주고 있기 때문에 상당히 기대되는 플랫폼입니다.Android Wear의 알림 기능은 연결된 mobile1 기기와 연동됩니다. 예를 들어 메시지를 받았을 때 mobile과 wear에서 모두 알림을 받아볼 수 있고, Google Now와 연동하여 교통, 날씨 등 상황에 맞는 알림을 제공합니다.또, 여러 가지 앱들의 다양한 기능을 음성으로 제어하도록 하여 사용자에게 기존의 시계와는 완전히 다른 경험을 주고 있습니다.한국에서는 Google Play Store의 기기 섹션에서 구매가 가능합니다.Android Wear 개발하기Android Wear는 Android 플랫폼을 거의 그대로 사용하기 때문에, Android 개발 경험이 있는 개발자라면 아주 쉽게 개발을 시작할 수 있습니다. 비트윈에서는 구글의 80:20 프로젝트를 패러디한 100+20 프로젝트를 통해 개발을 진행하게 되었습니다. (하던 일을 다 해내면서 시간을 내어 진행한다는 의미로 100+20 프로젝트입니다. 하지만 가끔은 '20' 부분에 너무 몰입하여 0+20이 되기도 한다는 게 함정입니다...)Activity, Service 등 Android의 기본 component들을 모두 그대로 사용 가능하며, 손목에 찰 수 있는 크기의 화면에서 유용하게 사용할 수 있는 WearableListView, GridViewPager 같은 새 widget들이 추가되었습니다. 구글 개발자 사이트의 wearable training 섹션에서 자세한 안내를 볼 수 있습니다.비트윈의 아이디어비트윈 Android Wear 기능의 컨셉은, 항상 몸에 착용하는 Wear의 특징을 살려, '커플이 떨어져 있더라도, 항상 함께 있는 느낌을 주기' 였습니다. 그래서 아래와 같은 기능들이 기획되었습니다.Feel His/Her Heart (그대의 심장박동 느끼기): 상대방의 심장박동을 진동으로 재현해주기Where He/She Is (그/그녀는 어느 방향에 있을까?): 상대방의 위치를 나침반과 같은 형태로 보여주기 (안심하세요. 여러분. 방향만 알려주고 정확한 위치는 알려주지 않습니다!)Feel Memories (메모리박스): 언제든 추억을 떠올릴 수 있도록 비트윈의 기존 기능인 메모리박스(추억상자)를 Android Wear에서 구현하지만 이 아이디어들은 하루 만에 망하게 됩니다.메인 아이디어였던 심장박동 느끼기는 사용자가 요청하면 상대방의 시계에서 심장박동이 측정되어 사용자에게 상대방의 심장박동을 진동으로 재현해주는 멋진 기능이었습니다. 하지만 이 아이디어를 낼 때 심박센서가 탑재된 Android Wear 기기가 없었던 게 함정이었습니다.다음날 Android Wear Bootcamp에 참가하여 심박센서가 작동하는 삼성 Gear Live 기기를 사용해 볼 수 있었습니다. 결과는 충격이었습니다. 생각과는 달리 심박박동 측정 결과가 나오는데 10~20초가 걸리고, 그나마도 측정되는 동안은 올바른 위치에 시계를 차고 가만히 있어야 했습니다. 결국, 이러한 제약 때문에 사용자들이 실제로 유용하게 사용할 수 있는 기능이 될 수 없었습니다.그래서 계획을 수정하여 현실적으로 구현 가능한 기능들을 먼저 만들어 보기로 했습니다.목소리로 답변하기: 상대방에게 온 메시지에 Android Wear Framework에서 제공하는 음성인식을 이용하여 목소리를 텍스트로 바꾸어서 답장하기이모티콘 답변하기: 이모티콘을 사용자가 선택하여 이모티콘으로 답장하기비트윈 메모리박스: 비트윈의 기존 기능인 메모리박스(추억상자)를 Android Wear에서 구현처음의 원대한 계획에서 뭔가 많이 변경된 것 같지만, 기분 탓일 겁니다.내부 구현비트윈 Android Wear 앱은 크게 두 가지 기능을 가지고 있습니다. 하나는 상대방에게 메시지를 받았을 때, 메시지 내용을 확인하고 여러 가지 형태로 답장할 수 있는 Notification 기능이고, 다른 하나는 Wear에서 원래 Application의 일부 기능을 시작 메뉴를 통하거나 목소리로 실행시킬 수 있게 해주는 Micro App입니다. 해당 기능들의 스크린샷과 함께 내부 구조를 설명하겠습니다.우선 Notification 부분입니다. 앱 개발사에서 아무 작업도 하지 않더라도, 기본적으로 Android Wear Framework이 스크린샷 윗줄 첫 번째, 네 번째 화면과 같이 예쁜 알림화면과 Open on phone 버튼을 만들어 줍니다. 여기에 추가적인 기능을 붙이기 위하여 WearableExtender를 이용하여 목소리로 답장하기, 이모티콘 보내기 버튼을 덧붙였습니다.비트윈 Android Wear 스크린샷 - Notification둘째로는 Micro App 부분입니다. 여기에는 이모티콘 전송과 메모리박스를 넣었습니다. 이 부분은 일반적인 Android 앱을 만들듯이 작업할 수 있습니다비트윈 Android Wear 스크린샷 - Micro App화면을 보면 무척 단순해 보이지만 내부 구조는 간단하지가 않습니다. 연결된 화면들을 만들어내는 코드가 한곳에 모여있지 않고, 각기 다른 곳에 있는 코드들을 연결하여야 하기 때문입니다. Notification 하나를 만들 때에 Framework에서 만들어주는 1, 4번째 화면, Notification에 WearableExtender를 이용하여 덧붙이는 2, 3번째 화면, 그리고 다시 Framework에서 만들어주는 목소리로 답장하기 화면, 그리고 Wear 쪽의 Micro App을 통해 구동되는 이모티콘 선택 화면과 같이 여러 군데에 나누어 존재하는 코드가 연결됩니다.하나의 앱처럼 느껴지는 화면이지만 각각 다른 곳에 코드가 쓰여있습니다.그러면 이번에는 각 화면이 어떻게 연결되는지 알아보겠습니다.사용자가 상대방으로부터 받은 메시지를 Android Wear의 Notification으로 확인하고, 답장으로 이모티콘을 보내고자 하는 상황을 가정해 봅시다. 사용자가 Send Emoticon 버튼을 눌렀을 때 이모티콘 선택화면을 보여주고 싶은데, 이 행동에 대한 pending intent를 wear 쪽의 micro app이 아닌, mobile 쪽에서 받게 되어 있습니다. 이 때문에 아래의 표와 같이 mobile 쪽에서 pending intent를 받은 뒤 다시 wear 쪽으로 이모티콘 선택 화면을 보여주라는 메시지를 전송해줘야 합니다.이모티콘 전송 과정이번에는 메모리박스를 보겠습니다. 메모리박스도 단순한 화면이지만 mobile 쪽과 통신하여 내용을 불러와야 하므로 생각보다 해야 하는 일이 많습니다. Android Wear Message API와 Data API를 이용하여 데이터를 주고받아 사진을 화면에 보여줍니다.메모리박스를 보여주는 과정개발 시 신경 써야 하는 점개발하면서 주의 깊게 신경 써야 하는 점들이 있습니다.첫 번째로 코드 퀄리티입니다.Android Wear는 아직 성숙하지 않은 플랫폼이기 때문에 많은 사람이 받아들인 정형화된 패턴이 없습니다. 앞서 살펴보았듯이, 간단한 기능을 구현하려고 해도 상당히 복잡한 구조를 가진 앱을 만들게 되기에, 코드 퀄리티를 높게 유지하기 어려웠습니다비트윈 팀에서는 EventBus를 활용하여 코드를 깔끔하게 유지하려고 노력하였습니다. 이러한 문제를 해결할 수 있는 Guava의 Concurrent 패키지나, RxJava 등의 도구들이 있으니 익숙한 도구를 선택하여 진행하는 것을 추천합니다. 또한, 구글의 Android Wear 코드랩 튜토리얼의 내용이 매우 좋으니, 한번 처음부터 수행해 보면 좋은 코드를 만들 수 있는 아이디어가 많이 나올 것입니다.두 번째로는 원형 디바이스 지원 및 에러 처리입니다.처음부터 원형 디바이스를 신경 쓰지 않으면 마무리 작업 시 상당한 고통을 받게 됩니다. 원형 디바이스에 대한 대응법은 Android 개발자 트레이닝 사이트의 wearable layout 섹션에 자세히 나와 있습니다. 현재는 원형 디바이스를 처리하는 프레임웍에 약간 버그가 있지만, 곧 수정될 것으로 생각합니다.사용자 입력이 있을 때, 그리고 에러가 났을 때 적절하게 처리해주는 것은 제품의 완성도에 있어 중요한 부분입니다. Android Wear Framework에서 제공하는 ConfirmationActivity등을 활용하여 처리하면 됩니다.마지막으로 패키징입니다.자동 설치 패키징은 비트윈 팀에서도 가장 고생했던 부분입니다. Android Wear는 본체 앱을 설치하면 자동으로 함께 설치되는데, 앱이 정상작동하기 위해서는 몇 가지 까다로운 조건이 있습니다.build.gradle 의 applicationId 를 wear와 mobile 양쪽 모두 똑같이 맞춰야 합니다.Wear app의 AndroidManifest에 새롭게 선언한 permission이 있다면 mobile 쪽에도 포함해 주어야 합니다.기본적으로, 똑같은 key로 서명합니다. 다른 key로 sign 하는 경우는 문서를 참고해서 신경 써서 합니다.위 항목들은 아주 중요한 내용이지만 아직 문서화가 완벽하지 않으니 주의 깊게 진행해야 합니다.후기개발 과정에서 여러 가지 어려움이 있었지만, 무척 즐거웠던 프로젝트였습니다!우선 새로운 플랫폼에서 새로운 제품의 아이디어를 내고 만들어내는 과정이 많은 영감과 즐거움을 주었습니다.두 번째로는 Android Wear를 포함한 버전 출시 이후 구글플레이의 Android Wear 섹션 및 추천 앱 섹션에 올라가게 되어 홍보 효과도 얻을 수 있었습니다. 또한, 구글의 신기술을 적극적으로 사용하고자 하는 팀에게는 구글 쪽에서도 많은 지원을 해주기 때문에 도움도 많이 받았습니다.세 번째로는 기존의 Android 개발과 비슷하여 접근하기 쉬우면서도, 원하는 것을 구현하려면 상당히 도전적이어서 재미있었습니다.다만 조심해야 할 점은, 구글에서 적극적으로 밀고 있는 프로젝트라고 해서 다 성공하는 것은 아니라는 점입니다. 얼마만큼의 시간과 자원을 투자할지는 신중하게 생각하면 좋겠습니다.정리Android Wear는 새로운 기술과 플랫폼에 관심이 많은 개발자, 혹은 팀이라면 시간을 투자해서 해볼 만한 재미있는 프로젝트입니다. 하지만 완성도 있는 좋은 제품을 만들기 위해서는 생각보다 할 일이 많으니 이를 신중하게 고려하여 결정해야 합니다.끝으로 2014 GDG Korea Android Conference에서 같은 주제로 발표하였던 슬라이드를 첨부합니다.<iframe class="speakerdeck-iframe" frameborder="0" src="//speakerdeck.com/player/a1415af04644013234cf7a3f7c519e69?" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 750px; height: 563px;">구글의 튜토리얼 등에서 지칭하는 것과 마찬가지로, 이 글에서도 Android Wear와 연결된 휴대폰을 mobile이라 하겠습니다.↩
조회수 2319

Angular Lazy Loading 모듈 사용하기

Angular는 비동기식 라우팅이 가능합니다. 나중에 사용할 기능들을 NgModule로 분리하여 사용자의 요청이 들어왔을 때 모듈을 불러와 기능을 사용할 수 있고, 이러한 기술을 지연 로딩이라 합니다.프로젝트가 진행되고 기능이 추가될수록 어플리케이션 번들 크기가 커지고, 결국엔 초기 로딩 시간도 길어지게 됩니다. 지연 로딩을 사용하면 초기 로딩 시간을 줄일 수 있습니다. 컴파일 단계에서 나중에 사용할 모듈들을 메인 모듈에서 분리하여 번들을 생성합니다. 그리고 사용자가 기능을 요청할 때 비동기로 스크립트를 불러와 실행합니다. 지연 로딩에 대한 소개와 사용법은 Angular 공식 문서의 Routing & Navigation — Milestom 6: Asynchronous routing 을 참고하시길 바랍니다하지만 지연 로딩을 사용할 때 유의해야할 점이 몇 가지 있습니다.지연 로딩 모듈과 인젝터(Injector)지연 로딩이 완료되었을 때 Angular는 지연 로딩된 모듈을 루트 인젝터(Root Injector)의 자식이 되는 자식 인젝터를 이용하여 초기화하고, 서비스들을 자식 인젝터에 추가합니다. 즉, 인젝터가 분리되기에 지연 로딩된 모듈의 클래스들은 자식 인젝터로의 서비스 주입이 가능하지만 루트 인젝터로 만들어진 클래스들은 불가능합니다.이는 Angular의 독특한 의존성 주입 시스템 때문입니다. Angular의 인젝터는 처음 애플리케이션이 시작되었을 때, 컴포넌트나 다른 서비스에 주입되기 전에 포함된 모든 모듈들의 서비스 제공자들을 블러와 루트 인젝터를 생성합니다. 애플리케이션이 시작되고 나면 인젝터는 서비스들을 생성하고 주입을 시작하고, 새로운 서비스들을 제공자로 추가가 불가능합니다.그러므로 지연 로딩된 서비스들은 이미 생성이 완료된 루트 인젝터로 추가가 불가능합니다. 따라서 Angular는 지연 로딩된 모듈에 대해서 새로운 자식 인젝터를 만들는 전략을 취하게 된 것입니다.자식 인젝터가 새로 만들어지기 때문에 공통된 모듈을 사용할 때 주의하여야 합니다. 예를 들어 다음과 같이 SharedModule 에 CounterService 를 서비스로 추가하고 루트 모듈인 AppModule 과 지연 로딩 모듈인 LazyModule 에 각각 SharedModule 을 import 하였습니다.import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SharedModule } from './shared/shared.module'; import { AppShellComponent } from './app-shell.component'; const APP_ROUTES = [ { path: 'lazy', loadChildren: 'app/lazy/lazy.module#LazyModule' } ]; @NgModule({ imports: [ BrowserModule, SharedModule, RouterModule.forRoot(APP_ROUTES) ], declarations: [ AppShellComponent ], bootstrap: [AppShellComponent] }) export class AppModule { }import { Injectable } from '@angular/core'; @Injectable() export class CounterService { count = 0; increase(): void { this.count++; } decrease(): void { this.count--; } }import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SharedModule } from '../shared/shared.module'; import { SomeLazyComponent } from './some-lazy.component'; const LAZY_ROUTES = [ { path: '', component: SomeLazyComponent } ]; @NgModule({ imports: [ SharedModule, RouterModule.forChild(LAZY_ROUTES) ] }) export class LazyModule { }import { NgModule } from '@angular/core'; @NgModule({ providers: [ CounterService ] }) export class SharedModule { }그리고 루트 모듈의 컴포넌트와 지연 로딩 모듈의 컴포넌트에서 각각 CounterService 를 사용하여 숫자 값을 바꿔봅니다.서로 다른 인젝터에 CounterService 인스턴스가 만들어졌기 때문에 두 컴포넌트에 표시되는 숫자값은 다릅니다. 앞에서 말했듯이 지연 로딩 모듈은 루트 인젝터가 아닌 자식 인젝터를 이용하여 초기화하기 때문입니다.만약, 지연 로딩 모듈에서 제공되는 서비스를 다른 모듈에서 사용하려면 루트 모듈에 포함시켜 줘야 합니다. 다음과 같이 루트 모듈에게만 노출시킬 서비스 제공자들을 따로 빼내어 줄 수 있습니다.import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AccountLoginPageComponent } from './login-page.component'; const ACCOUNT_ROUTES: Routes = [ { path: 'login', component: AccountLoginPageComponent } ]; @NgModule({ imports: [ ... RouterModule.forChild(ACCOUNT_ROUTES) ], decalartions: [ AccountLoginPageComponent ] }) export class AccountLazyModule { }import { ModuleWithProviders, NgModule } from '@angular/core'; import { AccountAuthService } from './auth.service'; @NgModule({ imports: [...] }) export class AccountModule { static forRoot(): ModuleWithProviders { return { ngModule: AccountModule, providers: [ AccountAuthService ] }; } }AccountModule.forRoot() 를 루트 모듈에 import하면 다른 모듈에서도 AccountAuthService 를 사용할 수 있게 됩니다. 물론 이 경우 AccountModule를 지연로딩 모듈로 만들면 루트 모듈에 포함되기 때문에 번들을 나누는 의미가 없어질 수 있으니 AccountLazyModule 을 따로 두어 코드를 분리하였습니다.#타운컴퍼니 #개발 #개발자 #인사이트 #꿀팁
조회수 3883

크몽 개발팀 문화와 구조 이야기

안녕하세요. 크몽 개발자들과 함께하고 있는 크레이그(a.k.a. 크알)입니다.크몽 개발자 그룹은 1년 내 그 규모가 3배로 커지고, Data Science, Growth Hacking 조직이 만들어지는 등 질적, 양적으로 급성장하고 있는 팀입니다.크몽 개발 부서에 계신 분들은 크몽에 대해 이렇게 이야기 합니다.(참고 : 크몽 개발팀원 더팀스 인터뷰 - '신뢰할 수 있는 동료와 함께 초고속 성장을 만들어가는 크몽 팀' )"제가 크몽에서 전반적으로 느낀 인상은 능동적인 분들이 많다는 거예요. 수동적인 업무를 책임감 있게 하는 것도 중요하지만 문제를 스스로 찾고, 동료들에게 제기하고, 문제를 해결했을 때 진심으로 기뻐하면서 행복감을 느끼시는 분들이 많아요. 그게 큰 조직에 있다가 온 저에게는 정말 많은 자극이 되었어요. "- 데이터분석 KM님"크몽이 저의 개발자 커리어에서 마지막 회사였으면 좋겠다고 생각해요. 실은 진심이고요. 그동안 회사의 성장을 지켜봤고 개발적으로도 많은 변화를 경험했어요"- BackEnd Sean님이렇게 개발자들이 행복하게 개발할 수 있는 환경을 우선시하고 있습니다. 그리고 크몽의 오픈 커뮤니케이션 문화를 지향함과 동시에 ‘Work Happy’와 'Freedom with Responsibility’ 란 가치 아래 최대한 자율성을 보장된 실무자 중심의 개발 문화를 추구합니다.크몽 개발 조직 구조위 핵심 가치 아래 크몽 개발 조직 구조는 크게 ‘Go’와 ‘Chapter’로 구성되어 있습니다.Go  ; 고우선 ‘Go’는 프로젝트 개발 팀 단위로 크몽 서비스를 개선하기 위한 목표 중심의 조직입니다. 다른 회사에서는 ‘Silo’, ‘Team'로 명칭 하기도 합니다. 물리적으로 한 공간에서 스크럼을 이루어 일할 수 있도록 자원을 갖추고 있습니다. Go 안에는 Go Leader(GL) 가 있어 팀 업무 관리 및 우선순위를 정합니다.현재 크몽 개발 파트의 Go는 아래와 같이 구성되어 있습니다.UX-Go크몽 서비스 UX를 개선하기 위한 목표로 데이터를 기반으로한 UX Iteration & Growth Mission 을 수행하는 팀Data-Go데이터 파이프라인을 구축, 활용하여 조직 내 필요한 데이터 자료를 공급하고, 크몽 서비스안에 머신러닝/딥러닝 등의 인공지능 기술 영역을 담당하는 팀Dasi-Go서비스 안정적인 운영 및 릴리즈,  CRM 기술 지원을 담당하는 팀Mobile-Go검색 서비스, 서비스 카테고리 개선 등 크몽 서비스 향상을 위한 모듈 개발팀크몽 라운지Chapter  ; 챕터'Chapter'는 직군별 조직 단위로 주 1회 정도의 커뮤니케이션 타임을 통해 업무 및 기술 동향을 교환합니다. 더불어 챕터 안에서 필요한 스터디, 외부 교육 등의 직군별 자기 능력 향상을 도모하고, 회사에선 이를 적극 지원합니다. 그리고 챕터 내 프로젝트를 통해 서비스 개선에 기여하기도 합니다.크몽 개발 파트는 아래와 같은 챕터가 있습니다.(참고 : 웹 프로트엔드 챕터의 'gulp 개선기' -  https://brunch.co.kr/@kmongdev/5 )**챕터 프로젝트는 챕터 내에서 개발자분들이 스스로 필요하다는 판단 하에 빌딩 된 프로젝트입니다. 챕터 내에는 CL(Chapter Leader)가 존재하며, Chapter 구성원 관리 및 의견을 모아 조직에 전파하는 역할을 담당합니다.Guild  ; 길드개발 파트 안에서의 'Guild'는 토이 프로젝트 같은 성격의 공통 관심 분야를 지닌 프로젝트 팀이라고 볼 수 있습니다. 길드 기획 단계에서 회사 전사적으로 적용되면서, 동호회 성격으로 피보팅(Pivoting) 되어 있지만, 기본적으로 공통의 관심 분야를 같이 학습하고 프로젝트에 적용하는 팀입니다. 매주 수요일 오후 2~3시 사이의 시간은 챕터(Chapter), 고(Go)를 떠나 본인이 원하는 길드에 들어가서 새로운 영역을 탐색하고 연구하는 시간입니다.크몽 개발 파트는 아래와 같은 길드가 있습니다.(참고 : 코틀린 길드의 코틀린 리서치 이야기  https://brunch.co.kr/@kmongdev/9 )정리모든 개발 조직은 '성과 중심' 또는 '성장 중심'의 문화를 가지고 있습니다. 균형을 꾀하는 게 이상적이긴 하지만 스타트업에선 쉽지 않은 일입니다.하지만 크몽 개발 부서에선 인적 성장 중심 문화를 고민하고, 끊임없이 시도하고 있습니다. 이를 위해 여러 전문 교육 기관과 협약을 맺고 교육 지원을 하고 있으며, 국내 정상급 권위자 분들로 구성된 외부 컨설턴트 그룹을 구성해 개발자 분들께 배움과 성장의 기회를 부여하려고 노력하고 있습니다. 1년의 기간 동안 이직률3%의 수치를 기록하고 있는 크몽 개발 파트에선 신규 인력 채용 시 제 1의 인사 기준은 '높은 학력'도, '화려한 커리어'도 아닌우리와 '오랫동안' 함께 '성장'할 수 있는가?입니다. 이를 위해선 개발자 성장을 돕기 위한 환경 구축 및 관리가 필수이고,  그것이 궁극적으로는 회사 및 팀원에게도 장기적인 발전을 가져올 꺼란 굳은 믿음이 있습니다.크몽 개발 그룹CTO#크몽 #개발팀 #개발자 #사내복지 #기업문화 #조직문화 #사내스터디 #CTO
조회수 7186

KT 채용 필수 정보! 실무자가 직접 말하는 KT 人사이드(IT 직무 편)

다가오는 하반기 공채 시즌에 앞서, 지난주 KT 직원들이 직접 말하는 KT 人사이드 ‘영업/마케팅’ 직무 편 잘 보셨나요? 자율적이고 수평적인 회사 분위기와 신입사원에게 주도적으로 역량을 펼칠 기회를 주는 KT의 기업문화를 간접적으로 접할 수 있었는데요. 알면 알수록 빠져드는 KT의 매력! 이번 주에도 더욱 빠져보시라고 새로운 인터뷰를 준비했습니다. KT 人사이드 영업/마케팅 직무 편 보러 가기 지난주에 이어 이번 주에는 KT 기술의 핵심! IT 직무를 맡고 계신 KT人들의 이야기를 들어보려고 합니다. 그들이 말하는 사람을 향한 KT의 기술! 지금. 들어갑니다.  “KT는 다양한 기술 분야를 융합한, 성장 가능성이 가장 큰 곳입니다.”- KT 기업사업컨설팅본부 IoT컨설팅팀 조아영 Q. 현재 어떤 직무를 담당하고 계신가요?A. IT 컨설팅이라는 직무를 맡고 있습니다. 제 직무는 기업 및 공공고객들에게 저희 KT 상품을 제안하는 일이며, 저는 그 중에서도 IoT컨설팅팀에서 일하고 있습니다. ‘IoT를 B2B에 어떻게 적용하느냐’라고 많이들 궁금해하시는데, 원격검침부터 차량, 통신까지 다양한 분야에 적용을 하고 있습니다. 신사업이니 만큼 정형화된 제안보다는 조금 더 사업을 주도적으로 진행하면서 컨설팅하는 재미가 있습니다. 그리고 ‘IT컨설팅’은 프로젝트 수주 전까지 제안서를 작성하고 컨설팅하는 직무가 주 업무이고, ‘IT수행’은 프로젝트 수주 이후에 협력사와 같이 프로젝트를 진행하는 것이 주 업무라고 할 수 있습니다. Q. KT를 선택한 이유는 무엇인가요?A. KT는 기존 사업인 통신기술(CT)뿐 아니라 정보기술(IT)까지 광대한 사업영역을 가지고 있습니다. 두 분야를 융합하여 확장할 가능성이 매우 크다고 생각해 선택하게 되었습니다. 특히 IT컨설팅을 지원한 이유는, 컴퓨터를 전공하며 습득한 이공계적 지식과 더불어 대학 신문사 활동을 통해 얻게 된 논리적 사고, 커뮤니케이션 능력을 함께 활용하여 역량을 발휘할 수 있을 것이라 생각했기 때문입니다. 현실적으로는 전공을 살리면서 광화문에서 근무할 수 있다는 점 또한 큰 장점으로 다가왔습니다.Q. 하루 일과를 설명해주세요.A. 일과는 근무장소에 따라 크게 두 가지 경우로 나뉩니다. 광화문에서는 주로 선제안이나 보고 등 일상적인 업무가 주를 이룹니다. 선제안을 위해서는 보통 타 부서와의 회의, 고객사 방문, 선제안서 작성 등을 합니다. 시장 조사, 실적 파악 등 내부 보고를 위한 보고서 작성 업무도 함께 진행되곤 합니다. 프로젝트에 투입이 되면 보안 상의 이유로 제안센터에 가게 됩니다. 보안이 철저한 제안센터에서 제안서를 작성하는데, PM(Project Manager)의 지휘 아래 각PL(Part Leader)들은 제안요청서에 맞게 담당한 부분을 작성해 나갑니다. 매일 유사하게 반복되는 업무보다 마감에 따라 업무강도에 강약이 있는 사이클식 업무를 선호한다면 컨설팅 직무에 적합하다고 생각합니다. Q. 지원자에게 마지막으로 전하고 싶은 취업 팁은?A. KT는 지원자들의 자소서를 모두 읽기로 유명한 기업입니다. 취업의 첫 시작인 자소서에 진심이 보인다면 아주 특별한 스펙이 없다 하더라도 가능성이 충분하다고 생각합니다. KT의 면접 분위기 또한 비교적 정중하다고 생각합니다. 면접관마다 다르겠지만, 입사 후에도 느낀 전반적인 회사의 분위기는 온화하다는 것입니다. 면접관들 모두 최대한 피면접자의 이야기를 들어주려고 노력하신다는 점을 기억해 주세요. 식상한 말이지만, 면접 때 너무 꾸며낸 모습을 보여주려고 하지 마세요. 자소서와 면대면 상황에서 일관되고 자연스러운 모습을 보여준다면 좋은 결과가 있을 것이라 생각합니다. 제 경험에 비추어 생각해보면, 말을 유창하게 잘하는 것도 중요하겠지만 내용이 논리적이고 일관되냐가 더 중요했던 것 같습니다.“KT인에게는 동료와의 커뮤니케이션이 가장 중요한 포인트입니다.”- kt skylife 기술본부 ICT운영팀 손형락Q. 현재 어떤 직무를 담당하고 계신가요?A. ICT운영팀에서 고객시스템 운영을 맡고 있습니다. 스카이라이프의 고객님들을 맞이하기 위한 고객정보관리시스템을 관리합니다. 고객님들을 유치할 때 필요한 시스템을 고객센터 및 파트너社에 최상의 품질로 제공하기 위해 노력합니다. 시시각각 변화하는 영업환경에 대응하면서, 시스템을 관리 해야 하기 때문에 중요한 업무라 생각합니다. Q. kt skylife를 선택한 이유는 무엇인가요?A. kt skylife는 국내 유일의 위성방송 사업자입니다. 유일하다는 것은 그만큼 시장에서의 경쟁력이 있다는 것을 의미합니다. 경쟁사에서 시도하지 못하는 기술을, 위성을 통해 우리만의 기술로 사용할 수 있을 것입니다. 하루가 다르게 변해가는 시장에서 유일하다는 것은 기업의 가장 중요한 매력 포인트라고 생각합니다.Q. 하루 일과를 설명해주세요.A. 9시 출근이나 항상 30분 일찍 도착합니다. 혹시 모를 장애에 대비하기 위한 습관이라고나 할까요. 퇴근 후에 온 메일이 있는지 확인하고, 그날의 업무를 정리합니다. 스케줄대로 움직이다 보면 어느새 6시. 오전∙오후 시간 모두 각 사업부서와 협의하기 위한 시간으로 사용하지만, 짬짬이 나는 시간들을 잘 활용하면 6시에 퇴근할 수 있습니다. 6시 이후에는 어학 공부 및 새로운 IT 트렌드를 접할 수 있는 각종 세미나에 다니며 틈틈이 자기 계발을 위해 시간을 보내고 있습니다. Q. 지원자에게 마지막으로 전하고 싶은 취업 팁은?A. 상대방의 의견을 들을 수 있는 자세가 되어 있어야 합니다. 어떤 집단에 들어간다는 것은 그 때부터 스스로를 조금은 놓아야 한다고 생각합니다. 회사생활은 혼자서는 해낼 수 없는 중요한 업무들로 가득 차 있습니다. 동료들과 함께 나아갈 수 있는 사람임을 어필할 수 있다면 좋은 점수를 받지 않을까요? 커뮤니케이션이 가장 중요한 포인트인 것 같네요.  “KT는 생활 밀착형 복지 혜택이 좋은 기업입니다.“- KT 소프트웨어개발단 GIS정보제공서비스개발TF 송민정Q. 현재 어떤 직무를 담당하고 계신가요?A. 현재 GIS(지리정보시스템)의 검색 파트에서 개발 업무를 담당하고 있어요. 구체적으로는 크게 3가지로 나눌 수 있는데 데이터 정제 및 현행화 모듈 개발, 검색 엔진 개발 및 질의 최적화, 테스팅 도구 개발을 진행하고 있습니다. GIS 분야, 특히 검색 서비스는 올해 제가 처음 하는 분야라 기술 리서치 하는데 상대적으로 시간을 많이 쓰고 있어요. 또한 기존 서비스와의 차별점을 내세우기 위해 고객 요구 사항뿐만 아니라 자체적으로 요구 사항을 만들어 적용해 보기도 합니다. 국내외 유수 기업 고객의 지도 서비스, 나아가 KT 내비와 지도의 검색서비스로 출시될 생각에 벌써 가슴이 설레네요. Q. KT를 선택한 이유는 무엇인가요?A. 대학교 때 친하게 지냈던 선배가 KT로 입사했어요. 그래서 자연스럽게 업무 환경이나 조직 분위기에 대해 알 수 있었는데, 그때 저에게 있어 KT 기업 이미지가 긍정적으로 생기기 시작했던 것 같아요. 대부제도나 경조사 지원정책, 자녀를 임신하거나 출산한 여성에게 친화적인 제도 등 생활 밀착형 복지가 잘 되어 있다고 들었는데, 실제로 입사 후에 혜택을 많이 받고 있어요. 또한 다양한 ICT 사업시도를 하고 있는 KT에서 SW개발자에 대한 중요성이 점점 강조되고 있고, 전폭적인 지원을 해주고 있다는 소식도 선택의 큰 이유였던 것 같아요.Q. 회사에서 가장 보람 있었던 일은 무엇인가요?A. 입사 1년 차에 담당했던 'KT 패밀리박스' 앱 서비스 개발 업무 때의 일이에요. 경험이 부족한데도 믿고 맡겨주신 선배님 덕분에 앱 리뉴얼 서버 개발에 상당 부분 참여하게 되었습니다. 지금 생각해보면 그때 같은 상황을 기회라고 하는 것 같아요. 크고 작은 실수가 있었지만 모두 한마음으로 이해해 주셨어요. 출시 임박해서는 여타 서비스 개발이 그러하듯이 다소 고된 시간이 있었지만, 사업부서와 협업이 잘되어 그 어느 때보다 즐겁게 일했어요. 무엇보다 자식 같은 서비스가 출시되었을 때의 기쁨은 이루 말할 수가 없었네요. Q. 하루 일과를 설명해주세요.A. 매일 오전 10시에 20-30분간 진행되는 팀 미팅이 있어요. 어제 한 일, 오늘 할 일, 이슈사항을 공유합니다. 월/수/금요일 점심시간에는 운동 동호회 활동을 해요. 회사 헬스장에서 트레이너 선생님을 모시고 회원들과 40여 분 운동을 하며 체력 관리도 하고 스트레스도 풀어요. 오후에는 특별한 일이 없으면 업무에 집중해서 개발 업무를 해요. 비교적 자유롭게 동료들과 대화하며 문제를 해결하거나 토론을 해요. 동료와 한 자리에 앉아 페어 코딩을 할 때도 있어요. 6시가 넘으면 팀장님께서는 퇴근을 장려하세요. 더하고 싶거나 잔업이 있는 경우에는 자율적으로 야근을 하지만, 가급적 일과 시간에 마치려고 노력하는 편입니다.“KT에는 격려와 조언을 아끼지 않는 선배님들이 있습니다.“- kt telecop 차세대IT추진단 IT구축팀 편광일Q. 현재 어떤 직무를 담당하고 계신가요?A. 저는 IT구축팀에서 ‘케이티텔레캅’ App을 담당하고 있습니다. ‘케이티텔레캅’ App은 kt telecop 서비스, 요금 조회, 상담 등 고객님들께 꼭 필요한 서비스를 하나의 App을 통해서 해결할 수 있는 기능을 가지고 있습니다. 저는 이런 ‘케이티텔레캅’ App과 관련하여 사업부서와 Daily Meeting을 하고, 추가 기능 개발 및 유지 보수를 진행합니다. 또한, 새로운 기능 개발에 있어서 협력업체와 co-work할 경우 협력업체 선택, 프로젝트에 대한 전반적인 일정 관리, 새로운 기능에 대한 전략을 제시합니다. Q. 회사에서 가장 보람 있었던 일은 무엇인가요?A. 제가 회사에서 가장 보람 있었던 일은 ‘케이티텔레캅’ App 기능 중 하나를 개발한 것입니다. 개발 당시 신입사원인 저에게 큰 부담이 되어 홀로 인터넷, 서적 등을 참고하며 수차례 야근도 했습니다. ‘과연 내가 해낼 수 있을까?’라는 생각을 할 때쯤 팀 선배님들께서 이를 알아차리고, 격려와 함께 부족한 부분에 대한 조언과 자료 공유를 통해 하나씩 차근히 진행할 수 있도록 도와주셨습니다. 그 결과 무사히 프로젝트를 완료할 수 있었고, 이는 저를 응원해 주고 격려해 주는 선배님들이 있었기에 가능했다고 생각합니다. 신입사원 분들도 업무를 진행 할 때, 힘든 점이 있다고 혼자 고민하기보다 선배님 혹은 동기들에게 도움을 요청하면 더 좋은 결과를 얻을 수 있을 것입니다.Q. 하루 일과를 설명해주세요.A. 출근 후, 팀 동료들과 반가운 인사를 나누며 하루를 시작합니다. 오늘 해야 할 일들을 우선순위로 작성하고, 월/수/금요일에는 KT그룹의 사내방송(KBN)을 시청합니다.9시 - 팀 회의를 통해 그날의 이슈사항과 각자 할 일에 대해 공유합니다.10시 - 회사 내 시스템을 모니터링하며 실시간 상황을 체크합니다. 협력사와 함께 프로젝트 개발 이슈를 정리하고, 보완해야 할 부분은 직접 개발합니다.12시 - 즐거운 점심시간입니다! 저희 회사 지하에 위치한 구내식당 밥의 맛과 영양은 정말 최고입니다^^ 식사를 마치면 팀장님과 팀원들 모두 사다리 타기, 다트 등을 통해 음료 사주기 시간을 갖습니다.13시 - 점심 먹고 졸린 시간인 만큼 팀 내부적으로 안마해주기, 재미있는 이야기 하기 등으로 식곤증을 극복합니다.14시 - 사업부서와 시스템에 대한 추가 요구사항이나 이슈에 대해 공유하는 회의를 진행합니다. 회의를 통해 새롭게 도출된 요구사항을 시스템에 반영하고 수정∙보완합니다.18시 - 하루의 일과를 마치고 퇴근시간을 갖습니다. 특히, 매주 수요일은 ‘가족사랑의 날’이기 때문에 본부장님, 팀장님들과 함께 정시 퇴근합니다. Q. 지원자에게 마지막으로 전하고 싶은 취업 팁은?A. 대부분 취업준비생들은 자기소개서를 작성할 때, 회사 홈페이지 혹은 기사를 참고하면서 쓰곤 하는데, 저는 다른 지원자들보다 차별화를 두기 위해서 직접 본사에 찾아가 선배님들에게 많은 이야기를 들으려고 노력했습니다. 또한, ‘우수기업-청년 채용박람회’에 참석해 kt telecop 부스에서 인사지원팀 과장님들과 이야기를 나누며 회사에 대한 정보를 얻고, 저에 대해 강한 어필을 했습니다. 이 때 보여드린 ‘저의 입사 의지와 진정성이 합격에 결정적인 역할을 하지 않았나!’라는 생각을 하게 됩니다. 신입 공채를 지원하는 후배님들도 남들과는 다른 차별성을 갖고 우리kt telecop에 지원하게 된다면, 분명 좋은 결과를 얻을 수 있을 것입니다.지난주 영업/마케팅 직무에 이어 지금까지 IT 직무를 맡고 계신 KT人들의 이야기를 들어봤는데요. KT의 핵심 기술을 담당하고 있는 KT人들의 인터뷰를 보니, KT가 바라는 인재상에 대해 감이 잡히는 것 같지 않나요? 특히, IT 직무에 필요한 주요 역량으로는 동료∙고객사와의 원활한 커뮤니케이션 능력과 더불어, 체계적인 분석력과 참신한 개발능력이 필요할 것 같습니다. 이와 함께, IT분야에 종사하는 KT人들의 취업 핵심 팁은 자소서를 진솔하고 꼼꼼하게 쓸 것, 면접 시 자연스럽고 일관된 태도를 보이는 것, 그리고 입사 후 동료들과 협력하여 직무를 수행해낼 수 있는 가능성을 보이는 것! 여러분도 모두 해낼 수 있을 겁니다. KT 직무 인터뷰는 다음주에 더욱 풍성한 이야기로 찾아오겠습니다. 안녕!#kt #기업문화 #사내문화 #조직문화 #복지혜택 #kt공채 #하루일과 #kt일상 #구성원인터뷰 #직무정보
조회수 2101

Kubernetes에 EBS 볼륨 붙이기

Kubernetes에서 컨테이너에 Persistent Volume을 붙이는 방법은 몇가지 있다. 여기서는 Kafka 서비스를 예로 삼아 주요 접근방법을 간단히 알아본다.Kubernetes v1.4.0를 기준으로 문서를 작성한다.Static말이 Static이지 수동 마운트를 뜻한다. 기본적으로 관리자가 EBS 볼륨을 만들고특정 Pod에 그 볼륨을 붙이는 작업을 한다. Volumes 문서에 나오는대로 하면 간단하다.apiVersion: v1 kind: Service metadata: name: kafka1 labels: app: kafka1 tier: backend spec: ports: # the port that this service should serve on — port: 9092 name: port targetPort: 9092 protocol: TCP selector: app: kafka1 tier: backend — - apiVersion: extensions/v1beta1 kind: Deployment metadata: name: kafka1 spec: replicas: 1 template: metadata: labels: app: kafka1 tier: backend spec: containers: — name: kafka1 image: wurstmeister/kafka imagePullPolicy: Always volumeMounts: — mountPath: “/kafka” name: kafka1volume ports: — containerPort: 9092 volumes: — name: kafka1volume awsElasticBlockStore: volumeID: vol-688d7099 fsType: ext4여기서 핵심은 다음의 두 줄 뿐이다.awsElasticBlockStore: volumeID: vol-688d7099Dynamic수동으로 볼륨을 붙이는 방법은 간단해서 좋다. 하지만 Autoscaling하는 서비스에 넣기에는 아무래도 무리다. 서비스가 뜰 때 요구사항에 맞는 볼륨을 스스로 만들어 붙이는 방법도 있다. Kubernetes Persistent Volumes를 참고해 작업해본다.우선 Kubernetes 생성할 EBS 볼륨의 사양을 정한다.# storages.yaml apiVersion: storage.k8s.io/v1beta1 kind: StorageClass metadata: name: default1a provisioner: kubernetes.io/aws-ebs parameters: type: gp2 zone: ap-northeast-1a iopsPerGB: “10” — - apiVersion: storage.k8s.io/v1beta1 kind: StorageClass metadata: name: default1c provisioner: kubernetes.io/aws-ebs parameters: type: gp2 zone: ap-northeast-1c iopsPerGB: “10”default1a를 선택하면 ap-northeast-1a Availablity Zone에 기가바이트당 IOPS는 10인 General SSD EBS 볼륨을 생성한다. 이제 다시 Kafka의 돌아가면apiVersion: v1 kind: Service metadata: name: kafka1 labels: app: kafka1 tier: backend spec: ports: # the port that this service should serve on — port: 9092 name: port targetPort: 9092 protocol: TCP selector: app: kafka1 tier: backend — - apiVersion: extensions/v1beta1 kind: Deployment metadata: name: kafka1 spec: replicas: 1 template: metadata: labels: app: kafka1 tier: backend spec: containers: — name: kafka1 image: wurstmeister/kafka imagePullPolicy: Always volumeMounts: — mountPath: “/kafka” name: kafka1volume ports: — containerPort: 9092 volumes: — name: kafka1volume persistentVolumeClaim: claimName: kafka1volumeclaim — - kind: PersistentVolumeClaim apiVersion: v1 metadata: name: kafka1volumeclaim annotations: volume.beta.kubernetes.io/storage-class: “default1a” spec: accessModes: — ReadWriteOnce resources: requests: storage: 300Gi이제 awsElasticBlockStore가 아닌 PersistentVolumeClaim을 통해 볼륨을 할당받는다. kafka1volumeclaim은 default1을 기준으로 스토리지 정책을 정하므로Availablity Zone: ap-northeast-1aIOPS: 기가바이트당 10General SSD300Gi 이상인 스토리지를 원한다는 요구사항을 기술한다. 위의 설정은 이러한 스토리지에 부합하는 EBS 볼륨을 생성하여 kafka1 Pod에 할당한다.분석Dynamic은 Autoscaling에는 적합하나 kubectl delete [service] 또는 kubectl delete [deployment] 등의 명령을 수행하여 서비스를 내렸다가 다시 올린 경우에 기존에 쓰던 볼륨을 마운트하지 않고 새 볼륨을 만드는 문제가 있다. 물론 delete를 하지 않고 서비스를 업데이트만 하는 경우에는 볼륨이 유지되지만 이래선 아무래도 문제의 소지가 많다.그래서 또다른 시나리오를 고민해볼 수는 있다. 짧게 설명하자면관리자가 Volumn Pool을 만들어놓고 Autoscaling 서비스가 이 풀 안에서 볼륨을 할당받게 한다. 이러면 앞서 본 두 가지 방식의 장점을 골고루 흡수할 수 있다.flocker 또는 glushterfs 같은 스토리지 관리 서비스를 활용해도 좋다. 하지만 배보다 배꼽이 큰 것 같은 느낌이 들지도 모르겠다.#데일리 #데일리호텔 #개발 #개발자 #개발팀 #인사이트 #꿀팁
조회수 1581

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 #리액트 #리액트가이드 #한글 #번역 #문서번역
조회수 1600

2017 NDC 리뷰) 크립돈 퓨처 미디어와 하츠네미쿠

 이번글은 덕력이 솟구친다는!!!은 아니고요(진짜 아니에요), 혹시 "하츠네 미쿠"라는 캐릭터를 보신 적 있으신가요?하츠네 미쿠! 설마 처음보는 분들이 계신가요?? 출처: https://ec.crypton.co.jp/pages/prod/vocaloid하츠네 미쿠는 VOCALOID(보컬로이드)로서, 간단히 설명하면, 야마하에서 만든 음성 엔진입니다(자세한 내용은 링크를 확인!). 해당 엔진을 기반(자세히는 VOCALOID2인데... 아, 저는 잘 몰라요 진짜예요...)을 기반으로 크립톤 퓨처 미디어사가 아티스트를 만들고, 이를 지적 재산권(이하 IP라고 하겠습니다)으로 창출해 낸 사례입니다! 해당 세션은 이 보컬로이드가 성공할 수 있게 된, 창작자들에게 프로그램 번들 시디를 팔던, 크립톤 퓨처 미디어사가 새로운 미디어와 아트의 중심에 설 수 있게 된 이유를 들을 수 있게 된 좋은 시간이었습니다. 앞으론 말이 매우 딱딱하니 이점 히해해 주세요~! 시작하겠습니다!씨디파는 회사가 인터넷 시대를 맞이하며 겪게 된 위기, 그리고 해결방안. 앞서 말씀드렸든, 크립톤 퓨처 미디어(이하 크립톤이라고 하겠습니다)는 창작자들을 위한 서비스(또는 프로그램)를 번들 또는 디스크 형식으로 판매하는 회사였습니다. 그리고 새로운 세대로 들어서면서, 해당 사업이 사양되고 있고(디스크 판매> 콘텐츠 다운로드의 변화), 특히 음악 제작 서비스의 경우, 작은 시장의 규모 때문에 비즈니스에 대한 한계를 느끼고, 새로운 사업 영역을 펼쳐나가기 위해 방향 모색하기 시작했다고 합니다. 그리고 크립톤이 생각할 수 있는 "자사가 가장 잘할 수 있는 것"을 생각해 보았을 때, "소리"라는 콘텐츠를 방점으로 서비스를 응용해 나가면서 스팩트럼을 넓히자!라는 생각을 했다고 합니다. 그래서 시작한 것이 바로, 보컬로이드!라는 것이었죠.보컬 합성 기술(보컬로이드) + IP의 도입은 처음부터 성공적이진 않았습니다. 처음 크립톤은 야마하의 보컬로이드 기술을 기반, Leon과 Lola라는 소프트웨어를 제작,  당사에서 유통을 시작했을 때에는, 타깃 유저를 잡는데 실패해 매출에 전혀 도움이 되지 않았다고 합니다(아래 사진을 보면 왠지 알 거 같...)첫 보컬로이드 레온과 로라입니다..... 음... 입술이 매력적 이네요.... 출처: http://vocaloid.wikia.com/wiki/Forever_(Zero-G_song) 그 이유는 해당 서비스를 사용할 것이라고 타게팅한 아티스트들의 경우, 목소리에 관해 리얼함을 추구하는 데, 해당 소프트웨어는 하드웨어로 조정하는 음과 음성들이 리얼함이 다소 떨어져 전혀 니즈가 없었던 것이죠.그래서 트립톤은"해당 서비스를 진짜 사용하는 유저들은 어떤 사람들 일까?"에 대한 고려를 기반으로,"메이코"라는 일본어로 노래하는 보컬로이드를 제작, 흥미를 끌 수 있도록 캐릭터를 모티브로 하는 커버 디자인 작업 시작(안드로이드 아니 보컬로이드 이니깐요!)이제는 버전 쓰리가 된 메이코! (출처:http://vocaloid.wikia.com/wiki/MEIKO) 첫 출시 당시, 거부감도 있었지만, 당시 KPI 목표인 500개를 훌쩍 넘어 3,000개의 판매 성공을 거뒀고, 성공의 요인은 패키징 디자인과 단순한 아티스트뿐만이 아닌, 다양한 콘텐츠에 관심을 가지는 다양한 유저들을 유저들을 이끌 수 있는 요소들이 있어서 라고 판단하였다고 합니다. (서비스를 사용할 것이다 라는 사용자의 경험에 대한 고려를 더 많이 한 포인트라고 생각되는 부분이지요!)메이코 이후 드디어 그분을 만들어 내는 것을 준비합니다.크립톤은 이때부터 정말로 사용자들이 무엇을 원하는가에 대한 생각을 많이 한 것 같다고 보이는 포인트입니다. 메이코의 등장 이후, "하츠네 미쿠"라는 캐릭터 산업으로 만들어 내는 것을 준비합니다. 그리고 해당 캐릭터를 하나의 "사업전략"으로 생각해 낸 이유는 메이코의 KPI달성도 있겠지만, "사람의 목소리와 극히 다른 목소리로 노래를 부르게 된다면, 이상하지 않을까?라는 부분을 오히려 역으로 기획, "인간이 아닌 다른 안드로이드가 하는 노래"라는 새로운 존재로서 IP를 만들어 버린 것이죠!또,  캐릭터를 기반으로 다양한 성격을 가질 수 있도록 "성우"라는 시스템을 집어넣어 "특별한 존재"라는 특징 성을 추가하였고, 기존의 보컬로이드는 "인간의 가수를 대체하는 것"이었으나, 하츠네 미쿠는 "안드로이드 가 부르는 진짜 보컬로이드"라는 접근을 통해 새로운 존재를 만들고, 메이코 디자인을 기반으로, "아이돌 라이즈 된 새로운 사이버 가수"를 만든 것이죠!아아.... 이제 고인이 되신 사이버 가수 아담... (http://beautinaru.tistory.com/196 또한, 해당 콘텐츠를 기반으로 음악을 만들었던 유저들에게 레트로 한 마크들을 집어넣어서 예전에는 이랬었지 라는 향수를 불러일으키고, 해당 콘텐츠를 기반으로 다시 작업을 할 동기를 줄 수 있도록 유저들의 의견을 듣고 반영하는 일들을 굉장히 많이 했다고 합니다!그리고 하츠네 미쿠의 진정한 아이덴티티를 생성합니다. 그것은 바로 Chain of Co-creation!!!하츠네 미쿠가 이렇게 성장할 수 있었던 이유는 저는 하나만 꼽으라 라고 한다면 이쁘잖아요! 가아니라... "확산 가능 여부"에 대한 많은 고려가 있었기에 가능했다고 생각합니다.인터넷 덕분에 음악 등을 만드는 사람들이 쉽게 업로드하고 공유할 수 있는 많은 플랫폼들이 생성되는 현실.덕이 많은 분들이 공유를 통해 자아실현을 하는 공감대를 형성할 수 있는 움직임이 확산.콘텐츠가 콘텐츠를 만들고 퍼져나가는 순기능적인 부분들이 늘어나는 현상들을 확인하고,2차 3차 저작물을 통한 확산> Chain of co-creation의 순선환 적인 기능들이 생겨나는 것이죠!! 그리고 그런 상황을 기반으로, 궁극적으론,모든 사람들이 제작자가 될 수 있는 현실 상황을 받아들이고,제작할 수는 있지만,  저작에 관련한 법률 등에서 막히는 상황을 막기 위해, 창작자들의 창작활동을 돕고, 실제 업로드된 콘텐츠를 기반으로, 실제 사업이 일어날 수 있는 방향으로 전개합니다!그리고 수익화를 통해서 창작자들이 창작활동 = 수익활동이 될 수 있도록 플랫폼 화를 추진한 것이죠!그래서 처음 하츠네 미쿠가 나온 2007년부터 10년이 지난 지금까지도 "보컬로이드"의 선두 주자로 전 세계적으로 콘서트를 다니며 성공적인 투어를 하고 있습니다.투어는 계속된다. (출처: http://mikuexpo.com/) 저는 하츠네 미쿠가 단지 덕후들의 승리라고 요만큼도 생각하지 않습니다.하츠네 미쿠를 성장시킬 수 있었던 건 "우리가 제공하는 서비스가 어떤 유저들에게 더 많은 강점이 있고, 해당 유저들은 어떤 행동을 통해서 자아를 성찰할 수 있을까? 그리고 해당 행동을 통해 유저가 얻는 궁극적인 이익들이 잇을까?"를 생각했던, 크립톤의 유저를 생각하는, 유저의 직접적인 경험을 서비스에 반영하려고 하는 강한 의지가 해당 서비스를 성공시킬 수 있게 한 요인이라고 생각해요. 그런 의미에서 저에겐 정말로 뜻깊고 즐거웠던 세션이었습니다!P.S.: 이제 슬슬 NDC2017 영상들이 올라오기 시작하네요! 관심 있는 분들은 https://ndc.nexon.com/main에서 확인해 보세요~오늘도 긴 글 읽어주셔서 감사합니다! 다소 글이 엉망진창이라도 이해해 주세요! #코인원 #블록체인 #기술기업 #암호화폐 #스타트업인사이트

기업문화 엿볼 때, 더팀스

로그인

/