스토리 홈

인터뷰

피드

뉴스

조회수 1147

[인공지능 in IT] 인공지능과 저널리즘

얼마 전, 재미있는 기사를 읽었다. 일본의 한 SF 공모전에 응모한 작품 1,400편 중 인공지능이 작성한 소설 두 편이 예선 심사를 통과했다는 내용이었다. 이 중 소설 한편의 제목은 '컴퓨터가 소설을 쓴 날'이다. 소설을 작성하는 인공지능 기술을 개발한 연구팀은 육하원칙 등의 제시어를 준 뒤, 연관어에 따라 소설을 쓰는 알고리즘을 활용했다.미디어 혹은 인공지능 분야에 생소한 독자들에게 다소 신기할 수 있겠지만, 사실 인공지능을 활용한 저널리즘은 수 년 전부터 진행 중이다. 국내에서는 2014년 서울대학교 언론정보학과의 'hci+d Lab' 이준환 교수팀이 개발한 알고리즘을 시초라고 할 수 있다. '프로야구 뉴스 로봇'이라고 불리는 소프트웨어는 KBL의 모든 경기를 자동으로 요약해 정리한다. 연구팀이 처음부터 이 같은 기능을 염두에 둔 것은 아니었고, 데이터를 시각화하는 과정에서 시각화 방식을 텍스트로 바꿔본 것이 연구의 시작이라고 한다. 위 사례는 사람이 아닌 기계가 직접 '글'을 작성했다는 점에 있어 의미가 크다. 미디어 업계에서도 디지털화는 불가항력 같은 존재가 되고 있다.얼마 전, 옥스퍼드-로이터 저널리즘 연구소에서 미디어 업계를 대상으로 조사를 시행했다. "2018년 실행해야 할 가장 중요한 과제는 어떤 것이라고 생각하는지"에 대한 물음에 "데이터 수용량을 증가시키는 것"을 가장 많이 답변했다. 모바일 알림, 웹사이트나 애플리케이션에 사용자를 등록시키는 일 등 여러 과제들이 있었지만, IT 솔루션 업계도 아닌 미디어 업계가 데이터 수용량 증가를 최우선 과제로 생각하고 있다는 사실은 개인적으로 매우 충격적이었다. 또한, "현재 귀사에서는 기사 보도에 있어 어떠한 용도로 적극적인 인공지능 기술을 도입할 예정입니까?"라는 질문에 '컨텐츠 추천', '업무 자동화', '기삿거리 탐색' 등 다양한 분야에서 인공지능 기술 도입을 계획하고 있었다. 그만큼 이미 언론에서도 인공지능 기술은 먼 세상 이야기가 아닌, 당장 피부로 느껴질 정도로 가까워졌다.세계 최대 통신사 중 하나인 'Associated Press(AP)'는 2017년 'The Future of Augmented Journalism: A guide for newsrooms in the age of smart machines'이라는 인공지능 활용 기술 가이드를 발간했다. 해당 가이드에 따르면, 인공지능은 언론에서 크게 다섯가지 영역으로 활용된다. 이에 대한 예시를 하나씩 살펴보도록 하자.첫번째로 'Machine Learning', 즉 기계학습이다. 기계학습을 이용하면, 방대한 데이터로부터 결론을 도출하는 과정을 쉽게 처리할 수 있다. 그리고 기계학습 알고리즘을 통해 기자들은 이미지를 포함한 막대한 양의 자료를 한 번에 처리할 수도 있다. 미국의 매체 'Quartz' 소속 'Sarah Slobin' 기자가 트럼프 미국 대통령의 취임 연설에 대한 기사에 기계학습을 이용한 분석 자료를 쓴 일례가 있다. 트럼프의 얼굴 표정과 연설에서 표현된 감정을 판단하는 데에 기계학습 알고리즘을 사용한 것.< 출처: Quartz, 제공: 스켈터랩스 >두번째 활용 영역은 'Language'다. 인공지능 분야에서 언어에 대한 연구는 꾸준히 이어지고 있는데, 언어 처리 분야 중에서도 저널리즘과 관련 있는 기술은 '자연어 생성'과 '자연어 처리'다. 당연하겠지만, 자동으로 문장을 생성하는 것은 언론에서 매우 유용하게 사용할 수 있는 기술 중 하나다. 'LA Times'는 'LA Quakebot'이라는 서비스를 개발했다. 'LA Quakebot'은 자연어 생성 기술을 활용해 지역에서 지진이 일어난 순간, 이미 작성된 프레임에 맞춰 기사를 작성하며, 완성된 기사는 트위터를 통해 송출한다.< 출처: LA QuakeBot 트위터, 제공: 스켈터랩스 >세번째는 'Speech'로, 저널리즘에서 대화형 인터페이스가 뉴스 소비 및 유통에 어떠한 영향을 미칠 지 관심을 가지고 있다. 이미 'AP', 'Wall Street Journal', 'BBC', 'Economist' 등 여러 미디어가 오디오 인터페이스 기술을 시도하는 것으로 알려졌다. Speech 역시 크게 두 가지로 나뉘는데, 'TTS'라고 불리는 'Text-To-Speech'를 활용하면 뉴스룸에서 제공하는 문자 기사를 음성으로 변환시키고, 합성된 음성을 콘텐츠로 송출할 수 있다. 반대로 'STT', 즉 'Speech-To-Text'를 활용하면 음성으로부터 의미를 잡아내고, 모든 의도와 목적에 맞춰 음성을 문자로 변환시키며, 이를 통해 기자들이 인터뷰 내용을 녹취하는데 소요하는 시간을 줄일 수 있다.< 출처: BBC NEWS LABS, 제공: 스켈터랩스 >네번째, 듣는 것과 녹취하는 것을 넘어 눈으로 본 것을 기록할 수 있는 'Vision' 기술이다. 컴퓨터 비전을 활용하면 빠르고 쉽게 이미지 및 영상을 분류하고 정리할 수 있다. 용이한 검색을 통해 궁극적으로 편집 속도까지 높일 수 있는 셈이다. 'AP'는 인공위성으로 수집한 영상 데이터를 공급하는 'Digital Globe'라는 기업을 통해 동남아 선박의 고해상도 위성사진을 확보했다. 이를 통해 노예선에 관한 탐사보도에 필요한 결정적인 증거를 찾으며, 2016년 공공서비스 부문 퓰리처상을 수상했다.< 출처: AP, 제공: 스켈터랩스 >마지막으로 'Robotics'를 꼽을 수 있다. 로봇 센서를 활용해 사건 사고에 대한 사람들의 반응을 실시간으로 측정할 수 있으며, 앞서 언급한 'Quakebot'의 예처럼 자연재해가 발생하는 것에 대해 다룰 수 있다. 'AP'는 2016년 하계올림픽 당시, 로봇과 원격 카메라를 이용해 기자들이 물리적으로 직접 접근할 수 없는 지역에 카메라를 설치하고, 원격 조종해 촬영했다. 또한, 드론을 이용해 이라크 모술 남동쪽 다이바가 근처에 추방된 이라크인들을 촬영해 중독 지역 난민 위기에 대해서도 보도한 바 있다.< 출처: AP, 제공: 스켈터랩스 >이렇듯 인공지능이 미디어 업계 전체에 긍정적인 영향을 주고 있으며, 이를 활용한 사례는 앞으로도 더욱 늘어날 것으로 전망한다. 다만, 지속적으로 발전하는 인공지능을 무조건 도입하는 것만이 능사는 아니다. 인공지능 기술의 확산으로 보도 속도, 보도 규모 및 범위 등에 도움될지라도, 데이터의 질에 따라 좋지 않은 기사가 나올 수 있기 때문이다. 'AP'의 스마트머신 시대 뉴스룸을 위한 가이드에도 언급된 포인트로 마무리를 해보자.1. 인공지능은 저널리즘의 도구이지, 저널리즘을 대체하지 않을 것이다.2. 인공지능은 인간과 마찬가지로 편향적이고, 실수를 할 수도 있다. 이는 데이터가 모든 것을 결정하기 때문이다.3. 인공지능이 만병통치약은 아니다. 최근 자율주행 자동차 사고 이슈처럼 기술이 극복하지 못하는 문제는 여전히 존재한다.4. 인공지능에 대해 더 많이 알아야 인공지능 활용 가능성의 문이 크게 열린다.5. 저널리즘의 도구가 변한다고 해서 저널리즘의 법칙이 변하지 않는다. 언제나 윤리와 기준은 매우 중요하다.이호진, 스켈터랩스 마케팅 매니저조원규 전 구글코리아 R&D총괄 사장을 주축으로 구글, 삼성, 카이스트 AI 랩 출신들로 구성된 인공지능 기술 기업 스켈터랩스에서 마케팅을 담당하고 있다 #스켈터랩스 #기업문화 #인사이트 #경험공유 #조직문화 #인공지능기업 #기술기업
조회수 1190

테이블이냐, 컬렉션이냐, 그것이 문제로다!(KOR)

편집자 주 외래어 표기법에 따르면 ‘원어에서 띄어 쓴 말은 띄어 쓴 대로 한글 표기를 하되, 붙여 쓸 수도 있다.’고 규정하고 있다.(제3장 제1절 영어의 표기, 제10항과, 컴퓨터 전문어, 전기 전문어 등) 즉 ‘원칙’과 ‘허용’이 모두 가능하다는 의미다. 이를 바탕으로 여러 표기 용례를 참고한 결과, TableView는 ‘테이블뷰(원칙)’로 표기해야 하나, 본문에서는 독자의 가독성을 높이기 위해 ‘테이블 뷰(허용)’로 표기한다. 응용하여, CollectionView는 ‘컬렉션 뷰’로, TableViewCell은 ‘테이블 뷰 셀’ 등으로 띄어 쓴다. Overview앱에서 데이터를 사용자에게 보여줄 땐 여러 가지의 모습으로 나타납니다. 설정 앱처럼 목록으로 보여줄 때도 있고, 사진 앱처럼 그리드(grid) 형식으로 보여줄 때도 있습니다. 이처럼 데이터를 보여줄 때 많이 사용되는 뷰는 테이블 뷰(UITableView) 또는 컬렉션 뷰(UICollectionView)입니다. 각자 특징이 있기 때문에 앱의 성격에 따라 적절한 뷰를 사용해야 합니다. 왜냐하면 목록을 보여주는 디자인을 바꿀 때, 다시 개발해야 하는 수고를 덜 수 있기 때문입니다. 이번 글에선 각각의 뷰를 간략하게 알아보겠습니다. 목록 형식의 설정 앱과 그리드 형식의 사진 앱 스크린샷테이블 뷰(UITableView)단일 열에 배열된 행을 사용해 데이터를 표시하는 뷰입니다. 수직 스크롤만 가능하며, 테이블의 개별 항목을 구성하는 셀은 테이블 뷰 셀(UITableViewCell) 객체입니다. 테이블 뷰는 이 객체들을 이용해 테이블에 표시되는 행을 그립니다. 여러 행은 하나의 섹션 안에 구성될 수 있으며, 각 섹션은 헤더(header)와 푸터(footer)를 가질 수 있습니다. 섹션과 행은 인덱스 번호로 구별하는데, 번호는 0부터 시작합니다. 테이블 뷰는 plain과 grouped 스타일 중 한 가지의 스타일을 가질 수 있습니다. Plain 스타일은 보통 목록 스타일입니다. 섹션의 헤더와 푸터는 섹션 분리기(inline separators)로 표시되고 스크롤을 할 때 해당 섹션 안에 있는 콘텐츠 위에 나타납니다. Grouped 스타일은 시각적으로 뚜렷한 행 그룹을 표시하는 섹션이 있습니다. 섹션의 헤더와 푸터는 콘텐츠 위에 나타나지 않습니다. 아래와 같은 사진을 보시면 확연히 차이를 볼 수 있습니다. plain 스타일의 연락처 앱과 grouped 스타일의 설정 앱테이블 뷰의 많은 메소드들은 인덱스패스(NSIndexPath) 객체를 매개변수 또는 리턴 값으로 사용합니다. 테이블 뷰는 해당하는 행의 색인 인덱스와 섹션 인덱스 값을 가져올 수 있게 인덱스패스의 범주를 선언합니다. 또한 색인 인덱스와 섹션 인덱스 값을 가지고 인덱스패스를 만들 수 있습니다. 특히 여러 섹션이 있는 테이블 뷰는 섹션 인덱스 값이 반드시 있어야 행의 인덱스 번호로 구별할 수 있습니다.override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> AttractionTableViewCell {         // Table view cells are reused and should be dequeued using a cell identifier.         let cellIdentifier = "AttractionTableViewCell"              guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? AttractionTableViewCell else {             fatalError("The dequeued cell is not an instance of AttractionTableViewCell.")         }                 let attraction = attractions[indexPath.row]                 cell.attractionLabel.text = "\(indexPath.row). \(attraction.nameWithDescription)"         cell.attractionImage.image = attraction.photo                 cell.attractionImage.tag = indexPath.row                 attraction.indexPath = indexPath                 ...                 return cell     } 위의 코드는 데이터 소스(data source) 메소드로, 테이블 뷰의 특정한 위치에 셀을 추가합니다. 다시 말해, 이 메소드는 테이블 뷰가 ‘표시할 새로운 셀이 필요할 때마다’ 특정 행에 노출할 정보가 있는 셀을 만들고 리턴하는 걸 말합니다. 매개변수로 필요한 셀 객체의 행을 가리키는 indexPath 값을 전달합니다. 그리고 indexPath의 row 값을 이용해서 attraction이라는 배열 인덱스로 활용하고, 셀에 표시할 정보들을 설정합니다. 여기서 attraction 배열은 관광 명소들의 정보들이 담고 있는 배열인데, 1행은 첫 번째로 저장한 관광 명소, 2행은 두 번째로 저장한 관광 명소 등 순서대로 설정하도록 indexPath.row 값을 이용하는 것입니다. indexPath의 row 값과 배열의 인덱스 값은 0부터 시작하기 때문입니다. 해당 예제는 섹션이 1인 경우이기 때문에 섹션 인덱스 값이 없지만, 섹션이 여러 개 있다면 반드시 섹션 인덱스 값을 이용해서 설정해야 합니다.테이블 뷰 객체는 데이터 소스(data source)와 델리게이트(delegate)가 필요합니다. 데이터 소스는 UITableViewDataSource 프로토콜을 구현해야 하고, 델리게이트는 UITableViewDelegate 프로토콜을 구현해야합니다. 데이터 소스는 테이블 뷰가 테이블을 만들 때 필요한 정보를 제공하고 테이블의 행이 추가, 삭제 또는 재정렬할 때 데이터 모델을 관리합니다. 델리게이트는 화면에 보이는 모습과 행동을 담당합니다. 예를 들어 표시할 행의 수, 사용자가 특정 행을 터치했을 때, 행의 재정렬 등과 같은 것입니다.override func numberOfSections(in tableView: UITableView) -> Int {         // #warning Incomplete implementation, return the number of sections         return 1     }      override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {         // #warning Incomplete implementation, return the number of rows         return attractions.count     } 위의 두 소스는 데이터 소스가 필수적으로 구현해야 하는 메소드입니다. 하나는 섹션의 개수를 리턴하고, 또 하나는 한 섹션 안에 있는 행의 개수를 리턴합니다.테이블 뷰는 수정 모드에서 행을 추가, 삭제, 재정렬할 수 있습니다. 각 행은 테이블 뷰 셀에 연관된 editingStyle에 따라서 추가, 삭제, 재정렬을 할 수 있는데, 예를 들어 editingStyle이 insert라면 추가하는 메소드를 실행하고, delete면 삭제하는 메소드를 실행합니다. 행의 showsReorderControl 속성이 true라면, 재정렬하는 메소드를 실행할 수 있습니다.// Override to support editing the table view.     override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {         if editingStyle == .delete {             // Delete the row from the data source             ...                 // delete rows and attractions and reload datas             attractions.remove(at: indexPath.row)             tableView.deleteRows(at: [indexPath], with: .middle)             tableView.reloadData()         } else if editingStyle == .insert {             // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view         }     } 위 소스는 editingStyle이 delete일 때 셀을 삭제하고 테이블 뷰를 다시 로드하는 기능을 구현한 것입니다.테이블 뷰를 만드는 가장 쉽고 권장하는 방법은 바로 스토리보드에서 테이블뷰컨트롤러(UITableViewController)를 이용해서 만드는 겁니다. 런타임에 테이블뷰컨트롤러는 테이블 뷰를 만들고 델리게이트와 데이터 소스를 자기 자신으로 할당합니다.컬렉션 뷰(UICollectionView)컬렉션 뷰는 테이블 뷰에서 할 수 있는 모든 것을 할 수 있습니다. 섹션을 가질 수 있고, 인덱스패스 값을 이용해서 셀을 구별합니다. 이 셀들은 컬렉션 뷰 셀(UICollectionViewCell)의 서브 클래스이며 데이터 소스(UICollectionViewDataSource)와 델리게이트(UICollectionViewDelegate)가 필요합니다. 셀을 추가, 삭제, 재정렬하는 기능도 구현할 수 있습니다. 그렇다면 컬렉션 뷰와 테이블 뷰를 구분하는 특징은 무엇일까요? 바로 레이아웃입니다. 컬렉션 뷰는 여러 개의 열과 행으로 셀을 표현할 수 있습니다. 예를 들어, 그리드(grid) 형태로 아이템의 목록을 보여줄 수 있습니다. 그래서 수직 스크롤뿐만 아니라 수평 스크롤도 할 수 있습니다.스토리보드에서 디자인한 테이블 뷰 셀과 컬렉션 뷰 셀위 스크린샷에서 테이블 뷰와 컬렉션 뷰의 가장 큰 차이는 바로 셀입니다. 테이블 뷰에서는 하나의 열에 여러 행을 표시하는 형식이기 때문에, 셀의 모습을 행에 맞춰서 디자인합니다. 하지만 컬렉션 뷰는 열과 행을 만들 수 있기 때문에, 꼭 행의 모습이 아니더라도 다양한 모습으로 셀을 디자인할 수 있습니다. 컬렉션 뷰 셀의 가장 큰 특징이기도 하죠. 위처럼 셀을 디자인하고 앱을 실행하면 아래의 화면이 나타납니다.테이블 뷰와 컬렉션 뷰의 앱 화면 차이또한 컬렉션 뷰는 레이아웃 객체가 있습니다. 기존에 제공하는 flow layout을 사용해도 괜찮지만, 본인이 원하는 레이아웃 모양을 custom layout을 만들어서 사용합니다. 이를 담당하는 프로토콜은 UICollectionViewDelegateFlowLayout 입니다.func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {         let fullWidth = collectionView.frame.size.width - (self.CGFLOAT_INSET_WIDTH * 3) - (self.CGFLOAT_ITEMSPACING * 3)         let width = fullWidth/3         return CGSize(width: width, height: width + self.CGFLOAT_HEIGHT_ATTRACTIONCELL_DEFAULT)     }         func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {         return UIEdgeInsetsMake(self.CGFLOAT_LINESPACING_VERTICAL, self.CGFLOAT_INSET_WIDTH, self.CGFLOAT_LINESPACING_VERTICAL, self.CGFLOAT_INSET_WIDTH)     } 위 소스에서 collectionView(:layout:sizeForItemAt:) 메소드는 해당하는 셀의 사이즈를 설정하고, collectionView(:layout:insetForSectionAt:) 메소드는 섹션 안에 margin을 설정합니다.여러 모양의 셀을 이루어 하나의 뷰 화면을 구현할 수도 있습니다. 섹션마다 셀을 만들어 각각 다른 모습의 셀을 설정하고, 한 화면에 다양한 모습의 셀을 가진 뷰를 만드는 것입니다. 예를 들어, 헤더, 메뉴, 본문, 푸터 각각 셀을 만들어서 원하는 모양으로 만들고, 하나의 뷰 컨트롤러에 셀을 조합해서 한 화면에 나타나게 할 수 있습니다. 이 방법을 사용하면 자주 사용하는 셀을 재활용할 수 있습니다. 똑같은 헤더와 푸터 셀을 여러 번 만들지 않고 기존의 셀을 재활용하면 시간도 절약하고, 훨씬 깔끔한 소스를 만들 수 있을 겁니다.브랜디 앱 스크린샷 일부위의 스크린샷처럼 여러 화면에서 보여줘야 할 똑같은 뷰가 있을 때, 셀 xib 파일을 만들고 컬렉션 뷰에서 셀을 섹션별로 설정 및 사용하면 재활용하기 좋습니다.Conclusion지금까지 테이블 뷰와 컬렉션 뷰의 특징들을 살펴봤습니다. 한마디로 정리하면 테이블 뷰는 가장 간단한 목록을 만들 수 있습니다. 컬렉션 뷰는 다양한 모습의 목록으로 커스터마이징(Customizing)할 수 있습니다.그렇다면 우리는 어떤 것을 선택해야 할까요? 구현할 목록이 얼마나 복잡한지에 따라 선택은 달라집니다. 테이블 뷰는 간단하고 보편적인 목록을 만듭니다. 반면에 컬렉션 뷰는 특정한 모습의 목록을 만들 수 있습니다. 그래서 테이블 뷰는 목록이 간단하고 디자인 변경이 없을 때만 사용하길 권장합니다. 하지만 나중에 디자인이 바뀔 수도 있다면 컬렉션 뷰를 사용하는게 더 좋겠죠.Simple is the best! 간단하게 구현할 수 있는 건 테이블 뷰를 사용합시다. 테이블 뷰에서 구현하기 힘들다면 컬렉션 뷰를 이용해 개성 있는 목록을 마음껏 만들어봅시다!참고UITableView - UIKit | Apple Developer DocumentationUICollectionView - UIKit | Apple Developer Documentation 글김주희 사원 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 1082

안드로이드 클라이언트 Reflection 극복기 - VCNC Engineering Blog

 비트윈 팀은 비트윈 안드로이드 클라이언트(이하 안드로이드 클라이언트)를 가볍고 반응성 좋은 애플리케이션으로 만들기 위해 노력하고 있습니다. 이 글에서는 간결하고 유지보수하기 쉬운 코드를 작성하기 위해 Reflection을 사용했었고 그로 인해 성능 이슈가 발생했던 것을 소개합니다. 또한 그 과정에서 발생한 Reflection 성능저하를 해결하기 위해 시도했던 여러 방법을 공유하도록 하겠습니다.다양한 형태의 데이터Java를 이용해 서비스를 개발하는 경우 POJO로 서비스에 필요한 다양한 모델 클래스들을 만들어 사용하곤 합니다. 안드로이드 클라이언트 역시 모델을 클래스 정의해 사용하고 있습니다. 하지만 서비스 내에서 데이터는 정의된 클래스 이외에도 다양한 형태로 존재합니다. 안드로이드 클라이언트에서 하나의 데이터는 아래와 같은 형태로 존재합니다.JSON: 비트윈 서비스에서 HTTP API는 JSON 형태로 요청과 응답을 주고 받고 있습니다.Thrift: TCP를 이용한 채팅 API는 Thrift를 이용하여 프로토콜을 정의해 서버와 통신을 합니다.ContentValues: 안드로이드에서는 Database 에 데이터를 저장할 때, 해당 정보는 ContentValues 형태로 변환돼야 합니다.Cursor: Database에 저장된 정보는 Cursor 형태로 접근가능 합니다.POJO: 변수와 Getter/Setter로 구성된 클래스 입니다. 비지니스 로직에서 사용됩니다.코드 전반에서 다양한 형태의 데이터가 주는 혼란을 줄이기 위해 항상 POJO로 변환한 뒤 코드를 작성하기로 했습니다.다양한 데이터를 어떻게 상호 변환할 것 인가?JSON 같은 경우는 Parsing 후 Object로 변환해 주는 라이브러리(Gson, Jackson JSON)가 존재하지만 다른 형태(Thrift, Cursor..)들은 만족스러운 라이브러리가 존재하지 않았습니다. 그렇다고 모든 형태에 대해 변환하는 코드를 직접 작성하면 필요한 경우 아래와 같은 코드를 매번 작성해줘야 합니다. 이와 같이 작성하는 경우 Cursor에서 원하는 데이터를 일일이 가져와야 합니다.@Override public void bindView(View view, Context context, Cursor cursor) { final ViewHolder holder = getViewHolder(view); final String author = cursor.getString("author"); final String content = cursor.getString("content"); final Long timeMills = cursor.getLong("time"); final ReadStatus readStatus = ReadStatus.fromValue(cursor.getString("readStatus")); final CAttachment attachment = JSONUtils.parseAttachment(cursor.getLong("createdTime")); holder.authorTextView.setText(author); holder.contentTextView.setText(content); holder.readStatusView.setReadStatus(readStatus); ... } 하지만 각 형태의 필드명(Key)이 서로 같도록 맞춰주면 각각의 Getter와 Setter를 호출해 형태를 변환해주는 Utility Class를 제작할 수 있습니다.@Override public void bindView(View view, Context context, Cursor cursor) { final ViewHolder holder = getViewHolder(view); Message message = ReflectionUtils.fromCursor(cursor, Message.class); holder.authorTextView.setText(message.getAuthor()); holder.contentTextView.setText(message.getContent()); holder.readStatusView.setReadStatus(message.getReadStatus()); ... } 이런 식으로 코드를 작성하면 이해하기 쉽고, 모델이 변경되는 경우에도 유지보수가 비교적 편하다는 장점이 있습니다. 따라서 필요한 데이터를 POJO로 작성하고 다양한 형태의 데이터를 POJO로 변환하기로 했습니다. 서버로부터 받은 JSON 혹은 Thrift객체는 자동으로 POJO로 변환되고 POJO는 다시 ContentValues 형태로 DB에 저장됩니다. DB에 있는 데이터를 화면에 보여줄때는 Cursor로부터 데이터를 가져와서 POJO로 변환 후 적절한 가공을 하여 View에 보여주게 됩니다.POJO 형태로 여러 데이터 변환필요Reflection 사용과 성능저하처음에는 Reflection을 이용해 여러 데이터를 POJO로 만들거나 POJO를 다른 형태로 변환하도록 구현했습니다. 대상 Class의 newInstance/getMethod/invoke 함수를 이용해 객체 인스턴스를 생성하고 Getter/Setter를 호출하여 값을 세팅하거나 가져오도록 했습니다. 앞서 설명한 ReflectionUtils.fromCursor(cursor, Message.class)를 예를 들면 아래와 같습니다.public T fromCursor(Cursor cursor, Class clazz) { T instance = (T) clazz.newInstance(); for (int i=0; i Reflection을 이용하면 동적으로 Class의 정보(필드, 메서드)를 조회하고 호출할 수 있기 때문에 코드를 손쉽게 작성할 수 있습니다. 하지만 Reflection은 튜토리얼 문서에서 설명된 것처럼 성능저하 문제가 있습니다. 한두 번의 Relfection 호출로 인한 성능저하는 무시할 수 있다고 해도, 필드가 많거나 필드로 Collection을 가진 클래스의 경우에는 수십 번이 넘는 Reflection이 호출될 수 있습니다. 실제로 이 때문에 안드로이드 클라이언트에서 종종 반응성이 떨어지는 경우가 발생했습니다. 특히 CursorAdapter에서 Cursor를 POJO로 변환하는 코드 때문에 ListView에서의 스크롤이 버벅이기도 했습니다. Bytecode 생성 Reflection 성능저하를 해결하려고 처음으로 선택한 방식은 Bytecode 생성입니다. Google Guice 등의 다양한 자바 프로젝트에서도 Bytecode를 생성하는 방식으로 성능 문제를 해결합니다. 다만 안드로이드의 Dalvik VM의 경우 일반적인 JVM의 Bytecode와는 스펙이 다릅니다. 이 때문에 기존의 자바 프로젝트에서 Bytecode 생성에 사용되는 CGLib 같은 라이브러리 대신 Dexmaker를 이용하여야 했습니다. CGLib CGLib는 Bytecode를 직접 생성하는 대신 FastClass, FastMethod 등 펀리한 클래스를 이용할 수 있습니다. FastClass나 FastMethod를 이용하면 내부적으로 알맞게 Bytecode를 만들거나 이미 생성된 Bytecode를 이용해 비교적 빠른 속도로 객체를 만들거나 함수를 호출 할 수 있습니다. public T create() { return (T) fastClazz.newInstance(); } public Object get(Object target) { result = fastMethod.invoke(target, (Object[]) null); } public void set(Object target, Object value) { Object[] params = { value }; fastMethod.invoke(target, params); }  Dexmaker 하지만 Dexmaker는 Bytecode 생성 자체에 초점이 맞춰진 라이브러리라서 FastClass나 FastMethod 같은 편리한 클래스가 존재하지 않습니다. 결국, 다음과 같이 Bytecode 생성하는 코드를 직접 한땀 한땀 작성해야 합니다. public DexMethod generateClasses(Class<?> clazz, String clazzName){ dexMaker.declare(declaringType, ..., Modifier.PUBLIC, TypeId.OBJECT, ...); TypeId<?> targetClassTypeId = TypeId.get(clazz); MethodId invokeId = declaringType.getMethod(TypeId.OBJECT, "invoke", TypeId.OBJECT, TypeId.OBJECT); Code code = dexMaker.declare(invokeId, Modifier.PUBLIC); if (isGetter == true) { Local<Object> insertedInstance = code.getParameter(0, TypeId.OBJECT); Local instance = code.newLocal(targetClassTypeId); Local returnValue = code.newLocal(TypeId.get(method.getReturnType())); Local value = code.newLocal(TypeId.OBJECT); code.cast(instance, insertedInstance); MethodId executeId = ... code.invokeVirtual(executeId, returnValue, instance); code.cast(value, returnValue); code.returnValue(value); } else { ... } // constructor Code constructor = dexMaker.declare(declaringType.getConstructor(), Modifier.PUBLIC); Local<?> thisRef = constructor.getThis(declaringType); constructor.invokeDirect(TypeId.OBJECT.getConstructor(), null, thisRef); constructor.returnVoid(); }  Dexmaker를 이용한 방식을 구현하여 동작까지 확인했으나, 다음과 같은 이유로 실제 적용은 하지 못했습니다. Bytecode를 메모리에 저장하는 경우, 프로세스가 종료된 이후 실행 시 Bytecode를 다시 생성해 애플리케이션의 처음 실행성능이 떨어진다.Bytecode를 스토리지에 저장하는 경우, 원본 클래스가 변경됐는지를 매번 검사하거나 업데이트마다 해당 스토리지를 지워야 한다.더 좋은 방법이 생각났다. Annotation Processor 최종적으로 저희가 선택한 방식은 컴파일 시점에 형태변환 코드를 자동으로 생성하는 것입니다. Reflection으로 접근하지 않아 속도도 빠르고, Java코드가 미리 작성돼 관리하기도 편하기 때문입니다. POJO 클래스에 알맞은 Annotation을 달아두고, APT를 이용해 Annotation이 달린 모델 클래스에 대해 형태변환 코드를 자동으로 생성했습니다. 형태 변환이 필요한 클래스에 Annotation(@GenerateAccessor)을 표시합니다. @GenerateAccessor public class Message { private Integer id; private String content; public Integer getId() { return id; } ... }  javac에서 APT 사용 옵션과 Processor를 지정합니다. 그러면 Annotation이 표시된 클래스에 대해 Processor의 작업이 수행됩니다. Processor에서 코드를 생성할 때에는 StringBuilder 등으로 실제 코드를 일일이 작성하는 것이 아니라 Velocity라는 template 라이브러리를 이용합니다. Processor는 아래와 같은 소스코드를 생성합니다. public class Message$$Accessor implements Accessor { public kr.co.vcnc.binding.performance.Message create() { return new kr.co.vcnc.binding.performance.Message(); } public Object get(Object target, String fieldName) throws IllegalArgumentException { kr.co.vcnc.binding.performance.Message source = (kr.co.vcnc.binding.performance.Message) target; switch(fieldName.hashCode()) { case 3355: { return source.getId(); } case -1724546052: { return source.getContent(); } ... default: throw new IllegalArgumentException(...); } } public void set(Object target, String fieldName, Object value) throws IllegalArgumentException { kr.co.vcnc.binding.performance.Message source = (kr.co.vcnc.binding.performance.Message) target; switch(fieldName.hashCode()) { case 3355: { source.setId( (java.lang.Integer) value); return; } case -1724546052: { source.setContent( (java.lang.String) value); return; } ... default: throw new IllegalArgumentException(...); } } }  여기서 저희가 정의한 Accessor는 객체를 만들거나 특정 필드의 값을 가져오거나 세팅하는 인터페이스로, 객체의 형태를 변환할 때 이용됩니다. get,set 메서드는 필드 이름의 hashCode 값을 이용해 해당하는 getter,setter를 호출합니다. hashCode를 이용해 switch-case문을 사용한 이유는 Map을 이용하는 것보다 성능상 이득이 있기 때문입니다. 단순 메모리 접근이 Java에서 제공하는 HashMap과 같은 자료구조 사용보다 훨씬 빠릅니다. APT를 이용해 변환코드를 자동으로 생성하면 여러 장점이 있습니다. Reflection을 사용하지 않고 Method를 직접 수행해서 빠르다.Bytecode 생성과 달리 애플리케이션 처음 실행될 때 코드 생성이 필요 없고 만들어진 코드가 APK에 포함된다.Compile 시점에 코드가 생성돼서 Model 변화가 바로 반영된다. APT를 이용한 Code생성으로 Reflection 속도저하를 해결할 수 있습니다. 이 방식은 애플리케이션 반응성이 중요하고 상대적으로 Reflection 속도저하가 큰 안드로이드 라이브러리에서 최근 많이 사용하고 있습니다. (AndroidAnnotations, ButterKnife, Dagger) 성능 비교 다음은 Reflection, Dexmaker, Code Generating(APT)를 이용해 JSONObject를 Object로 변환하는 작업을 50번 수행한 결과입니다.성능 비교 결과 이처럼 최신 OS 버전일수록 Reflection의 성능저하가 다른 방법에 비해 상대적으로 더 큽니다. 반대로 Dexmaker의 생성 속도는 빨라져 APT 방식과의 성능격차는 점점 작아집니다. 하지만 역시 APT를 통한 Code 생성이 모든 환경에서 가장 좋은 성능을 보입니다. 마치며 서비스 모델을 반복적으로 정의하지 않으면서 변환하는 방법을 알아봤습니다. 그 과정에서 Reflection 의 속도저하, Dexmaker 의 단점도 설명해 드렸고 결국 APT가 좋은 해결책이라고 판단했습니다. 저희는 이 글에서 설명해 드린 방식을 추상화해 Binding이라는 라이브러리를 만들어 사용하고 있습니다. Binding은 POJO를 다양한 JSON, Cursor, ContentValues등 다양한 형태로 변환해주는 라이브러리입니다. 뛰어난 확장성으로 다양한 형태의 데이터로 변경하는 플러그인을 만들어서 사용할 수 있습니다. Message message = Bindings.for(Message.class).bind().from(AndroidSources.cursor(cursor)); Message message = Bindings.for(Message.class).bind().from(JSONSources.jsonString(jsonString)); String jsonString = Bindings.for(Message.class).bind(message).to(JSONTargets.jsonString());  위와 같이 Java상에 존재할 수 있는 다양한 타입의 객체에 대해 일종의 데이터 Binding 기능을 수행합니다. Binding 라이브러리도 기회가 되면 소개해드리겠습니다. 윗글에서 궁금하신 점이 있으시거나 잘못된 부분이 있으면 답글을 달아주시기 바랍니다. 감사합니다. 
조회수 1628

Humans of TODAIT : 안드로이드 천재 개발자 김범준을 만나다

‘Humans of TODAIT’의 네번째 주인공, 투데잇 안드로이드 개발자 김범준씨를 만나보았습니다. 투데잇의 천재 개발자로 불리는 그의 이야기를 함께 들어볼까요?(2017.08)Q. 자기소개 부탁드려요.안녕하세요! 투데잇에서 까칠남을 맡고 있는 안드로이드 개발자 김범준입니다. 퇴사자 인터뷰를 하게 되니, 정들었던 팀원분들과 헤어질 생각에 아쉽고 싱숭생숭하네요. (웃음) 작년 초 쯤 ‘SW 마에스트로’ 프로그램에서 만난 멘토님께서 제게 투데잇 안드로이드 개발자 자리를 추천해주신 덕분에 이렇게 투데잇과 인연이 닿게 되었어요. 사실 처음에는 큰 생각이 없었는데, 대표님과 팀장님을 만나보니 저와 코드도 잘 맞고 개발 쪽으로도 많이 배워볼 수 있을 것 같아서 그 날 바로 입사 결정을 내렸고, 지금은 퇴사를 앞두고 있네요.Q. 그렇게 좋은 투데잇을 떠나는 이유는 무엇인가요?원래 병특을 가야 했어요. 제가 군대를 아직 안 갔기 때문에, 군대 문제를 해결 해야 더 많은 기회도 생기고 지금 가지고 있는 마음의 짐 같은 것도 덜 수 있거든요. 아쉽게도 투데잇이 병특 산업기능요원지정업체가 아니어서 군대 문제를 해결하기 위해서는 퇴사할 수 밖에 없는 상황이에요. 사실 원래부터 군대 문제 때문에 잠시 동안만 일하기로 했던건데, 회사생활이 너무 만족스럽고 일이 즐거워서 계속 미루다가 이제서야 결정을 내렸네요. 지금도 많이 아쉬워요. 투데잇만한 회사 없거든요.Q. 팀 내에서 평소 자기계발을 많이 하는 것으로 유명한데, 혹시 자기계발 노하우가 있나요?사실 공부는 진짜 하는 것보다 시작하는 것이 어렵잖아요. 그래서 저는 일부러 저한테 강제성을 주는 편이에요. 매주 하는 동아리 활동이라든지 발표 기회를 만든다든지 관련 세미나를 참여한다든지 그런 일정이 생기면 자연스럽게 하게 되더라고요. 하면 또 잘하고 싶은 게 사람 마음이니까 자꾸 강제적으로 그런 기회를 만들죠.그리고 저는 일상에서 배울 수 있는 기회를 얻으려고 해요. 일하다가 힘들거나 머리가 잘 안 돌아갈 때 저장해둔 아티클을 보곤 하죠. 또 술마실 때도 같은 직업군의 친구들을 만나면 그런 얘기를 많이 하잖아요. 너 이거 시도해봤냐 어땠냐 이건 어떻게 하는거냐 같은 이야기요. 제가 주위 사람들에게 자극을 많이 받거든요. 책상 앞에 앉아서 하는 공부보다는 일상적 시간을 활용하고 뭔가를 준비하기 위한 공부의 자기계발을 하는 것 같아요.Q. 지난 1년을 돌아보는 의미에서, 개발자로서의 좌우명이나 철학이 있을까요?저는 어떤 일을 하든 명확한 근거가 있어야 한다고 생각해요. 커뮤니케이션에서도 그렇고 개발에 있어도 마찬가지예요. 내가 하는 일에 대한 충분한 이유가 있어야 하고 그게 코드에 녹아 있어야 해요.예를 들면, 같은 풍경을 보고 글을 쓸 때도 여러 방법이 있잖아요. 사람마다 글 쓰는 방법이 다르고. 그 방법을 선택한 데엔 저마다 이유가 있어요. 코드도 마찬가지예요. 어떤 기능을 개발할 때 그 기능을 구현할 수 있는 여러 방법이 있는데, 개발자라면 내가 만든 코드에 대해 내가 왜 이렇게 짰는지 다른 사람에게 자신 있게 말할 수 있는 개발자가 되어야 한다고 생각해요.저는 힙한 개발자가 되고 싶어요. 그러니까 최신 트렌드에 민감하고, 새로운 것에 도전하고 두려워 하지 않는 그런 개발자요. (웃음)Q. 힙한 개발자 멋지네요. 그렇다면 10년 후에는 무엇을 하고 싶은지 궁금한데요?제 꿈은 그냥 행복하게 사는거예요. (하하) 추상적인 이야기 같겠지만, 행복하게 살기 위해선 많은 것들이 필요하잖아요? 우리가 말하는 이상적인 행복이란 것은 돈, 인간관계, 사회적 직위, 건강과 같은 모든 박자가 잘 맞아 떨어졌을 때 이루어지는 행복이거든요. 그래서 저는 행복하기 위해서는 끊임없이 노력해야 한다고 생각해요. 장차 10년 후에 제가 뭘 하고 있을지는 모르지만, 지금 현재의 상황에서 제가 할 수 있는 최선의 선택을 하면서 열심히 단계적으로 이루어나가면, 10년 후에도 충분히 행복할 것 같아요. 저는 지금 행복하거든요. (웃음)Q. 일하다 보면 해결하기 힘든 난제를 만날 때가 있을 것 같은데, 그럴 땐 어떻게 극복하나요?내가 스트레스를 많이 받고 있다는 걸 깨달으면, 그냥 최대한 스트레스 받지 않으려고 해요. 그냥 뭐 하면 되지 라는 생각이죠. 하면 되지 하면서 하다보면 결국 되는 것 같아요. 어차피 해야 될 일인데, 스트레스 받으면서 하기 보다는 그냥 아무 생각 없이 열심히 하는 게 나으니까요. 만약에 제가 몰라서 못하고 있는 일이면 여러 사람들에게 물어보려고 하면서 어떻게든 해결하려고 하고요.Q. 그렇다면 투데잇에서 가장 만족스러운 결과물은 무엇인가요? 개인적으로 뿌듯하다거나 실제 반응이 좋았다거나 그런 것들이요!‘스탑워치’ 기능이 두 개 다 포함돼요. 이전 개발자가 스파게티 코드(엉망진창의 코드)로 만들어 놓았던 것이 있는데 그 코드를 제가 깔끔하게 다 수정했고, 계속 유저분들이 요청해주셨던 시간 잠금, 극강의 잠금 모드 같은 기능들을 추가해서 코드를 예쁘게 잘 만들어놓았거든요. 일단 제가 기발한 기능과 함께 코드를 예쁘게 잘 만들어냈다는 점에서 스스로도 만족을 했었고, 유저분들도 팀원분들도 좋은 피드백을 해주셔서 굉장히 좋았습니다.Q. 지금 이 글을 보고 계시는 스탑워치 기능 애용 유저분들께 한마디 해주세요!우선 잘 사용해주셔서 감사해요! 제가 만든 기능을 이용해 공부하시는 걸 보면, 저도 정말 큰 자부심을 느끼거든요. :) 다만, 아직 스탑워치 기능에 문제가 조금 있는 거로 알고 있어요. 약간 불편하더라도 이왕이면 둥글게 좋게 별 5점으로 리뷰 주시면! 저희와 의사소통하면서 함께 좋은 서비스 만들어 나갈 수 있을 것 같아요. 안 보는 것 같지만 투데잇 개발자 전체가 매일 열심히 읽고 있거든요. 정말 리뷰 하나에 울고 리뷰 하나에 웃습니다. 저희 투데잇 지금까지 사랑해주셨지만, 앞으로도 계속 사랑해주시면 감사하겠습니다. :)Q. 반대로 투데잇 안드로이드 개발에 있어 아쉬운 부분도 있을 것 같아요. 나 이거 진짜 욕심났다! 혹시 있을까요?음.. 저는 옛날에 있던 아키텍처를 일단 전부 바꾸고 싶어요. 최근에 꽂힌 아키텍쳐가 있는데, 그 아키텍쳐에 맞게 코드를 다 변경해보고 싶다는 욕심이 있거든요. 근데 그 아키텍쳐 특성상 현재 코드에서는 완전히 대대적인 수정이 들어가야되는데, 제가 남은 시간이 얼마 없어서 많이 수정을 못했죠. 우리가 좀 더 많은 시간이 있고 여유가 있었더라면 더 바꿔볼 수 있었을텐데 그런 부분들을 못한 게 조금 아쉬워요.“투데잇의 힘은 서로에 대한 믿음인 것 같아요”Q. 범준님에게 투데잇이란? 투데잇 팀의 힘이 무엇이라고 생각하시나요?무엇보다 투데잇의 힘은 서로에 대한 믿음인 것 같아요. 커뮤니케이션이 잘 되려면 그 사람에 대한 믿음이 있어야 되잖아요. 근데 저흰 그게 되게 잘 되고 있다고 생각되거든요. 업무적으로 제 이야기를 자신있게 할 수 있었던 이유도 이 사람들은 전부 다 각자 일을 열심히 하고 책임을 지려는 사람, 멋있는 사람이라는 걸 알고 있었기 때문에 가능했거든요. 다들 맡은 바에 있어서 최선을 다하고 정말 열심히해요. 그 분위기가 서로에 대한 믿음을 만들고 우리의 원동력을 만들죠. 확실히 저희 팀은 일단은 진짜 서로에 대한 믿음이 강하다? 업무적 믿음이 강하다? 그런 게 있는 것 같아요.Q. 투데잇에서 가장 고마웠던 사람은 누구였나요?솔직히 다 고마운데, 저는 대표님께 가장 감사했어요. 이번에도 혼자 고민하다가 힘들게 퇴사 의사를 밝혔는데, 대표님께서 그건 당연한 거라고 이야기해주시더라고요. 저는 투데잇 팀이 참 좋은 게 어떤 이야기를 했을 때 명확한 근거가 있다면 그 후에 뒤끝이 하나도 없어요. 이번 일도 그렇고 일적으로 이야기 할 때도 그렇고, 이유가 확실하면 OK하고 쿨하게 가곤 하셨거든요. 다 업무적 믿음이 있기 때문이라고 생각해요 저는. 여러모로 저를 많이 믿어주신 대표님한테 제일 감사하죠. 대표님 에너지도 너무 좋고 카리스마도 본받고 싶고 제가 되게 좋아하는 분이에요.Q. 범준님의 다음 타자가 될! 투데잇에 입사하고 싶은, 입사할 분들에게 한 마디 부탁드려요!“팀원 하나하나가 굉장히 중요한 역할을 하고 있는 사람들이어서 그만큼 책임감이 있지만, 그만큼의 자율성도 있는 회사에요”굉장히 좋은 팀이에요. 일적에서는 절대 스트레스 주는 일이 없고요. 뭔가 일이 밀리거나 못하는 거에 있어서는 스트레스가 있을 수도 있어요. 팀원 하나하나가 굉장히 중요한 역할을 하고 있는 사람들이어서 그만큼 책임감이 있지만, 그만큼의 자율성도 있는 회사에요. 노력하는 그대로의 모습을 사람들에게 보여줄 수 있고 인정 받을 수 있기 때문에 흔히 말하는 꼰대 문화가 싫으신 분들은 투데잇에서 행복하게 일할 수 있을 거예요. 업무적으로나 환경적으로나 대우도 근무 환경도 굉장히 좋으니까 관심 있으신 분이면, 특히 안드로이드 개발자 분이면 지금 바로 들어오실 수 있을 것 같아요. 유저한테 피드백도 받을 수 있고 개인적으로 리스펙하는 멋진 CTO분도 계시고, 개발자로서 특히 굉장히 좋은 곳입니다. 주저 마세요!#투데잇 #팀원소개 #팀원인터뷰 #팀원자랑 #기업문화 #조직문화
조회수 1084

안드로이드 색상 투명도

제 깃헙블로그 https://heelog.github.io/about/ 에서 동시에 포스팅을 진행하고 있습니다.개발 관련 글을 보기에는 블로그를 통하시는 것이 더 좋습니다!안드로이드에서 색상을 표현할 때는 #AARRGGBB 형태로 표현한다. 앞의 AA 자리에 16진수를 이용하여 투명도를 표현해줄 수 있다. 범위는 0~255이다.0%~100% 투명도 값  100% — FF99% — FC98% — FA97% — F796% — F595% — F294% — F093% — ED92% — EB91% — E890% — E689% — E388% — E087% — DE86% — DB85% — D984% — D683% — D482% — D181% — CF80% — CC79% — C978% — C777% — C476% — C275% — BF74% — BD73% — BA72% — B871% — B570% — B369% — B068% — AD67% — AB66% — A865% — A664% — A363% — A162% — 9E61% — 9C60% — 9959% — 9657% — 9456% — 9156% — 8F55% — 8C54% — 8A53% — 8752% — 8551% — 8250% — 8049% — 7D48% — 7A47% — 7846% — 7545% — 7344% — 7043% — 6E42% — 6B41% — 6940% — 6639% — 6338% — 6137% — 5E36% — 5C35% — 5934% — 5733% — 5432% — 5231% — 4F30% — 4D28% — 4A28% — 4727% — 4526% — 4225% — 4024% — 3D23% — 3B22% — 3821% — 3620% — 3319% — 3018% — 2E17% — 2B16% — 2915% — 2614% — 2413% — 2112% — 1F11% — 1C10% — 1A9% — 178% — 147% — 126% — 0F5% — 0D4% — 0A3% — 082% — 051% — 030% — 00참고한 블로그: 커피한잔의 여유와 코딩#트레바리 #개발자 #안드로이드 #앱개발 #인사이트 #경험공유 #꿀팁
조회수 1978

나는 이쁜 데일리룩을 보고 싶은걸? \w pose estimation

안녕하세요. 스타일쉐어 백엔드 개발자 김동현입니다.2018년의 스타일쉐어에서는 뷰티, 중고 그리고 데일리룩이라는 피드가 추가로 등장했는데요, 그중 제가 작업했던 데일리룩 피드를 만들게 된 배경과 개발 방향에 대해 공유드리고자 합니다.스타일쉐어 데일리룩#데일리룩 #ootd / 타자 치는 것은 귀찮아데일리룩에 관련된 스타일들만 뽑아내는 방법 중에 가장 간단한 방법은 텍스트로 분리해내는 방법이었을 것입니다.하지만 #데일리룩 #ootd는 사진이나 내용이 관계가 없더라도 들어가 있는 경우가 많았습니다.또한 위의 피드처럼 정성스러운 글을 써주는 유저도 많긴 했지만 자신의 데일리 로그를 남기면서 글을 작성하지 않는 경우도 더러 있었습니다.즉, 단순히 텍스트로만 구별해내기에는 이미지에 대한 질을 확신할 수 없었고, 텍스트가 주된 서비스가 아니다 보니 설명 없는 좋은 이미지들이 많았는데요.우리는 이 이미지들을 놓치고 싶지 않았습니다.그래서 결과적으로 텍스트 대신 이미지를 사용하는 방향을 선택하게 되었습니다.이미지로 어떻게 구별해낼까?다행히도 R-CNN의 높은 인식률과 Pre-Trained 된 모델의 label 중 person이 이미 학습되어있던 터라 별도의 Transfer Learning 없이 이미지 내에서 body parts가 있는지 없는지 찾아내는 것은 아주 어렵지 않았습니다.다만 문제가 있다면 body parts에 들어가는 모든 부분을 person이라고 예측하던 부분이었죠.예를 들자면 아래와 같습니다.다음과 같이 제가 사용한 모델에서는 body parts를 person이라는 라벨로 처리하고 있었습니다.단순히 R-CNN의 person 라벨만을 믿기에는 의도했던 데일리룩 외에도 너무나도 많은 것들이 데일리룩이라는 이름으로 필터링될 것 같았습니다.그래서 또 다른 필터가 하나 더 필요하다는 생각이 들었습니다.Pose EstimationBody Parts 중 우리가 원하는 부분이 사진에 있으면 좋겠다!라는 생각을 곰곰이 하다 보니 우연히 머릿속에 스쳐 지나가는 하나의 장면이 있었습니다.Source: http://graphics.berkeley.edu/papers/Kirk-SPE-2005-06/바로 3D 모델링 중에서 Motion Tracker 에 관련된 장면이었는데요. 이것을 Tracker가 아니라 이미지에서 stick figure를 뽑아낼 수 있으면 되지 않을까?라는 생각이 들었습니다.놀라운 딥러닝의 세계에는 이미 여러 명의 Stick Figure를 뽑아낼 수 있는 경지에 도달해 있었습니다.Source: https://github.com/ZheC/Realtime_Multi-Person_Pose_EstimationPose Estimation 딥러닝 모델을 사용하여 아래와 같은 결과물을 얻어낼 수 있었는데요.이미지 내의 Body Parts의 존재 여부를 알게 되었으니 우리가 원하는 Body Parts가 이미지 내에 있는지 검사할 수 있게 되었습니다.하지만 해당 모델이 마냥 가볍지는 않았기에 사용자의 업로드가 많은 순간에는 예측 Task가 밀리기 시작했습니다.그래서 아주 단순하지만, 효과적인 아이디어들을 적용하였는데요.pose estimation을 하기 전에 R-CNN을 돌린 후 person으로 예측된 bounding box가 있다면 pose estimation 모델을 돌리도록 했습니다.하지만 위의 필터를 통했음에도 원하는 결과물이 안 나오는 경우가 종종 있었는데요.바로 다음과 같은 경우입니다.생각보다 작은 사람의 stick figure도 잘 추출 내어서 해수욕장으로 떠나 찍은 사진 속의 저 멀리 있는 휴양객을 데일리룩으로 잡는 일이 종종 발생했거든요.그래서 위의 조건에 더불어서 person이라고 예측된 bounding box size가 전체 이미지 크기 대비 n % 이상의 크기 일 경우 Pose Estimation을 진행하자는 것이었죠.적당한 크기 이상의 데일리룩들을 뽑아내고 싶었고 사람이 너무 작아서 안 보이는 경우도 피할 수 있었습니다.빠른 분류 속도는 덤이었고요.덕분에 유저들이 올린 콘텐츠 중 데일리룩이라는 범주에 속하는 콘텐츠를 잘 뽑아낼 수 있었습니다.아래는 위의 과정을 거쳐서 Pose Estimation까지 처리되어 데일리룩 사진이라고 판별된 이미지입니다.이다음으론 무엇을 더 해볼 수 있을까요?사진 속의 자세를 알 수 있게 되었으니 좀 더 재밌는 것을 할 수 있을 것 같은데요.예를 들면 K-Means를 적용하면 비슷한 모습의 데일리룩들만 모아볼 수도 있고 스타일쉐어 유저들이 자주 찍는 자세 라던가 유저 별 자세 선호도 등등 재밌는 것들을 할 수 있을 것 같습니다.날 따라 해 봐요 같은 것도 해볼 수 있겠네요 :)같이 해보지 않을래요?아직도 재밌는 것들이 많이 남은 스타일쉐어 에서는 더 많은 것을 하기 위해 개발자분들을 모시고 있습니다 :)백엔드 개발자라고 해서 백엔드 개발에만 국한되지 않고 하고 싶은 것들을 해도 된다, 할 수 있다고 이야기해 주는 회사라고 생각합니다.스타일쉐어를 좀 더 알고 싶으시다면 여기를 눌러 주세요 :)#스타일쉐어 #개발팀 #개발자 #백엔드개발 #개발인사이트 #경험공유 #후기
조회수 4155

서버 비용을 70%나 줄인 온디맨드 리사이징 이야기

비트윈의 서버에는 사용자들이 올리는 수많은 사진이 저장되어 있습니다. 2016년 3월 기준으로 커플들이 데이트에서 찍은 사진, 각자의 프로필 사진, 채팅을 나누며 올린 재미있는 짤방까지 약 11억 장의 사진이 저장되어 있습니다. 비트윈에서는 이러한 사용자들의 소중한 추억을 잘 보관하고, 사용자들의 요청을 빠르고 비용 효율적으로 처리하기 위해서 많은 노력을 기울이고 있습니다. 이번 포스팅에서는 비트윈 개발팀이 사용자들의 사진 처리를 보다 효율적으로 하기 위해서 어떠한 노력을 하였는지 공유하고자 합니다.기존의 아키텍쳐¶비트윈 사용자가 채팅창이나 모멘츠 탭에서 사진을 업로드 할 경우, 해당 사진은 업로더 서버라고 불리는 전 세계 각지에 퍼져 있는 사진 업로드 전용 서버 중 가장 가까운 서버를 자동으로 찾아서 업로드 됩니다. 업로더 서버는 사진을 해당 AWS Region의 S3 bucket에 적재하고, 미리 지정된 크기의 썸네일을 자동으로 생성하여 역시 S3에 저장합니다. 그리고 Tokyo Region에 있는 비트윈 메인 서버에 이 결과를 토큰 형태로 전송하여 DB에 그 정보를 저장하도록 합니다. 이러한 과정을 통해서 일반 HTTP request보다 훨씬 큰 용량을 가지고 있는 사용자의 사진이 최대한 적은 지연시간을 가지고 업로드되도록 합니다.사용자가 올린 사진은 원본이 S3에 저장됨과 동시에 미리 정해진 사이즈로 썸네일을 생성해서 저장된다.하나의 사진이 대략 5장에서 6장의 서로 다른 크기의 썸네일로 리사이징이 되는데, 이는 클라이언트의 디스플레이 크기에 따라서 최적화된 이미지를 내려주기 위함이었습니다. 예를 들어서 아주 작은 썸네일이면 충분한 채팅 프로필 표시 화면을 그리기 위해서 사용자가 올린 3백만 픽셀이나 되는 원본 사진을 받아서 클라이언트가 리사이징 하는 것은 지연 시간뿐 아니라 과도한 데이터 사용이라는 측면에서 효율적이지 않기 때문에 작게 리사이징 해놓은 사진을 내려주는 것이 더 바람직합니다.비트윈 사용자들의 넘치는 사랑(?)에 비트윈은 출시 후 5년 동안 약 11억 장, 썸네일을 모두 합치면 66억 장의 사진을 저장하게 되었습니다. 이 사진은 전부 AWS S3에 저장되어 있으며, 썸네일을 합친 총 용량은 2016년 3월 기준 무려 738TB였습니다. 이에 따라 사진을 저장하기 위한 S3 비용이 전체 인프라 운영 비용에서 상당 부분을 차지하게 되었습니다.기존 아키텍쳐의 비효율성¶비트윈 팀은 어느 날 위와 같은 기존의 사진 전송 아키텍쳐에 의문을 가지게 되었습니다. 비트윈 서비스가 다른 서비스와 가장 다른 특징 중의 하나는 커플 간의 데이터는 그 둘 사이에서만 공유된다는 점입니다. 일반적인 웹사이트 같은 경우, 하나의 게시물 혹은 이미지가 수천 수 만명의 유저에게 전달되지만 비트윈에서는 그렇지 않습니다. 즉, 개별 사진의 Fan-out이 작다는 점을 특징으로 가지고 있습니다.그리고 클라이언트에서 LRU를 기반으로 한 파일 캐쉬를 사용하고 있는데, 이를 통해서 위에서 말씀드린 채팅창 프로필 사진 같은 경우 클라이언트에서 캐쉬될 가능성이 매우 커지게 됩니다. 그리고 CDN으로 사용하고 있는 AWS의 CloudFront에서도 약 30~40%의 추가적인 Cache hit을 얻을 수 있었습니다. 즉, 이미 Fan-out이 낮은 리소스가 높은 Cache hit rate를 가지는 사용패턴을 가지고 있는 셈이 됩니다.더군다나 사용자의 디바이스 사이즈에 따라서 미리 리사이징 해놓은 썸네일 중 일부는 아예 사용하지 않는 사용패턴이 나타나기도 합니다. 아이패드와 같은 큰 디스플레이를 가진 클라이언트를 쓰는 사용자와 아이폰4를 사용하는 사용자가 필요로 하는 썸네일의 크기는 다를 수밖에 없기 때문입니다.아래의 그래프는 S3 접근 로그를 분석해서 파악한 특정 기간 내에 같은 해상도를 가지는 썸네일을 클라이언트가 한 번 이상 재요청 하는 비율을 나타내는 그래프입니다. 하루 내에 같은 해상도의 사진을 요청하는 경우는 10% 가 되지 않으며, 한 달 안에도 33% 정도에 불과한 것을 알 수 있습니다.특정 기간 내에 S3에 저장된 썸네일이 다시 요청되는 비율결국 비트윈 팀은 미리 여러 해상도의 썸네일을 준비해서 저장해 놓은 아키텍쳐보다는 사용자가 요청할 때 그 요청에 알맞게 리사이징된 썸네일을 새로 생성해서 내려주는 게 훨씬 비용 효율적이라는 결론에 도달하게 됩니다.새로운 아키텍쳐¶Skia¶하지만 이러한 온디맨드-리사이징 아키텍쳐로의 변환에 가장 큰 걸림돌이 있었습니다. 바로 사진의 리사이징에 오랜 시간이 걸린다는 점이었습니다. 비록 아키텍쳐 변화를 통해서 저희가 얻을 수 있는 비용 이득이 크더라도, 비트윈 사용자 경험에 느린 사진 리사이징이 방해가 되어서는 안 되었습니다.이때 저희가 찾은 것이 바로 Skia 라이브러리였습니다. Skia 라이브러리는 Google에 의해서 만들어진 2D 그래픽 라이브러리로써, 크롬이나 안드로이드, 모질라 파이어폭스 등에 사용되고 있었습니다. 그리고 이 라이브러리는 CPU 아키텍쳐에 따라서 인스트럭션 레벨로 매우 잘 최적화가 되어 있었습니다. 저희가 기존에 쓰고 있던 ImageMagicK에 비해서 거의 4배 속도로 이미지 리사이징을 처리할 수 있었으며, 총 CPU 사용량도 더 적었습니다. 저희는 이 라이브러리를 Python으로 wrapping한 PySkia라는 라이브러리를 내부적으로 만들어서 사진 리사이징에 사용하기로 하였습니다.WebP¶저희는 여기서 한발 더 나아가 보기로 했습니다. 단순히 리사이징만 Skia로 대체하는 것이 아니라, 원본 사진의 저장도 더 효율적으로 할 방법을 찾게 되었습니다. 그 결과 자연스럽게 떠오른 것이 비트윈 스티커 시스템에서 사용되었던 WebP 방식이었습니다. WebP 역시 구글이 만든 이미지 인코딩 방식으로써, 비슷한 화질을 가지는 JPEG에 비해서 약 26% 정도의 용량이 절약된다는 점에서 장점이 있습니다.온디멘드-리사이징¶위에서 언급한 대로 Skia 리사이징과 WebP 원본 저장을 합하여 아래와 같이 필요한 해상도의 사진을 그때그때 리사이징 하는 온디멘드-리사이징 아키텍쳐로 옮겨가게 되었습니다.사용자가 올린 사진은 원본이 WebP로 변환되어 S3에 저장된다. 클라이언트의 요청이 있을 때는 그때그때 요청한 사이즈로 리사이징한 썸네일을 생성해서 내려준다.리사이저 서버가 사용자의 요청을 받아서 원하는 해상도의 사진을 리사이징해서 내려주기까지 채 100ms가 걸리지 않는데, 이 정도면 사용자의 경험에 영향을 주지 않는다고 판단하였습니다. 리사이저 서버는 업로더 서버와 함께 세계 각지의 AWS Region에 배포되어 있으며, 이는 사용자가 요청한 사진을 최대한 빨리 받아가기 위함입니다.기존 사진 마이그레이션¶위와 같은 아키텍쳐 전환을 통해서 새롭게 업로드 되는 사진들은 원본만 WebP로 변환되어 저장한 후 요청이 들어올 때만 온디멘드 리사이징이 되지만, 그동안 비트윈 사용자들이 축적해 놓은 11억 장의 사진은 여전히 여러 사이즈의 썸네일로 미리 리사이징이 되어 있는 비효율적인 상태였습니다. 저희는 이 사진들도 마이그레이션하는 작업에 착수했습니다.11억 장이나 되는 원본 사진들을 전부 WebP로 변환하고, 나머지 50억 장의 미리 생성된 썸네일 사진을 지우는 작업은 결코 간단한 작업이 아니었습니다. 저희는 이 작업을 AWS의 Spot Instance와 SQS를 통해서 비용 효율적으로 진행할 수 있었습니다.Auto Scaling with Spot instance¶마이그레이션 작업은 크게 다섯 단계로 이루어져 있습니다.커플 단위로 작업을 쪼개서 SQS에 쌓아놓습니다.Worker가 SQS로부터 단위 작업을 받아와서, 해당 커플에 존재하는 모든 사진을 WebP로 변환하고 S3에 올립니다.S3로의 업로드가 확인되면, 그 변경 사항을 DB에 적습니다.기존 썸네일 사진들을 삭제합니다.기존 썸네일이 삭제되었다는 사실을 DB에 적습니다.작업을 하는 도중에 얼마든지 Worker가 중단되거나 같은 커플에 대한 작업이 두 번 중복되어서 이루어질 위험이 있습니다. 이를 위해서 마이그레이션 작업을 멱등적으로 구성하여서 사용자의 사진이 손실되는 등의 사고가 발생하지 않도록 하였습니다. 중간마다 DB에 접근해서 변경된 내용을 기록해야 하는 작업의 특성상, 작업의 병목 구간은 비트윈 DB였습니다. 그리고 사진 인코딩을 바꾸는 작업의 특징상 많은 CPU 자원이 소모될 것으로 생각하였습니다.DB에 부담이 가지 않는 범위내에서 많은 CPU 자원을 끌어와서 작업을 진행해야 할 필요성이 생긴 것입니다. 이 조건을 만족하게 하기 위해서 SQS를 바라보는 Worker들로 Auto-scaling group을 만들었습니다. 그리고 이 Auto-scaling group은 c3.2xlarge와 c3.4xlarge spot instance로 구성되어 있으며, DB의 CPU 사용량을 메트릭으로 하여 Scaling이 되도록 하였습니다. 작업은 주로 DB의 부하가 적은 새벽 시간에 집중적으로 이루어졌으며, 이 인코딩 작업은 대략 4일 정도가 소모되었습니다. 작업 과정에서 Tokyo Region에 있던 c4.2xlarge와 c3.4xlarge spot instance를 최대 140대를 사용했고, 총 사용 시간은 6,767시간이었습니다. 사용한 instance의 계산 능력을 ECU로 환산하면 총 303,933 ECU · hour를 작업에 사용하였습니다. 마이그레이션에 사용된 EC2 비용을 바탕으로 계산해 보면, 백만 장의 WebP 인코딩을 위해서 사용한 비용이 $1.8 밖에 되지 않았다는 것을 알 수 있습니다.작업 과정에서 AWS 서비스에 의외의 병목 구간이 있다는 것을 알게 되었는데, S3 단일 버킷에 1분당 1천만 개 이상의 object에 대한 삭제 요청이 들어오면 Throttling이 걸린다는 사실과 SQS의 in-flight message의 개수가 12만 개를 넘을 수 없다는 것입니다.결과¶위의 아키텍쳐 변화와 마이그레이션 작업 후 저희의 S3 비용은 70%가 넘게 감소했으며 전체 인프라 비용의 상당 부분이 감소하였습니다. 온디멘드 리사이징으로의 아키텍쳐 변화는 Storage 비용과 Computation 비용 사이의 교환이라고 볼 수 있는데, 아래 그래프에서 볼 수 있듯이 확연한 비용 절감을 달성할 수 있었습니다.총 마이그레이션 비용¶항목사용량비용 ($)EC2 spot instance6,767 hrs1,959.11SQS188,204,10489.59S3 Put/Get Requests2,492,466,8605,608.34총비용7,657.04마이그레이션 결과¶항목Before MigrationAfter Migration감소량 (%)S3 # of objects6.65 B1.17 B82.40S3 storage738 TB184 TB75.06비용 감소¶사진 저장과 리사이징에 관련된 비용이 68% 감소하였음못다 한 이야기¶이번 포스팅에서는 최근에 있었던 비트윈 사진 아키텍쳐의 변화에 대해서 알아보았습니다. 주로 사용자의 경험을 방해하지 않는 조건에서 비용을 아끼는 부분에 중점을 두고 저희 비트윈의 아키텍쳐 변화에 대해서 설명해 드렸습니다. 하지만 이 글에서 미처 언급하지 못한 변화나 개선 사항들에 대해서는 다루지 못했습니다. Tokyo Region에서 멀리 떨어져 있는 사용자를 위해서 전 세계 여러 Region에 사진 저장/전송 서버를 배포하는 일이나, 사진을 로딩할 때 낮은 해상도로부터 차례대로 로딩되도록 하는 Progressive JPEG의 적용, 사진을 아직 받아오지 못했을 때 Placeholder 역할을 할 수 있는 사진의 대표색을 찾아내는 방법 등이 그것입니다. 이에 관해서는 후에 자세히 다뤄보도록 하겠습니다.정리¶비트윈 개발팀에서는 많은 인프라 비용을 소모하는 기존 썸네일 저장 방식을 개선하여 70%에 가까운 비용 절감 효과를 보았습니다. 기존의 썸네일을 미리 생성해놓는 방식으로부터 클라이언트가 요청할 때 해당 크기의 썸네일을 리사이징해서 내려주는 방식으로 변경하였고, WebP와 Skia등의 새로운 기술을 적용하였습니다. 이를 통해서 사용자 경험에는 거의 영향을 주지 않은 상태로 비용 절감 효과를 볼 수 있었습니다.저희는 언제나 타다 및 비트윈 서비스를 함께 만들며 기술적인 문제를 함께 풀어나갈 능력있는 개발자를 모시고 있습니다. 언제든 부담없이 [email protected]로 이메일을 주시기 바랍니다!
조회수 2293

JIRA하고 자빠졌네!?

Overview“JIRA하고, 자빠졌네!” 세종대왕은 확실히 개발자의 두뇌를 가지고 있었던 게 분명합니다. 먼 시대를 지나 오늘날 QA를 하는 저에게 응원을 해주시니 말입니다. 하지만 그는 틀렸습니다. 걱정과는 다르게 다행히 자빠지진 않았거든요. 지라(JIRA) 덕분입니다.갑자기 지라 이야기가 나와 당황하셨죠? 축하해주세요. 드디어 브랜디도 지라를 사용하게 되었답니다. (짝짝짝!) 지라 도입은 처음이라 세팅부터 쉽지 않았는데요. 이번 글은 눈물겨웠던 지라 세팅 과정과 브랜디의 이슈관리를 소개하겠습니다. 스크럼을 쓰면 좋은 점스크럼(Scrum)은 요구 사항 분석부터 하는 칸반(Kanban)보다 효율적입니다. 안드로이드와 iOS로도 나눠져 있고 업무를 짧게 반복하기 때문이죠. 스크럼에 적합한 워크플로우(Workflow)를 볼까요? 이것은 실제로 브랜디 R&D본부에서 사용하고 있기도 합니다. 스크럼에 적합한 워크플로우IN PROGRESS: 이슈나 개발 요건을 티켓으로 만들면 IN PROGRESS 상태가 됩니다. RESOLVED: 이슈나 개발 요건이 완료되면 RESOLVED 상태로 변경합니다.QA: QA가 필요한 개발 요건은 QA상태로 변경합니다.PASS: 이슈 또는 개발 요건이 수정되었거나 문제가 없다면 PASS 상태로 변경합니다.FAIL: 이슈 또는 개발 요건이 제대로 수정되지 않았거나 다른 이슈가 발생하면 FAIL 상태로 변경합니다.QA불필요: QA가 필요하지 않은 개발 요건은 QA불필요 상태로 변경합니다.DONE: 이슈를 해결했거나 개발을 완료하면 DONE 상태로 변경합니다CLOSE: 담당 팀장님이 이슈 확인 후 CLOSE 처리합니다. 예를 들어보겠습니다. 킥오프 서비스 회의를 하고, SB를 제작, 리뷰합니다. 이후에 디자인팀과 개발팀 일정을 공유하고 스크럼 마스터는 스프린트 주기를 책정하죠. 스프린트가 시작되면 개발자는 스토리 티켓을 작성하는데요. 개발이 끝나면 QA가 필요한 티켓은 테스트를 진행하고, QA가 종료되면 스프린트도 종료됩니다.Epic 티켓위의 이미지는 Epic 티켓입니다. Android, iOS, 이슈 등 모든 티켓은 Epic 안에서 관리합니다. 한 곳에서 한꺼번에 관리하기 때문에 히스토리 관리가 편하고, 진행 상황도 확인할 수 있습니다.티켓 생성개발팀의 티켓 생성입니다. 개발자는 SB를 보고 개발 티켓을 작성합니다. 개발 티켓 작성 후에 개발이 진행되며 QA 판단 여부를 체크해 QA 상태로 변경합니다. 변경된 티켓에 관한 QA가 진행되며 문제가 없으면 해당 티켓은 종료됩니다.이슈 생성다음은 이슈 생성입니다. 파악한 SB는 디자인 시안과 비교하며 개발이 된 Android, iOS 테스트 파일을 QA합니다. QA를 진행할 때 발생한 이슈는 지라 티켓으로 등록하여 이슈를 관리합니다. 모든 이슈 티켓 종료되면 해당 차수의 QA는 끝나고 마침내 상용에 배포합니다. 배포가 완료되면 필수 및 크리티컬 리그레이션 테스트가 진행됩니다. Conclusion실수는 항상 모든 것이 끝난 이후에 보이기 마련입니다. 수십 번 QA를 해도 보이지 않던 문제들이 상용에 올라간 이후부터 보이기 시작하죠. 스크럼은 이런 실수들을 가장 최소화할 수 있는 툴이 아닐까 생각합니다. 물론 아무리 좋은 툴을 써도 팀원들과 함께 뭉치는 것보다 중요한 것은 없겠죠. 다음 글은 자동화를 주제로 찾아뵙겠습니다. JIRA하고 자빠지지 않는 개발자가 됩시다!글김치영 대리 | R&D PM팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발자 #개발팀 #인사이트 #경험공유 #JIRA
조회수 1623

워크인사이트 프론트엔드 개발환경

조이코퍼레이션은 오프라인 고객 분석 서비스인 워크인사이트를 만들고 있습니다. 워크인사이트는 스마트폰 신호를 통해 매장 방문객의 출입 및 체류 패턴을 측정하고 분석합니다. 분석된 데이터는 웹 대시보드를 통해 한 눈에 파악하기 쉬운 형태로 매장에 제공됩니다. 매장들은 이 대시보드를 보고 중요한 판단과 의사 결정을 내리기 때문에 대시보드는 보기 쉬워야 하고 쓰기 편해야 하며 무엇보다 아름다워야 합니다. 조이의 빅데이터 기술을 통해 분석된 데이터를 매장에 효과적으로 전달하기 위해 프론트엔드 기술에 많은 노력을 기울이고 있습니다. 이 글에서는 조이의 대시보드를 만들기 위해 사용하고 있는 기술과 개발 환경 그리고 기술적인 관점에서 고민하고 있는 부분들을 간략하게 공유하고자 합니다.그림1. 대시보드 화면사용하는 기술AngularJS: AngularJS를 기본 프레임워크로 사용하고 있습니다. AngularJS는 SPA (Single Page Application) 형태의 웹 애플리케이션을 빠르게 개발할 수 있도록 도와주는 MVC 프레임워크입니다. 조이에서는 현재 프로덕션 버전인 1.3.x를 사용하고 있습니다. 대시보드는 사용자의 이벤트에 따라 동적으로 데이터를 변경해야하는 애플리케이션적 요소가 많기 때문에 AngularJS의 양방향 데이터 바인딩의 유용함을 느끼고 있습니다.D3.js: 다양한 그래프를 아름답게 보여주기 위해서 D3.js를 사용합니다. D3.js는 데이터 시각화를 위한 자바스크립트 라이브러리로, HTML/CSS/SVG 등의 웹 기술을 이용해 그래프를 그릴 수 있습니다. 자유도가 매우 높아서 생각할 수 있는 많은 형태의 그래프를 그릴 수 있으며 부드러운 전환이나 애니메이션도 추가할 수 있습니다. 다만 초기 학습 비용이 높고 신경쓰지 않으면 너저분한 코드가 양산될 수 있다는 단점도 있습니다.CoffeeScript: 자바스크립트를 더 깔끔하고 효율적으로 사용할 수 있도록 Compile to JS 언어를 사용하는데, 여러 선택 사항 중 CoffeeScript를 사용하고 있습니다. CoffeeScript는 문법적 간결함 덕분에 타이핑을 줄이고 빠르게 코드를 작성할 수 있습니다. 특히 클래스와 클래스 상속 등을 문법적으로 지원하기 때문에 OOP적인 설계를 할 때 도움을 받았습니다. 하지만 자바스크립트와는 다른 새로운 문법을 익혀야하고 그마저도 일관성이 떨어지는 문제가 있습니다. 또 특별한 신경을 쓰지 않으면 가독성이 안 좋은 코드를 작성하기 쉽습니다. 조이에서는 Lint 툴과 코드 리뷰를 통해 코딩스타일을 엄격히 제한하고 있습니다.개발 환경빌드 및 배포: Bower와 npm(Node Package Manager)을 이용해서 패키지를 관리합니다. 빌드 시에는 JS Minify & Uglify, HTML/CSS 최적화, CoffeeScript Lint를 통한 코드 품질 검증, Karma를 이용한 테스트 수행 등의 과정을 거치며 이 모든 빌드 과정은 Grunt를 사용하여 자동화하고 있습니다. 빌드가 끝난 파일들은 AWS (Amazon Web Service)의 S3 저장소로 배포하고 있습니다. 배포 과정 역시 Grunt의 task로 자동화되어 있습니다.코드 관리: 모든 코드는 Jenkins로 통합되어 자동화된 테스트를 통과해야 합니다. 모든 커밋은Gerrit을 통해 다른 엔지니어의 리뷰를 거쳐야만 머지를 할 수 있습니다. 따라서 모든 코드는 적어도 둘 이상의 엔지니어가 이해하고 있습니다. 같은 코드에 대해 더 좋은 설계가 있는지 논의하면서 함께 코드를 발전시켜 나갑니다. 더 좋은 설계가 발견될 때마다 수시로 리팩토링을 진행합니다.프로젝트 관리: 디자이너와 엔지니어 그리고 기획에 참여하는 데이터 분석가 등은 Trello를 이용해 태스크와 이슈를 관리합니다. 일주일을 한 번의 스프린트로 보고 매주 월요일에 일을 분배하고 금요일에 회고를 합니다. 이 과정에서 엔지니어도 기획에 능동적으로 참여할 수 있으며, 어떤 데이터를 어떤 형태의 그래프로 보여주어야 효과적인지를 함께 고민할 수도 있습니다.그림2. 트렐로를 이용한 프로젝트 관리지속적으로 고민하는 부분성능 이슈: 대시보드에는 많은 수치 데이터를 다룹니다. API 서버로부터 하나의 큰 JSON 데이터를 받아서 시간별/일별/요일별/날씨별/최고기온별/평일휴일별 방문객 정보, 방문전환율/체류전환율/구매전환율 등의 지표를 그리기 위한 데이터로 가공합니다. 여기에는 계산량이 적지 않기 때문에 성능에 대한 고민을 많이 하게 됩니다. 연산 로직을 더 간단히 하거나 더 적게 Draw/Redraw 하는 방법을 고민하고 응답성을 향상시키기 위해 Async하게 연산하는 등의 고민을 합니다. 설계: 대시보드는 빠르게 업그레이드됩니다. 기능이 추가되고 변경됨에 따라 그에 맞는 좋은 설계도 계속해서 변합니다. 수시로 진행하는 리팩토링이 좋은 설계를 만든다고 생각합니다. 따라서 리팩토링에 쓰는 시간을 아까워하지 않습니다.테스트: 테스트는 매우 중요합니다. 특히 버그로 인해 잘못된 데이터가 보여지는 것은 용납될 수 없습니다. 그래서 데이터를 가공하는 로직에 대한 테스트는 엄격하게 수행됩니다. 설계 단에서도 테스트하기 쉬운 코드를 작성하려고 노력합니다.최신 기술: 조이의 프론트엔드 팀은 최신 기술에 민감합니다. Gulp, Angular 2.0, EcmaScript6, TypeScript, React와 같은 자바스크립트 최신 기술들에 관심을 갖고 그들의 기본 철학이나 장단점들을 파악하려고 노력합니다. 때때로 우리에게 더 잘 맞는 기술이 등장하면 과감하게 적용하기도 합니다.맺음말조이는 임베디드 기술과 빅데이터 기술을 보유한 기술 회사입니다. 그러나 프론트엔드 기술 역시 그 못지 않게 중요하게 생각하고 있습니다. 말뿐이 아니라 며칠 전에는 OKKY 자바스크립트 컨퍼런스에 후원을 하고 좋은 자바스크립트 개발자들을 만나기 위해서 부스를 차리기도 했습니다. 앞으로도 프론트엔드 기술 관련 컨퍼런스에 후원도 하고 기여도 계속 할 계획입니다.저도 조이에서 엔지니어로 일하면서 훌륭한 동료 엔지니어들과 함께 많은 성장을 했습니다. 무엇보다 기술적인 욕심과 의욕이 넘치는 분위기 속에서 일하는 것 자체가 즐겁고요. 혹시 이 글을 읽고 위와 같은 고민을 공유하고 폭풍 성장을 함께 할 멋진 자바스크립트 개발자가 있다면 이 글을 읽어보시길 바래요 :)그림3. OKKY 자바스크립트 컨퍼런스 부스#조이코퍼레이션 #개발팀 #개발자 #개발환경 #업무환경 #기업문화 #조직문화
조회수 1575

PyCon2017 첫번째날 후기

아침에 느지막이 일어났다. 어제 회사일로 피곤하기도 했지만 왠지 컨디션이 좋은 상태로 발표를 하러 가야지!라는 생각 때문에 깼던 잠을 다시 청했던것 같다. 일어나 아침식사를 하고 아이 둘과 와이프를 두고 집을 나섰다. 작년 파이콘에는 참가해서 티셔츠만 받고 아이들과 함께 그 옆에 있는 유아교육전을 갔었기에 이번에는 한참 전부터 와이프에게 양해를 구해둔 터였다.코엑스에 도착해서 파이콘 행사장으로 가까이 가면 갈수록 백팩을 메고, 면바지를 입고, 영어 글자가 쓰인 티셔츠를 입은 사람의 비율이 높아지는 것으로 보아 내가 제대로 찾아가고 있구나 라는 생각이 들었다.                                               늦게 왔더니 한산하다.지난번에는 입구에서 에코백과 가방을 나눠줬던 것 같은데 이번에는 2층에서 나눠준다고 한다. 1층이 아무래도 복잡해지니 그런 것 같기도 하고, 2층에서 열리는 이벤트들에도 좀 더 관심을 가져줬으면 하는 것 같기도 하다. 우선 스피커 옷을 받고 싶어서 (솔직히 입고 다니고 싶어서) 2층에 있는 스피커방에 들어갔다.                         허락 받지 않고 사진찍기가 좀 그래서 옆방을 찍었다.첫 번째 키노트는 놓쳤지만 두 번째 키노트는 꼭 듣고 싶었기에 간단히 인사만 하고 티셔츠를 들고 나왔다. (외국에서 오신 연사분과 영어로 대화를 나누고 있어서 자리를 피한것은 아니다.) 나가는 길에 보니 영코더(초등학교 5학년 부터 고등학생 까지 파이썬 교육을 하는 프로그램)을 진행하고 있었다. 의미있는 시도를 하고 있다는 생각이 들었다.                          이 친구들 2년 뒤에 나보다 잘할지도 모른다.키노트 발표장에 갔더니 아웃사이더님이 뒤에 서 게셨다. 지난 파이콘 때 뵙고 이번에 다시 뵈었으니 파이콘이 사람들을 이어주는 역할을 하는구나 싶었다.키노트에서는 현우 님의 노잼, 빅잼 발표 분석 이야기를 들을 수 있었다. 그리고 발표를 통해 괜히 이것저것 알려줘야만 할 것 같아 발표가 부담스러워지는 것 같다는 이야기를 들었다. 나 또한 뭔가 하나라도 지식을 전달해야 한다는 압박감을 느끼고 있었던 터라 현우 님의 키노트 발표를 듣고 나니 좀 더 오늘을 즐겨야겠다는 생각이 들었다.                                              오늘은 재미있었습니다!현우님 키노트를 듣고 같은 시간(1시)에 발표를 하시는 경업님과 이한님 그리고 내일 발표이신 대명님, 파이콘 준비위원회를 하고 계신 연태님과 함께 식사를 하러 갔다. 가는 길에 두숟갈 스터디를 함께 하고 계신 현주님과 희진 님도 함께했다. 사실 이번에는 발표자도 티켓을 사야 한다고 해서 조금 삐져 있었는데 양일 점심 쿠폰을 주신다고 해서 삐진 마음이 눈 녹듯이 사라졌다.                                                  부담 부담식사를 하고 발표를 할 101방으로 들어가 봤다. 아직 아무도 없는 방이라 그런지 괜히 긴장감이 더 생기는 느낌이다. 발표 자료를 열어 처음부터 끝까지를 한번 넘겨 보고 다시 닫았다. 처음에는 가장 첫 발표라 불만이었는데 생각해보니 발표를 빨리 마치고 즐기는 게 훨씬 좋겠다는 생각이 들었다. 발표 자료를 다듬을까 하다가 집중이 되지 않아 밖으로 나갔다. “열린 공간” 현황판에 충동적으로 포스트잇을 하나 붙이고 왔다. 어차피 발표는 나중에 온라인으로도 볼 수 있으니까 사람들과 이야기를 나눠 봐야 겠다 싶었다. (내 발표에는 사람이 많이 왔으면 하면서도, 다른 사람의 발표는 온라인으로 보겠다는 이기적인 생각이라니..)                                            진짜 궁금하긴 합니다다시 발표장으로 돌아왔다. 왠지 모르는 분들은 괜찮은데 아는 분들이 발표장에 와 계시니 괜히 더 불안하다. 다른 분들은 발표자료에 짤방도 많이 넣으셨던데.. 나는 짤방도 없는 노잼 발표인데.. 어찌해야 하나. 하지만 시간은 다가오고 발표를 시작했다.                                            얼굴이 반짝 반짝리허설을 할 때 22분 정도 시간이 걸렸던 터라 조금 당겨서 진행을 했더니 발표를 거의 20분에 맞춰서 끝냈다. 그 뒤에 몇몇 분이 오셔서 질문을 해주셨다. 어리버리 대답을 한 것 같다. 여하튼 내 발표를 찾아오신 분들께 도움이 되었기를. 그리고 앞으로 좀 더 정확한 계산을 하시기를.대단히 발표 준비를 많이 하지도 못하면서 마음에 부담만 쌓아두고 있는 상황이었는데, 발표가 끝나니 아주 홀가분한 마음이 되었다. 발표장을 나가서 이제 부스를 돌아보기 시작했다. 매해 참여해 주고 계신 스마트스터디도 보이고 (정말 안 받고 싶은 ‘기술부채’도 받고 말았다.) 쿠팡, 레진 등 친숙한 회사들이 많이 보였다. 내년에는 우리 회사도 돈을 많이 벌어 여기에 부스를 내고 재미있는 이벤트를 하면 좋겠다는 생각이 들었다.부스를 돌아다니다가 이제 파이콘의 명물이 된 내 이름 찾기를 시작했다. 이름을 찾기가 쉽지가 않다. 매년 참여자가 늘어나서 올해는 거의 2000명에 다다른다고 하니 파이썬 커뮤니티의 성장이 놀랍다. 10년 전에 파이썬을 쓸 때에는 그리고 첫 번째 한국 파이콘이 열릴 때만 해도 꽤 마이너 한 느낌이었는데, 이제 주류가 된 것 같아 내 마음이 다 뿌듯하다. (그리고 내 밥줄이 이어질 수 있는 것 같아 역시 기쁘다)                                          어디 한 번 찾아보시라다음으로는 박영우님의 "Django admin site를 커스텀하여 적극적으로 활용하기” 발표를 들으러 갔다. (짧은 발표를 좋아한다.) 알고 있었던 것도 있었지만 커스텀이 가능한지 몰랐던 것들도 있어서 몇 개의 기능들을 킵해 두었다. 역시 컨퍼런스에 오면 내게 필요한 ‘새로운 것’에 대한 실마리를 주워가는 재미가 있다.                                     익숙하다고 생각했지만 모르는 것이 많다4시가 되어 OST(Open Space Talk)를 하기로 한 208B 방으로 조금 일찍 갔다. 주제가 뭐였는지는 잘 모르겠는데 주식 투자, Tensor Flow, 비트코인, 머신러닝 등등의 이야기들이 오가고 있었다. 4시가 되어 내가 정한 주제에 대해 관심 있는 사람들이 모였다. 괜히 모일 사람도 없는데 큰방을 잡은 것이 아닐까 하고 생각하고 있었는데, 생각보다 많은 분들이 오셨다.각 회사들이 어떤 도구를 사용하는지 설문조사도 해보고, 또 어떤 개발 방법론을 사용하는지, 코드 리뷰, QA는 어떻게 하고 있는지에 대한 이야기를 나눴다. 다양한 회사에서 다양한 일을 하는 사람들이 모여 있다 보니 생각보다 꽤 재미있게 논의가 진행되었다. 사실 내가 뭔가 말을 많이 해야 할 줄 알았는데, 이야기하고 싶은 분들이 많이 있어서 진행을 하는 역할만 하면 되었다. 마지막으로는 “우리 회사에서 잘 사용하고 있어서 다른 회사에도 추천해 주고 싶은 것”을 주제로 몇 가지 추천을 받은 것도 재미가 있었다.                                  열심히 오간 대화를 적어두긴 했다5시에 OST를 마치고는 바로 집으로 돌아왔다. 오늘 저녁에 아이들을 잘 돌보고 집 청소도 열심히 해두어야 내일 파이콘에 참여할 수 있기 때문이다. 기대된다. 내일의 파이콘도.그리고 정말 감사드린다. 파이콘을 준비해주시고 운영해주고 계신 많은 분들께.                                                   #8퍼센트 #에잇퍼센트 #이벤트 #참가후기 #파이콘 #개발자 #개발 #파이썬 #Python #Pycon
조회수 3946

PHP Codeigniter 환경에서 VUE 사용해보기

Overview이번에는 PHP Codeigniter 기반의 서비스에 VUE를 적용시키려고 고민했던 것들을 나누려고 합니다. VUE JS는 가상 DOM을 활용하여 실시간으로 반응 컴포넌트를 제작할 수 있는 프레임워크입니다. 또한, VUE-ROUTER 및 VUEX라는 컴페니언 라이브러리를 통해 url 라우팅 및 전역상태를 관리하기에도 탁월하죠. VUE와 다른 프레임워크와의 비교 부분은 여기를 참고해주세요. 브랜디의 관리자 서비스는 PHP Codeigniter 프레임워크로 제작되었습니다. 하지만 관리자 서비스의 규모가 점점 커지고 기능이 다양해지면서 “자주 사용하는 기능을 묶어 컴포넌트화하자!”라는 숙제가 남아 있었죠. 요즘 잠깐의 여유가 생겨 이때다 싶었습니다. 관리자 서비스에 VUE를 도입하기 위한 시도를 시작했는데요. 얼마 지나지 않아 문제점에 봉착했습니다. 바로 IE9.0…. 개발자의 숙적 IE가 또 한 번 발목을 잡았습니다. 임포트가 되지 않아….VUE를 좀 더 편리하게 사용하려면 JS의 모듈화가 필요했지만, ES2015에서는 import 혹은 require 구문을 지원하지 않아 불편하고, arrow 함수 또한 사용할 수 없습니다. 게다가 VUE의 JAX 탬플릿 구문을 사용할 수도 없었죠!! 뭔가 배보다 배꼽이 더 커질 것 같은 조짐이 보였습니다.결국 Webpack의 도움 없이 VUE를 적용하려던 시도는 여러 가지 난관을 만났고, Codeigniter 프로젝트 내부에서 Webpack을 사용하는 방법을 연구하기 시작했습니다. Webpack은 모듈 번들러입니다. Webpack의 메인 페이지를 방문하면 아래 네 개의 슬로건이 빙글빙글 돕니다.Bundle your scriptsBundle your imagesBundle your stylesBundle your assets아래의 이미지는 Webpack이 무엇을 하는 녀석인지 잘 설명해줍니다.Webpack은 실제로 번들러라고 광고하는것 처럼 Only Webpack 빌드만으로는 소스 파일들을 모아줍니다. 만약 webpack-dev-server로 실행하면 websocket을 통해 소스가 변경됐을 때 실시간으로 화면을 갱신해주는 개발 툴 제공 정도의 역할 밖에 없습니다. (…충분히 훌륭하잖아?)대부분의 기능은 엄청난 확장성을 가진 webpack의 설정으로 모듈로서 작동할 수 있죠. 예를 들면 Babel은 우리의 발목을 잡았던 IE를 위해 ES6로 작성된 js 문법을 IE에서 사용할 수 있는 ES5문법으로 너무나 쉽게 트랜스컴파일할 수 있습니다.하지만… 관리자 서비스는 위에서 언급했듯이 Codeigniter 기반입니다. 따라서 완벽히 VUE와 API서버를 분리하려면 로그인, 메뉴구성, 헤더, 푸터 등 PHP 기반으로 제작된 모든 기능들과 인증 등 기존 방식을 전부 새로 만들어야만 VUE를 온전히 사용할 수 있습니다.문제점들을 모두 해결하고 넘어가기엔 여유가 부족하기 때문에 조금씩 적용하자고 생각했습니다. 덕분에 webpack-dev-server의 실시간 소스 반영 기능을 포기해야만 했죠.(눈물) 우리의 서버는 node기반이 아닌 apache-php 기반이었기 때문입니다.자, 그럼 Codeigniter 프로잭트 하위에 웹팩을 포함시켜 Hello World까지 가는 짧은(?)여정을 시작해봅시다.Hello world로 가는 여정Node, npm 설치맥에서도 유사한 명령어로 제작할 수 있도록 CMD 위주로 진행하겠습니다. 먼저, 여기를 클릭해 Node를 설치합시다. 8.11.3 LTS버전으로 진행했습니다.맥에서는 Homebrew를 통해 간편하게~brew install node 설치 확인npm 잘 설치되었네요.web pack 폴더 생성 및 이동mkdir webpack cd webpack nom init으로 초기화npm init webpack, vue, babel 설치npm install -D webpack webpack-cli webpack-dev-server npm install -D vue-loader vue-template-compiler npm install -D babel-core babel-loader babel-preset-es2015 여기서 VUE는 설치하지 않습니다! 왜냐하면 VUE.js는 로딩만 하면 되고 필요하지 않습니다! (읭?) VUE는 Codeigniter view에서도 사용해야 하기 때문에 해당 view에서 import 해줍니다. 따라서 VUE 컴포넌트가 들어가는 시점에는 이미 전역에 vue.js 가 있습니다. 따라서 굳이 각 모듈마다 VUE를 import 했다가 webpack 설정에서 다시 vue.js를 제외할 필요는 없습니다.VUE와 template 태그를 로딩할 수 있는 로더도 설치하고, 트랜스컴파일을 위한 바벨, IE9를 지원하기 위한 es2015프리셋도 함께 설치합니다.webpack 빌드명령어 package.json의 script부분에 추가"scripts": { "build": "webpack --mode production", "build-dev": "webpack --mode development",   } 이제 VUE를 빌드할 명령어를 작성합니다. 위처럼 두 가지 명령어를 제작해두면, 추후 env를 통해 webpack.config.js를 분기시켜 원하는 환경으로 빌드할 수 있습니다. 또한 production 모드로 빌드할 땐 자동으로 옵티마이저 - uglify 내장 플러그인이 적용되어 익숙한 min.js형태로 빌드되며 development를 빌드할 땐 사람이 알아볼 수 있는 형태로 빌드되고, debugger 코드 또한 살아있습니다.weboack.config.js 작성const { VueLoaderPlugin } = require('vue-loader'); module.exports = {   entry: {     HelloWorld: './src/main.js'   },    module: {     rules: [       {         test: /\.vue$/,         loader: 'vue-loader',       },       {         test: /\.js$/,         loader: 'babel-loader',       }     ]   },    resolve: {     alias: {       'vue$':'vue/dist/vue.esm.js'     }   },    plugins: [     new VueLoaderPlugin()   ]  } webpack.config.js 가 없다면 생성한 후 위와 같이 작성합니다..babelrc 작성{     "presets": ["es2015"] } 테스트용 파일 작성1)main.js 작성import HelloWorld from './HelloWorld.vue' Vue.component('hello-world', HelloWorld); 2)HelloWorld.vue 작성 [removed] export default {   name: 'app',   data: () => {     return {       word1: 'Hello',       word2: 'World'     }   }  } [removed] 테스트 빌드npm run build-dev 빌드를 할 땐 기본적으로 ‘/dist/’ 하위에 소스코드가 떨어집니다. 자, 여기까지 진행하셨다면 폴더 구조는 다음과 같을 것입니다.지금까지 진행한 파일 모습입니다.뷰 컴포넌트가 잘 제작되고 등록되는지 확인하려면 기본 빌드 폴더인 dist 폴더에 Test.html을 작성해 브라우저로 열어봅시다.확인용 html 파일 작성<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>VUE Test</title>     <!-- VUE 플러그인 -->     [removed][removed] </head> <body>                     [removed][removed]     [removed]         new Vue({             el: '#vue'         })     [removed] </body> </html> 잘 나옵니다.정상적으로 VUE가 적용된 것을 확인합니다.코드이그나이터 설치이제 코드이그나이터 프로젝트 내부에서 VUE 컴포넌트를 출력해보기 위해 코드이그나이터 프로젝트를 생성합시다. 먼저 Codeigniter와 XAMPP를 다운로드 받습니다.Codeigniter 받으러 가기XAMPP 받으러 가기프로젝트 폴더 하위에 Codeigniter 프로젝트용 폴더를 생성합니다.mkdir codeigniter-with-vue-webpack cd codeigniter-with-vue-webpack 다운받은 Codeigniter를 해당 폴더에 압축 해제하면 Codeigniter 설치가 끝납니다.XAMPP 설치 및 DocumentRoot 변경XAMPP를 설치하고 DocumentRoot를 테스트 프로젝트 폴더로 설정한 뒤 아파치를 실행합니다.Codeigniter 프로젝트가 생성되었고, 서버 실행이 완료되었습니다. webpack 폴더를 Codeigniter 프로젝트 하위로 이동node-modules는 너무 크기 때문에 기본 파일만 복사하고, npm install로 설치합니다.Codeigniter에서 VUE를 사용하기 위한 webpack dist설정기존의 프로젝트에서 스크립트를 모아두는 폴더 하위로 빌드 결과 파일을 보내기 위하여 webpack 빌드 시 dist 폴더가 아닌 /application/scripts/vue/hello_world 하위로 빌드 결과 파일이 생성되도록 설정합니다.// 기존 module.exports = {   entry: {     HelloWorld: './src/main.js'   },    //... 생략 } // 변경후 module.exports = {   entry: {     '../../application/scripts/vue/hello_world/HelloWorld.js': './src/main.js'   },    //... 생략 } Codeigniter의 load->view 기능을 활용하여 파일 작성1)header.php// application/views/common/header.php <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>VUE Test</title>     <!-- VUE 플러그인 -->     [removed][removed] </head> 2)실제 view// application/views/vue/hello_world/vueTestPage.php <?php $this->load->view( 'common/header' ); ?> <body>                 [removed] [removed]     [removed]         new Vue({             el: '#vue'         })     [removed] </body> <?php $this->load->view( 'common/footer' ); ?> 3)footer.php// application/views/common/footer.php </html> 실제 프로젝트 구성과 유사하게 header, body, footer로 나누어 파일을 작성해봅니다. 실제로는 더 복잡하지만 이 정도만 나누겠습니다.Codeigniter 테스트용 컨트롤러 작성// application/controllers/Vue.php <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');   class Vue extends CI_Controller {      public function index()     {         $this->load->view('vue/vueTestPage');     }  } 정말 심플(?)한 테스트용 파일 작성이 모두 끝났습니다! 이제 잘 작동하는지 확인해볼까요?코드이그나이터에서 helloworld 출력짜잔이번엔 문제의 IE에서 확인해봅시다.IE9.0 환경에서 확인IE에서도 무사히 출력되는군요. 이제 코드이그나이터 환경의 프로젝트에서도 IE까지 지원하며 무사히 VUE를 사용할 수 있게 되었습니다! (시간이 없어서 가상머신에 IE9가 설치된 윈도우7까지 테스트하진 못했습니다!) 모든 작업이 완료한 후, 파일 폴더 구조는 아래와 같습니다.붉은 네모 부분이 실제로 제작하거나 수정한 파일들입니다.Conclusion여기까지가 Codeigniter 프래임워크 환경에서 webpack + vue를 사용하기 위한 웹팩의 설정 과정 및 테스트 결과였습니다. php 서버를 사용해야 하기 때문에 webpack-dev-server의 핫리로드 기능을 사용하지 못하는 건 매우 안타까운 일입니다. 하지만 짧은 시간에 신기술을 도입하면서도 수많은 리스크를 회피할 수 있다는 건 나쁘지 않은 선택이라 생각합니다.위의 웹팩설정을 조금만 활용한다면 다른 프레임워크 프로젝트에서도 무리없이 VUE를 사용할 수 있을 겁니다! 비슷한 고민을 하셨던 개발자님들… 집에 가기 전 말고 오전에 Webpack을 설치해보세요. 안 그러면 저처럼 집에 못갈 수도 있으니까요!참고.gitignore 작성, index.php 제거 등은 내용에 포함하지 않았으며, 아래의 링크로 자세히 알 수 있음.Codeigniter index.php 없애기글강원우 과장 | R&D 개발2팀[email protected]브랜디, 오직 예쁜 옷만 #브랜디 #개발자 #개발팀 #인사이트 #경험공유 #PHP

기업문화 엿볼 때, 더팀스

로그인

/