TECH PLAY

株式会社ZOZO

株式会社ZOZO の技術ブログ

981

Making Sequences work for you こんにちは! VASILYのiOSエンジニア にこらす です。 SwiftのコントリビューターとSwift Evolution SE-0053 の作者です。 他のOSSプロジェクトにも貢献してるので興味があれば Github でフォローしてください。 今回のトピックはSwift2.0以降の SequenceType というプロトコルと、その内部的な動きについて紹介します。 class や struct を SequenceType プロトコルに準拠させると、 for in ループや map , filter などを使えるようになります。 さあ、始めましょう! struct Unique <T: Comparable> { private var backingStore : [T] = [] var count : Int { return backingStore.count } func setHas (element : T ) -> Bool { return backingStore.filter { $0 == element }.count != 0 } mutating func append (element : T ) { guard ! setHas(element) else { return } backingStore.append(element) } } この Unique という構造体はとてもシンプルです。 Comparable に準拠した型の要素を append すると内部的な配列に要素を追加します。配列中に同じ要素が存在する場合は追加しません。 それでは Unique をテストしてみましょう! var mySet = Unique < String > () // Our set can mySet.setHas( "A Careless Cat" ) //false mySet.append( "A Careless Cat" ) mySet.setHas( "A Careless Cat" ) //true mySet.append( "A Careless Cat" )  //すでにある文字列 mySet.count //まだ1です! //もうちょっと項目追加しましょう! mySet.append( "A Dangerous Dog" ) mySet.append( "A Diamond Dog" ) mySet.append( "Petty Parrot" ) mySet.append( "North American Reckless Raccoon" ) mySet.append( "A Monadic Mole" ) 動物は十分入ったので、名前を print してみよう! for animal in mySet { println(animal) } あれ?うまくいきませんね。。。: なぜうまくいかなかったかというと、 for in を使うために必要な実装が足りないからです。 Stringの配列で for in を書くのは、下記の書き方と同じです。 let vowels = [ "A" , "E" , "I" , "O" , "U" ] var gen = vowels.generate() while let letter = gen.next() { print(letter) } generate() は SequenceType プロトコルのメソッドです。 Unique は SequenceType プロトコルに準拠してないので for in が動かないのです。 それでは、 SequenceType プロトコルに必要な条件を見て実装してみましょう! public protocol SequenceType { associatedtype Generator : GeneratorType public func generate () -> Self.Generator } generate() の戻り値の型は型推論が効くので、 typealias Generator = ... を書く必要はありません。 Unique を SequenceType プロトコルに準拠させて、 generate() メソッドを実装するべきですが、まだ GeneratorType のことをよく知りません。 それでは GeneratorType をチェックしましょう! Generator とは? public protocol GeneratorType { associatedtype Element public mutating func next () -> Self.Element ? } SequenceType と同じように一つのメソッドしかありません。 next() の戻り値の型は、 Element 型となっていますが型推論が効きます。 [String] であれば、実際には String になります。 Generator は next() メソッドで、保持しているデータを順番に返します。最後のデータを返したあと再度 next() を呼ぶと nil を返します。 GeneratorType を使い終わると再利用できません。同じデータセットをもう一度 GeneratorType で読みたければ、新しく GeneratorType を生成する必要があります。 そして、 generate() を実装する時に戻り値の GeneratorType と他の GeneratorType 変数の状態を共有しないようにしなければいけません。 基本的な上限がある GeneratorType : struct CountToGenerator : GeneratorType { private var limit : Int private var currentCount = 0 init (limit : Int ) { self .limit = limit } mutating func next () -> Int ? { guard currentCount < limit else { return nil } defer { currentCount += 1 } return currentCount } } var goTillTen = CountToGenerator(limit : 10 ) while let num = goTillTen.next() { print(num) } SequenceType と GeneratorType を学んだので、 Unique 専用の GeneratorType を作りましょう! class UniqueGenerator <T>: GeneratorType { private var _generationMethod : () -> T ? init (_generationMethod : () -> T ?) { self ._generationMethod = _generationMethod } func next () -> T ? { return _generationMethod() } } extension Unique : SequenceType { func generate () -> UniqueGenerator <T> { var iteration = 0 return UniqueGenerator { if iteration < self .backingStore.count { let result = self .backingStore[iteration] iteration += 1 return result } return nil } } } ここまで学んだことで、 Unique の GeneratorType の実装ができましたが、もう少し短く書けます。 AnyGenerator というGenericタイプを使うと UniqueGenerator を作る必要がありません。 extension Unique : SequenceType { func generate () -> AnyGenerator <T> { var iteration = 0 return AnyGenerator { if iteration < self .backingStore.count { let result = self .backingStore[iteration] iteration += 1 return result } return nil } } } もう一度 for in ループを書きましょう! for item in mySet { print(item) } やっと動きました! map と filter も動きます!やった! let cnt = mySet.map { Int( $0 .characters.count) } //[14, 15, 13, 12, 31, 14] mySet.filter { $0 .characters.first != "A" } //["Petty Parrot", "North American Reckless Raccoon"] Controlling Sequences with Sequences SequenceType の実装次第で、とても大きいリストや無限リストを作ることができます。 そういったリストから先頭から n 個取り出そうとすると、そのまま使うと無限ループになってしまいます。 例えば下記の ThePattern は無限リストです。 for in で使うと、 Generator が nil を返さないため、永遠に 0 か 1 を返します。 class ThePattern : SequenceType { func generate () -> AnyGenerator <Int> { var isOne = true return AnyGenerator { isOne = ! isOne return isOne ? 1 : 0 } } } // 無限ループ for i in ThePattern() { print(i) } この無限リストから class First <S: SequenceType>: SequenceType { private let limit : Int private var counter : Int = 0 private var generator : S.Generator init (_ limit : Int , sequence : S ) { self .limit = limit self .generator = sequence.generate() } func generate () -> AnyGenerator <S.Generator.Element> { return AnyGenerator { defer { self .counter += 1 } guard self .counter < self .limit else { return nil } return self .generator.next() } } } for item in First( 5 , sequence : ThePattern ()) { print(item) // 0 1 0 1 0 } いいですね! First(n, sequence: s) を呼び出すと、 s の先頭 n 個の要素を取り出せます。 s が無限リストだとしても最初の n 個しかチェックしないので効率的です。 まとめ 配列のように扱うクラスやコンテナクラスであれば SequenceType プロトコルを採用しましょう。 コードが読みやすくなりますし、自然に for in や map , filter を使えるようになります。 ぜひ気軽に SequenceType のプロトコルを使ってみてください。 最後に VASILYではSwift好きなiOSエンジニアを募集しています!興味があればぜひ応募してみてください。 https://www.wantedly.com/projects/27397 www.wantedly.com
2016年3月22日、第二回目となる Fashion Tech meetup を開催しました。前回は MERY を運営する株式会社peroli様との開催でしたが、今回は FRIL を運営する株式会社Fablic様が加わり、VASILYを含め3社での開催となりました。 イベント公開開始時、参加枠70席のところ120名を超える申し込みがあり、増枠を設けるほどの大盛況となりました。 最終的に180人を超える申し込みを頂き、FashionxTechnologyに少しずつ関心が持たれているのではないかと感じています。 弊社からも3名が登壇し、Fashion x Technologyを題材とした発表を行ってきました。 今回はその資料を公開します。残念ながら参加出来なかった方、Fashion Tech meetupを初めて知った方、是非ご一読ください。 メインセッション 「ディープラーニングを使って商品カテゴリの分類をしてみました」 弊社の新卒エンジニアの塩崎と、内定者インターンの後藤が発表しました。 塩崎は原子核物理学、後藤は観測的宇宙論・銀河形成論というファッションとはかけ離れた研究をしていた2人が、ディープラーニングでファッションのカテゴリを分類していくという発表になっています。 LT ファッションアイテムの類似画像検索を実装してみました こちらは2016年3月より弊社に入社したデータサイエンティストの中村が類似画像検索についてLTを行いました。まだ入社して3週間ですが、さっそく自身の知識と探究心で、技術的なアプローチをしています。 まとめ VASILYの資料を公開しましたが、いかがでしたでしょうか。 ファッションやWeb業界と大きく異なる経歴を持つ若いメンバーが、FashionxTechnologyに対して様々なアプローチをして活躍していることは伝わったのではないかと思います。 今回弊社の資料を公開しましたが、peroli様、Fablic様の発表も面白く、各社の個性がでたFashion Tech meetupでした。来場された方もファッションに関わる方、技術に興味を持つ方の集まりなので懇親会も活発なものでした。 Fashion Tech meetupを通して、少しずつ業界を盛り上げていけるのではないかと思います。 本記事で興味を持ち、Fashion Tech meetup #3が開催される際に足を運んでくださる方が増えれば幸いです。 おわりに 弊社の資料を通して興味を持たれた方、是非一度オフィスまでお越しください。 一緒にFashionxTechnology盛り上げていきましょう! https://www.wantedly.com/projects/45354 www.wantedly.com https://www.wantedly.com/projects/42989 www.wantedly.com
こんにちは。デザイナーの権です。 先日、何かと話題のAdobeのプロトタイピングツール 「Adobe XD(Adobe Experience Design)」 のプレビュー版がリリースされました。 早速いろいろと触ってみたところ、プレビュー版ということもありまだシンプルな機能のみという感じでしたが、さすがAdobeだけあっての直感的なUIで、初めて使うツールでありながら扱いやすさは抜群に良いと感じています。 今回は、まだ1~2日使っての感想ではありますが、実際に自分たちのアプリ開発の現場に取り入れられそうかを前提に「Adobe XD」を使っての操作感や印象をご紹介します。 ※使い方などは、Adobeの こちら のブログでの詳しい解説を参考にしていただければと思います。 全体的な操作感 類似の機能を兼ね備えていることから、 Sketch との比較を余儀なくしてしまいますが、レイヤー管理ができない、グラデの扱いがない、縦に長いページのプロトタイプが作成できないなどできないことがまだまだ多く、現段階だとSketch+ InVision の組み合わせの方が勝っている印象です。( Craft のPrototype機能がリリースされると尚更敵わなそう。) しかし、GUI画面はシンプル&直感的で扱いやすく、ショートカットもAdobeの他ソフトとほぼ共通化されてるので、操作しやすく軽いです。 そして、「UIデザインする上でプロト作りはデザインの一環」と言わんばかりに「Design」と「Prototype」がタブ切り替えで反映できるため、スイッチングコスト無しに確認できるフローは素晴らしい体験だと思いました。 また、個人的に特にいいと思ったのは、数あるプロトタイプツールに比べてダントツでわかりやすさを感じる理由として考えられる、チュートリアルのクオリティの高さ。 デモ動画でももちろんわかりやすく紹介されてましたが、起動画面にはサンプルファイルが用意されていて、ステップに沿って操作することにより体験しながら覚えていけます。こういったしっかりしたチュートリアルが用意されていることがAdobeツールの扱いやすさにつながっているのだと感じます。 他Adobeソフトとの互換性 デザインツールやプロトタイピングツールといえば、Sketchや Prott など今では様々なツールが世に溢れていますが、なんだかんだでAdobeソフトが一番扱いやすいし手に馴染むといったデザイナーの方は多いのではないのでしょうか。 かくいう私もその一人で、個人制作ではSketchも使用していますが、様々な業務が交錯する社内での制作ではAdobeソフトをメインで使用しているため、「Adobe XD」に関してはPhotoshopやIllustratorとの互換性に優れていて使いやすいのでは?と熱く期待していました。 新規から作成するユーザーに向けてを想定しているとは思いますが、それぞれのソフトでデータをインポートしてみて、どの程度連携できそうかを以下にまとめてみました。 Photoshop⇄XD PSDデータをインポートすることはできない Photoshopからは画像のインポートのみできる スマートフィルターで再編集などもできない テキスト情報をコピーすると、テキストはペーストされるがスタイルは解除される Illustrator⇄XD Photoshopと同じく、Aiデータをインポートすることはできない 図形などのベクターオブジェクト、テキスト情報はインポートできる。しかし、テキストを編集しようとするとブツ切りになり、特に日本語の文字組みは崩れてしまう 画像、シンボルデータ、シャドウグローなどの効果はインポートできない 今まで作成したデータをインポートできてプロト制作まで完了できたら、さぞ便利だろうと思っていましたが、 正直なところ期待には及ばずといったところで、上記のような現象が起こってしまいました。 現場で使うにはまだちょっと難しいかなといった印象ですが、これからのアップデートに期待していきたいです。 「Adobe XD」に今後期待するもの 上記であげたような現象もありましたが、「Adobe XD」を触ってみて最大の魅力と感じたのは、「Design」と「Prototype」がワンクリックでタブ移動できてシームレスに制作できる部分ではないかと思います。 デザインに限らずどんな事象でも、「オブジェクト間の距離や時間」は体験の向上に関与するものであり、近くなればなるほど新しい体験と感動が提供できるはずなので、「Adobe XD」のように作業間の距離や時間を縮めることは、開発効率のアップに確実につながると思います。 そして、Adobeユーザーとしては、さらに詳細な作り込みを経ての制作となればPhotoshopやIllustratorなどが必要となるため、他Adobeソフトとの互換性が高くなれば、さらにストレスなく制作が進められるので、ツール名である「Experience Design」のごとく、体験をデザインするにふさわしいソフトとして将来欠かせないツールになるのではと心から期待しています。 さいごに VASILYでは、デザイナーも様々なツールを試して、常に試行錯誤しながら、最適な制作&開発方法を模索しております。 そんな環境にご興味のある方は、以下ページからまずは話だけでも聞きに来てみてください。 学生、インターンも大歓迎です。素敵なポートフォリオとともに遊びに来てください。 お待ちしております。 https://www.wantedly.com/projects/41710 www.wantedly.com https://www.wantedly.com/projects/42672 www.wantedly.com
この記事は2016年3月15日時点の情報です。 最新の仕様とは異なるかもしれないので、ご注意ください。 概要 Facebookのモバイルページ向けネイティブアドがオープンβで提供を開始していたようなので、 埋め込んでみました。 基本的にドキュメントが英語しかなかったので、この記事では、Facebookが提供しているドキュメントの意訳と実際に試してみて得られた知見を合わせて書きます。 Audience Network for Mobile Web - Open Beta 埋め込むまでの手順 Facebook developer登録(登録済みならば省略可) Facebookアプリ登録(登録済みならば省略可) placement発行 コードの埋め込み Facebook developer登録 Web上に多くのドキュメントがあるので、詳しい手順は省略します。 Facebookアプリ登録 こちらも、Web上に多くのドキュメントがあるので、詳しい手順は省略します。 placement発行 Facebook developerページにアクセスし、左メニューのAudience Networkを選択します。 ページ内のPlacementsタブ内の「Create Ad Placement」ボタンを選択します。 表示されたモーダル内の項目を埋めます。 「名前」は自分がわかりやすいものを入力しましょう。 「広告を表示するステップ」はどのような手順をとった際に、広告が表示されるかを記述します。 場合によっては、この情報を元に、facebookの広告チームが提案を行ってくれるそうです。 「表示フォーマット」は、「ネイティブ」を選択します(他の形式として「バナー」がありますが、この記事では省略します)。 ステータスはオンを選択します(オフは配信を行わず統計データを見るだけの場合に使います)。 最適化はとりあえず、フィルレート最適化を選んだほうが良さそうです。 最後に保存するを選択して、placementを発行しましょう。 コードの埋め込み Placementsタブ下のAcive placementsに先ほど発行したplacementが追加されていると思いますので、 そこにある「Get HTML」のボタンを押します。 基本は、ダイレクトを選択します(DFPを利用する場合は、DFPを選択してください)。 セレクトボックスのほうは、ネイティブを選択します。 表示されたHTMLコードをコピーし、自分のサイトの表示させたい箇所に貼り付けます。 以下のようなコードが表示されます。 < style > #ad_root { display : none ; font-size : 14px ; height : 250px ; line-height : 16px ; position : relative ; width : 300px ; } .thirdPartyMediaClass { height : 157px ; width : 300px ; } .thirdPartyTitleClass { font-weight : 600 ; font-size : 16px ; margin : 8px 0 4px 0 ; overflow : hidden ; text-overflow : ellipsis ; white-space : nowrap ; } .thirdPartyBodyClass { display : -webkit- box; height : 32px ; -webkit- line-clamp: 2 ; overflow : hidden ; } .thirdPartyCallToActionClass { color : #326891 ; font-family : sans-serif ; font-weight : 600 ; margin-top : 8px ; } </ style > < script > window .fbAsyncInit = function () { FB. Event .subscribe ( 'ad.loaded' , function ( placementId ) { console.log ( 'Audience Network ad loaded' ) ; document .getElementById ( 'ad_root' ) .style.display = 'block' ; } ) ; FB. Event .subscribe ( 'ad.error' , function ( errorCode, errorMessage, placementId ) { console.log ( 'Audience Network error (' + errorCode + ') ' + errorMessage ) ; } ) ; } ; ( function ( d, s, id ) { var js, fjs = d.getElementsByTagName ( s ) [ 0 ] ; if ( d.getElementById ( id )) return ; js = d.createElement ( s ) ; js.id = id; js.src = "//connect.facebook.net/en_US/sdk/xfbml.ad.js#xfbml=1&version=v2.5&appId=999999999999999" ; fjs.parentNode.insertBefore ( js, fjs ) ; } ( document , 'script' , 'facebook-jssdk' )) ; </ script > < fb :ad placementid= "111111111111111_1111111111111111" format= "native" nativeadid= "ad_root" testmode= "false" ></ fb :ad> < div id = "ad_root" > < a class = "fbAdLink" > < div class = "fbAdMedia thirdPartyMediaClass" ></ div > < div class = "fbAdTitle thirdPartyTitleClass" ></ div > < div class = "fbAdBody thirdPartyBodyClass" ></ div > < div class = "fbAdCallToAction thirdPartyCallToActionClass" ></ div > </ a > </ div > これでとりあえず表示はできたかと思います。 もし、表示できない場合には、Facebook developerページで広告を設置したサイトのドメインを許可しているかどうかを確認してください。 また、開発環境などで確認をしたい場合には、fb:adタグのtestmode属性の値を true にし、 Facebook developerページの設定で、Developer以上の権限が付与されたアカウントでFacebookにログイン した状態でアクセスしてみてください。 testmodeがtrueの場合には、テストデータしか配信されず、クリックなどの挙動も確認できないため、注意が必要です。 Getting Started with the Audience Network 一つのページに複数のADを埋め込む方法 今回は、コンテンツ中とコンテンツ下の2箇所にアドを設置しましたが、 2箇所に設置する場合にどうするかのドキュメントがなかったので、以下のように実装しました。 placementを表示するADの数だけ発行する コールバックをplacementIDに応じて処理する サンプルコード < script > window .fbAsyncInit = function () { FB. Event .subscribe ( 'ad.loaded' , function ( placementId ) { if ( placementId == '111111111111111_1111111111111111' ) { document .getElementById ( 'ad_root1' ) .style.display = 'block' ; } if ( placementId == '222222222222222_2222222222222222' ) { document .getElementById ( 'ad_root2' ) .style.display = 'block' ; } } ) ; FB. Event .subscribe ( 'ad.error' , function ( errorCode, errorMessage, placementId ) { if ( placementId == '111111111111111_1111111111111111' ) { document .getElementById ( 'ad_root1' ) .classList.add ( 'error' ) ; } if ( placementId == '222222222222222_2222222222222222' ) { document .getElementById ( 'ad_root2' ) .classList.add ( 'error' ) ; } } ) ; } ; ( function ( d, s, id ) { var js, fjs = d.getElementsByTagName ( s ) [ 0 ] ; if ( d.getElementById ( id )) return ; js = d.createElement ( s ) ; js.id = id; js.src = "//connect.facebook.net/en_US/sdk/xfbml.ad.js#xfbml=1&version=v2.5&appId=999999999999999" ; fjs.parentNode.insertBefore ( js, fjs ) ; } ( document , 'script' , 'facebook-jssdk' )) ; </ script > ネイティブアドのデザイン・コーディング 実際に表示してみるとわかると思いますが、サンプルそのままではレイアウトや文字色などサイトに合っていません。 ここからは、ネイティブアドらしく、各サイトに合った形で広告を埋め込むための情報を説明していきます。 Native Ads in Mobile Web ネイティブアドの構成要素 広告の配信ネットワークから広告が配信されてくる際に、 自分のコンテンツに合うように表示する要素を取捨選択することができます。 以下に利用可能な要素と簡単な説明を載せておきます。 要素名 クラス名 説明 省略してもよいか Icon Image fbAdIcon 正方形(128x128)のアイコン。広告主のアイコン(企業ロゴ)などが表示されます 省略可 Ad Image fbAdMedia 長方形(600x314と書かれていますが、実際は796x415などのサイズ)の広告のメインビジュアル 省略可 Title fbAdTitle 広告のタイトル 省略不可 Subtitle fbAdSubtitle 広告のサブタイトル 省略可 Body fbAdBody 広告の本文 省略可 Call-to-action fbAdCallToAction いわゆるCTA。「ゲームであそぶ」「詳しくはこちら」などのクリックを誘う文言が入ります 省略不可 Ad Link fbAdLink 広告へのリンク。広告のクリック領域を決めるに使います 省略可 Social Context fbAdSocialContext アプリの評価や追加情報(200万ダウンロード)などを表示できるらしいです(今回は未使用) 省略可 Native Mobile Web Components デザインガイドライン コンテンツに合うようにデザインを調整できるネイティブアドですが、 facebookが規定する最低限のガイドラインを守る必要があります。 Design Guidelines for Audience Network Ads 最低限表示する必要のある要素に関するガイドライン 次の3つの要素を表示する必要があります。 表示しなかった場合には、Facebook側から広告の配信が停止されてしまいます。 広告のタイトル CTA AdChoicesと広告だと見分けがつく要素(例:SponseredやAdといった表記) また、それぞれの要素は表示の上でいくつか規定があります。 広告のタイトル 広告のタイトルは少なくとも20文字までは表示する必要があります。 タイトルが20文字より多い場合には、ellipsisなどを使って省略することが可能です。 CTA CTAの文言は省略してはいけませんが、最大でも25文字なので、とても長くなることはありません。 また、配信される広告を見たところ、10文字以内が多そうです(25文字の場合を考慮したデザインにしたほうがよいと思います)。 CTAはテキストのまま表示してもよいですが、ボタンのようにデザインすることも許されています。 自分のコンテンツとの親和性やクリック率を考えて、使い分けるとよいと思います。 AdChoicesと広告だと見分けがつく要素 まず、広告とコンテンツの台(背景やカードを指す?)とを明確に線引きし、 誤ってクリックすることを避けるためにコンテンツとの間にスペース空ける必要があります。 この辺の感覚は、 ガイドライン に載っている例を参照したほうがよいかと思います。 ユーザが表示される広告について選ぶためにAdChoiceは必須になっています。 AdChoiceについては次の3つの方法の内どれか 一つ を行う必要があります。 (元々、アプリ向けのガイドラインなので、正しいかどうかは怪しいです) Audience Network SDKによって提供されるAdChoiceの制御機構を用いて、クリック可能な「AdChices」というテキストを表示する コンテンツと区別するために明確に「Sponsored」「Promoted」「Ad」といった文言を表示し、かつ、広げて表示可能なAdChoices iconを表示する もし、1.と2.のどちらもできない場合には、「Sponsored」「Promoted」「Ad」といった文言を表示し、app storeページとプライバシーポリシーで閲覧可能なリンクとして https://m.facebook.com/ads/ad_choices へのリンクを設置する 実際に、モバイルページに広告を読み込むと自動でAdChoices icon(緑色の三角のようなアイコン)が読み込まれます。 それをタップすると広がって、AdChoicesのテキストが表示されますので、 モバイルページのネイティブアドでは、これを用いて2.の要件を満たすのがよさそうです。 (AdChoice iconの動作については、ドキュメントに書かれていない仕様なので、突然変わるかもしれません) その他の構成要素に関するガイドライン メインビジュアル メインビジュアルは拡大縮小や切り抜きを行ってもよいですが、描き変えたり歪めてはいけません。 最小限の文字と画像はメインビジュアルの下部20-25%の上に重ねて表示できますが、 メインビジュアルの上に表示されるテキストは読みやすくする必要があります。 本文 本文の表示は必須ではありませんが、表示する場合には少なくとも72文字は表示する必要があります。 72文字表示できない場合は、非表示にするか、アニメーションで全体を表示するほうがよいです。 実際に導入したデザイン 今回は次のようなデザインを採用しました。 実際の広告画面を表示するのは問題がありそうなので、あくまでイメージですが、参考にしてみてください。 まとめ 今回はFacebookのネイティブアドをとりえあず導入してみました。 導入したばかりなので、収益という観点でのパフォーマンスはまだわかりませんが、 融通の効くデザインガイドラインである点やドキュメントがしっかりしている点で好印象でした。 モバイルページ版はまだオープンβということもあり、情報が部分的ではあるので、 間違っている箇所もあるかもしれませんが、参考になれば幸いです。 VASILYでは、新しい技術に積極的に取り組めるようなメンバーを募集しています。 興味のある方は こちら からご応募ください。
こんにちは、VASILYのバックエンドエンジニアの塩崎です。 iQONの中ではクローラーと検索サーバーを担当しています。 iQONのクローラーには提携ECサイトさんからクロールした商品を商品カテゴリー(Tシャツ、ワンピース、etc.)に自動的に分類する機能があり、商品タイトルや商品説明文などのテキスト情報を元に分類を行っています。 しかし、一部のカテゴリー(セーター・ニット帽)の商品はテキスト情報だけからでは精度の良い分類を行うことができません。 そのため、これらのカテゴリーの商品については画像を用いたカテゴリー分類を導入しました。 これらの機能を実現するために、当社のデータサイエンスチームとも協力を行い、ディープラーニングを用いたカテゴリー判定器を開発しました。 また、この機能は既存のクローラーの機能からの独立性が高いので、クローラーに組み込むときにはマイクロサービス化をして組み込みました。 その結果、カテゴリ判定の精度として、セーターカテゴリーでは99.7%、ニット帽カテゴリーでは99.8%という結果を得ることができました。 背景 iQONのクローラーは提携ECサイトさんから毎日数百万商品のクロールを行っており、毎日数千〜数万商品がiQONに新たに追加されます。 これらの数の商品のカテゴリー分類を人間が行うことは大変な労力を要するため、iQONのクローラーには商品カテゴリーの分類を自動的に行う機能があります。 商品タイトルや商品説明文といったテキスト情報に対して形態素解析を行い、文脈情報を考慮した上で候補となる単語を1つに絞り込み、その単語に対して辞書マッチを行うことでカテゴリー分類を行います。 その結果、精度98%での商品カテゴリー分類を実現しました。 しかし、セーター(iQONでのカテゴリ分類ではトップス・ニットと表記されるが、紛らわしいのでこの記事ではセーターと表記)とニット帽の判定をテキストだけから精度良く行うことは困難です。 これらの商品のタイトル、説明文には「ニット」という表記しかされていない場合が多いからです。 なぜこのような曖昧な表記になってしまっているかというと、人間が商品情報を見るときには画像とテキストを同時に見て判断をするために、このような表記でも通常のECサイトには十分なためだと思います。 この問題を解決するためには、商品画像を用いたカテゴリー判定が必要になります。 画像からカテゴリー判定する手法の検討 近年、画像判定の分野では畳み込みニューラルネットワーク(CNN)が注目されていますが、この問題に対してもCNNが有効であるかを確認するために、以下に列挙された3つの手法との比較を行いました。 多層パーセプトロン (MLP) HOG特徴量 + k近傍法 (HOG + kNN) HOG特徴量 + サポートベクターマシン (HOG + SVM) これら合計4つの手法を用いて、セーターとニット帽の分類を行った結果を以下の表に示します。 なお、この検証では速度を優先してRGB画像をグレースケール画像に変換して分析を行いました。 手法 accuracy (%) CNN 97.84 MLP 93.08 HOG + kNN 96.74 HOG + SVM 97.47 この結果からCNNでも他の手法に比肩する精度が出ることがわかりました。 この後、約100のカテゴリー分類するための分類器を作る必要があることや、商品に対して複数のタグ(トップス + ニット)を付与する必要があることなどを考慮し、CNNを採用しました。 ディープラーニングを用いた商品カテゴリーの判定 画像からのカテゴリー判定を行うために、以下のようなCNNのモデルをChainerを用いて作成しました。 import chainer.functions as F import chainer.links as L from chainer import Variable, FunctionSet model = FunctionSet( conv1 = F.Convolution2D( 3 , 64 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 3 )), conv2 = F.Convolution2D( 64 , 128 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 64 )), conv3 = F.Convolution2D( 128 , 256 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 128 )), conv4 = F.Convolution2D( 256 , 512 , 4 , stride = 2 , pad = 1 , wscale = 0.02 *math.sqrt( 4 * 4 * 256 )), fl = L.Linear( 6 * 6 * 512 , 2 , wscale = 0.02 *math.sqrt( 6 * 6 * 512 )), bn1 = F.BatchNormalization( 64 ), bn2 = F.BatchNormalization( 128 ), bn3 = F.BatchNormalization( 256 ), bn4 = F.BatchNormalization( 512 )) def forward (x_data, train = True ): x = Variable(x_data, volatile = not train) h = F.relu(model.bn1(model.conv1(x))) h = F.relu(model.bn2(model.conv2(h))) h = F.relu(model.bn3(model.conv3(h))) h = F.relu(model.bn4(model.conv4(h))) y = model.fl(h) return F.softmax(y) CNNの層数は4層で全ての層が畳み込み層です。 出力層にはソフトマックス関数を用いているために、各カテゴリに属している確率が出力されます。 入力は96 x 96 pixelのカラー画像(RGB)です。 ECサイトから取得する画像はこのサイズではないことがほとんどですので、CNNに入力する前にリサイズ処理を行います。 アスペクト比1:1ではない画像については、正方形のエリアに貼り付けを行います。 プーリング層も組み合わせたモデルも作成し検証を行いましたが、全てが畳み込み層であるようなモデルの精度が最も高かったたために、このような構成にしています。 ECサイトから取得する画像の中にあるアイテムの位置は大体固定されているため、このような結果になったのではないのかと思います。 学習にはセーター・ニット帽の各カテゴリーの画像を1.5万枚ずつ使用しました。 テキストからのカテゴリー判定の結果を補助的に用いながら、すべての画像を目視でチェックしデータクレンジングを行いました。 また、学習の際にはGoogleが2015年に発表したBatch Normalization( http://arxiv.org/abs/1502.03167 )という手法も用いて、学習の高速化を図っています。 マイクロサービス化した理由 この機能をiQONのクローラーに組み込む際にはマイクロサービスとして組み込みを行いました。 使用している言語・ライブラリなどの違いにより同一ホスト上で稼働させるよりも、独立した機能として切り出した方がクローラーシステム全体の構成がシンプルになると判断したためです。 また、画像からカテゴリーを判定するという機能それそのものの独立性が高いことも理由の1つです。 現在クローラーはCentOS 6.5 + rubyで稼働しているのに対して、画像判定処理はUbuntu 14.04 + pythonで稼働しています。 画像判定サーバーの構成 画像判定用のサーバーの構成は以下のようにしました。 リバースプロキシ: nginx( http://nginx.org/ ) アプリケーションサーバー: uWSGI( https://uwsgi-docs.readthedocs.org/en/latest/# ) webアプリケーションフレームワーク: flask( http://flask.pocoo.org/ ) 現在の負荷状況ではインスタンスタイプc4.largeが一台あれば十分です。 負荷が増えた時にはサーバーのスケールアップ・スケールアウトも検討に入れる必要があります。 VASILYではデプロイツールとしてcapistranoを使用していますが、今回はPythonで製作をしたのでデプロイツールもPython製のfabric( http://www.fabfile.org/ )を使用しました。 通常のWEBアプリケーションのデプロイに要求されるようなアプリケーションコードの変更をするデプロイの他に、モデルのパラメーターを変更した時にモデルファイルをAmazon S3から取得し更新を行うデプロイ方法もサポートしています。 Teslaを搭載した学習用サーバーで生成したモデルファイルをAmazon S3にアップロードした後に、そのファイルをAmazon S3から取得し、サーバーの再起動を行います。 このサーバーに対して以下のようにリクエストを投げることで、画像判定の結果を得ることができます。 サーバーに対するリクエスト・レスポンスのフォーマットはGoogle Cloud Vision APIを参考にしました。 $ curl -X POST -H "Content-Type: application/json" -d '\ { \ "requests":[ \ { \ "features":[ "CATEGORY_DETECTION"], \ "image":{ \ "uri":"http://www.example.com/image.jpg" \ } \ } \ ] \ }' \ "http://image-determination:8080/annotate" { "responses": [ { "categoryAnnotations": [ { "score": 80.0, "description": "sweater" }, { "score": 20.0, "description": "knit_caps" } ] } ] } ここで返されるscoreはそのカテゴリーに属する確率を%で表現したものです。 確率からカテゴリーへのマッピング カテゴリー判定器の出力は、各カテゴリに属している確率ですので、それを元にカテゴリーにマッピングする必要があります。 今回は以下のような関数を用いて各カテゴリーにマッピングを行いました。 def map_to_category : threshold = 90 if score_sweater > threshold return SWEATER elif score_knit_caps > threshold return KNIT_CAP else return UNKNOWN 単純にscoreの高い方のみを採用するということはせずに、scoreの高い方でありなおかつ score > threshold を満たすものを判定結果としています。 これは、カテゴリ判定ミスのFalse PositiveとFalse Negativeがユーザーに与える影響が大きく異なるための処理です。 簡単に言いますと、判定結果が曖昧な商品を間違ったカテゴリーに表示するよりも、その商品を出さない方がマシという考えです。 結果 このカテゴリー判定器を用いてセーターとニット帽の画像1万枚ずつを分類した結果を以下に示します。 予測 実測 セーター ニット帽 不明 セーター 9839 15 146 ニット帽 32 9636 332 これを元に、セーターカテゴリーの精度(precision)を求めると、9839 / (9839 + 32) = 99.7% という結果が得られました。 同様にニット帽カテゴリーの精度を求めると、99.8%という結果が得られました。 以下のiQONの画面からわかる通り、平置きの商品画像・人間が着用している商品の画像・マネキンが着用している画像など、様々な種類の画像を問題なく分類できています。 セーターの判定結果 ニット帽の判定結果 これからの展望 このカテゴリー判定器をさらに発展させたものとして、iQONで使用している約100のカテゴリに分類を行う判定器も開発しています。 それを実現するためには画像情報だけではなく、テキスト情報やブランド情報などの画像以外の情報も統合的に用いて判定を行います。 VASILYでは、最新の研究論文にアンテナを張りながら、同時にユーザーの課題解決を積極的に行うメンバーを募集しています。 興味のある方はこちらからご応募ください。 https://www.wantedly.com/projects/42989
概要 畳み込みニューラルネットワークによる画像生成モデル(DCGAN)に弊社のワンピース画像10万枚を学習させました。 得られた生成モデルを使って、乱数で作った100次元ベクトルからワンピース画像を生成しました。 逆に、一枚のワンピース画像を100次元ベクトルに圧縮し、可視化しました。 可視化したことで、モデルがワンピースの【色】【形】【柄】【モデルやマネキンの有無】など、基本的な特徴を捉えられていることがわかります。 この技術は、自動タグ付けや類似画像検索に応用することができます。 はじめに  はじめまして。データサイエンスチームの後藤と申します。現在、アイテム画像のカテゴリ判定モデルを作ったり、各部門のKPIの日々の変動やシステムの異常を知らせるダッシュボードを作る仕事をしています。  また、最新の研究論文にもアンテナを張り、提案手法の検証にも取り組んでいます。今回は、去年の暮れあたりから話題になっている、ディープラーニングによる画像生成モデル(DCGAN)を弊社の所有するワンピースの画像に適用して、その利用価値を検討してみたいと思います。ちなみに、私はディープラーニングに入門したばかりで、今回の記事には間違いが含まれている可能性があります。その点を注意してお読み頂けば幸いです。 使用するモデル  今回用いるモデルは、DCGAN( http://arxiv.org/abs/1511.06434 )と呼ばれるものです。Deep Convolutional Generative Adversarial Networksの略で、名の通り、畳み込みニューラルネットワークによる生成モデルの一種です。大雑把に言えば、学習した特徴量を組み合わせて本物らしい画像を生成するGeneratorと、本物の画像と生成された画像を見破るDiscriminatorの二つのニューラルネットワークが互いに切磋琢磨して学習を進めていくというモデルになっています。論文中では「人の顔」や「ベッドルーム」の画像を学習し、見事に生成できています。この研究をうけて、アルゴリズムをchainerで実装し、アニメキャラなどのイラストを描かせた記事もあります。 Chainerで顔イラストの自動生成 Chainerを使ってコンピュータにイラストを描かせる 前者はアルゴリズムの定性的な解説が、後者は歴史的経緯が詳しいです。  調べた範囲では、ファッションアイテムの画像に適応した例はなかったので、アルゴリズムの適用範囲を探るという意味でやる価値はあると思いますし、特徴量を自動で抽出するというディープラーニングの特性を使ってこれまでにないサービスを作れるんじゃないか、という期待もしながら分析を進めます。  実装は、以下の二つを参考にしています。ネットワークの構成は、論文と同じものを採用します。学習を安定させるBatch normalizationを組み込んだことが肝のようです。 https://github.com/mattya/chainer-DCGAN https://github.com/Hi-king/chainer-DCGAN 学習データ   今回は、弊社のサービスで使われているワンピースの画像、約10万枚を学習に用います。前処理として、画像を96×96ピクセルのRGBに変換しておきます。数あるカテゴリの中からワンピースを選んだのは、個人的な趣味です。 ワンピースの生成  学習をある程度進めると、100個の乱数で与えることで、以下の様なワンピースを作ることができました。特徴を組み合わせて新しく生み出したものなので、この世にないワンピース、と言うことができます。生成モデル特有の不気味さがありますが、ご興味のある方は是非拡大して見てみてください。  遠目でみると自然な画像が生成されています。一枚一枚をじっくりみると、アイテムが大きく写っている画像は違和感なく綺麗に生成できていることがわかります。一方で、モデルの顔や腕の位置などバリエーションが多いパーツが含まれている場合は、若干不自然で、本物の画像でないことがすぐにわかります。まれに、マネキンの体に、首元がハンガーになっているようなものもあり、このモデルは現実を理解してワンピースを生成しているわけではない、ということも伺えます。  全体的に見て、それなりに自然で、個々の要素の特徴をある程度、捉えられていることがわかりました。ここから様々なことができるようになります。  例えば、さらなる検証が必要ですが、100次元ベクトルと得られた画像の対応関係を調べることで、モデル特有のベクトルを抽出したりできそうです。モデル着用のワンピースの画像から、モデルだけを取り除く演算をして、服だけの画像を生成することも可能になるのでは、と想像しています。 画像のベクトル化でアイテムを整理する  今度は逆に、一枚のワンピース画像を100次元ベクトルに変換して情報を圧縮します。100次元でもまだ、人が理解するには難しいので、多少の正確さを犠牲にして、2次元にマッピングします。ここでは、t-SNEというアルゴリズムを利用しています。  上図では、2000点程度のアイテムをマッピングしてみました。画像の分布を見てみると、領域ごとになんらかの特徴を持つことがわかります。具体的に4箇所拡大してみました。左から【形】、【柄】、【色】、【モデル】という特徴を持ったアイテムが集まっています。  今回のモデルは、画像の特徴を上手く100次元ベクトルで表現できているようです。このように画像データの特徴を数値で表現できると、サービスに組み込みやすくなります。例えば、どの数値が【柄】と対応しているのかを把握しておくことで、花柄やボーダー柄といったタグを自動で付与することが可能になります。また、手元にある写真の類似画像を検索する、といったタスクも少ない計算でできるようになりそうです。ほかにも様々な切り口でアイテムを捉えることできそうなので、アイテム検索がより便利な機能になるかもしれません。今回得られた知見をより深めて、サービスをより使いやすいものにしていきたいと思います。 最後に VASILYでは、技術的チャレンジをしながらiQONを開発していただける仲間を募集しています。ご興味のあるかたは こちら からご応募よろしくお願い致します。
こんにちは。iOSエンジニアの庄司です。 普段のアプリ開発において、バックエンドチームから「○○のページで△△のデータ取得するためにリクエストしているURLってどんなの?」と聞かれることがよくあります。 その都度、APIリクエストとリクエスト結果をprintで表示するフラグをONにしてアプリをビルドするということをしていたため、かなり手間がかかっていました。 こういった作業を楽にするためにネットワークデバッグライブラリをいくつか比較してみました。 ライブラリ ResponseDetective https://github.com/netguru/ResponseDetective NSURLSessionのリクエストやレスポンスをデバッガのログに流してくれるライブラリです。 特徴・利点 フルSwiftで書かれており、NSURLProtocolのメソッドに割り込んでログを落とす事ができます。 また、HTML, JSON, imageなどのレスポンスのタイプによって処理を分けることができます。 欠点 通信のデリゲートを受け取ることはできますが、デフォルトでは何もログを落とさないため、printも自分で書きます。 その結果、そこそこのコード量になってしまいますし、printを書くということは、ログを見るためにその都度ビルドが必要になってしまいます。 Wireshark https://www.wireshark.org/ Windows, OSXどちらでも使えるパケットキャプチャツールです。 特徴・利点 Remote Virtual Interface(RVI)を使って、UUIDごとの端末の通信をキャプチャできます。 あくまでパケットキャプチャツールなので、アプリにコーディングは不要。 Androidにも使えますし、他社製アプリの通信もキャプチャすることができます。 欠点 httpsの通信はドメインを見ることはできますが、エントリーポイントやレスポンスは暗号化されたままで、見ることはできません。 自分のアプリであれば、httpsの通信も 見ることができる ようです。 Charles https://www.charlesproxy.com/ OSX上で動くHTTPプロキシツールです。 多くのiOSエンジニアが使っているのではないでしょうか。 インストール手順はこちらの Qiitaの記事 が参考になります。 特徴・利点 Wiresharkと同様、アプリに対する実装は不要です。 通信を内容を見るだけではなく、ブレークポイントを仕掛けたり、レスポンスをローカルファイルの別の値に変更したりすることができるので、APIの異常系のテストにも使えます。 またWiresharkと違って、httpsの通信の中身を見ることができます。 欠点 30日は無料で使えますが、それ以降はライセンスの購入が必要になります。 1ライセンス最大$50ですが、豊富な機能を考えるとその価値があると思います。 PonyDebugger https://github.com/square/PonyDebugger square社製のネットワークデバッガです。 特徴・利点 iOSアプリ内部にライブラリを実装し、OSX側で立ち上げたサーバを介してGoogle ChromeのDeveloper Tool通信やView階層を見ることができます。 ChromeのDeveloper Toolを使い慣れている人にはとても親しみやすく見やすいです。 欠点 OSX側にpythonのサーバを立ち上げるのですが、これの環境構築に手間取りました。(OSX側のサーバインストールのエラーについてのissueが何種類もありました。今回参考になった issue ) 2013年から運用されているObjective-Cのライブラリで、もはや積極的にメンテナンスされていません。 netfox https://github.com/kasketis/netfox Swiftで書かれたの通信監視ライブラリ、通信結果をアプリ上で確認することができます。 GitHubのREADMEから 特徴・利点 セットアップは、AppDelegateに一行書くだけです。 アプリをシェイクするとリクエストしたURLリストがモーダル表示されます。 また、ジェスチャーが既存機能と衝突する場合は、カスタマイズすることもできます。 欠点 モーダル表示で確認するため、リアルタイムでURLを確認できません。 AlamofireやAPIKitなど、利用しているライブラリによっては ひと手間 かけないと通信ログを取れません。 mitmproxy 【2016/02/12 追記】 https://github.com/mitmproxy/mitmproxy OS X, Linux 上で動作する、CUIのHTTPプロキシです。Pythonでできています。 特徴・利点 Charles同様SSLのリクエストの中身を確認することができます。 リクエストのインターセプトや書き換えはもちろんのこと、Pythonスクリプトで動的にレスポンスを書き換えることができます。 ターミナルからキーボード操作で扱えるのも嬉しいですが、一番嬉しいのはここまで使えて無料であることです。 また、 公式ドキュメント も充実しています。 欠点 ちょっと触って見る限り、Wireshark, Charles でできることはだいたいできる上に無料なので、欠点は見当たりません。 まとめ ここまでのライブラリのメリット/デメリットを簡単にまとめました。 ライブラリ メリット デメリット ResponseDetective カスタマイズ性が高い アプリ実装多め Wireshark アプリ実装不要 https不可 Charles アプリ実装不要・設定が簡単・高機能 有料 PonyDebugger ChromeのDeveloper Toolが見やすい 古い・メンテ少ない netfox アプリ実装は一行だけ リアルタイムに通信を見れない mitmproxy アプリ実装不要・CUI・無料でSSLも確認できる なし 僕の場合は最終的に Charlesにお金を払うことに決めました。 mitmroxy を使うことに決めました。 冒頭であったように、「○○のページで△△のデータ取得するためにリクエストしているURLってどんなの?」となった場合でも、アプリをビルドすることなく、リクエストしているURLをすぐに確認することができます。 また、Xcodeとは別のツールであることで、APIリクエストとそのレスポンスがXcodeの狭いデバッグエリアを占領することがなくなり、ログがかなり見やすくなりました。 最後に VASILYでは、一緒にiQONのアプリを開発してくれる 仲間を募集 しています。少しでもご興味のある方は是非こちらからご応募よろしくお願いいたします。 参考URL Technical Q&A QA1176 - Getting a Packet Trace - Mac Developer Library 通信系のデバッグには Charles が便利 - Qiita
こんにちは。デザイナーの権です。 アプリ開発に関わるデザイナーの方々は、仕様とデザインが決まって実装に移る際、どのようにチームでやりとりしていますか? それぞれの開発体制や状況に合わせて進めていくので、会社によって様々だと思います。弊社でも効率的な開発のために、デザインデータや仕様書、指示書、プロトタイプの準備等、日々試行錯誤しながら最適な方法を探しています。 数ある開発手段の中で今回は、弊社が運営するサービス「 iQON 」の機能改善やデザインリニューアルに効率的に対応できるよう新たに作成した、デザインガイドラインについてご紹介したいと思います。 デザインガイドライン作成の背景 弊社では1年ほど前からデザイナーが5人に増えたことにより、プラットフォームごとに一人づつオーナーシップをとって制作を進める体制に変更されました。 私も以前は、アプリデザインや広告のデザインなどジャンル関係なく携わっていましたが、現在はiOSアプリのデザインをメインで制作しています。 しかし、体制の変化だけではまだ効率的な開発とは言えず、たとえば、UI改善する度にデザインの統一性が取れてない箇所があることで実装時にコミュニケーションコストが高くなってしまう問題などが浮き彫りになり、改めてアプリ全体でのデザインルールを定義することが必要不可欠になってきました。 ユーザビリティを考慮し、デザインの統一性が取れたクオリティの高いアプリを効率的に開発するためにも、デザインガイドラインを作成することになったのです。   デザインガイドライン作成のポイント いざ、「デザインガイドラインを作ろう」としても、正解がないため試行錯誤しながら見つけていくしかありません。 それぞれのアプリやプロジェクトごとに必要な項目など違うので、マッチしないものもあるかもしれませんが、弊社での作成ポイントをご紹介します。   -サービスやアプリのブランドを理解し、あるべき姿をデザインする ガイドラインは、これからアプリのビジュアルの基準になるものなので、どんな機能や画面にも適用できるように設計しなければなりません。 iQONはすでに成長しているサービスであり、アプリを利用している既存ユーザーもたくさんいるので、新しい機能や画面を制作する時もブランドイメージを壊さないようなデザインに仕上がるように、まずはホームやリスト、詳細などの主要画面からコンセプトデザインを作成し、あるべき姿を探ります。 そして、どのような場合でも適用できるような共通パーツを用意できるようにデザインし、ガイドライン作成に入ります。   -目的を設定し、セオリーから最適な方法を選択し作成する やみくもにモジュールを書き出して指定していくことはしないように、作成する目的を以下のように設定。 新しい画面もルールに沿って作成していくようなフローに変える   共通パーツやテキスト指定などをガイドラインから選んで指定する(または、エンジニアがガイドラインを基に実装できる)形にする。   常に更新&運用していき、チームで共有できる形にする 目的を設定することによって、本当にこのガイドラインが実用的なのかどうかを確認できたので、方向性がブレそうになっても立ち返って軌道修正することができます。 そして、全く同じ方法で展開できるものはないのですが、ガイドラインのセオリーや特性を理解するため、一般的にはどのように作られて活用されているのかを Google Material Design や 海外のUIスタイルガイドなど を見て参考にしました。 その中から自身のアプリ開発に最適だと思われる方法を選択して作成していきます。 iQONではどのようなものを作成したのか、一部ご紹介したいと思います。   1.オブジェクト別スタイル アプリ内で使用するカラー、フォント、テキストなど、オブジェクトのスタイルを一覧で表記。 乱立してしまいがちなテキストのスタイルも用途やルールを定義することで、 新しい画面作成の場合でも、用意された候補の中から適用することができます。   2.コンポーネント別スタイル 画面からコンポーネント別に抽出して、使われるテキストや画像のスタイルやマージンなどを表記。 コンポーネントで分解して表記しているので、細かい指定もまとめて確認できるようにしています。 例えば、グリッド表示のコンポーネントを、別の画面でも同じスタイルで実装して欲しい時などにも便利。   3.ステータス別スタイル ボタンのnormal時、hover時などのステータス別のスタイルを表記。 通常時やhover時など、アプリ内で基本このルールを共有しておけば、 毎回の指定が必要なくなります。   -メンバーに共有し、フィードバックをもらいながら改善する 弊社の場合だと、仕様や遷移の確認手段として InVision でプロトタイプを作成しているので、それとともにデザインガイドラインをエンジニアへお渡しして実装してもらいます。 実際に役立ててもらわないと意味がないので、はじめてガイドラインを導入してもらう時は利用の仕方などをお互いに確認しながらメリットを理解してもらい、問題や修正点があれば都度フィードバックをもらいながら修正していきました。 この際重要なのは、メンバーに「デザインガイドライン利用のメリットを実感してもらう」こと。 「同じようなモジュールを実装する時は、このガイドを参照できます」、「テキストの指定はこのリストの中から用途に当てはまるものを指定してください」などの便利な使い方を共有することで、スムーズに開発フローへの導入をすすめることができました。   -チーム内アンケートで効果測定する メンバーに「デザインガイドライン利用のメリットを実感してもらう」ために行ったことがもう一つ。 実際にデザインガイドラインを導入することで、そもそも問題を解決できているのかを測定したくても、プロジェクトによって開発期間や難易度は変わるので、定量的な数字を測ることはなかなか難しいのが現実です。 そこで、デザインガイドライン作成の目的にある、"ガイドラインは常に更新&運用"のためにも必ず行おうと思っていたのが、チーム内アンケートでした。 「デザインガイドラインを導入して開発効率が上がりましたか?」という質問に対して、メンバー全員から「上がった」との回答をもらい、以下のような具体的な効果も答えていただけました。 fromエンジニア " デザイナーに実装途中の画面を見せた時に、見てもらいたいところと関係ない箇所で「○○の色が違います」とか指摘されることが減った。 " " デザインがまだできていなくても、おおまかにガイドを見て実装ができるのでいい。 " fromデザイナー " 基準にすべきデザインデータが明確なので、都度デザインデータの場所を確認する必要がなくなった。 " " ルールが決まったことで微調整がなくなった。(作成時間を比較すると、3h〜5hくらい早くなった。) " 上記回答は、定性的ではありますがメリットとして感じるには十分な結果になったのではと思います。 個人的にも、今まで曖昧だった部分が明確になり、ルールを元にデザインができるので、「悩む時間」を「作業時間」へと変換できていたことが、効率化を実感できた瞬間でした。   改善点 効率化を成し遂げられた部分はあるものの、改善すべき点はまだたくさんあります。 アンケートではメンバーから改善点や意見も書いてもらい、その中でも多かったのが、「更新と管理」の問題でした。 ガイドライン自体の作成にはやはり時間はかかる 今の仕様のままだと少しずつボリュームが増えていくので、肥大化してしまう 人力が必要な場面が多いので、ヒューマンエラーが起きかねない GitHubで変更履歴も追えるようにするといいのでは このような問題はこれからも出てくると考えていますので、さらに改善してデザインガイドラインを日々アップデートしていく次第です。   まとめ 今回は、「効率的なアプリ開発のためにデザインガイドラインを作成した話」をご紹介しましたが、いかがでしたでしょうか。 デザインガイドライン自体は、ユーザーの目に直接触れることはありませんが、最高のユーザー体験を提供していくためには効率のいい開発環境があってこそ実現するので、デザインガイドラインの作成はデザイナーにとって大事なミッションでした。 はじめは手間がかかる作業ではありますが、チームメンバー達に、わかりやすく効率的に仕様を伝えるための手段の一つになると思います。 皆さんも、効率的なアプリ開発のために独自のデザインガイドラインを作成していきませんか。 今回の記事を少しでも参考にしていただき、さらに良い方法やご意見など、是非教えていただければ幸いです。   さいごに 「iQON」は、Webサービス開始から約5年半、アプリリリースからは3年半以上が経とうとしています。 もっと使いやすいサービスになれるよう、機能改善/追加やリニューアルしてみたいというデザイナーの方々、ただいま絶賛募集中です。 ご興味のある方は、以下ページからまずは話だけでも聞きに来てみてください。お待ちしております。 https://www.wantedly.com/projects/41710 www.wantedly.com https://www.wantedly.com/projects/42672 www.wantedly.com  
RecyclerViewが発表されて1年半ほど経ちましたが、みなさんRecyclerViewは活用していますか? これまではListView・GridViewを頑張って使っていたiQONも、直近のリリースから少しずつRecyclerViewに置き換えはじめました。 RecyclerViewはListView・GridViewよりも柔軟になり拡張しやすくなった代わりに、必要なものは自分で実装しないといけなくなりました。 そのため、ListView・GridViewにはあったけどRecyclerViewではなくなった機能が存在します。 今回はRecyclerViewの GridLayoutManager を使う際、データロード中フッターにProgressBarを出す方法を紹介したいと思います。 LinearLayoutManagerに関しては今回触れませんが 『ProgressBarを表示する』 の項を参考にしてもらえれば実装できると思います。 サンプルコード 今回の内容のサンプルコードはこちらになります。 https://github.com/nissiy/GridLayoutSample 参照していただけると理解が深まると思います。 興味がある方はビルドもしてみてください。 実装 ProgressBarを表示する RecyclerViewには ListView#addFooterView のような仕組みがないためフッターを自分で実装しないといけません。 フッターを作成してそこにProgressBarを表示させるには、以下のことを行う必要があります。 データロード前と後でデータセットに細工をする データセットの中身を見て RecyclerView.Adapter#getItemViewType の返す値を変える データロード前と後でデータセットに細工をする 以下のように、通信処理の前後でデータセットにStubをセットしたり、取り除いたりします。 // MainActivity.java private void loadData( final int page) { // ProgressBarを表示させるためにStubをセット if (page > 1 ) { adapter.add( new ProgressStub()); } // postDelayedして通信処理を仮想しています handler.postDelayed( new Runnable() { @Override public void run() { // 通信処理が終わったのでセットしたStubを取り除く if (page > 1 ) { adapter.remove(adapter.getItemCount() - 1 ); } // 通信して取得したデータを処理 ... } }, 2000 ); } データセットの中身を見て RecyclerView.Adapter#getItemViewType の返す値を変える RecyclerView.Adapter#getItemViewType(int position) をOverrideして、返す値を変えることで RecyclerView.Adapter#onCreateViewHolder 側で、ViewTypeによってViewHolderを分けることができます。 今回もデータセット内のStubの有無をチェックして、ViewTypeを返し分けて、ViewHolderを分けることでProgressBarを表示させるようにしています。 ヘッダーなどを実装する場合にも同様のアプローチを取ることで実装できます。 // PhotoGridAdapter.java @Override public int getItemViewType( int position) { Object object = objects.get(position); if (object instanceof ProgressStub) { // データがProgressStubの場合は通常とは違う値を返す return TYPE_PROG; } else { return TYPE_ITEM; } } // PhotoGridAdapter.java @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder viewHolder; if (viewType == TYPE_ITEM) { FrameLayout view = (FrameLayout) inflater.inflate(R.layout.photo_layout, parent, false ); AppCompatImageView photoImageView = (AppCompatImageView) view.findViewById(R.id.photo_image_view); photoImageView.setLayoutParams( new FrameLayout.LayoutParams(imageSize, imageSize)); viewHolder = new PhotoLayoutHolder(view); } else { // ViewTypeがTYPE_PROGの場合はProgressBarのViewHolderを返す FrameLayout view = (FrameLayout) inflater.inflate(R.layout.progress_bar_layout, parent, false ); viewHolder = new ProgressBarLayoutHolder(view); } return viewHolder; } カラム数をpositionごとに変える GridLayoutManagerを使う際には、SpanCountを 2 や 3 などに設定してカラム数を決めると思います。 GridLayoutManagerは拡張することでカラム数をpositionごとに変更することができるため、ヘッダー・フッターを作りたい時や、グリッドの途中でぶち抜きのコンテンツを出したいときに細工を行います。 今回もフッターに出すProgressBarはキレイに中央寄りになってほしいので、ProgressBarを表示するpositionではカラム数が変わるようにGridLayoutManagerを拡張しました。 public class GridWithProgressLayoutManager extends GridLayoutManager { public GridWithProgressLayoutManager(Context context, final int spanCount, final RecyclerBaseAdapter adapter) { super (context, spanCount); setSpanSizeLookup( new GridLayoutManager.SpanSizeLookup() { // 今回はここを細工しています @Override public int getSpanSize( int position) { // ProgressBarを表示するpositionではSpanSizeをいっぱいに広げる if (adapter != null && adapter.getItemViewType(position) == RecyclerBaseAdapter.TYPE_PROG) { return spanCount; } // 1を返すと通常通りのSpanSizeになる return 1 ; } // 今回は触れませんが高速化のためにOverrideしています。詳しくは下記のURLを参照してください。 // http://developer.android.com/intl/ja/reference/android/support/v7/widget/GridLayoutManager.SpanSizeLookup.html @Override public int getSpanIndex( int position, int spanCount) { if (adapter != null && adapter.getItemViewType(position) == RecyclerBaseAdapter.TYPE_PROG) { return 0 ; } return position % spanCount; } }); } } ItemDecorationを使っている場合は注意が必要 ItemDecorationを使っている場合、処理が複数回呼ばれてProgressBarがカクついてしまいます。 そのため、ProgressBarの場合は処理をスキップしてあげる必要があります。 GridLayoutManager特有の問題のためLinearLayoutManagerに関しては気にしなくて大丈夫です。 // GridSpacingItemDecoration.java @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { // ProgressBarのViewHolderの場合は処理をスキップする RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view); if (viewHolder instanceof ProgressBarLayoutHolder) { return ; } int position = parent.getChildAdapterPosition(view); int column = position % spanCount; outRect.left = column * spacing / spanCount; outRect.right = spacing - (column + 1 ) * spacing / spanCount; outRect.bottom = spacing; } まとめ 長年、ListView・GridViewを使い続けているプロジェクトの場合、RecyclerViewへ移行するとなると自分で実装しないといけないものが多くかなりハードであると思います。 iQONの場合も最適化のための独自の仕組みや、広告表示処理などが複雑に絡まっているためRecyclerViewへの移行には時間がかかっています。 ただ、RecyclerViewへ置き換えが完了したページを見ると、もともと巨大で手を入れにくかった処理がモジュールごとに分散できているのでメンテナンスがしやすくなっています。 シンプルなリスト表示・グリッド表示の場合には今まで通りListView・GridViewを使った方が良いと思いますが、positionごとにコンテンツを変えたり、アニメーションを駆使したりしたい場合は、長期的考えてRecyclerViewを使ったほうが良いと思います。 最後に VASILYでは、一緒にiQONを開発してくれる仲間を募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
  あけましておめでとうございます。データサイエンティストの金田です。現在 iQON では、データ分析の基盤として BigQuery を利用しており、データ分析や計算負荷の高いバッチ処理等に活用しています。しかしながら、通常のデータベースとは若干異なる点があり、効率的な運用ができるまでに様々な試行錯誤がありました。今回はそれらの試行錯誤によって得られた知見をベストプラクティスとして紹介したいと思います。   ログデータのテーブル名に日付を入れる BigQuery でログを保存する場合は、テーブル名の最後に yyyymmdd 形式で日付を入れることをお勧めします。理由は二つあり、1つ目はスキャン対象となるデータ量を抑えられるため、2つ目が Web UI でのテーブル管理が容易になるためです。 処理データ量の抑制 まず、スキャンするデータ量を抑えられるという点ですが、BigQuery ではクエリを発行した際、スキャンしたデータ量に応じて課金が決まってくるため、扱うデータ量が大量になってくると、いかに無駄なデータへのクエリの発行を減らせるかが重要になってきます。 その点、BigQuery では テーブルワイルドカード関数 という関数が容易されており、テーブル名の最後に yyyymmdd 形式で日付を入れておくと、指定した期間のテーブルだけを参照するといったことが可能です。 具体的には、「nginx」というデータセットに、「access_yyyymmdd」といった形式でアクセスログのデータが保存されているとします。その際、11月分のログを分析したいといった場合は、TABLE_DATE_RANGE 関数を使って、下記のようにクエリを書くことで、簡単に指定した期間のログだけを処理対象とすることが可能です。 [sql] SELECT * FROM (TABLE_DATE_RANGE(nginx.access_, TIMESTAMP('2015-11-01'), TIMESTAMP('2015-11-30'))) [/sql] これにより、必要な期間のログだけをクエリの対象とできるため、スキャンするデータ量を抑えることが可能になります。 Web UI でのテーブル管理 また、ログデータを日別で管理することで、Web UI でのテーブル管理も容易になります。 通常は、データセットに日別に多くのテーブルを作成してしまうと、すべてのテーブルが表示されてしまうため、Web UI での管理が煩雑になってしまいますが、テーブル名の最後に日付を入れておくことで、下記のようにまとめて表示をしてくれます。 個別の日付のテーブルに対して操作を行いたいときは、右側のプルダウンで日付を選択すると、特定の日付データだけ操作することが可能です。 \   ビューを活用する 他のデータベースと同様に、BigQuery では仮想テーブルを作成するビューの機能を持っていますので、この機能を活用することで効率的に運用をすることができます。 通常、ログやデータベースのデータを分析に使う際は、JOIN 操作を行って、トランザクションテーブルとマスタデータを結合したり、SQLの関数を使って加工を行ったりしますが、そのような操作はビューとして保存することができます。具体的には、Web UI のクエリ作成画面の下に、「Save View」というボタンがありますので、こちらを押すことでクエリで作成したテーブルを仮想テーブルとして保存することができます。 保存されたテーブルは、下記のように緑色のアイコンと共に表示されるので、ビューであることがするわかります。 実際の運用においては、 データを利用するユーザーは、分析用に加工されたビューに対してクエリを発行するようにすることで、煩雑なSQL文が量産されるという事態を防ぐことができます。 また、例えば上記で説明した TABLE_DATA_RANGE 関数と CURRENT_TIMESTAMP という関数を組み合わせて、任意の期間のデータだけを参照対象とするといったことも可能です。例えば、下記のクエリでは過去60日のログだけを計算対象にしていますが、このように期間を絞ったユーザー参照用のビューを用意することで、無駄なデータに対するクエリ発行を防ぐことができると同時に、ユーザーが意図せず大量のデータに対してクエリを発行してしまうというリスクを抑えることもできます。 [sql] SELECT * FROM (TABLE_DATE_RANGE(nginx.access_, DATE_ADD(CURRENT_TIMESTAMP(), -60, "day"), CURRENT_TIMESTAMP())) [/sql] ただし、大量の JOIN が発生したりするビューを作成する場合は、クエリの結果が返ってくるのに時間がかかることがありますので、レスポンスタイムが気になる場合は、必要に応じてビューではなく、バッチで実体のテーブルを作成することも考慮に入れてください。 非構造化データをBigQueryで扱う BigQuery には、 JSON 関数 というものが用意されており、文字列で保存されているJSONから、キーを指定して必要な値だけを抜き出すということが可能なため、非構造化データを扱うことも可能です。 特に、アクセスログ等は、ビジネス要件によって取得項目が変更になったりすることもありますので、BigQuery で非構造化データも扱うことができると知っておくと、設計の幅が広がります。 具体的には、下記のような形式で文字列としてJSONのデータが入っているとした場合、 スキーマ name type timestamp TIMESTAMP log_json STRING データ例 timestamp log_json 2015-12-27 12:07:49 UTC {"where":"item_detail","item_id":"3030910","model":"iPhone 6"} テーブルから item_id だけを抜き出したいときは、下記のように記述することで JSON 形式で保存してあるデータを取り出すことが可能です。 [sql] SELECT * FROM (TABLE_DATE_RANGE(nginx.access_, TIMESTAMP('2015-11-01'), TIMESTAMP('2015-11-30'))) [/sql] ただし、この方法を使う場合は、JSON文字列として保存されている全てのデータをスキャンする形になりますので、データ量が大きく、頻繁にクエリを発行する必要があるものに関しては、予め必要な項目だけを抜き出して、構造化したデータを別テーブルとして保存しておいた方がよいと思います。 ストリーミングインサートは必要に応じて BigQuery ではストリーミングインサートという機能があり、例えば Fluentd から直接ストリーミングで BigQuery へデータをインポートすることが可能です。 ただし、便利なストリーミングインサートですが、通常のバッチでのインポートに比べて、料金がかかるということと、データに欠損が生じる可能性があるという事があるため、特にリアルタイムに結果を把握必要のないものに関しては、日次バッチでのインポートも考慮した方がよいと思います。 また、ストリーミングインサートを使う場合でも、データに欠損が生じる場合がありますので、バックアップとしてS3やGCSにも保存しておくようにしておきましょう。   バッチ処理にAirflow を活用する 上記でも記載した通り、データ分析基盤として BigQuery を活用するためには、日次でデータを BigQuery へインポートしたり、分析用のテーブルを日次で作成したりする必要がでてきます。 通常、このようなバッチの処理には、cron を使ってバッチを起動する場合が多いと思いますが、cron だと、ジョブに依存関係をもたせたり、ジョブが失敗した場合に、どこで失敗したのか特定することが難しいということがありました。 そこで、VASILYでは、バッチの処理に AirBnB 社が開発した、Airflow というジョブ管理ツールを使うことで、バッチ処理にまつわる業務を効率化しています。このツールを使うことで、例えば下記のように複数のジョブに依存関係がある場合も、その依存関係を考慮して処理を実行してくれ、万が一失敗した場合でも、リトライ処理を自動で行ってくれたり、問題の特定がすぐにできるようになったため、データの管理に関する業務が大幅に改善されました。 最後に 今回は、データ分析基盤として BigQuery を活用するにあたり、どのような点に気をつけて設計や運用を行なったらよいかについて説明を行いました。通常のデータベースとは若干異なるところがありますので、その点は注意が必要ですが、使いこなせば従来では考えられなかった低コストでデータ分析の環境を整えることが可能です。 また、今回ご紹介した BigQuery 以外にも、VASILY では、Tableau を使ってビジネスユーザーがクエリを書くことなく自由にデータを取得できるような仕組みを構築したりと、様々な取り組みを行っていますので、次回はその辺りの仕組みも説明できればと思っています。 最後に、VASILY では、データサイエンティスト を募集しています。データの分析だけでなく、データエンジニアの役割も担う必要がありますので、幅広い経験を積むことができます。 ご興味のある方は是非 こちら からご応募よろしくお願いいたします。
  Merry Christmas! フロントエンド開発の荒井です。今回はフロントエンド開発陣が3ヶ月で行ったサイト最適化を紹介したいと思います。短期間で多くの変更を施したため、今回は取り組みやすく、特に大きなインパクトがあったと思われる内容2つを紹介します。   はじめに VASILYでは提供したい価値を再定義し、ユーザーにとって価値のあるコンテンツを届けるため、2015年8月にiQONのPCサイトのリニューアルを行いました。このリニューアルでは「サイトマップの見直し」「文書構造(h1, title, description etc)」の見直し等を行い、サイトの中身をガラッと変えています。 リニューアルを行った後、Google Search ConsoleやNewRelicで日々サイトの状態を確認していたのですが、日が経つにつれ、細かな問題が出てきました。これから挙げる事例は、日々のチェックにより発見された問題の紹介と解決策です。   Google Search Consoleで発見されたエラー対応 フロントエンドエンジニアの日課として Google Search Console の確認があります。 iQONではECサイトをクロールしてアイテムを集め、ユーザーがそのアイテムを使用してコーディネートを作るというサービスの特性上、出来上がったページに以下の問題が多数報告されていました。 タイトルタグの重複 重複するメタデータ(descriptions) 「Google Search Consoleのエラーを徹底的になくそう」 当たり前のことですが、膨大なコンテンツを保持するサイトですと、数が少ないエラーは見過ごしがちです。エンジニアはまずGoogle Search Consoleに現れたエラーを無くすことに着手しました。例えそれが1件でも改修対象として手を動かしています。   タイトルタグの重複 こちらは主にユーザーが投稿したコーディネートに発生していた問題です。 タイトルタグにはユーザーが入力したコーディネートのタイトルを採用していたのですが、「冬コーデ」のような重複したタイトルのページが多数存在してしまいました。   ユーザーが作成したページのタイトルやメタデータはユニークであるべきです。 iQONでは以下のようにしてユニーク化を行っています。 [bash] 投稿されたタイトル + ユーザー名 + つくったコーディネート(◯◯件目) ex)「ワントーンコーデ♪メイクは派手めだけどコーデはシンプルに☆…」XXXさんがつくったコーディネート(335件目) [/bash] 上記は一例で、様々なところをユニークにする見直しを行っています。   重複するメタデータ こちらはアイテムページに出ていたエラーです。同じ商品が異なるECサイトで販売されていた場合などに説明文が重複してしまい、エラーが起きていました。 そこで、商品の「商品名」「ブランド」「説明文」などの条件から同一商品を判断し、自動でcanonicalを貼るように改修を続けています。   その他の対応 blockquote見直し、no indexの設定、リダイレクトの見直し..etc.. 数えていませんが、数十項目の改善をしたと思います。 大事なのはCGMでコントール不可能だったところをコントロール可能な構成に直していったということです。サイト最適化はこうした塵を積もらせる改善が必要です。   HTTPSの対応 次にHTTPSの話をします。昨今、セキュリティを優先事項とする企業が増えました。VASILYではフロントエンドサーバーのHTTPS対応が遅れていたため、iOS 9のApp Transport Securityが話題になったタイミングでHTTPS化に踏み切りました。   Google HTML/CSS Style Guide まず、100を超えるファイルにベタ書きされたhttpを書き換えるところから始めました。その際のコーディングはGoogle HTML/CSS StyleのProtocolに準拠しました。 httpとhttps両方のプロトコルに対応するところはhttp/httpsを省略し//から始めるようにします。   body { background: url( http://iqon-img.s3.amazonaws.com/static/images/top/iqon_bg_gray.png ) 0 0 repeat; }   body { background: url(//iqon-img.s3.amazonaws.com/static/images/top/iqon_bg_gray.png) 0 0 repeat; }    詳細は Google HTML/CSS Style Guide に記載があります。   HTTPSを標準にする 上記書き換えがすべて終わった上で、サイトの標準をhttpsにします。 リダイレクトを設定すれば良いのですが、外部に提供しているウィジェットなどもあるので、単純に一律切り替えることは出来ず、確認の時間が非常に取られました。 また、CDNがhttps対応をしてないため画像まわりで問題が発生したりするなども起きますので、踏み切る際は事前に確認しておくことをおすすめします。   環境整備 フロントエンドでは社内確認用のステージングの環境にAmazonの Elastic Load Balancing を使用しておらず、その役割を古いバージョンの Varnish に頼っていたため、プロダクトの確認環境を整えるのにも体力を使いました。HTTPS化を進めるにあたり、規模が大きくなるほどコード修正以外にも作業が発生します。   HTTPS化をしてみて サイトの健全さの意味でも対応するべきでしたし、 HTTPS ページが優先的にインデックスに登録されるようになる といった記事にもある通り、インデックスにも効果があるようです。DAUを倍にした要因の一つだと考えています。   まとめ 今回紹介した2つは取り組みやすい2つの事例を紹介しました。 他にも App Indexing 対応や パフォーマンス最適化 などといったことも行っています。Google Search Consoleで確認する限り、クロール数は日に日に増え、3ヶ月で約5倍程度の伸びがありました。それに伴い、DAUは3ヶ月で倍になり、検索からの流入が増えている傾向にあります。 今後は AMP や Service Worker の導入を検討しているので、次回は結果を報告出来ればと思います。   さいごに VASILYではエンジニアを募集しています。 ご興味のある方は是非 こちら からご応募よろしくお願いいたします。それでは良いお年を。
こんにちは。VASILYデザイナーの文山です。 入社して1年が経つタイミングで、弊社のコーポレートサイトリニューアルを経験しました。今日はそれを例に、コンセプトをキーワードまで分解しデザインに落とし込むまでの過程や、デザイナーとして大切だったことをご紹介します。   コンセプトをキーワードまで分解するために社内アンケートを実施 弊社では採用に力を入れていることもありターゲットは採用面接応募者とし、コンセプトは「VASILYらしさを伝える」ことに決まりました。 「VASILYらしさ」をデザインに落とし込むためには、より具体的に言語化する必要があります。そこで社内アンケートを実施し、VASILYらしさをキーワードとしてあげてもらいました。 社内アンケートを実施した理由は、 ・社員が思っているVASILYらしさが把握できる ・自分にない視点が見える ・コンセプトが具体化できる というメリットがあったためです。   アンケートを集計した結果、 先進的 ・ シャープ  ・ エネルギー という3つのキーワードが抽出できました。    どのようにデザインに落とし込んだか より良いデザインにするためにも、チームで共通のコンセプトを持ち、同じ方向性を持つことがとても重要です。 デザインに取り掛かる前に、様々な参考サイトをピックアップし、キーワードと一致するものをまとめてチーム内で共有しました。 そうすることで、デザインの方向性が具体化し、チーム内で共通認識を持つことができます。 参考サイトをピックアップする際は、以下のようなWEBデザインリンク集から情報を集めました。優れたデザインのリンクがカテゴリー別にたくさん紹介されているので、キーワード別に検索することもできておすすめです。 ー参考にしたWEBデザインリンク集ー AWWWARDS I/O 3000 siteinspire straightline bookmark 上記でのWEBデザインを参考に、チームでの方向性をすり合わせた後、社内アンケートから抽出したキーワードをもとに、デザインに取り掛かりました。 ここからは、TOPページ / リクルートページ / 各職種の詳細ページの3つを例に、キーワードからどのようにデザインに落とし込んだのか、ご説明していきたいと思います。  TOPページ TOPページ は第一印象となる重要なページです。 社内アンケートから抽出したキーワードの「先進的」や「エネルギー」というところが伝わるよう、会社のミッションの英字テキストを大胆にレイアウトし、フォントはDINを使用しました。 少し縦長でスタンダードなフォントではありますが、大きく使用することで、スタイリッシュな印象になります。 しかし、スタイリッシュな印象を持たせることができた反面、インパクトが強すぎてしまい、男性的なデザインになってしまいました。 社内アンケートからピックアップしたキーワードには女性的という要素は入っていませんでしたが、弊社は女性向けサービスを運営していることから、男性的なデザインに寄りすぎても会社のイメージと合いません。 そこで英字テキストに透過をかけ抜け感を出すことで、女性的な表現に近づけるよう調整をしています。 リクルートページ リクルートページ はVASILYの行動指標である「HIPSTER」がメインとなるページにしました。 弊社では「HIPSTER」という7つの項目から構成されている、VASILY社員としての行動指標があります。判断に迷った際や、何かを始める時、悩んでいる時など、この7つの項目のもと行動しています。 入社を検討されている方々にこの「HIPSTER」を理解してもらい、同じマインドのもと一緒に働きたいという意図があったため、HIPSTERの各項目の説明を入れ、その後に各職種のエントリーページに繋がるという構成にしました。 また、縦に並べてHIPSTERを説明すると項目が多く、下までスクロールしてもらえない可能性があったため、目線を自然に誘導できるよう斜めのラインを使用したレイアウトにし、「シャープ」「エネルギー」という2つのキーワードが伝わる表現にしました。  各職種の詳細ページ   各職種の詳細ページ では、働くイメージを持ってエントリーボタンへ誘導できるよう、以下の構成にしました。  ・各職種のミッション ・業務内容 ・取り組み ・よく関わる職種 しかし、これらの後にエントリー項目が続くため、コンバージョンして欲しいエントリーボタンがページ下部に下がってしまい目立たなくなってしまいます。そのため、こちらでもキーワードである「シャープ」を取り入れ、ページの背景の色を斜めで区切ったレイアウトにし、メリハリをつけることでエントリー項目がきちんと目立つよう工夫をしました。 大切だったこと   より良いデザインにするためにも、一緒に制作するチーム内で方向性を合わせることは重要です。 そのためには、コンセプトをキーワードまで分解して言語化することがとても大切なことでした。そうすることで、コンセプトをより具体的にすることができ、チームでデザインの方向性を話し合う際にも、そこを軸として考えることができます。 また、コンセプト通りに制作するだけでなく、自身で考え+αしてデザインに落とし込むということもデザイナーとして大切なことだとリニューアルを通して、改めて感じました。 最後に   2015年も残りわずかですね。皆様良い年末をお過ごしください。 現在VASILYではコンセプトをキーワードまで分解して考え、デザインに落とし込めるデザイナーを絶賛募集中です! たくさんのご応募お待ちしております。 https://www.wantedly.com/projects/5485 連絡先:info[at]vasily.jp
こんにちは。iOSエンジニアの遠藤です。 最近のiQONはコンテンツ量が増えてユーザーの詳細ページに表示する情報が多くなってきました。今のデザインでは情報量が多すぎて詳細ページが見づらい状況になっています。そこで以下のようなコンテンツをタブで管理できるかつユーザー情報を表示してスクロールするデザインを実装しました。 実装について色々と調べたのですがあまり情報が無かったので共有したいと思います。実装する上で参考になれば幸いです。   今回のUIを実現する上で解決しなければいけない課題 ・スワイプでもタブの切り替えができる ・ある一定以上スクロールした場合にタブの位置を固定する    解決策 既にiQONの仕組みとしてある、スワイプでタブの切り替えをするところから考えていきたいと思います。 【スワイプでタブの切り替えについて】 ・スワイプでタブを切り替えるにはUIPageViewControllerを使用 ・リストの部分はUIPageViewControllerのChildViewControllerとして実装 ・ユーザー情報を表示するViewはUIPageViewControllerに貼り付ける   UIPageViewControllerを使用することで以下のようなViewの構造になります。   【スクロールについて】 リストで表示する部分とユーザー情報を表示する部分が分割されたので2つのコンテンツのスクロールを連動させなければいけないという課題が増え、以下の3つの項目について考えていきます。   1: ある一定以上スクロールした場合にタブの位置を固定する 2: リストの部分をスクロールするとユーザー情報を表示する部分もスクロールする 3: ユーザーの情報を表示する部分をスクロールするとリストの部分もスクロールする   1: ある一定以上スクロールした場合にタブの位置を固定する リストのスクロール量を見て一定の位置まではユーザー情報を表示する部分のフレームの位置を動かします。   2: リストの部分をスクロールするとユーザー情報を表示する部分もスクロールする ユーザーの情報を表示す部分でスクロールした量をリストに渡してリストのscrollViewのcontentOffset.yを更新させます。   3: ユーザーの情報を表示する部分をスクロールするとリストの部分もスクロールする リストでスクロールした量をUIPageViewControllerに渡してユーザー情報を表示する部分のフレームを動かします。   実装について 以上の課題をふまえて以下の3つのクラスで実現していきます 今回は実装する上で大変だったスクロールについてフォーカスして3つのクラスでの処理を紹介したいと思います。 タブを含む詳細な実装については サンプルコード を用意しましたのでそちらを御覧ください   ・A: PageViewController (UIPageViewControllerを継承) ・B: ViewController(リスト部分) ・C: ContentView(ユーザー情報を表示する部分)   3つのクラスはそれぞれ以下の役割になっています ・PageViewControllerはViewControllerとViewの仲介役 ・ViewControllerをPageViewControllerのChildViewControllerにすればいいように実装 ・PageViewControllerの中にframeやcontentOffsetの調整を書く A: PageViewController スクロールの課題を解決するために以下の処理を書きます。 1: ある一定以上スクロールした場合にタブの位置を固定する   2: TableViewControllerスクロールするとユーザー情報を表示する部分もスクロールする   ・ChildViewControllerがスクロールされるたびに呼び出される ・ChildViewControllerのスクロール量を見てContentViewのフレームの位置を変更 ・ContentViewがある一定の位置まで来たらフレームの位置のを動かさない   ``` swift // ChildViewControllerがスクロールした時に呼び出され、ContentViewのフレームの位置を変更する func upadteContentViewFrame() { guard let currentIndex = currentIndex, vc = pageViewControllers[currentIndex] as? ScrollTabPageViewControllerProtocol else { return } if vc.scrollView.contentOffset.y >= -tabViewHeight { let scroll = contentViewHeight - tabViewHeight updateContentView(-scroll) vc.scrollView.scrollIndicatorInsets.top = tabViewHeight } else { let scroll = contentViewHeihgt + vc.scrollView.contentOffset.y updateContentView(-scroll) vc.scrollView.scrollIndicatorInsets.top = -vc.scrollView.contentOffset.y } } // ContentViewのフレームの位置を変更する private func updateContentView(scroll: CGFloat) { if shouldScrollFrame { contentView.frame.origin.y = scroll scrollContentOffsetY = scroll } shouldScrollFrame = true } ```   3: ContentViewをスクロールするとリストの部分もスクロールされる ・ContentViewのスクロール量を受け取りChildViewControllerのcontentOffset.yを調整する     ``` swift private func updateContentOffsetY(scroll: CGFloat) { if let currentIndex = currentIndex, vc = pageViewControllers[currentIndex] as? ScrollTabPageViewControllerProtocol { vc.scrollView.contentOffset.y += scroll } } ```   Protocolについて 下記のことを実現するためにPageViewControllerにProtocolを宣言しています。 ・PageViewController側からChildViewControllerのscrollViewを触りたい ・ChildViewControllerのUIScrollViewDelegateが呼ばれた時にPageViewControllerのメソッドを呼び出したい このProtocolを宣言することでPageViewController内でcontentOffsetやcontentInsetを調整することができるのでChildViewControllerに処理を書くことを減らせます。   ``` swift // PageViewController.swift ScrollTabPageViewControllerProtocol { var scrollTabPageViewController: ScrollTabPageViewController { ge } var scrollView: UIScrollView { get } } ```   B: ViewController ViewControllerはPageViewControllerProtocolと以下の2つの処理を書けば動くようになっています。 ``` swift // ViewController.swift override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) scrollTabPageViewController.updateLayoutIfNeeded() } func scrollViewDidScroll(scrollView: UIScrollView) { // スクロールしたらPageViewControllerに通知してContentViewのフレームを動かす scrollTabPageViewController.updateContentViewFrame() } ```   C: ContentView ContentViewは以下のような構成になっています。 scrollの量を渡すためだけならUIViewだけの実装で"userInteractionEnabled = false"にすれば動かすことは出来ますが、UIButtonなどのアクションをするパーツを置いた時の制御が難しかったのでUIScrollViewを実装しています。     ``` swift // ContentView.swift var scrollDidChangedBlock: ((scroll: CGFloat, shouldScroll: Bool) -> Void)? func scrollViewDidScroll(scrollView: UIScrollView) { // スクロールした量をPageViewControllerに渡す     if scrollView.contentOffset.y > 0.0 || frame.minY < 0.0 {         scrollDidChangedBlock?(scroll: scrollView.contentOffset.y, shouldScroll: true)         scrollView.contentOffset.y = 0.0 } else {         let scroll = scrollView.contentOffset.y - scrollStart         scrollDidChangedBlock?(scroll: scroll, shouldScroll: false)         scrollStart = scrollView.contentOffset.y } } ```   ・上の方向にスクロールする場合はscrollViewのcontentOffset.yを0にしてフレームだけを動かすようにしています ・逆に下方向にスクロールする場合はフレームを動かさず、scrollViewのスクロール量を渡すだけで調整しています   ``` swift // PageViewController.swift // スクロールした量を受け取りContentViewのフレームを動かす contentsView.scrollDidChangedBlock = { [weak self] (scroll: CGFloat, shouldScrollFrame: Bool) in     self?.shouldScrollFrame = shouldScrollFrame     self?.updateContentOffsetY(scroll) } ```     まとめ ・コンテンツを表示するViewにScrollViewを実装することでアクションを妨害せずにスクロールできる ・UIPageViewControllerを継承したクラスにスクロールの調整の処理を書くことでコンテンツを表示するViewController側は少ないコードを書くだけで済む 一定以上スクロールするとタブの位置が固定されるUIの実装についての紹介でした。 まだ改善しないといけないところはありますが、概ねやりたいことを実現することが出来ました。 最後に VASILYでは、エンジニア&学生インターンを募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
こんにちは。VASILYデザイナーの半澤です。 今回はデザインに必要不可欠な写真についてご紹介します。 クオリティの高い写真素材はネットにたくさんありますが、自社のコーポレートサイトや求人用となると、自分たちで撮影せざるを得ません。 VASILYでは撮影をデザイナーが担当しており、絵のキレイさだけではなく「伝わる写真」であるかどうかを意識して撮影しています。 伝えたいことを写真でどう見せているのか、求人のバナーを例に撮影の工程とポイントをご紹介します。 STEP1:ヒアリング 求人のバナー制作依頼が来たら、まず話を聞くことから始まります。 伝えたいことを明確にすることが1番重要です。デザインする上で伝えたいことを言語化すると思いますが、写真も同じことが言えます。 今回の例は求人なので、以下の項目を依頼者からヒアリングします。 ・募集する職種 ・実際にどんな仕事をするのか ・年齢/性別 ・人柄 以上の項目をヒアリングする理由は、「来て欲しい人物像により近いイメージにするため」です。 極端ですが、以下の例を御覧ください。 このバナーはエンジニアの募集に見えますよね。でも、右側では「PUBLIC RELATIONS(広報)」と書いてあります。 広報の募集で黒い画面を開いているPCが写っているこの写真では、イメージが離れすぎてて広報希望の方は応募しにくいと思います。  応募する人にとって「自分が働く姿」をイメージしやすい絵作りが大事 なので、その素材あつめの一つとしてヒアリングを念入りにしておきます。 STEP2:構図を考える STEP1のヒアリングを元に、絵作りにはいっていきます。 1.キャストを決める ヒアリングで来て欲しい人物像を明確にしたので、その人物像に近しい年齢、性別のキャストを決めます。 2.撮影場所を決める キャストが決まったら撮影場所です。求人の写真はバナーとして求人一覧にならびます。毎回社内で同じ場所では人が違うだけで印象が似てしまうので、似すぎないよう心がけています。 3.構図を絵コンテにする キャスト、撮影場所が決まれば構図を考えます。 構図を考える際にも意識することは 応募する人にとって「自分が働く姿」がイメージしやすい絵 なので、なるべく働いている場面を再現するようにしています。 働いている場面といっても、PCに向かって仕事をしている絵だけではなく、複数人で話をしている姿を撮るようにしています。 複数人だと自然な表情が撮りやすいというメリットもありますが、VASILYで働いている人たちの雰囲気を表現するためでもあります。 以上を意識して、ざっくりと絵コンテにしていきます。こちらの絵コンテはデータサイエンティスト募集の時使用したものです。 複数人出演してもらうので、それぞれのキャスト立ち位置や、メインの人物がわかるように描いています。 絵コンテを描くことによって、撮影時にキャストへの立ち位置の説明が楽になります。 4.必要であれば小道具も考える より具体的にイメージを伝えるために小道具の使用も効果的です。例えば「データサイエンティスト」の求人用に撮影したときはホワイトボードに計算式を書いてもらいました。 キャストの年齢や性別、表情だけでは伝えきれないことは小道具も使って雰囲気作りをしましょう。 構図については以上ですが、加えて忘れていけないのは キャストへの事前の連絡 です。 服装や髪型も雰囲気作りにとても重要なので、どういう絵を撮りたいのか求人の概要と共に伝えどのような格好で参加してほしいのか伝えます。 例えば営業の求人用に撮影したときには男性は襟つきのシャツ着用で参加のお願いをしました。 実際に職種が営業の人が写真に出ていても、文脈を知らないひとには伝わりません。 「営業っぽさ」を演出する必要があるので、襟つきのシャツを着用してもらいました。 以上のことから、キャストへ事前に撮影の概要と共に、服装や髪型も事前に伝えておきます。 STEP3:撮影 いよいよ撮影です。 1.撮影準備 キャストを集めるということは、それだけ コストがかかっている ということです。 なるべく時間がかからないよう、撮影前の準備は念入りにしておきたいです。 決めた撮影場所でどの角度で撮るのか、明るさはどれくらい、邪魔なものがないか、あらかじめ確認し準備をしておきます。 2.撮影 撮影は30分以内で終わらせるようにしています。 概要説明   5分 テスト撮影  5〜15分 本番     10分 キャストに概要説明をする際に便利なのが、構図を考えたときのコンテです。 コンテを見せながら、「こういう人材がほしいから、こういう構図で、◯◯な雰囲気でとりたいです。」とキャストに伝えます。 しかし、紙に書いた構図で一発できまるわけではありません。 私はテスト撮影をしながらキャストからもフィードバックをもらうようにしています。 フィードバックをもらう時に便利なのが Eye-fi です。Eye-fiとはWi-fi内臓のSDカードで、撮った写真がリアルタイムで好きなデバイスに転送することができます。 カメラの小さいモニターでキャストに都度見てもらうのではなく、PCの画面で見てもらう事が可能です。 小道具で使用しているPCに転送すると撮影を止めることなく、キャストからフィードバックを貰えます。 フィードバックを受け、構図が決まれば本番です。ここまで念入りに準備をしたので、本番は実際10分もかからずサクッと終わります。 3.デザイン 撮影が終わればチェックです。撮影時には気にならなかった細かい髪の乱れや、洋服のヨレなど、レタッチする箇所をチェックします。 レタッチが完了すれば、デザイン作業です。 VASILYの求人バナーではデザインを統一するようにしています。 写真で職種ごとのイメージを伝えつつ、VASILYという会社のイメージもバナーで表現するためです。 最初にデザインテンプレを複数作り、どれがVASILYらしいのか検討し、現在掲載しているデザインになりました。 しかし、一部デザインが違う例もあります。 上の例はiOSエンジニアの求人バナーです。 アプリエンジニアは、それぞれのOSの専門だとわかりやすくする為に筐体を入れています。 必要に応じてレイアウトを変えていますが、一覧で見た時に統一感が出るようにデザインしています。 おわりに いかがでしたでしょうか。 求人のバナーを例に「伝わる写真」の撮影フローを紹介しました。 あくまで写真は伝える手法の1つで、目的は 応募しようとする人へ自分が働いているイメージが伝わり一緒に働きたいと思ってもらうこと です。 撮影を重ねる度にデザインと同様に伝えるべき内容を明確にし、写真でどう表現するのかが重要だと感じています。 VASILYではクリエイティブを通してユーザーに最適な価値を届けるために、あらゆるアプローチをしてくれる新卒デザイナーを募集しています! ご応募お待ちしております! https://www.wantedly.com/projects/5485 連絡先:info[at]vasily.jp
   Carthageとは? こんにちはVASILYでiOSエンジニアをしているニコラスです。 今回はCarthageの紹介をしたいと思います。 Carthage はiOS / OS X開発のための分散型の (decentralized) パッケージマネージャーです。 既にプロジェクトでCocoaPodsを使っていても、同じプロジェクトでCarthageを使うことができます。 CarthageはCocoaPodsと違って中央レポジトリ型ではないので高速です。 また、100%Swiftで作られているため信頼性が高く、欧米のプログラマーコミュニティで特に盛んに利用されています。 Carthageの基本を学べば(たった5分で)すぐ使えるようになります。   Carthageを使うメリット コミュニティの活発さ Carthageのコミュニティはとても活発で、バグがあればだいたい1日以内に修正のPRが送られています。 環境依存性の低さ Carthageは100%Swiftでできているので、CocoaPodsと違って、環境ごとのRubyバージョンの違いによる問題に悩まされることがありません。 Swiftでできているので、Rubyが書けなくてもiOS開発者がOSSに貢献することができます。 (私も Pull Request を送りました。) シンプルさ Carthageはダウンロードしたらすぐに使うことができます。CocoaPodsの場合はRubyやRubyGemの依存関係により、CocoaPodsのコマンド実行に失敗することがあります。   インストール   Homebrewでインストールする方法 Carthageをインストールする一番簡単な方法は Homebrew です。 [bash] $ brew install carthage [/bash]   直接ダウンロードする Homebrewがなくても、GitHubから carthage.pkg をダウンロードすることができます。 Releases Page   ソースコードからインストール 最新の開発バージョンをインストールすることもできます。 [bash] $ git clone git@github.com:Carthage/Carthage.git $ cd Carthage $ make install [/bash]   ライブラリをインストール   Cartfileを作成 Cartfileに含めたいライブラリを書いてください。 CartfileはCocoaPodsのPodfileやBundlerのGemfileと似たようなものです。   データフォーマット CocoaPodsではインストールするライブラリのリストをPodfileというRubyのDSLで管理します。 Carthageでは、Cartfileというファイルに記述します。 これは OGDL というデータフォーマットで書かれていて、 YAML と似たようなフォーマットです。 Cartfileの文法 一つ目のキーワードに github か git を指定します。 GitHubでホストされている場合は github を使い、それ以外でホストされている場合は、 git を使います。 github キーワードでは、ブランチ名の指定やバージョンを制限することができます。 [bash] github "Username/RepositoryName" "BRANCH_NAME" OR == / >= / <= / ~> [VERSION_NUMBER] [/bash]   実例 [bash] github "ReactiveCocoa/ReactiveCocoa" "master" #最新のmasterブランチをインストール github "rs/SDWebImage" ~> 3.7 #v3.7.xx の最新版をインストール github "realm/realm-cocoa" == 0.96.2 #v0.96.2をインストール [/bash]   コマンドを実行してライブラリをインストール プロジェクトのルートディレクトリで下記のコマンドを実行してください。 [bash] $ carthage bootstrap #ios, mac, watchos, tvosなど、プラットフォームを指定することもできます。 $ carthage bootstrap --platform ios [/bash] 正常に完了すると、[bash] Carthage/Build , Carthage/Checkouts [/bash]という2つのフォルダが作成されます。   Carthage/Build .framework , .framework.dySYM がここに生成されます。   Carthage/Checkouts ライブラリのソースコードファイルがここにダウンロードされます。   Xcode設定 Run Script Phase を追加する プロジェクトファイルを開く TARGETS内のプロジェクト名を選択 Build Phasesを選択 左上の「+」ボタンをクリックから"New Run Script Phase"を選択 コマンド入力欄に /usr/local/bin/carthage copy-frameworks と入力 Input Filesのところにインストールするフレームワークを追加します。   .frameworkをリンクする プロジェクトファイルを選択 Generalを選択 Linked Frameworks and Libraries にCarthageフォルダからフレームワークを選択して追加します。 これでライブラリのインストールが完了し、アプリをビルドすることができます。   Git管理 - Carthage運用のコツ 基本的にgitにコミットするべきものは Cartfile と Cartfile.resolved と Carthage フォルダです。 その理由として、いくつかのメリットがあります。 git clone した時や、ブランチを切り替えるたびに carthage update をする必要がありません。 また、プロジェクトに新しいメンバーが入った時など、 carthage update するタイミングごとにCartfileのバージョンが違ってしまうといったことが避けられます。   まとめ 今回はCarthageの導入方法を紹介しました。 CocoaPodsは根強い人気がありますが、個人的にはiOS開発の本質とは関係ないところでいろいろ問題がありました。 (RubyGemsのバージョンごとのコンフリクトであったり、CocoaPods自身のアップデートでPodsのレポジトリが破損したりなど) CarthageはSwiftで作られているため、Swiftのライブラリしか依存関係がありません。 CocoaPodsの運用で苦労している方は、ぜひCarthageを使ってみてください。
こんにちは、神崎です。今年の6月頃にアドサーバーのアプリケーションサーバ群をAWS ElasticBeanstalk w/ Dockerに置き換えをおこないました。 これにより、アプリケーションレイヤーのauto-scaling環境の構築、deployのフローの自動化、rubyなどのMiddlewareの入れ替えが比較的容易にできる環境になりました。 既存の環境は、以前ブログで紹介したとおり、EC2上にruby2.0でunicorn+sinatraで構築していました。 -  iQONの広告配信システム - VASILY DEVELOPERS BLOG     構成 Containerの内部構成は下記のようになっており、中央にある、ad-serverがアプリケーションの実態のContainerになります。それ以外は、nginx,mackerel,fluentdのContainerがあり、それぞれが特定の役割を担ってアドサーバー全体を構成しています。   デプロイフローは以下のようなフローになり、特定のbranchのgithubにpullrequestをおくりmergeされると、hookされて、eb deployがはしり、docker containerがdeployされます。   またアプリケーション用のDocker imageは、baseとなるrubyとsupervisorをbuildしたimageを作成し、さらにアドサーバで必要なgem類を構築したimageを作成しています。baseのimageとアプリケーションのimageを分けているのは、アドサーバー以外のアプリケーションでも流用しやすくするためです。 外部サービスの利用 コードの管理にはgithub、DockerRegistryに quay.io 、CIに CircleCI を利用しています。これらを連携させることで、上記のように自動でdeployしたり、auto-scalingさせたりを実現しています。 トラブル ElasticBeanstalk+Docker環境に移行してから、概ね大きな問題も起きずにいたのですが、数回トラブルも発生しました。 docker-pullに失敗してダウン 一番大きなトラブルとしては、人的ミスが大きな原因ですが、DockerRegistryとして利用しているquay.ioのパスワードを変更したために、docker pullに失敗して、デプロイができず、アドサーバー全体がダウンすることがおきました。 dockercfgファイルにquay.ioのマスタのaccountを設定してしまっていたため、quay.ioのパスワードを変更したタイミングで、invalidな状態となり、docker pull時にログインに失敗。 また、auto-scalingにより大量にdocker pullが行われるタイミングがあったため、quay.ioのaccountがロックされ、しばらくの間、docker pullができない状態が発生してしまいました。 quay.ioにはdeploy用のrobot accountが設定できるので、そちらを利用することで、上記のような問題は起きないようにしました。 deployに失敗 eb deploy prod でebコマンドがデプロイ時にs3にputするソース一式のファイルの名前がconflicして、デプロイに失敗しました。ebコマンドのs3にputするファイル名は、gitのcommithashの4文字で固定されているので、それが重複して発生しました。 このときは、ElasticBeanstalk+Docker環境を構築中にebコマンド以外で作ったファイルと重複してしまった様子だったので、常にebコマンドでdeployを実行すれば防げるものだと思います。デプロイに失敗するだけならまだ良いのですがフロー的には、コードがデグレードした状態でデプロイされることも考えられるので、そこは気を使う必要があります。 また、Elasticbeanstalkのapplication versions(ソース一式)は500個が上限なので、定期的にWebConsoleから掃除してあげたほうが安心です。 ruby2.2.3へ Docker化したので、rubyの入れ替えもやりやすくなりました。そこで先日ruby2.2.3へ入れ替えを実施しました。 下準備としては、ベースとなるDocker imageをruby2.2.3でdocker buildしてそれを元にしてアドサーバーに必要なものをセットアップしたDocker imageを作成することで完了です。 デプロイには、ruby2.2.3を含んだDocker imageのsha1 hashを、Dockerrun.aws.jsonで指定してあげるだけで、入れ替えのデプロイ作業は完了です。アドサーバーはもともと、広告の配信と各種イベントの処理だけで、そこまで複雑なことはしていないので、ruby2.2.3にしたことによるコードの変更は、利用しているgemのupdateの対応を一箇所しただけでした。 ruby2.2.3による変更で遅くなっては意味が無いのですが、今回は、GCの最適化の恩恵をうけることができました。アドサーバーは50msec以下のレイテンシを最低ラインにしており、GC Executionが5~10msecかかっていたのが1msec以下に縮小しました。 以上、アドサーバーをElasticBeanstalk+Dockerに移行し、ruby2.2.3へ入れ替えの紹介でした。 最後に VASILYでは、新しい技術が大好きなエンジニアを募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
みなさんこんにちは、今村( @kyuns )です。今回は弊社の新規サービス開発にて、Hashicorp製品を中心にインフラ周りを整えたお話をしていきたいと思います。今回はTerraformとAtlasの話が中心になります。 今回実現したこと TerraformでAWS上のリソースをコードで管理 GithubでPullRequestを作ってインフラに対する変更をコードベースでレビュー Github上でPullRequestに対して変更がテストされ、テスト結果が貼られる Pull Requestをマージすると自動的にAtlas経由でterraformが実行されてインフラの変更が適用される いわゆるインフラのコード化&自動化です。 導入によるメリット インフラがコードで管理されることにより属人性を排除することができる インフラの変更に対して事前にレビューすることにより事故を減らせる 変更の適用はPullRequestのmergeボタンを1つ押すだけなので格段にデプロイの手間が省けて安全(etc) 今回は0からのインフラ構築ということもあり、HashicorpのTerraformとAtlasを利用して上記を実現してみることにしました。それでは順を追って説明していきたいと思います。 Terraformって何? Terraform とはHashicorpが提供している Infrastructure as Code を体現するためのツールです。今回は新規サービス開発ということもあり、ちょうど0から作るいいタイミングであった為、採用に踏み切りました。TerraformはAWSのほとんどの設定に対応しているため、コードベースでインスタンスをたてたり、ELBやS3の設定などのAWSのリソースを管理することができます。 Terraformの導入 インストール ダウンロード ダウンロードページ から各プラットフォーム別のバイナリを取得します。プログラム自体はGoのバイナリなので好きなフォルダに展開後、PATHを通しておきましょう。 また、ruby gem経由でもインストールすることができます。 $gem install terraform $terraform -version Terraform v0.6.7 現時点(2015.11.27)での最新版は0.6.7です。 AWSクレデンシャルの設定 AWSのログインクレデンシャルを環境変数に設定しましょう。 bash/zshならexport、fish shellならsetenvを利用します。 export AWS_ACCESS_KEY_ID=XXXXX export AWS_SECRET_ACCESS_KEY=XXXXX export AWS_DEFAULT_REGION=ap-northeast-1 setenv AWS_ACCESS_KEY_ID XXXXX setenv AWS_SECRET_ACCESS_KEY XXXXX setenv AWS_DEFAULT_REGION ap-northeast-1 以後、terraformコマンドを実行する際にこのアカウントの設定が利用されます。 Terraformを理解する Terraformの構成 Terraformを理解するのに重要なファイルが2つ存在します。 .tf ファイルと .tfstate ファイルです。 .tfファイル terraformの設定を記述するファイルです。 .tfstateファイル terraformコマンドを実行した後、現在のインフラの状態が記述されたファイルです。tfstateファイルはterraformを実行すると自動的に生成されるので通常は触る必要はありません。 また、terraformコマンドを実行すると自動的に tfstate.backup というバックアップファイルも生成されます。 Terraformを使ってEC2インスタンスを立ててみる tfファイルの記述 Terraformを使って試しにインスタンスを立ててみます。 新しくインスタンスを立てる場合、例えばec2の設定は以下の様な感じになります。 ec2.tf resource "aws_instance" "web01" { ami = "ami-6927d769" availability_zone = "ap-northeast-1c" ebs_optimized = false instance_type = "t2.medium" monitoring = false key_name = "iqon" subnet_id = "subnet-4ee15817" vpc_security_group_ids = [ "sg-2f765a4a" , "sg-55755930" ] associate_public_ip_address = true private_ip = "10.0.0.10" source_dest_check = true root_block_device { volume_type = "gp2" volume_size = 20 delete_on_termination = true } tags { "Name" = "web01" } } 非常に直感的ですね。設定を記述できたらterraformコマンドを実行してみますが、その前に設定を確認してみましょう。 設定を確認してみる(Dry run) terraformにはDry runの機能があります。 予め適用する内容を出力してくれるので、事前にどのような変更が起きるかを確認することができます。 $terraform plan 気をつけなければならないのですが、完璧なDry runというわけではなくplanでうまくいっても実際に実行すると失敗するということもありますので、あくまでも目安程度に捉えておいたほうが良いと思います。 コマンドを実行する 先ほど作ったec2.tfファイルを実行するためにはapplyコマンドを使います。 $terraform apply これでしばらくするとEC2インスタンスが作成されます。 ec2のほかにもS3やRDS、ELBなどあらゆるAWSサービスに対応しています。 既存のAWSインフラの設定からtfファイルを生成する 完全に0からインフラを構築する機会はそんなにないと思うので、殆どの人は既存のインフラに少しずつ適用して試してみたいと思うのではないでしょうか。また既存のAWSの設定からtfファイルを生成したい場合はどうしたらいいでしょうか?公式ではそのようなexportの機能はサポートされていないので3rdParty製のツールを使います。 terraforming という便利なgemがあるので今回はこちらを使ってみます。 gem install terraforming 試しに現在のs3の設定を取得してみます。 $terraforming s3 resource "aws_s3_bucket" "iqonsample" { bucket = "iqonsample" acl = "private" } resource "aws_s3_bucket" "iqonsample.test" { bucket = "iqonsample.test" acl = "private" } このように $terraforming サービス名 を指定すると、自動的に現在のAWSのリソース設定からtfファイルの形式で出力してくれます。また、同時に tfstate ファイル形式を出力する機能も備えています。tfstate形式の場合はオプションで --tfstte を指定します。 $ terraforming s3 --tfstate { "version" : 1, "serial" : 1, "modules" : [ { "path" : [ "root" ] , "outputs" : { } , "resources" : { "aws_s3_bucket.iqonapi" : { "type" : "aws_s3_bucket" , "primary" : { "id" : "iqonapi" , "attributes" : { "acl" : "private" , "bucket" : "iqonapi" , "force_destroy" : "false" , "id" : "iqonapi" , "policy" : "" } } } , "aws_s3_bucket.iqonapi-test" : { "type" : "aws_s3_bucket" , "primary" : { "id" : "iqonapi-test" , "attributes" : { "acl" : "private" , "bucket" : "iqonapi-test" , "force_destroy" : "false" , "id" : "iqonapi-test" , "policy" : "" } } } } } ] } 現在のAWSの状態からtfstateファイルの出力までしてくれます。 こちらを既存のtfstateファイルにmergeする場合は --merge オプションを利用してマージすることもできます。 AtlasとGithubでインフラを自動化する Terraformで設定を記述した後は、次にAtlasとGithubを利用してインフラを自動化します。 ATLAS はHashicorpが提供するクラウド型の統合プラットフォームであり、Vagrant,Packer,TerraformやConsulなどの機能を内包しています。ここではATLASとGithubを連携させてGithub上でPRを出した時点でterraform planの実行結果がPRに貼られ、mergeすると自動的にATLAS経由でterraform applyが実行されるのを目指します。 Atlasの設定 まずはAtlasにアカウントを作成します。 次にCreate and manage infrastructure with Terraformをクリックして設定に進みます。 画面に表示されるATLAS_TOKENを環境変数に設定します。 export ATLAS_TOKEN=XXXXXXXXXXX TerraformプロジェクトをAtlasにpushする 次にローカルにあるterraformプロジェクトをAtlasにプッシュします。(チュートリアルに従うとexampleになる) $ terraform remote config -backend-config "name=vasily/example" Remote configuration updated Remote state configured and pulled. Configuration "vasily/example" uploaded! (v1) 設定がAtlasにプッシュされました。 この仕組みは、terraformのtfstateファイルを リモートで管理する機能/terraform remote を利用したものです。 注意点ですが、この段階でローカルにあるtfstateファイルは削除されます。(Atlas上で管理されます) 自動デプロイ用の設定をする Auto applyの設定 Github上でデフォルトブランチにmergeされた際に、terraform applyを自動実行するかどうかの設定ができます。ここでは自動化をするのでAuto applyにチェックをいれます。 Githubとの連携設定 次にgithubのイベントをフックできるようにAtlasの管理画面のサイドバーにあるIntegrationにてGithubの設定をします。 レポジトリのルートディレクトリにterraformの管理ファイルがない場合は、 サブディレクトリを指定することもできます。 環境変数の設定 次に管理画面のVariablesにて環境変数を設定します。 access_key,とsecret_keyを指定します。 GithubでPRを作成してみる Atlas側で設定ができたらGithubに実際にPRを作成してみます。 PRのTESTの部分にTerraformでのterraform planの実行結果が表示されます。 PRをマージしてterraform applyが実行されるのを確認する。 Github側でPRをマージするとあとは自動的にAtlas側で terraform applyが実行されます。もちろん管理画面からでも実行結果が確認できます。 これでGithubでPR作成->mergeからAWSへの反映まで自動化することができました。 terraform applyに失敗した場合 このようにterraform planが成功してもapplyが失敗する可能性があるので、手動でapplyする場合はコンソールからQueue Planを押すと再度実行されます。 まとめ おすすめの導入方法 いきなりインフラ全てをTerraformで管理する、というのは0からインフラを作る時以外はあまりオススメできません。 TerraformはほとんどのAWSリソースに対応しています、よって全てをコードで管理できるわけではありますが、全てをTerraformで管理しようとすると個人的には結構事故の原因になりかねないなぁと運用してみて感じています。 例えばVPCのサブネットやインターネットゲートウェイの設定などの変更頻度のかなり低いものは場合に応じてTerraformで管理しない、という使い方もありだとは思います。 すでにAWSのインフラがある人は、まずは前述のterraformingを用いて既存のEC2インスタンスの設定などからtfファイルをつくり、同じ設定でインスタンスをたてたりするところから始めて見ると導入しやすいかなと思います。 また、Terraformへの完全移行の際には、tfstateファイルとの差分を排除するために、AWSのコンソールからポチポチというのは無くすというルールをつくって徹底する必要があります。(IAMとかをきちんと管理する) 導入後の感想 Terraformによって運用に必要な部分のインフラがコード化され、インフラの変更に対してもPRで確認してから適用できるようなったことで格段に属人性を排除することができました。また、変更の適用はGithubでPull Requestをmergeするだけで自動的にTerraformが実行されるようになり、安全性も確保することができました。 今日は紹介しませんでしたが、今後は弊社でのPackerやConsulまわりの取り組みもご紹介できればと思います。 VASILYではHashicorp製品をつかってインフラで新しいチャレンジをしてみたいエンジニアを募集しています。 ぜひとも一緒に取り組みましょう。興味があるインフラエンジニアはぜひ こちら から応募してみてください。
先日、弊社がAndroid版iQONの開発に使用しているサービスとツールについて紹介させていただきました。 Android版iQONの開発で利用しているサービス&ツールを紹介します その中でアプリの動作確認にSORACOM Airを導入したとご紹介しましたが、今回 SORACOM ユーザーグループ発足記念リレーブログ の11/19日分として、もう少し掘り下げてご紹介しようと思います。 SORACOM Airを導入しようと思ったきっかけ VASILYでは、 Qiita:Team を導入しており、職種を問わず全員がDaily Reportと呼んでいる弊社テンプレートでの日報(以下、DR)を書いています。詳細は こちら をご覧ください。DRには、テンプレで困ったことや思ったことを書く欄があるのですが、ある社員がDRで以下のように書いていました。 http://ramenandicon.hatenablog.com/entry/2015/10/30/091352 本文とは趣旨が違うのですが、冒頭に「月末と言えば7GB超過の通信速度制限。」とあり、IT系企業従事者以外にとっては、自宅にWifiがなかったり、通信速度制限が当たり前の文化としてあるのかもしれない=通信速度をもっとシビアに意識したほうがいい、と感じました 10月はあえて自宅のWifiを切って生活してみたのですが、そうなると月の半分ほどで通信速度制限がかかり、その状態だと使うアプリもシビアに選ぶようになったので、実感をもてた気がします(意識的には乗換えや検索系は必須で使うので再優先で残したいと思い、それ以外の通信が発生するアプリは立ち上げなくなりました) ちなみに速度制限がかかった状態の4GでiQONを立ち上げると、ホーム画面が開くまで平均して3秒〜5秒ほど待ちます、自宅にいるときに使うとそこまで感じないですが、電車などで暇つぶしに立ち上げようとしたときには「かなり遅いな」という印象です このDRを読み実際に調べてみたところ、iQONユーザーの中にも通信速度の低下で快適にサービスを利用できないユーザーが一定数以上存在することがわかり、帯域制限によって通信速度が低下した場合のアプリの挙動について調査・改善することになりました。 しかし、帯域制限を受けた状態は再現しにくく、Facebookの ATC のようなOSSはありますが、より簡単に帯域制限を受けた状態を再現できる方法を探しました。 その結果注目したのがSORACOM Airです。SORACOM Airの開発者が通信速度を自由に変更できるという特徴を利用して帯域制限を受けた状態を再現し、アプリの動作確認を行う事にしました。 導入してみた結果 SORACOM Airを導入することで通信速度を制限した動作確認が簡単に行えるようになりました。ユーザーコンソールからSIM単位で簡単に速度の変更を行え、非常に便利です。実際に速度クラスをs1.slowに設定してアプリを起動してみた結果は以下の通りです。s1.fastを指定した時と比較して、ホーム画面を表示するまでの時間が平均で3倍程度かかることがわかりました。また、画像の表示完了にはより長い時間がかかることがわかります。 以下はs1.fastに設定してアプリを起動した結果です。 SORACOM Airの速度クラスをHubotから変更 また、弊社ではSORACOM Airの速度クラスをSlackからHubot経由で変更できるようにもしています。 Hubotのコードは以下の通りです。SORACOM API Clientには soracom を使用しました。AWS Lambdaを経由して通信量が一定を超えたらSlackに通知する等もやりたいと考えています。 まとめ SORACOM Airは開発者がSIMの操作を簡単に行えるので非常に便利です。また、APIドキュメントもわかりやすく、簡単に利用することができました。今後、SORACOM Airを使用し、通信速度が遅い場合でも快適に利用できるようサービスを改善していきたいと思います。 最後に VASILYでは、新しい技術が大好きなエンジニア&学生インターンを募集しています。少しでもご興味のある方は是非 こちら からご応募よろしくお願いいたします。
こんにちはVASILYエンジニアの松本です。先日 MERY を運営する株式会社ペロリと合同で Fashion Tech meetup #1 と題した勉強会を開催しました。   当日は約100名のエンジニアの方々に集まっていただき、Fashion × Technologyを題材として各社のエンジニアが、お互いのサービスを支える技術について発表しました。今回はFashion Tech meetup #1でのVASILYの発表資料をご紹介します。 iQONを支えるクローラーの裏側 iQONでは提携先ECサイトからアイテム情報をクロールしています。VASILYでは2015年の夏にクローラーの仕組みを大幅に変更することによって、1ヶ月間で400サイト分のクローラーを製作することができるようになりました。エンジニア以外のメンバーでもクローラーを作ることができるようにしたそのシステムの裏側を紹介。 iQONを支えるクローラーの裏側 from Takehiro Shiozaki トピックモデルを用いた 潜在ファッション嗜好の推定 自然言語処理の潜在意味解析の分野で主に文章解析に用いられるトピックモデルを用いて、ユーザーの行動データからファッションの嗜好を分析。分析結果からユーザーのペルソナを定義するところに至るまでを紹介。 トピックモデルを用いた 潜在ファッション嗜好の推定 from Takashi Kaneda OpenCVを使ったiQONの画像処理の全容 iQONでクロールしたアイテム画像がコーディネートに使われるまでの画像処理の全容を公開。モデル画像、トルソー画像の機械学習を用いた自動判定。さらにECサイトの画像から物体領域を抽出し背景の透過処理を行うまでの手法を公開。 OpenCVを使ったiQONの画像処理の全容 from Kazuki Matsumoto iQON ADのfluentd, mackerelを使ったモニタリング iQONの広告配信システムiQON ADのモニタリングシステムを公開。fluentdを用いたログ集計方法とmackerelでの監視方法を紹介しています。 Google Cloud Test Labによるテスト自動化 今年のGoogle I/Oで発表されたクラウドベースのAndroidアプリのテスト基盤である、 Google Cloud Test Lab について発表。具体的にどのようなテストを実行可能なのかや、Web UIやAndroid Studio、CLIからのテストのスケジューリング、テスト結果の解析、そして弊社で実際に使用しての感想について紹介しました。 最後に 今回の勉強会では懇親会とLTセッションまで多くの方々に参加していただき、大盛況に第一回を終えることができました。これからもFashion Tech meetupは定期的に開催し、ファッション領域に取り組むベンチャー企業を盛り上げていこうと思っています。今回の勉強会に参加できなかった方々も是非次回ご参加ください。 また、今回の勉強会の会場と懇親会での軽食・飲み物はすべて株式会社ディー・エヌ・エーにご提供いただきました。本当にありがとうございました。 VASILYではFashion × Technologyに興味のあるエンジニアの方々をお待ちしています。一緒に最新のテクノロジーでファッション業界を盛り上げていきましょう。 VASILYのエンジニア募集要項はこちら
こんにちはVASILYエンジニアの松本です。VASILYではクローラーの仕組みを大幅に見直した際にDynamoDBの導入を行いました。今回はその導入方法とDynamic DynamoDBを用いた運用方法について話したいと思います。   DynamoDBを導入した理由 iQONではクローラーで取得したデータをDynamoDBに保存しています。DynamoDBを導入した理由は以下の通りです。 ・ ECサイトごと、さらには商品ごとにクロールするデータの形式が異なるためスキーマレスである必要があったこと。 ・ DynamoDBはデータベース容量が増大した際も自動でスケールしてくれるのでメンテナンスコストがかからないこと。 ・ 平均レイテンシーは1桁台のミリ秒単位であること。   iQONでは1日約80万点のアイテムをクロールしているので、メンテナンスコストがかからず、ある程度のパフォーマンスが担保できることがDynamoDBの魅力でした。DynamoDBに関する説明は このスライド に詳しく書いてあります。 DynamoDBの導入手順 AWS SDK for Rubyを使ったDynamoDBの利用 まずaws-sdkのgemをインストールします。 DynamoDBはサーバーにインストールして使うものではなくあくまでサービスなので、開発環境やCircleCIなどのテスト環境ではDynamoDBと互換性のある DynamoDB Local を使います。 テーブルを作成 クロールしたアイテムを保存するテーブルを以下のように作成します。AWSコンソール上からでもテーブルの作成は可能ですが、DynamoDB Localにも同様のテーブルを作成したかったのでその方法を記載しました。 DynamoDBは基本的にはスキーマレスなのですが、HASH keyとRANGE keyについてはスキーマを定義する必要があります。DynamoDBを使いこなすためのkeyやインデックス定義の仕方は この記事 が参考になります。 データの書き込みはこんな感じ HASH keyとRANGE keyの二つをスキーマに定義した場合は二つのkeyで値を取り出す。 CircleCIへのDynamoDB Local導入 VASILYではテストツールとしてCircleCIを利用しています。 CircleCIはDynamoDBを標準ではサポートしていないため、テスト時にDynamoDB Localを動かすように設定する必要がありました。 まずはDynamoDBをubuntuに入れるシェルスクリプトを記述。 circleci.sh dynamodbディレクトリがなければAmazonから最新版を取得して展開するようにしています。 circle.ymlでDynamoDB Localを起動するように指定します。 circle.yml dependenciesのcache_directoriesに展開済のdynamodbディレクトリをキャッシュするように指定し、次回以降のCircleCIのセットアップの時間を短縮することができます。 あとは、preでDynamoDB Localを起動させればOKです。 参考にした記事 Dynamic DynamoDBを用いたプロビジョニング量自動調整 DynamoDBの利用料金はストレージ容量、転送容量、書き込み・読み込みスループットのプロビジョニング量で決まります。詳しい説明は 公式ドキュメント にあります。その中でも、プロビジョニング量の課金が一番料金を左右する部分です。AWSコンソール上でプロビジョニング量は手動で調整できるので、最初はとりあえず余裕を持って設定しました。(赤線がプロビジョニング量、青が実際に発生したスループット)  しかし、書き込み・読み込みスループットは時間帯や時期によって大きく変動するので、プロビジョニング量を大きめに設定すると無駄な料金が発生してしまい、小さくしてしまうと性能劣化を招く危険性があります。そこで、プロビジョニング量をスループットの増減に応じて自動で調整する Dynamic DynamoDB を導入しました。導入の際に参考にしたのは この記事 です。 Dynamic DynamoDBで指定できるオプションについて DynamoDBはconfigファイルの設定に様々なオプションを設定することができます。オプションが多すぎるので、最低限のものだけ設定しました。 dynamic-dynamodb.conf その他のオプションについては こちらのドキュメント を参照してください。 これらのオプションを設定し、Dynamic-DynamoDBを動作させたところ以下のようにプロビジョニング量がスループットに応じて自動調整されるようになりました。  まとめ DynamoDBはフルマネージドサービスなのでメンテナンスコストもかからず、パフォーマンスも高いことが一番の利点です。しかし、部分的にスキーマ定義を行う必要があることから、スキーマレスであるmongodbと全く同じ使い方ができるわけではありません。詳しくは MongoDBとDynamoDBの性能比較記事 を参照してください。 ただし、あくまでKVS的な使いかたで利用するのであればメンテナンスコストの低いDBとして幅広く使うことができます。今回紹介したDynamic DynamoDBも併用すれば利用金額も大幅に節約することができます。 VASILYでは今回の事例のように新しい技術を使って課題を解決していけるエンジニアを募集しています。一緒に新しい技術をどんどん取り入れて、ファッション業界にイノベーションを起こしていきましょう。 VASILYのエンジニア募集要項はこちら