스토리 홈

인터뷰

피드

뉴스

조회수 1391

원하는 정보를 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개발 #철학 #인사이트
조회수 1486

[Tech Blog] Compare Software Architectures: Monoliths, SOA and Microservices

요즘 Software architecture 라는 단어를 들으면 아마도 Client engineer 분들은 MVC, MVP, MVVM 이 먼저 떠오를 것이고, Server engineer 분들은 Microservice architecture 를 먼저 떠오를 것 같네요. Clean architecture 나 Event-driven architecture 등을 떠올리는 분들도 계실 것 같구요. Software architecture 를 어떻게 정의할 수 있을지에 대해서는 Software architecture: The important stuff 에 적어 봤으니 여기에선 넘어가도록 하죠. https://mherman.org/blog/developing-microservices-node-react-docker/ Microservice architecture 는 대세라고 말할 수 있습니다. Netflix, Amazon 등 굴지의 기업들이 성공적으로 적용해서 운영하고 있고, 국내 기술적으로 뛰어난 많은 기업들 역시 이미 적용했거나 시도하고 있습니다. “남들 다 하는데 이러다 도태 되는거 아냐?” 라는 생각이 들 정도로 말이죠. 그러나 이전 글에서 얘기했듯이 정답은 없으며, Microservice architecture 역시 예외는 아닙니다. 모든 선택에는 Tradeoff 가 있고, Microservices 는 다른 architecture 에 비해 어떤 장점이 있는지 살펴봐야 합니다. 이와 관련하여 정말 많은 좋은 글들이 이미 있으니, 이 글에서는 몇 가지 Software architecture 들을 가볍게 정리 및 비교해 보도록 하겠습니다. Monolithic Architecture Monolithic architecture 는 Microservice architecture 의 장점을 얘기할 때 반드시 언급될 정도로 대척점에 있는 architecture 입니다. Monolithic architecture 는 하나의 큰 덩어리로 구성되어 있고, 모든 기능이 하나의 프로젝트에 집중되어 있습니다. 쉽게 구성이 가능하고 초기에 기능을 빠르게 추가하기에 용이하나, 복잡도가 늘어날수록 기능 추가 속도가 느려지고 문제가 발생할 가능성이 높습니다. PoC(Proof of Concept)를 위한 가벼운 프로젝트나 아주 초기 프로젝트에 적용 가능합니다. Semi-Monolithic Architecture Monolithic architecture 보다는 작지만, 여전히 기능들이 몇 개의 프로젝트에 집중되어 있는 architecture 입니다. 예를 들어 frontend 와 backend 프로젝트를 나누었지만 각 프로젝트가 monoliths 인 경우 semi-monolithic architecture 라고 볼 수 있습니다. 다만 Semi-monoliths 의 경우 몇 군데에서 언급한 것을 볼 수 있지만, 일반적으로 사용되는 architecture 용어는 아닌 듯하고, Semi-monoliths 로 구분될 수 있는 경우 Monolithic architecture 라고 분류할 수 있을 듯합니다. 단순 frontend / backend 보다 좀 더 많은 수의 service 로 분할된 architecture 를 구성하더라도 각 service 가 monoliths 로 구분될 수 있다면 여전히 monolithic architecture 를 구성하고 있다고 할 수 있습니다. Service-Oriented Architecture 여러 조직이 다수의 application 사이에서 로직과 데이터를 공유하기 위해 제안된 architecture 입니다. Monolithic architecture 와 달리 기능을 나눠서 여러 개의 서비스로 구성하고, 서비스 사이는 API 를 통해서 통신합니다. Microservice architecture 와 Service-oriented architecture (SOA) 를 비교하기 위해 Enterprise Service Bus (ESB)가 많이 언급됩니다. ESB는 Enterprise Application Interface (EAI) 와 대조적으로 가볍고 흔한 통신을 위해 제안되었으나, 통제와 관리를 위해 점점 무거운 방향으로 진행되면서 최초의 의도와 달라졌습니다. SOA 가 무거워짐에 따라 최초의 의도였던 빠른 적용, 민첩한 개발 및 적은 통합 비용과 멀어지게 되면서 자연스럽게 도태되었습니다. 서비스 사이에 데이터베이스를 공유할 수 있느냐 아니냐로 Microservice 와 구분을 짓는 의견도 있습니다만, SOA의 정의가 넓어서 이 부분에 대해서는 이견들이 있습니다.   https://dzone.com/articles/microservices-vs-soa-2 SOA가 넓은 범위에서 정의됐기 때문에 ESB 나 DB 공유 여부로 SOA 를 규정 짓기는 어렵습니다. 정의 상으로 보면 Microservice architecture 역시 SOA 의 일종이라고도 볼 수 있습니다. Microservice 의 예시로 자주 등장하는 Netflix 와 Amazon 역시 Microservice 라는 단어가 사용되기 전에는 스스로의 시스템을 SOA 라고 지칭했습니다. Microservice Architecture: The O’Reilly Book 의 공동 저자 Matt McLarty 는 Learn from SOA: 5 lessons for the microservices era 라는 글에서 SOA 와 Microservice architecture 가 같은가 다른가는 그다지 중요한 것이 아니며, 우리가 SOA 로부터 어떤 것들을 배웠는가가 중요하다고 강조합니다. Microservice Architecture Microservice architecture는 규모가 빠르게 커져도 제품 생산 속도를 빠르게 유지하고 안정성을 가질 수 있는 architecture 입니다. 충분히 작은 서비스들이 서로 통신하면서 기능을 수행합니다. Microservice architecture 를 SOA의 잘 구현된 형태라고 보는 시각도 있지만, micro 라는 단어가 SOA 에서 정의하는 서비스보다 작은 크기의 서비스임을 명시적으로 표현하기 때문에 매우 다르다는 의견 역시 있습니다. Microservice architecture 는 각 서비스의 크기를 작고 가볍게 유지함으로써 더 깔끔하고 명확하게 서비스를 유지할 수 있습니다. 잘 구성될 경우 특정 서비스에 장애가 생겨도 다른 서비스에 영향을 적게 미치거나 유연하게 대응할 수 있기 때문에 전체 시스템 오류(e.g Single Point of Failure)를 방지할 수 있습니다. 각 서비스는 독립적으로 배포 및 확장 가능하기 때문에 기능 배포가 빠르고 많은 트래픽에 유연하게 대처할 수 있습니다. 한편 Microsoft architecture 는 구조적인 면에서 복잡도가 증가하며, 많아진 서비스 및 서비스 간 통신에 대한 유지 보수 비용이 추가됩니다. 이를 대응하기 위해서 충분히 자동화되고 잘 구성된 시스템이 필수적으로 필요합니다. Conclusion 판단과 결정은 근거를 필요로 합니다. 가끔 감을 믿고 밀어붙여야 할 때(e.g 오늘 점심은 해장국을 먹어야 한다던가)도 있다는 점은 인정합니다. 하지만 그 역시 설득력을 가지지 못하면 하나의 목표를 향해 모두가 미친듯이 달려가기는 어렵겠죠. Software architecture 를 결정하기 위해서는 추구하는 비전과 비지니스를 이해하고 그에 맞는 근거 하에 모든 팀원을 판단하고 설득해야 합니다. 버즈빌 에서는 더 빠르고 큰 성장을 위해 Architecture Task Force 팀을 구성하였습니다. ATF 팀은 버즈빌에 최적인 Software architecture 를 판단하고, 구성하고, 실행하기 위해 바쁘게 움직이고 있습니다. Buzzvil Services Characteristic:  제품이 다양하고 제품별로 제공해야 할 기능이 많다. 각 제품이 공통적으로 필요로 하는 기능이 많다. 서비스 혹은 기능별로 대응해야 하는 트래픽이 다르다. 전체 서비스 장애 발생 시 많은 후속 문제가 발생한다. 트래픽 변동이 특정 이벤트에 의해 크게 일어날 수 있다.  Buzzvil 의 제품과 비지니스는 위와 같은 성격을 가지고 있습니다. 이를 바탕으로 우리는 Microservice architecture 가 가장 적절하다고 판단하였고, 현재 microservices 의 장점을 살리면서 안정적이고 빠르게 우리가 원하는 목표에 도달할 수 있도록 다양한 방면에서 변화를 가져가고 있습니다. References  Learn from SOA: 5 lessons for the microservices era Microservices vs. SOA On monoliths, service-oriented architectures and microservices Microservices.io Microservices Resource Guide Design Microservice Architectures the Right Way Developing Microservices – Node, React, and Docker    *버즈빌에서 개발자를 채용 중입니다. (전문연구요원 포함)작가소개 Whale, Chief Architect “Keep calm and dream on.”
조회수 1021

제니퍼 개발 이야기(UI/UX)_ 좀 더 쉽고 빠르게 더 멋지게 모니터링하자.

APM ,변화의 시작기업용 소프트웨어의 UI는 어렵다. 특히 애플리케이션 성능 관리(Application Performance Management, 이하 APM) 제품의 경우 더욱더 그렇다. APM은 애플리케이션의 성능 모니터링과 장애 예측을 통해 최적의 애플리케이션 상태를 보장하고 관리하는 일련의 시스템 관리 체계다. 사용자들은 애플리케이션의 성능을 모니터링하고 경우에 따라 발생할 수 있는 장애를 신속히 감지 및 예방해, 기업이 보유하고 있는 정보시스템의 성능을 최적의 상태로 유지하기 위해 APM을 구매하여 사용한다.  그런 이유로 초기 APM은 특정 부서나 특정 분야의 전문가만 다룰 수 있는 제품이었다. 사용법이 복잡하고 데이터의 분석을 통해 장애의 원인을 수동적으로 찾아야 하는 까닭에 APM 제품은 애플리케이션 모니터링 담당자나 개발자 등 전문가만 이해할 수 있는 영역으로 인식돼 왔다. 이런 APM이 최근 여러 현업 부서에서 사용하기 시작하면서 APM의 UI/UX에도 변화가 시작되었다. 기업 내 비즈니스가 다양해지고 복잡해 지면서 기업이 운영하고 관리하는 서비스에 대한 안정성 확보가 중요한 화두로 떠올랐다. 특히 APM은 서비스 지연이나 장애를 감지하고 대처하기 위한 영역으로 확장되고 있는데 모바일 등의 서비스를 통한 비즈니스 기회와 수익이 높아지고, 시장 경쟁이 더욱 치열해지면서 사용자 응답 지연이나 서비스 에러와 같은 문제들은 고객들에게 바로 다른 서비스로 전환을 하게 만드는 원인을 제공하게 된다. 이는 비즈니스 관점에서 매출이나 고객 충성도에 많은 영향을 미칠 수 있기 때문에 기업 내 IT 부서나 사용자 중심적인 서비스 관리 또한 중요한 관리 영역이 되고 있다.< 제니퍼소프트 개발자들이 만든 UI Framework인 JUI, 오픈소스로 공개하였다>APM, 사용자 만족을 위해 UI/UX를 더 쉽고 직관적으로 설계 제니퍼소프트의 APM 솔루션인 「제니퍼(JENNIFER)」는 이해가 어려운 제품을 사용자들이 좀 더 쉽게 이용하게 할 수 없을까 하는 고민에서 출발한 제품이다. 대게 APM 제품은 모니터링 제품의 특성상 이용자들이 모니터링 화면을 전광판으로 띄워 놓고 보거나 데스크에 서브 모니터를 두어 애플리케이션의 운영상태를 상시 모니터링한다. 만약 APM 의 UI/UX가 복잡하여 가시성 확보가 어렵거나 사용하기 어렵다면 중요한 정보를 즉각적으로 파악할 수 없다. 이런 이유로 제품의 UI가 보여주는 데이터의 시각화는 비전문가라 할지라도 애플리케이션의 성능 이상을 인지할 수 있도록 설계되어야 한다.제니퍼는 이런 모니터링의 중요한 본질을 담아 설계하였다. 2005년부터 사용자가 직관적으로 모니터링할 수 있는 뷰에 관심을 기울였는데, 이때만 해도 엔터프라이즈 제품은 UX/UI에 신경을 쓰지 않던 시기였다. 대부분의 APM 제품은 분석적인 요소가 강한 외산 제품이 출시되어 사용되고 있었고 사용자들은 사후 분석 요소가 강한 APM의 UI/UX에 아쉬움을 느꼈다.APM은 문제가 발생한 시점에 이를 직관적으로 인지하고, 분석하여 문제 해결에 실질적인 도움을 주고 이를 통해 얼마나 빠른 장애 대응을 할 수 있냐가 가장 중요한 관건이다. 제니퍼는 제품에 이런 기능의 의미와 가치에 대한 중요한 요소를 놓치지 않았다. 핵심적인 기능을 하나 추가하거나 화면의 적절한 유기적 배치, 심지어 그래프 색감을 선택하는 디자인적 고려에서 조차도, 요건이나 형식 맞추기에 급급하지 않았다. 해당 기능의 본질적인 의미와 가치와 쓰임새를 찾아 담기 위해 노력했다. 제니퍼 출시 이후, 국내 고객은 사후 분석에 치중한 해외 제품을 쓰지 않고 있다. APM 경쟁 업체들 또한 UI/UX에 많은 고민을 하고 있으며, 제품에 이를 반영하고 있다. 그리고 그 중요성은 점점 더 커지고 있다.  사용자의 폭이 넓어지고 관리해야 하는 시스템이 많아지면서 좀 더 쉽고 빠르게 혹은 직관적으로 문제를 파악하고 해결할 수 있는 UI/UX 제품을 선호하게 된 것이다.  이는 사용자에게 좋은 경험을 주는 것이 제품의 경쟁력을 가질 수 있는 중요한 요소임을 반증하는 것이 아닐까 한다. 제니퍼 개발 이야기 _ 2편 <제니퍼 제품 UI/UX 특징>
조회수 1836

입사 후 4개월, 나는 그동안 무엇을 했을까

8월 18일에 입사하여 글을 쓰는 오늘까지 4개월이란 시간이 흘렀다. 트레바리는 4개월을 한 시즌으로 묶어 운영하는 멤버십 서비스이기 때문에 트레바리에서 4개월을 일했다는 건 한 시즌에 필요한 모든 시기를 거쳤다는 의미이다. 4개월을 함께 해야지만 비로소 트레바리를 한 번 했다고 말할 수 있게 된다. 나는 이제서야 트레바리에서 한 번 일했다.트레바리에서의 한 번을 보내며 나는 그동안 무엇을 했는지 정리해보려고 한다. 할 말이 많다 보니 이번 글에서는 기능적으로 무엇을 했는지만 이야기할 예정이다. 어떻게 일했는지, 잘했던 점은 무엇이었는지, 아쉬웠던 점은 얼마나 많았는지에 대한 건 아쉽지만 다음 글에 담기로 했다. 4개월 동안 내가 진행한 일 중 큰 단위의 작업 위주로 살펴보고자 한다.4개월 동안 내가 한 일은 크게 두 가지다. 첫 번째는 기존의 웹 서비스를 개선하는 일. 두 번째는 노가다로 했던 일들에 IT를 끼얹는 일. 두 가지 일에 대한 요구 사항들은 모두 추상적인 문장으로 주어졌고, 나의 역할은 그 추상적인 요구들을 정리하여 실질적인 기능으로 정의하고 구현하는 것이었다. 그동안 어떤 요구 사항들이 있었고 그에 대해 어떤 결과물을 내었는지 정리해보았다.1. 독후감을 활성화되게 만들어주세요.입사 후 최우선으로 개선이 요구됐던 부분은 독후감이었다. 독후감은 트레바리 서비스가 독특하다는 평가를 듣는 이유 중 하나이다. 아무리 돈을 내고 온 멤버일지라도 우리가 내어준 400자의 독후감이라는 숙제를 해오지 않으면 독서 모임에 참가하지 못한다. 우리 크루들은 독후감을 통해 자신의 생각을 한번 정리하고 참가한 독서 모임이 아무런 준비 없이 맞닥뜨리는 독서 모임보다 더 풍성해짐을 안다. 그렇기에 멤버들이 트레바리 홈페이지에서 더 열심히 독후감을 쓰고, 더 많이 다른 사람들의 독후감을 읽고, 더 다양하게 대화할 수 있도록 만들어야 했다.디자인 개선[이전 디자인]일을 시작하자마자 가장 먼저 갈아치우기 시작한 건 디자인이었다. 페이지에 보이는 정보들의 가독성이 나빴다. 독후감 정보와 관련 없는 이미지 배경을 가지고 있었고, 모바일에서는 본문을 포함하여 모든 요소들의 배열이 일정하지 않았다. 가장 문제였던 점은 좋아요 기능이 있음에도 불구하고 유저들이 좋아요 버튼이 있는지를 몰라 활성화가 되지 않는 것이었다.(대표님 얼굴과 우왕이라는 글자가 떡하니 자리 잡은 곳이 좋아요 버튼이다.) 답댓글 없이 한 줄로만 나열된 댓글도 불편했다.[변경한 디자인]전반적으로 컨텐츠가 더 잘 보일 수 있도록 변경했다. 불필요한 배경 이미지를 빼고 책 정보를 추가했다. 좋아요 버튼도 보다 쉽게 인지할 수 있도록 보편적인 모양의 하트로 바꾸었다. 한 줄로만 나왔던 댓글에는 대화하는 듯한 느낌의 UI로 변경하고 답댓글 기능을 추가했다. 특히 모바일에서 더 편하게 쓸 수 있도록 각 요소들을 일정하게 배열했고, 이미지로는 보이지 않겠지만 독후감을 읽고 목록으로 다시 갈 때마다 다른 모임 정보가 뜨는 이상한 시나리오도 개선했다.넛지 만들기더 나은 디자인만으로는 부족했다. 멤버들이 실제로 더 많이 좋아요를 누르고 댓글을 달고 싶게 만들어주는 넛지가 필요했다. 좋아요 수에 따라 재밌는 워딩이 나오고, 댓글 입력 창의 워딩이 항상 다르고 등의 디테일한 요소들을 살렸다. 페이스북 공유하기 기능도 추가했으며 우리 모임에 놀러 오는 멤버들을 보여주는 UI도 추가하는 등의 작업을 진행했다. 하지만 결정적인 한 방이 필요했고 그 한 방은 이달의 독후감 기능이었다.이달의 독후감 선정 기능홈페이지 밖의 운영에서 돌아가던 이달의 독후감이라는 시스템이 있었다. 매 모임마다 가장 좋았던 독후감을 선정하는 것이었는데 잘 알지 못하는 멤버들이 많아 좋아요 수 자체가 적었고, 선정된 독후감을 찾아보기 어려워 활성화가 되지 못했었다. 그래서 이달의 독후감 시스템을 홈페이지에 어워드 형태로 옮겨오면 동기부여와 동시에 별도의 안내 없이도 이달의 독후감 시스템을 학습시킬 수 있겠다고 생각했다.그래서 결과는?결과는 데이터로 나타났다. 디자인과 기능 개선 후 독후감 한 개 당 평균 좋아요/댓글 수가 대략 150% 증가했다. 크루들이 매번 이달의 독후감을 선정하고 하이라이트 구문을 뽑는 오퍼레이션도 줄일 수 있었다. 변경 후 독후감 쓰는 것이 더 즐거워졌다는 멤버 피드백도 종종 들을 수 있었다.2. 멤버십 신청 페이지를 개선해주세요.멤버십 신청 페이지는 트레바리 멤버가 아닌 유저들이 가장 많이 보게 되는 페이지다. 트레바리가 어떤 곳인지 어필하고 결제까지 진행하게 하는 가장 중요한 역할을 하고 있다. 흔히들 말하는 판매 페이지로 트레바리에서 가장 중요한 서비스인 독서 모임을 파는 곳이다. 그 중요성에 비해 디자인과 기능이 모두 엄청나게 부실했고 개선해야 했다.디자인 개선[이전 디자인]대체 트레바리가 어떤 곳인지 알 수 있는 부분이 하나도 없었다. 독서 모임에 대한 설명마저 줄글이 전부였다. 내가 트레바리 독서 모임에 가면 어떤 분위기를 즐길 수 있고 만나는 사람들은 어떨지 상상하기 어려웠다. 모바일에서는 특히 불편했고 필수적인 정보들만 보이는 곳에 불과했다.[변경한 디자인]각 독서 모임에 대한 소개가 풍성하지만 편하도록 변경했다. 이전과 다르게 사진을 많이 활용하여 트레바리 독서 모임이 어떤 분위기인지 보여주고 싶었다. 설명 글도 더 잘 읽힐 수 있도록 배치를 중점적으로 신경 썼고 포인트 컬러를 틈틈이 사용했다. 같은 모임이지만 다양한 시간과 장소가 있는 독서 모임인 경우에는 한 페이지에서 한 번에 볼 수 있도록 구성했다. 모바일 접속자가 압도적으로 많은 만큼 모바일 UI에 많은 시간을 들였다.결제 기능 추가멤버십 신청 페이지에서 가장 큰 문제는 결제였다. 실시간 결제가 아닌 계좌 이체로만 가능하게 되어있다 보니 엄청나게 불편했다. 유저들도 수동으로 이체를 해야 했고, 담당하는 대표님도 24시간 잠도 못자며 휴대폰을 붙잡고 있다가 계좌 이체 알림이 올때마다 등록 처리를 해주어야 했다.(매 시즌 대표님 혼자 몇천 명의 계좌 이체를 확인하고 등록해주셨다. 그래서 멤버십 신청 기간 때에는 제대로 자보신 적이 없다고...)그런데 트레바리는 작은 회사에다 무형의 서비스를 팔고 있다 보니 PG사를 통한 결제를 붙이는 게 어려웠다. PG를 제외한 편하게 결제할 방법을 찾다가 토스 결제를 찾아보게 되었다. 찾자마자 바로 미팅을 진행했고 토스 측에서도 트레바리의 가치를 잘 봐주셔서 미팅부터 결제 연동까지 빠르게 진행하여 구현했다.사랑해요 토스그래서 결과는?막상 개선하여 배포하니 예상보다 저조한 유저 반응이 나타났다. 물론 지난 시즌보다는 훨씬 더 많은 유저분들이 등록하시기는 하였으나 기대했던 목표치에는 못 미쳤다. 디자인이 좋아지고 이용하기 편해지면 당연히 등록 효율이 몇 배로 높아질 거라고 생각했으나 생각처럼 되지 않았다. 신청 기간 내내 저조한 이유에 대한 가설을 세우고, 변경하고, 데이터 보기를 반복했다. 그 과정에서 몇몇 유저분들과 인터뷰를 진행했고 막판에 등록에 영향을 미치는 것은 의외로 홈페이지 사용성이 아닌 다른 곳에 있음을 발견했다. 아쉽게도 늦게 원인을 찾아 더 많은 것을 해보기 전에 신청 기간이 끝나버렸지만, 다음 시즌에는 이번 시즌보다 뾰족하고 탁월하게 개선할 수 있겠다고 생각했다. 영훈님과 함께 공부도 시작해보기로 했다.결제 부분에서는 자세한 데이터를 공개할 수는 없지만 많은 유저들이 토스를 통해 결제를 진행했다. 원래도 트레바리는 N빵 할 일이 많아 토스 송금을 이용하는 유저들이 많았지만 이번 결제 연동을 덕분에 새로 쓰게 된 분도 많아진 것 같았다. 핀테크에 대한 막연한 불안감 때문에 쓰지 않았다는 유저들도 있었지만 막상 써보니 엄청나게 편해서 놀랐다는 피드백도 많이 받았다. 아마도 트레바리에서는 앞으로 계속 토스 송금/결제를 활발하게 사용할 것 같다.3. 트레바리가 어떤 곳인지 보여줍시다.위에서 말한 등록에 영향을 미치는 것은 서비스에 대한 설득이었다. 그동안 트레바리는 지인의 소개로 오는 유저들이 많았고, 기사를 보고 오는 유저들이 많았고, 소문을 듣고 오는 유저들이 많았다. 그래서 따로 트레바리가 어떤 곳인지 잘 설명할 필요성이 적었던 것 같다. 이제는 유저들이 점점 많아지면서 트레바리가 어떤 곳인지 적극적으로 보여줄 필요가 있었고 방치되어 있던 랜딩페이지를 끄집어냈다.[이전 랜딩페이지]이곳만 봐서는 트레바리가 어떤 곳인지 알 수 없었다. 트레바리가 얼마나 매력적인지 어필이 되지 않았고 어떤 활동을 할 수 있는지도 알기 어려웠다.[변경한 랜딩페이지]트레바리가 지향하는 가치들을 더 많이 설명했다. 중간중간 트레바리 사용설명서 영상을 볼 수 있는 곳을 추가했고 실제 멤버들의 후기도 담았다. 트레바리는 독서 모임을 제공하는 서비스이므로 대표적인 독서 모임이 무엇이 있는지도 보여주고 싶었다. 각종 미디어에서 이야기하는 트레바리와 멤버들만을 위한 혜택도 정리해두었다.그래서 결과는?급하게 만드느라 트레바리의 매력을 아직 반의반도 못담았다고 생각한다. 랜딩페이지만 봐도 트레바리가 어떤 곳이고 트레바리를 통해 당신이 얼마나 더 멋있어질 수 있는지를 보여주고 싶다. 랜딩페이지는 꾸준히 만지고 다듬어야 할 과제로 남겨두었다.4. 손에 잡히는 무언가를 주기(일명 손잡무)한 시즌을 끝낸 멤버들에게 각자가 한 시즌 동안 무엇을 해왔는지 쥐여주고 싶었다. 그래서 시즌 말 약 1700명의 멤버들 모두에게 개개인이 이뤄온 활동 데이터를 이미지로 재밌게 엮어 나눠주었다. 기발한 워딩과 이미지는 이 방면에 재능이 있는 세희님과 지현님이 함께해주셨다.[1705 시즌 손잡무][1709 시즌 손잡무]1700명 모두에게 개인화된 이미지를 노가다로 만들어주는 것은 불가능했다. 자동화를 하기 위해서 SQL 쿼리를 통해 필요한 로우 데이터를 추출하고, 스케치라는 디자인 툴을 활용해 이미지 생성을 자동화했다. 덕분에 모든 멤버들의 이미지를 한땀한땀 만드는 노가다를 피하면서도 개인화된 컨텐츠를 제작할 수 있었다. 이 방법은 이다윗님의 코드로 100명 이상의 네임택 한 번에 디자인하기 글을 보고 영감을 받아 가능했다.그래서 결과는?성공적이었다. 인스타그램에 #트레바리를 검색하면 손잡무를 나눠준 시점에 많은 멤버분들이 공유해주신걸 볼 수 있다.(개근상 받으신 분들이 제일 많이 공유해주셨다.) 이미지를 공유해주시면서 4개월 동안 얼마나 즐거웠고 많이 배웠는지에 대한 후기도 소상히 적혀있는 경우도 많아 더욱 뿌듯한 결과물이었다.5. 그 외 각종 버그/개선 요구 사항 해결도 해주세요.각종 UI 및 사용성 개선여러 페이지들의 UI를 개선하고 기능을 개선하여 배포하였다. 자잘한 기능 추가부터 페이지 통째로 갈아엎기까지 손을 댈 수 있는 리소스만큼 건들여보고 개선했다. 이 과정에서는 우선순위를 정하는 일이 중요했는데 우선순위에 대한 이야기는 후에 다시 해볼 예정이다.각종 버그/요구 사항 해결 + 그에 따른 CS내가 만든 것도 많았지만(…) 그거말고도 도대체 개발자가 없을땐 홈페이지가 어떻게 굴러갔지 싶을 정도로 버그가 많았다. 버그도 많고 요구되는 개선 사항도 많았다. 줄어들고는 있지만 아직까지도 버그 및 요구 사항에 응대하는 시간이 하루에 한 시간씩은 꼬박꼬박 들고 있다. 더 많이 줄여나가는 것을 목표로 하고 있다.자동화독서 모임을 이끄는 크루들이 노가다를 하느라 고생하는 시간이 많다. 위에서 이야기 했던 계좌 이체 확인이 가장 큰 사례이다. 그 외에도 개설되는 클럽 데이터 입력을 어드민에서 며칠동안 노가다로 진행해야하는 등의 낭비가 많았다. 이런 부분에서 IT를 끼얹어 공수를 덜 들이고 빠르게 끝낼 수 있도록 엑셀 import 등의 기능을 구현했다.트레바리의 한 번을 끝마치며 나는 그동안 무엇을 했는지 정리해보았다. 쓰다보니 만족스러운 것보다 아쉬운 것들이 눈에 더 많이 들어온다. 무엇이 아쉬웠나 하면 할말이 너무나도 많아 다른 글에 써보기로 하고 이번 글은 기능적인 이야기로만 마무리했다.돌이켜 생각해보면 트레바리에서 쓰이는 기술 스택인 루비도 레일스도, 서버 인프라도 하나 모르는 나를 믿고 이 모든걸 배우고 익힐때까지 기다려준 크루들이 새삼 대단하다고 느낀다. 그 과정에서 실수로 인한 버그도 엄청 많았고 그 버그 때문에 불필요하게 운영 코스트가 늘어났을 때도 있었지만 나무란 적 한 번 없이 격려와 함께 기다려주고 믿어주었다. 그래서 더 열심히 달릴 수 있었던 것 같다.아쉬움과 감사함 때문에라도 다음 4개월에는 일을 더 '잘'하는 사람이 되어야겠다고 다짐했다. 앞으로도 계속 성공하고 실패하고, 배우고 성장한 일들을 꾸준히 기록해나가며 일을 더 잘하는 사람이 되고 싶다. 다음 4개월은 지난 4개월보다 보다 더 실질적이고 큰 변화들을 만들 수 있는 사람이 되어야 겠다. 걱정반 기대반이지만 설레는 마음으로 새로운 시즌을 맞이하며 글을 끝맺으려 한다.어떻게 하면 더 잘할지 고뇌하는 모습의 크루들#트레바리 #기업문화 #조직문화 #CTO #스타트업CTO #CTO의일상 #인사이트
조회수 1180

[인터뷰] 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 얍얍얍앞으로도 더 멋진 미미박서와 미미박스의 이야기로꾸준히 찾아오도록 하겠습니다 !안녕히계세요 !
조회수 1329

비트윈이 사용자를 분석하는 방법

빅데이터분석이 최근 이슈가 되면서 관심이 많으실 것 같습니다. 비트윈팀도 데이터 분석 참 좋아하는데요, 저희도 한번 해보았습니다. 이번 포스팅에서는 비트윈팀의 데이터 분석 노하우를 아낌없이 공유해드립니다.왜 사용자의 데이터를 분석해야하는가요?비트윈같은 서비스는 초기 단계에는 앱을 기획하고 만들어낸 팀에 아이디어에 의해 계속해서 발전하고, 유지됩니다. 하지만 기능이 점점 다양해지고 사용자가 점점 많아지면서 사용자들의 앱 사용패턴을 점점 예측하기 어려워집니다. 게다가 비트윈은 해외 진출을 구상 중이었는데, 개인 혹은 팀의 아이디어만으로 해외에서의 사용패턴을 정확히 알기는 어려웠습니다.이런 시점에 필요한 것이 사용자 분석입니다.사용자들의 사용패턴을 분석해 보는 방법은 여러 가지가 있습니다. 초기에 해볼 수 있는 가장 직관적이고 쉬운 것은 비트윈을 사용하는 자기 자신의 사용 패턴을 돌아보고 분석해보는 것입니다. 또 친구들이나 익명 사용자들의 사용패턴을 물어보거나, 관찰하는 방법들이 있습니다. 이런 방법은 매우 효과적이고 많은 아이디어를 주지만 여러 가지 한계점이 있습니다. 지역적, 시간적인 한계 등이 그것입니다.그래서 택할 수 있는 방법이 실제로 사용자들의 행동을 컴퓨터로 수집해서 분석하는 것입니다. 말 그대로 '데이터 분석'을 하게 되는 것입니다.무엇을 분석할지 알아야 합니다데이터로 분석할 수 있는 것은 무궁무진합니다만, 먼저 데이터가 있어야합니다. 비트윈과 같이 서버와 통신하는 앱은 사용자들이 서버에 요청을 할 때마다 엑세스 로그를 남기게 됩니다. 이 엑세스 로그는 사용자들의 사용패턴을 고스란히 담고 있어, 소중한 데이터가 됩니다.엑세스 로그 분석은 전혀 어렵지 않습니다. 엑세스 로그에서 특정 행동에 해당하는 내용을 세는 것만으로도 여러 가지 유의미한 값을 얻어낼 수 있습니다. 하루 동안의 로그를 한줄씩 읽어서 메시지에 관련된 로그를 카운트하면 그날의 메시지 전송 건수를 얻을 수 있는 것입니다. (참 쉽죠?)엑세스로그에서 가입, 메시지, 사진, 메모 등 기본적인 내용에 해당하는 것들을 카운트하는 것만으로도 꽤 자세하게 앱 전체 사용자들의 전반적인 사용통계를 얻어낼 수 있습니다. 이제 해당 데이터를 엑셀에 넣어서 차트를 그려보면, 사용 통계에 대한 그럴싸한 차트가 그려집니다.엑세스 로그 분석에 성공했다면 좀 더 다양한 분석을 해볼 수 있을 텐데요, 사용자별 행동패턴 분석이나, 나라별, 혹은 아이폰, 안드로이드 디바이스별 분석 등 다양한 분석을 시도해볼 수 있습니다. 분석을 하기 전에 중요한 것은 무엇이 궁금한지, 어떻게 궁금한 데이터를 모을지 아이디어를 먼저 내는 것입니다. 여러 예제들을 찾아보며 공부해보면, 금방 좋은 아이디어를 얻으실 수 있을 겁니다.물론 여기서 중요한것은 개인정보나 사생활의 보호입니다. 로그가 유출되었을때의 보안 문제 뿐 아니라, 데이터 분석팀에게조차 개인정보가 노출된다면 곤란합니다. 이 문제에 저희가 어떻게 대처하고 있는지는 글 뒷부분에 자세히 알려드리겠습니다.특정 기술에 구애받지 말고 다양하게 구현해봅시다처음에는 로그 파일을 돌며 간단한 string을 검사하는 스크립트와 엑셀로도 충분했지만, 점점 복잡한 분석을 할수록 다양한 기술이 필요해집니다. 비트윈 사용자 분석도 점점 다양해지고 복잡해지면서 여러 가지 기술들을 사용하고 있습니다.비트윈 사용자 분석은 처음에는 6줄짜리 간단한 shell script에서 시작되었습니다.cat 2011-10-31.log | grep /messages | grep POST | wc -lcat 2011-10-31.log | grep /photos | grep POST | wc -lcat 2011-10-31.log | grep /memos | grep POST | wc -lcat 2011-10-31.log | grep /like | grep POST | wc -lcat 2011-10-31.log | grep SIGN | wc -lcat 2011-10-31.log | grep REL | grep POST | wc -l이런 스크립트를 만들어서 결과를 이메일로 공유하거나, 엑셀로 만들어 놓곤 했습니다.여기에 비트윈 분석은 조금 더 발전하여, 로그파일을 쿼리하여 Map Reduce 작업이 가능한 Hive를 사용하고, PHP로 통계 웹사이트를 만들어 차트를 그리기 시작했습니다. 이 방식은 처음에는 매우 편리했지만 차츰 쿼리만으로 원하는 결과를 얻기가 힘든 다소 복잡한 분석이 필요해지기 시작했습니다.현재는 모든 로그를 분산 데이터베이스인 HBase에 Date Key와 User Key로 넣고, 코드 생산성이 좋은 Scala로 직접 Map Reduce코드를 작성해서 데이터들을 분석하고 있습니다. 그래서 충분히 scalable하면서도 꽤 편리하게 이용할 수 있는 데이터베이스를 활용하고, Scala의 좋은 expression을 활용하여 짧고 유지보수나 확장이 쉬운 코드로 분석을 수행하면서도 Java와 호환되는 Scala의 특성을 이용하여 Map Reduce 코드 작성을 효과적으로 하고 있습니다. 이렇게 분석한 데이터는 MySQL에 넣어서 2차로 가공하고, Scala Web Framework인 Play Framework을 이용하여 분석 사이트를 구축하고 D3 Chart를 이용해서 Visualize하고 있습니다. 이렇게 함으로써 편리한 MySQL 쿼리 사용의 장점을 취하고 멋진 차트를 효과적으로 그려낼 수 있습니다.좋은 Visualization은 멋질 뿐만 아니라 손쉽게 아이디어를 공유할 수 있게 해줍니다.앞으로는 더 빠른 성능을 위해 Hive를 더 잘 사용해보거나, Elastic Search같은 index engine들을 사용해 볼 계획도 가지고 있습니다. 또한 End point들에서 직접 성능을 측정하여 중앙으로 모아서 분석해보려는 생각도 가지고 있습니다.기술을 선택함에 있어서 정답은 없는 거 같습니다. 널리쓰이는 MySQL같이 scalability가 좀 떨어지지만, 다양한 쿼리로 높은 생산성을 낼 수 있는 데이터베이스도 있고, HBase같이 scalability가 좋지만, 데이터를 저장하는 형태에 제한이 있어 생산성이 조금 떨어지는 데이터베이스도 있습니다. 저희는 앞서 소개드렸듯이 이 두 가지를 모두 혼용하여 사용하고 있습니다. 각자가 마주한 상황에 맞게, 또 각자가 익숙한 기술에 맞게 설계하고, 사용해보면 됩니다.개인정보 보호는 철저하게빅데이터 분석이 개인정보를 침해하는 빅 브라더가 될 수 있다는 우려들이 나오고 있습니다. 300만이 넘는 커플들의 비밀스러운 일기를 담고 있는 비트윈 서비스는 당연하게도 모든 업무를 진행하는 데 있어 보안과 개인정보를 최우선으로 하고 있습니다. 데이터 분석에서도 분석할 수 있는 내용을 상당히 제한받더라도, 예외 없이 그 원칙을 지키고 있습니다.비트윈의 API서버는 AWS클라우드에서 운영되고 있는데, 사용료가 상당히 비싸기 때문에 큰 컴퓨팅 파워를 사용해야 하는 데이터분석까지 AWS에서 하기엔 좀 부담이 되었습니다. 그래서 PC급 컴퓨터 여러 대를 구입하여 사무실 구석에 쌓아놓고 사용하고 있습니다.하지만 문제는 보안이었습니다. AWS의 비트윈 API서버는 다중으로 보안이 유지되고 있지만, 사무실에 있는 서버에 사용자들의 개인정보를 담아둘 수는 없는 일이었습니다. SECO*이 사무실을 지켜주고 있긴 하지만 보안회사에 고객들의 소중한 개인정보를 맡기고 안심할 수는 없으니까요. 그리고 설사 보안 문제가 잘 해결된다고 해도, 분석을 수행하는 비트윈 데이터분석팀원에 개인정보 혹은 사생활이 노출된다면 그 또한 문제라고 생각하였습니다.그래서 저희가 생각해낸 방법은 '익명화'입니다. Access Log들을 저장할 때 사용자의 아이디를 전부 단방향 salted-hash하여 누구인지 알 수 없게 만들었습니다. (물론 salt key는 데이터 분석팀은 알 수 없습니다.) 그리고 애초에 Access Log에는 '어떤 사람'이 '50글자짜리 메시지를 보냈다' 라던가, '사진을 올렸다' 정도만 기록이 되기 때문에, 이를 통계적으로 분석하는 것은 유의미하지만, 사적인 정보를 담고 있지는 않습니다.익명화되어 처리되고 있는 로그는 개인정보는 거의 담고 있지 않으면서도, 유익한 분석 결과를 만들어줍니다.이런식으로 운영을 한다면 데이터 분석팀에서도 사적인 정보(예: 메시지 내용)에 대해서는 접근할 수 없기 때문에, 회원들의 소중한 개인정보와 사생활을 지킬 수 있습니다. 어떤 분석을 수행할 때 언제나 비트윈팀은 언제나 보안과 사생활 보호의 원칙을 지킬 수 있는 범위에서만 진행하고 있습니다.아이디어의 공유, 그리고 액션아이템이 무엇보다도 중요합니다데이터 분석의 목표가 무엇인지, 왜 해야 하는지 생각해보면, 무엇을 해야 하는지 알 수 있습니다. 바로 분석으로부터 얻은 아이디어를 공유하고 액션아이템을 정하고 실천하는 것입니다.데이터를 visualization하는것이 중요한 이유가 여기에 있습니다. 보기 좋은 떡이 먹기도 좋다는 말이 있듯이, 데이터도 먹기 좋아야 합니다. 여러 사람이 쉽게 이해할 수 있어야 아이디어를 공유하고 의사결정을 내리기가 수월하기 때문입니다.민트&베리 사용량 분석. 연인들이 쓰는 앱이라 사랑표현이 인기가 많군요. 디자인팀이 이런 자료를 참고하여 이후 디자인 아이디어를 내는 데 도움이 되면 좋겠죠?비트윈팀은 매번 데이터 분석 미팅을 진행하고 나면 액션아이템을 정하고 실천합니다. 저희가 어떤 식으로 의사결정을 내리고 행동하는지에 대해서는 비트윈 팀블로그의 VCNC는 데이터분석에 기반해 어떤 결정을 내렸나 포스팅을 보시면 도움이 되실 것 같네요.맺으며이번 포스팅에서는 비트윈팀이 어떻게 무엇을 분석하는지 간단하게 다뤄봤습니다. 의견이나 참견 모두 환영이니 댓글 많이 남겨주세요! 다음번 포스팅엔 기술적인 부분에 대해 좀 더 자세하게 다뤄보도록 하겠습니다.저희는 언제나 타다 및 비트윈 서비스를 함께 만들며 기술적인 문제를 함께 풀어나갈 능력있는 개발자를 모시고 있습니다. 언제든 부담없이 [email protected]로 이메일을 주시기 바랍니다!
조회수 2939

코딩, 어떤 언어로 시작하지?

경영학과 학생 윤수는 요즘 주변에서 이런 말을 자주 듣는다."앞으로는 코딩을 모르면 문맹이다.""4차 산업혁명 시대에 대비해야 한다.""소프트웨어가 세상을 잡아먹고 있다."정확히 뭔 소리인지는 모르겠지만 일단 프로그래밍을 배우기로 결심한다. 그런데 '프로그래밍 언어'라는 게 또 뭐가 이렇게 많은지... 어떤 걸로 시작해야 할지 도무지 감이 안 잡힌다.그래서 공대생 친구들에게 물어본다. 친구 1: "일단 가장 기본인 C부터 배워."친구 2: "한국에서 제일 많이 쓰는 자바부터 배워."친구 3: "파이썬이 배우기 쉬워."친구 4: "요즘은 자바스크립트가 대세지."누가 정답일까? 사실 이 중에 "틀린" 사람은 없다. 각자 관심 분야와 목적이 다르기 때문이다. 만약 다음 달에 아이폰 어플을 개발해야 한다면 어쩔 수 없이 곧장 스위프트를 배워야 한다. 내일모레까지 사이트 레이아웃을 완성해야 한다면 더 물을 것도 없이 그냥 HTML과 CSS를 시작하면 된다. 그러나 일반적으로 첫 프로그래밍 언어를 추천하라면 내 톱 초이스는 단연 파이썬, 그다음은 HTML/CSS + 자바스크립트일 것이다. 이 3개의 기준으로 평가하였다:1. 배우기에 얼마나 어려운 언어인가?2. 이 언어에 대한 수요가 얼마나 있는가?3. 나는 프로그래밍으로 뭘 하고 싶은가?*우연히 보게 된 CSDojo라는 유튜브 채널에서 너무 훌륭하게 정리해줘서 참고하였다.1. 배우기에 얼마나 어려운 언어인가?나는 고등학교 때 C를 독학하는 것으로 프로그래밍에 입문했다가 금방 질려서 그만두었다. 창업에 관심이 생겨 다시 시작했는데 이번에는 파이썬으로 배워보았다. 그제야 흥미를 느꼈고, 프로그래밍이 마냥 어렵기만 한 게 아니라는 걸 깨달았다.어떤 차이가 있는 걸까?파이썬은 C보다 쓰기 쉽다. C로 수백 줄을 써야 하는 프로그램을 파이썬 몇십 줄로 쓸 수 있다. 파이썬 코드 몇 줄이면 쓸모 있고 흥미로운 결과물을 만들어낼 수 있다는 뜻이다. "Life is short, use Python"이라는 말이 있을 정도다. 물론 파이썬이 무조건적으로 C보다 좋은 건 아니다. 파이썬 프로그램은 C 프로그램보다 느리기 때문에 특정 업무에는 적합하지 않다. 하지만 그런 건 지금 신경 쓸 필요가 없다. 첫 프로그래밍 언어로 "컴퓨터적인 사고력"을 익히고 나면 새로운 언어를 배우는 것은 어렵지 않기 때문에, 일단은 배우기 쉬운 언어로 시작해라.비교적 배우기 쉬운 언어:    - Python    - Ruby    - JavaScript2. 이 언어에 대한 수요가 얼마나 있는가?시장에서 필요로 하는 언어를 배우는 게 좋다. 세계에서 가장 큰 웹사이트들은 어떤 기술을 사용하는지 살펴보자:출저: 위키피디아위 테이블에는 미국의 웹 서비스들만 정리되어 있다. 지역과 직군에 따라 요구하는 언어가 다르기 때문에 로켓펀치, 더팀스, 위시켓, 링크드인, 인디드, 사람인, 잡코리아 등의 구인/구직 사이트에서 직접 살펴보는 걸 추천한다.인기가 많은 언어일수록 커뮤니티가 크기 때문에, 도움을 받을 수 있는 자료들이 많고 가져다 쓸 수 있는 코드가 많다는 장점도 있다. 세계 최대 규모의 프로그래밍 커뮤니티인 스택오버플로우의 언어 점유율을 참고해보자.출저: 스택오버플로우이 중에서 HTML/CSS(웹 레이아웃), SQL(데이터베이스), Bash/Shell(Unix)은 아주 특수한 경우에 쓰이는 언어이기 때문에 제외하고 보자. 수요가 높은 언어:    - JavaScript    - Java    - Python3. 나는 프로그래밍으로 뭘 하고 싶은가?분야별로 자주 사용되는 언어가 있다. 간단하게 정리하자면:1. 데이터 과학, 공학 => Python, R, MATLAB2. 웹 프런트엔드 => HTML/CSS + JavaScript3. 웹 백엔드 (서버) => Python, Ruby, JavaScript, Java, Go, C, C++, PHP 등4. 아이폰 어플 => Objective-C, Swift (이제는 거의 Swift로 넘어갔다)5. 안드로이드 어플 => Java, Kotlin (슬슬 Kotlin으로 넘어가고 있다)6. 게임 개발 => C#, C++7. 임베디드 시스템 => C, C++결론: 왜 파이썬, 자바스크립트인가?앞서 이야기했듯 당장 급히 배워야 하는 언어가 있으면 그 언어를 배우면 된다. 교수님이 연구실에서 다음 주부터 C언어를 쓴다고 하면, 뭐 어쩌겠나? 그냥 당장 C를 배우는 수밖에. 하지만 조금 더 여유롭게, 제대로 프로그래밍을 배우고 싶다면 이 포스트에 나와 있는 세 가지 기준을 고려해서 결정하는 걸 추천한다. 배우기 쉬운 언어로는 파이썬, 루비, 자바스크립트를 선정했고, 수요가 높은 언어로는 자바스크립트, 자바, 파이썬을 선정했다. 두 기준에 모두 부합하는 언어는 파이썬과 자바스크립트이다. 이 중 무엇을 택할지는 3번 기준으로 결정하면 된다. 데이터 분석에 관심 있으면 파이썬부터, 웹 개발에 관심 있으면 자바스크립트부터 시작하면 된다. 참고로 자바스크립트를 하기 위해서는 기본적으로 HTML과 CSS를 알아야 한다!데이터 과학과 웹 개발 둘 다 관심 없으면 자바스크립트보다는 파이썬으로 시작하는 걸 추천한다. 개인적인 의견이지만 초보자 입장에서 파이썬 언어가 자바스크립트보다 깔끔하다고 생각하기 때문이다. 또한 HTML과 CSS를 미리 배워야 하는 수고를 덜 수 있다.어디서 배우지?온라인으로 프로그래밍을 가르치는 사이트가 굉장히 많다. 해외에는 Codecademy, Treehouse, Coursera, MIT OpenCourseWare 등이 있고 한국에는 인프런, 엘리스, 코드잇, 생활코딩 등이 있다. 국내외 서비스를 통틀어서 가장 추천하는 곳은 코드잇이다. 영어로 된 수업이 당연히 더 좋을 것이라 생각한다면 큰 오산이다. Codecademy나 Treehouse는 쉽고 재미있지만 막상 수업을 다 들어도 직접 무언가를 할 수 있겠다는 생각이 들지 않는다. 반면 Coursera나 MIT OpenCourseWare는 대학 수업과 흡사하기 때문에 지루하고 어려워서 이수율이 5% 정도밖에 되지 않는다. 코드잇은 내용의 깊이와 재미를 모두 잡았다. 심도 있는 내용을 난해하지 않고 간결하게 풀어내어 졸업률이 60%나 된다. 코드잇 수업 안 들은 사람은 있어도 하나만 들은 사람은 없다는 말이 있을 정도인데, 수강 후기를 보면 정말 수강생들의 애정이 드러난다. 무료로 샘플을 들어볼 수 있으니 일단 한 번 해보도록.Python으로 배우는 프로그래밍 기초 수업, HTML/CSS로 배우는 웹 퍼블리싱 수업, JavaScript로 배우는 인터랙티브 웹 수업을 모두 들으면 자신감을 갖고 프로그래밍 커뮤니티에 입문할 수 있을 것이다.이제 어떤 프로그래밍 언어를 어디서 배워야 하는지 알았으니, 주저 말고 시작해보길 바란다!#코드잇#코딩교육 #개발자양성 #교육기업 #인사이트 #경험공유
조회수 1493

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

AWS Rekognition + PHP를 이용한 이미지 분석 예제 (1/2)

OverviewAWS Rekognition은 딥 러닝 기반의 이미지, 동영상 분석 서비스입니다. Rekognition API를 사용하면 서비스에서 객체, 사람, 텍스트, 장면 및 동작을 식별하고 부적절한 콘텐츠를 탐지할 수 있습니다. Rekognition은 딥 러닝 기술을 기반으로 하고 있기 때문에 지금 이 순간에도 새로운 데이터를 통해 끊임없이 학습하고 있고, AWS에서도 새로운 레이블과 얼굴 인식 기능을 추가하고 관리합니다. 이번에는 AWS S3 Bucket에 업로드한 이미지로 이미지 분석 결과를 볼 수 있는 예제 사이트를 통해, Rekognition과 친해지는 시간을 갖도록 하겠습니다. 저는 예제 사이트를 개발하기 위해 PHP 프레임워크인 CodeIgniter 3, MAMP, Bootstrap을 사용했습니다.1. AWS Rekognition SDK 설치하기1-1) AWS Rekognition 사이트에 접속해 Download SDKs 를 클릭합니다.1-2) AWS 에서 제공하는 다양한 언어의 SDK를 확인할 수 있습니다. 저는 PHP를 사용할 것이므로 PHP 의 Install을 클릭하겠습니다.1-3) AWS SDK 를 설치할 수 있는 방법은 여러가지가 있습니다. 이 중에서 저는 Composer를 이용해 설치했습니다.curl -sS https://getcomposer.org/installer | php php -d memory_limit=-1 composer.phar require aws/aws-sdk-php 1-4) 짠! 짧은 명령어 2줄로 SDK 설치가 완료되었습니다 :)2. AWS S3 Bucket 에 업로드된 이미지를 분석하기2-1) 여기에 임의로 만든 예제 사이트가 있습니다. [이미지 선택] 과 [S3에 이미지 업로드하기] 를 통해 이미지 파일을 등록하면, 백단(Back-end) 에서는 해당 파일을 특정 S3 Bucket 에 업로드 한 후 Rekognition 에게 이미지 분석을 요청하도록 짜여있습니다. 관련 코드는 아래와 같습니다.{     "Image": {         "S3Object": {             "Bucket": "bucket",             "Name": "input.jpg"         }     },     "MaxLabels": 10,     "MinConfidence": 80 } 위의 코드 블록은 AWS Rekognition 개발자 안내서에 나와있는 예제 포맷이고, 아래의 코드는 예제 포맷을 PHP 에서 요청할 수 있는 방식으로 코딩한 것입니다.detectLabels 메소드 를 이용해 분석할 이미지가 저장되어 있는 S3 Bucket 과 이미지의 Name 을 전달해줍니다. 1) MaxLabels : 응답 받을 최대 Label 수 2) MinConfidence : Label 에 대한 최소 신뢰성 여기서 Label 이란 ‘이미지에서 발견되는 객체, 장면 또는 개념’ 이라고 생각하면 됩니다. 예를 들어 해변에 있는 사람들을 촬영한 사진에는 ‘사람’, ‘물’, ‘모래’ (객체) 및 ‘해변’ (장면) 그리고 ‘야외’ (개념) 등이 Label 이 될 수 있습니다. 자, 우주 사진을 한 번 분석해볼까요? array(3) {     ["Labels"]=>     array(8) {       [0]=>       array(2) {         ["Name"]=>         string(9) "Astronomy"         ["Confidence"]=>         float(96.8987350464)       }       [1]=>       array(2) {         ["Name"]=>         string(5) "Earth"         ["Confidence"]=>         float(96.8987350464)       }       [2]=>       array(2) {         ["Name"]=>         string(5) "Globe"         ["Confidence"]=>         float(96.8987350464)       }       [3]=>       array(2) {         ["Name"]=>         string(11) "Outer Space"         ["Confidence"]=>         float(96.8987350464)       } ...     } Rekognition이 업로드한 우주 사진을 분석하여 정확히 연관된 Label들만 반환한 것을 확인할 수 있습니다. 이 Label을 가지고 이미지 태그를 간단하게 구현했습니다.참 쉽죠 ?Conclusion이번 시간에는 AWS Rekognition 을 이용하여 기본적인 이미지 분석을 해보는 시간을 가져봤습니다. 다음 시간에는 ‘얼굴 감지 및 분석’ 기능을 응용하여 Collection 을 생성해보고, 얼굴 검색을 해보는 시간을 갖겠습니다. 참고놀라운 무료 이미지 · Pixabay핀터레스트 스타일 레이아웃 만들기 (masonry) - 생활코딩이미지에서 레이블 감지 - Amazon Rekognition글김우경 대리 | R&D 개발1팀[email protected]#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 1586

날짜 변환, 과연 그리 간단할까?

안드로이드에서는 입력한 날짜를 변환 및 검증하는 로직을 간단하게 구현하기 위해 SimpleDateFormat 클래스를 종종 활용하게 되는데 이 클래스는 규칙에 관대하다(lenient)는 재미난 특성이 있습니다. java.text.SimpleDateFormat 클래스의 근간이 되는 java.text.DateFormat 클래스의 다음 API 문서를 살펴봅시다.By default, parsing is lenient: If the input is not in the form used by this object’s format method but can still be parsed as a date, then the parse succeeds. Clients may insist on strict adherence to the format by calling setLenient(false).파싱 기본 동작은 관대합니다. 이 객체의 날짜 포맷과 일치하지 않는 입력이 주어지더라도 날짜 형태만 유지한다면 파싱이 성공합니다. 클라이언트 코드에서는 setLenient(false) 메소드를 호출해 파싱 규칙을 여전히 엄격하게 가져갈 수 있습니다.lenient 라는 흔하지 않은 단어 때문에 의미가 잘 와닿지 않습니다만, 캠브릿지 영영사전에 따르면 ‘관대하다’ 라는 뜻이 있다고 하네요.lenient /ˈliː.ni.ənt/ ▶ adjective ▶ Level C2(Mastery Proficiency)A lenient punishment is not severe.Thesaurus: allowing, forgiving, merciful, permissive, tolerant하지만 규칙에 관대하다는 말이 무슨 의미인지 여전히 와 닿지 않습니다. 잠시, 아래의 소스코드를 읽고 그 결과를 한번 예측해 볼까요? parse 메소드는 기본적으로 lenient 하다는 특성에 주의합시다./* * 2017년 13월 32일 이라는 입력에 대해 어떤 결과가 나타날까? * 1. 2017-13-32 * 2. 2018-02-04 * 3. 2017-01-01 * 4. 2018-01-01 * 5. ParseException 이 발생 */ val userDate = "2017-13-32" val date = SimpleDateFormat("yyyy-MM-dd").parse(userDate) val localDate = LocalDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC) println ("사용자의 ISO-8601 Date 입력 결과는 ${localDate.year}년-${localDate.month}월-${localDate.dayOfMonth}일 입니다.") lenient 라는 사전 hint 없이 바로 문제를 낼 경우 사람들이 제일 많이 선택한 결과는 ParseException 이 발생한다 였습니다. 하지만 lenient 한 특성으로 인해 실행 결과는 의외로 두번째, 즉 2018년 2월 4일 입니다. 막상 글로 풀어 쓰려니 별 것 아닌 내용처럼 보입니다만, 필자가 담당하는 서비스에서 이 특성을 제대로 파악하지 못해 특정 사용자의 생년월일을 제대로 인식하지 못한 문제가 있었습니다.또한 우리가 흔히 아는 달력을 쓰지 않는 국가도 있다는 점 까지 고려한다면 날짜 변환이라는 것이 간단한 문제가 아니게 됩니다. 즉, 한국인의 관념 속의 ‘달력’ 이란 Gregorian calendar 를 기반으로 한 ISO-8601 달력 입니다. 그런데 이 달력을 쓰지 않는 문화권도 있습니다(한국도 흔하진 않지만 ‘단기’ 라는 별도의 달력을 쓰기도 합니다). 이런 문제 때문에, 글로벌 서비스를 준비하고 계신다면 날짜 변환 문제를 꼭 점검해 보셔야 합니다.Android 에는 이 문제를 해결해 주는 클래스가 있습니다만 불행히도 API Level 이 26이나 되어 2018년 현재에는 제대로 쓰긴 어렵습니다. 다행히도 이 문제를 보완한 joda-time 라이브러리의 안드로이드 포팅 버전도 있으니 이 라이브러리의 도입을 검토해 보는 것도 좋은 문제 해결 방법이 될 것입니다.#개발 #인사이트 #하이퍼커넥트 #개발자 #안드로이드 #개발후기
조회수 4467

자바스크립트 기초 문법 정리 Part 2 - 객체

지난 Part 1 포스팅에 이어 자바스크립트 기초 문법에 대해 정리해보았습니다. 이번 포스팅에서는 여러 객체와 그 객체에서 제공하는 각 메서드에 대해 정리하였습니다. 다루는 객체의 여러 메서드에 대해 정리하였기 때문에 전 포스팅처럼 간략하지는 않지만 이번 포스팅을 저장해 두고 자바스크립트로 개발하면서 필요할 때마다 참고하여 보기에는 좋을 것 같습니다. 다만, 메서드 사용 예의 코드는 넣지 않았으니 예제 부분이 필요하다면 필히 공식 문서를 참고해주세요. 익히는 것 자체도 공식 문서를 통하여 보는 것이 가장 좋지만 혹여 영어에 취약하신 분이라면 이 포스팅을 참고하는 것도 괜찮을 것 같습니다. :)내장 객체브라우저의 자바스크립트 엔진에 내장된 객체. String/Date/Array/Nath/RegExp Object 등이 있음.날짜 객체 DateDate 객체 생성new Date()new Date(milliseconds)new Date(dateString)new Date(year, month, day, hours, minutes, seconds, milliseconds)Date Get 메서드getDate() - 일 정보를 가져옴.getDay() - 요일 정보를 가져옴. 0(일요일)-6(토요일)getFullYear - 연도 정보를 가져옴. (yyyy)getHours() - 시간 정보를 가져옴.getMilliseconds() - 밀리초 정보를 가져옴. 0-999 (1/1000 초의 단위)getMinutes() - 분 정보를 가져옴.getMonth() - 월 정보를 가져옴. 현재 월에서 -1한 값으로 옴.getSeconds() - 초 정보를 가져옴.getTime() - 1970년 1월 1일부터 경과된 시간을 밀리초로 가져옴.Date Set 메서드setDate() - 일 정보를 설정.setFullYear() - 연도 정보를 설정. 원한다면 월과 일 정보도 설정할 수 있다.setHours() - 시간 정보를 설정.setMillseconds() - 밀리초 정보를 설정.setMinutes() - 분 정보를 설정.setSeconds() - 초 정보를 설정.setTime() - 1970년 1월 1일부터 경과된 시간을 밀리초로 설정.기타 Date 메서드now() - 1970년 1월 1일부터 지금까지의 밀리초를 반환.parse() - 날짜 형태의 문자열을 변환하여 1970년 1월 1일부터 입력한 날짜까지의 밀리초를 반환.toString() - Date 객체를 문자열로 변환.toJSON() - Date 객체를 JSON 데이터로 변환.valueOf() - Date 객체를 밀리초로 반환.숫자 객체 NumberNumber 생성var num = 1;      var num2 = new Number(1);Number 객체의 속성MAX_VALUE - 표현 가능한 가장 큰 수.MIN_VALUE - 표현 가능한 가장 작은 수.POSITIVE_INFINITY - 무한대 수 표기.NEGATIVE_INFINITY - 음의 무한대 수 표기.NaN - 숫자가 아닌 경우 표기.Number 객체 메서드toExponential(n) - 자수 표기법으로 소수점 n자리만큼 문자형 데이터로 반환.toFixed(n) - 소수점 n자리만큼 반올림하여 문자형 데이터로 반환.toPrecision(n) - 유효 숫자 n의 개수만큼 반올림하여 문자형 데이터로 반환.toString() - 숫자형 데이터를 문자형 데이터로 반환.valueOf() - 객체의 원래 값을 반환.parseInt(값) - 데이터를 정수로 변환하여 반환.parseFloat(값) - 데이터를 실수로 변환하여 반환.수학 객체 MathMath 메서드 및 상수Math.abs(숫자) - 숫자의 절댓값을 반환.Math.max(숫자1, 숫자2, 숫자3) - 숫자 중 최댓값을 반환.Math.min(숫자1, 숫자2, 숫자3) - 숫자 중 최솟값을 반환.Math.pow(숫자, 제곱값) - 숫자의 거듭제곱한 값을 반환.Math.random() - 0~1 사이의 난수를 반환.Math.round(숫자) - 소수점 첫째 자리에서 반올림하여 정수를 반환.Math.ceil(숫자) - 소수점 첫째 자리에서 무조건 올림에서 정수를 반환.Math.floor(숫자) - 소수점 첫째 자리에서 무조건 내림해서 정수를 반환.Math.sqrt(숫자) - 숫자의 제곱근 값을 반환.Math.PI - 원주율 상수를 반환.배열 객체 ArrayArray 생성var array = new Array();array[0] = 1;array[1] = 2;var array2 = new Array(1, "temp", true);var array3 = [1, true, "문자열도 가능"];Array 객체의 메서드 및 속성join(연결문자) - 배열 객체에 데이터를 연결 문자 기준으로 1개의 문자형 데이터로 반환.reverse() - 배열 객체에 데이터의 순서를 거꾸로 바꾼 후 반환.sort() - 배열 객체에 데이터를 오름차순으로 정렬.slice(index1, index2) - 배열 객체에 데이터 중 원하는 인덱스 구간만큼 잘라서 배열 객체로 가져옴.splice() - 배열 객체에 지정 데이터를 삭제하고 그 구간에 새 데이터를 삽입할 수 있음.concat() - 2개의 배열 객체를 하나로 결합.pop() - 배열에 저장된 데이터 중 마지막 인덱스에 저장된 데이터 삭제.push(new data) - 배열 객체에 마지막 인덱스에 새 데이터를 삽입.shift() - 배열 객체에 저장된 데이터 중 첫 번째 인덱스에 저장된 데이터를 삭제.unshift(new data) - 배열 객체의 가장 앞의 인덱스에 새 데이터를 삽입.length - 배열에 저장된 총 데이터의 개수를 반환.문자 객체 StringString 생성var str = "hello";      var str2 = new String("hi");String 객체 메서드 및 속성charAt(index) - 문자열에서 인덱스 번호에 해당하는 문자 반환.indexOf("찾을 문자") - 문자열에서 왼쪽부터 찾을 문자와 일치하는 문자를 찾아 최초로 일치하는 문자의 인덱스 번호를 반환. 찾는 문자가 없으면 -1 반환.lastIndexOf("찾을 문자") - indexOf와 동일하나 문자열의 오른쪽부터 찾음.match("찾을 문자") - indexOf와 동일하나 찾는 문자가 없으면 null을 반환.replace("바꿀 문자", "새 문자") - 문자열에서 왼쪽부터 바꿀 문자와 일치하는 문자를 찾아 최초로 찾은 문자를 새 문자로 치환.search("찾을 문자") - 문자열 왼쪽부터 찾을 문자와 일치하는 문자를 찾아 최초로 일치하는 인덱스 번호를 반환.slice(a, b) - a개의 문자를 자르고 b번째 이후에 문자를 자른 후 남은 문자를 반환.substring(a, b) - a 인덱스부터 b 인덱스 이전 구간의 문자를 반환.substr(a, 문자 개수) - 문자열에 a 인덱스부터 지정한 문자 개수만큼 문자열을 반환.split("문자") - 지정한 문자를 기준으로 문자 데이터를 나누어 배열에 저장하여 반환.toLowerCase() - 문자열에서 영문 대문자를 모두 소문자로 바꿈.toUpperCase() - 문자열에서 영문 소문자를 모두 대문자로 바꿈.length - 문자열에서 문자의 개수를 반환.concat("새로운 문자") - 문자열에 새로운 문자열을 결합.charCodeAt("찾을 문자") - 찾을 문자의 아스키 코드 값을 반환.fromCharCode(아스키 코드 값) - 아스키 코드 값에 해당하는 문자를 반환.trim() - 문자의 앞 또는 뒤에 공백 문자열을 삭제.브라우저 객체 모델(BOM)브라우저에 내장된 객체. window 객체브라우저 객체의 최상위 객체.window 객체 메서드open("url 경로", "창 이름", "옵션 설정") - 새 창을 열 때 사용.- open() 메서드 옵션 설정: width/height/left/top/location/status/scrollbars/tollbarsalert("메세지") - 경고 창을 띄움.prompt("질의 내용", "기본 답변") - 질의응답 창을 띄움.confirm("질의 내용") - 확인/취소 창을 띄움.- 확인 클릭시 true 반환, 취소 클릭시 false 반환.moveTo(x 위치값, y 위치값) - 창의 위치를 이동시킬 때 사용.resizeTo(너빗값, 높잇값) - 창의 크기를 변경시킬 때 사용.setInterval("스크립트 실행문", 시간 간격) - 일정 간격으로 반복하여 실행문을 실행시킬 때 사용.clearIntervar(참조 변수) - 참조 변수에 참조되어 있는 setInterval() 삭제.setTimeout("스크립트 실행문", 시간 간격) - 일정 간격으로 한 번만 실행문을 실행시킬 때 사용.clearTimeout(참조 변수) - 참조 변수에 참조되어 있던 setTimeout() 삭제.screen 객체사용자의 모니터 정보를 제공하는 객체.screen 객체 속성width/height/availWidth/availHeight/colorDepth(사용자 모니터가 표현 가능한 컬러 bit)location 객체사용자 브라우저의 주소 창에 url에 대한 정보와 새로 고침 기능을 제공하는 객체.location 객체 속성 및 메서드href - 주소 영역에 참조 주소를 설정하거나 URL 반환.hash - URL의 해시값을 반환.hostname - URL의 호스트 이름을 설정하거나 반환.host - URL의 호스트 이름과 포트 번호를 반환.port - URL의 포트 번호를 반환.protocol - URL의 프로토콜을 반환.search - URL의 쿼리를 반환.reload() - 새로 고침.history 객체사용자가 방문한 사이트 중 이전에 방문한 사이트와 다음 방문한 사이트로 다시 돌아갈 수 있는 속성과 메서드를 제공하는 객체.history 메서드 및 속성back() - 이전 방문한 페이지로 이동.forward() - 다음 방문한 페이지로 이동.go(이동 숫자) - 이동 숫자만큼의 페이지로 이동. 음의 값이면 이전 페이지로 이동.length - 방문 기록에 저장된 목록의 개수 반환.navigator 객체현재 방문자가 사용하는 브라우저 정보와 운영체제의 정보를 제공하는 객체.navigator 속성appCodeName - 방문자의 브라우저 코드명을 반환.appName - 방문자의 브라우저 이름 반환.appVersion - 방문자의 브라우저 버전 정보를 반환.language - 방문자의 브라우저 사용 언어를 반환.product - 방문자의 브라우저 사용 엔진 이름을 반환.platform - 방문자의 브라우저를 실행하는 운영체제를 반환.userAgent - 방문자의 브라우저와 운영체제의 종합 정보를 제공.문자 객체 모델(DOM)HTML 문서의 구조.선택자직접 선택자직접 문서에서 요소를 선택함. (id/class/폼 명/요소 명 등)document.getElementById("아이디 명") - 아이디를 이용해 요소를 선택.document.getElmentsByTagName("요소 명") - 요소의 이름을 이용해 요소를 선택.document.formName.inputName - 폼 요소에 name 속성을 이용해 요소를 선택.인접 관계 선택자직접 선택자를 사용해 선택해 온 문서 객체를 기준으로 가까이에 있는 요소를 선택함. (parentNode/childeNodes 등)parentNode - 선택한 요소의 부모 요소를 선택.childNodes - 선택한 요소의 모든 자식 요소를 선택. 선택한 모든 요소가 저장됨.firstChild - 선택한 요소의 첫 번째 자식 요소만 선택.previousSibling - 선택한 요소의 이전에 오는 형제 요소만 선택.nextSibling - 선택한 요소의 다음에 오는 형제 요소만 선택.문서 객체 이벤트 핸들러 적용하기onclick - 선택한 요소를 클릭했을 때 이벤트 발생.onmousevoer - 선택한 요소에 마우스를 올렸을 때 이벤트 발생.onmouseout - 선택한 요소에 마우스가 벗어났을 때 이벤트 발생.submit - 선택한 폼에 전송이 일어났을 떄 이벤트 발생.버튼document.getElementById("btn").onclick = function() {    alert("welcome");}일단은 참고하는 책을 기준으로하여 정리해보았는데 후에 시간이 될 때마다 공식 문서를 참고하여 번역한다는 생각으로 보다 세부적인 사항을 정리해도 좋을 것 같다는 생각이 드네요. 우선적으로는 빠르게 함수와 이벤트에 대해 배우고 객체에 대한 더 자세한 사항을 정리하도록 하겠습니다. 다음 포스팅은 자바스크립트의 함수와 이벤트에 대해 다룰 예정입니다!참고문헌:Do it! 자바스크립트+제이쿼리 입문 - 정인용JavaScript 튜토리얼 문서 (http://www.w3schools.com/js/default.asp)티스토리 블로그와 동시에 포스팅을 진행하고 있습니다.http://madeitwantit.tistory.com#트레바리 #개발자 #안드로이드 #앱개발 #Node.js #백엔드 #인사이트 #경험공유
조회수 2189

Good Developer 3 | 나쁜 개발자의 11가지 습관

세상에 나쁜 개발자는 없다. 나쁜 개발 습관만 있을 뿐나쁜 개발자란 누구를 지칭하는 것일까? 코드가 별로인 개발자? 커뮤니케이션이 안되는 개발자? 나쁜 개발자로 지칭될 수 있는 사람들은 굉장히 많다. 하지만, 세상에는 나쁜 개발자는 없다고 생각한다. 단지, 나쁜 개발 습관만 존재할 뿐. 즉, 누구든지 나쁜 습관을 버리고 좋은 습관을 갖는다면 언제든지 좋은 개발자가 될 수 있다는 것이다. 좋은 개발자, 나쁜 개발자. 이것은 칭호가 아니라 속성일 뿐이다. 언제든지 바뀔 수 있는 속성 말이다.이것이 속성인 이유는 누구든지 좋은 개발자와 나쁜 개발자의 속성들을 가지고 있기 때문이다. 단지 그 속성의 비율의 차이가 그 사람이 어떤 개발자인지 결정할 뿐이다. 흔히, 좋은 개발자라고 불리는 사람도 나쁜 개발 습관이 있을 수 있다. 또, 나쁜 개발자라고 욕을 먹는 사람도 좋은 개발 습관이 있을 수 있다.우리는 이 글에서 나쁜 개발 습관(혹은 속성)들을 알아보고 왜 그것이 나쁜지 그리고 그것을 어떻게 피하는지에 대해 이야기할 것이다. 좋은 습관이 아니라 나쁜 습관들을 이야기하는 이유가 있다. 좋은 습관은 습득하기 어렵다. 하지만, 나쁜 습관을 버리는 것은 더더욱 어렵다. 나쁜 습관을 피하는 것이 때로는 좋은 개발자가 되기 위한 요건일 수도 있다. 아래의 습관들을 보면서 자신을 진단해 보자.(아래의 습관들 중 습관인 것들도 있고 단순히 사고방식이나 경향인 것들이 있다. 여기서 습관은 사고방식이나 행동의 양식 등 총체적인 행동 방식 등을 의미한다.)습관 1: 코드 리뷰가 없다.지난번에 같이 해보니까 험악만 말만 나오고, 분위기만 안 좋아졌다. 후배들에게 코드 지적받는 것도 자존심 상하고... 그리고 대부분 시니어들이 지적하고 주니어들은 고개만 끄덕이는 자리 아닌가? 코드 리뷰 할 시간에 코드 한 줄이라도 더 짜서 프로젝트 마감일이나 지키는 게 낫지. 솔직히, 프로라면 자기 코드는 자기가 책임져야 하는 거 아닌가?습관 2: 문서화를 하지 않는다.아니 개발할 시간도 부족한데 무슨 문서화인가. 개발자가 개발하는 사람이지 문서 만드는 사람인가? 인수인계받을 사람 오면 직접 알려주면서 일주일이면 끝날 텐데 말이다. 그리고 이때까지 만든 문서들 만들고 나서 본적이나 있나? 그냥 보여주기식 파일이지 뭐.습관 3: 커뮤니케이션 향상에 관심이 없다.지금도 말 잘하고 대화 잘 통하는데 더 향상시킬게 있나? 그리고 개발자의 핵심은 커뮤니케이션이 아니라 코딩인데 말이야. 컴퓨터랑만 잘 소통하면 되지. 어차피 다른 부서에 있는 사람들은 개발 기술에 대해서 잘 알지도 못하고... 커뮤니케이션 스킬은 그런 사람들이 향상시켜야 한다고 생각한다.습관 4: 업무 공유가 되지 않는다. 자신의 일에 대해 알고 있는 사람이 없다. 데드라인 잘 지키고, 주어진 일을 잘 해내면 된다고 생각한다. 보고를 하기 전까지 굳이 보고하지 않고, 동료나 후배들과 업무 공유를 잘 하지 않는다. 어차피 내가 하는 일에 별로 관심도 없는데 공유해봤자 무슨 소용인가?습관 5: 코드의 복붙(복사 후 붙여넣기)가 '일상화'되어 있다.직접 만드는 것보다 이미 만들어진 코드들을 찾아서 Ctrl +c,v하는게 더 빠르고 생산성 있다고 생각한다. 동료 개발자랑 공통 모듈을 만들어 사용할 수 있겠지만 그렇게 하기에는 너무 많은 리소스가 낭비된다고 생각한다. 잘 돌아가기만 하면 되지 않나?습관 6: 자신의 부족한 점을 드러내지 않는다.부족한 점에 대해 동료들과 터놓고 얘기하지 않는다. 괜히 부끄럽고 껄끄럽기도 하고 자신의 부족한 점이 드러나는 것이 두렵다. 동료들이 조언을 해주려고 해도 방어적으로 나오거나 피한다. 동료의 진솔한 피드백이 없으니 한 번 단점을 만들면 끝까지 내 것으로 가져간다.습관 7: 새로운 기술을 익히는데 시간을 투자하지 않는다.세상은 정말 빠르게 변하고 있다. 그리고 그 변화의 중심은 기술이고 기술 중에서도 IT 기술이 정점에 있다고 봐도 무방하다. 새로운 기술은 새로운 기술자들이 익히는 것이라 생각한다. 지금 하고 있는 일만으로도 벅차다. 그리고 지금 쓰는 기술이 시대의 주류인데 쉽게 바뀔까?습관 8: 자신의 개발 환경에서 벗어나지 않는다.개발자 모임이나 개발 커뮤니티에 시간을 쓰는 것은 낭비라고 생각한다. 개발에 대해 새로운 시도를 하지 않는다. 새로운 프레임워크나 협업 툴들이 나와도 기존의 환경을 고집한다. 왜냐하면 지금 개발 환경이 너무 편하고 익숙하니까.습관 9: 자신이 맡은 개발과 관련된 비즈니스를 이해하지 않는다.개발자는 개발에만 신경 쓰면 된다고 생각한다. 지금 개발하고 있는 서비스의 비즈니스적 관점은 생각해 본 적 없다. 어차피 기획자나 마케터, 프로덕트 매니저가 신경 써야 할 일이라고 생각한다. 개발만으로도 바쁜데 그것까지 신경 쓰면 정말 골치 아파진다.습관 10: 개발에 대한 지신만의 장기적인 목표가 없다.어떤 개발자가 되어야 하는지에 대한 목표가 없다. 주어진 프로젝트 외에 자신이 하고 싶은 프로젝트를 하면서 개발을 발전시키지 않는다. 그냥 개발의 메인 스트림을 따라만 간다. 커리어나 다른 생활에 대한 걱정은 종종 하지만, 개발 자체에 대한 고민은 하지 않는다.습관 11: 자신의 나쁜 개발 습관에 관심이 없다.(습관은 아니지만....)내가 나쁜 개발자라고...? 내가 하고 있는 것들이 나쁜 습관들이라고??? 글쎄..... 그냥저냥 잘 하고 있는 거 같은데.... 라고 생각하는 당신! 아무리 좋은 개발자라도 나쁜 습관은 존재하기 마련이다. 좋은 개발자는 좋은 습관들을 가지고 있는 개발자기도 하지만, 나쁜 습관들이 많지 않은 개발자이기도 하다.나쁜 환경은 나쁜 개발자를 만든다.당신이 만약 스스로를 나쁜 개발자라고 생각한다면 아마 '나쁜' 환경에서 개발을 했을 가능성이 크다. 혹은 선배가 나쁜 개발자여서 그 습관을 그대로 보고 배웠다든지, 아니면 좋은 개발자에 대한 고민 없이 흘러가듯 개발을 배웠을 것이다. 예를 들어, 코드 리뷰를 하지 않았던 것은 회사에서 코드 리뷰를 안 했을 가능성이 크다. 혹은 문서화를 안 하는 경우, 그 회사에서 그것에 대해 크게 신경 쓰기 있지 않을 가능성이 크다.Bad developers are not born, but created.위에서도 언급했듯이 나쁜 개발자는 없다. 나쁜 습관들이 있을 뿐. 당신이 지금 위의 습관에서 많은 부분들이 해당된다 하더라도, 그 습관들을 바꾸면 된다. 다른 개발자들에게 있는 좋은 습관들을 보고 배우면서 자신에게 해당되는 나쁜 습관들을 하나씩 바꿔나가는 것이다.환경이 바뀐다고 자신이 바뀌지는 않겠지만, 나쁜 환경이 나쁜 개발자를 만드는 것처럼, 좋은 환경은 좋은 개발자를 만든다. 좋은 환경을 찾아가라! 직장이 그걸 주지 못한다면 다른 곳에서라도 찾아라. 좋은 개발자는 나쁜 습관들을 하나씩 바꿔나갈 때 될 수 있을 것이다. 다음 포스팅에서는 좋은 개발자가 되기 위해 필요한 정보들을 압축적으로 모아 포스팅할 것이다.

기업문화 엿볼 때, 더팀스

로그인

/