22Inc. サービス開発日誌

スタンプスのサービス開発チームが日々の業務で得たノウハウ、経験の共有ブログです。

新機能の開発にあたってデザイナーがやってきたこと

f:id:yang22:20191118194618p:plain 22Inc.のUXデザイナー、台湾出身のヨウソウシです。 先日弊社サービススタンプスの管理画面に新機能がリリースされました。一通り要件定義・設計の段階からデザイン作成・検証・QAまでやってきたので、本記事はそれらのことをまとめたいと思います。

続きを読む

Elasticsearchのjoin datatypeを使ってみた

f:id:yang22:20191031195727p:plain

@kaibaと申します。業務委託として手伝わせていただいております。 ​ Stampsでは導入店舗様が様々な条件で、来店してくれたアプリユーザにお知らせ、クーポン、アンケートなどを送ることができます。
サービスが育つとデータ量も増え、RDBMSでは厳しくなってきました。 今回はElasticsearchで高速化する話を書きます。

はじめに。Elasticsearchとは?

​ ElasticsearchはElastic社がメンテしているOSSの全文検索エンジンです。ドキュメント指向のデータストアで、RDBが苦手とする部分一致検索や複雑な検索を高速に実行できます。Elastic cloud、Amazon Elasticsearch Serviceなどマネージドサービスにもなっていることも多く、環境構築と運用はすごく楽になりました。 ​ RDBと大きく異なるのは「ドキュメント」を扱うことです。ドキュメントとは雑にいうと1つのJSONです。Twitterを例に、ツイートを検索する例を考えると、以下のようなドキュメントを挿入することになります。 ​

{
    "id" : "6789",
    "full_text" : "こんにちは!",
    "created_at": "2019-01-01T04:00:00",
    "user" : {
        "id" : "1234",
        "name" : "kaiba",
        "screen_name" : "kaiba",
    }
}

​ RDBではユーザの情報は別のテーブルに保持して正規化するのが一般的ですが、 Elasticsearchではこのように非正規化した形でドキュメントを保持します。 ​ 「screen nameが変更になったら不整合がおきないか」という不安が湧いてきます。 悩ましいところですが以下の案があります。 ​

  • screen name変更時にすべてのドキュメントを更新してやる
    • サーバのコストや負荷が気になります
  • screen nameを保持しないようにする
    • screen nameでの検索を諦める
    • screen nameからuser IDに変換して検索する
  • join datatypeを使う(今回はこれを説明します) ​

StampsとElasticsearch

​ Stampsには様々な条件でアプリユーザにクーポン、お知らせ、アンケートなどをお届けできます。 例えば以下のようなケースです。​

  • 来店頻度によりお客様にクーポンを送る
  • イベントに来ていただいたアプリユーザにアンケートをお送りし業務の改善に活かす
  • 今月誕生日を迎えるアプリユーザに誕生日お祝いのクーポンをお送りする ​ 条件は複数組み合わせることができ、かなり複雑なクエリになっており、RDBだとどうしても速度と負荷の問題がありました。 ​

設計

​ Elasticsearchのドキュメントには検索に使用する項目だけを入れるのが一般的です。 今回検索に使用したい項目は大きく分けると以下になります。 ​

  • アプリユーザ(以後、単にユーザと呼びます)
  • 来店情報

    • ユーザに対して来店情報は複数あり、どのようなindexにするか悩みました。 今回は、join datatypeを使用したのですが、以下も検討しました。 ​
  • ユーザに来店情報を配列で持たせる

    • ユーザが来店すればするほどユーザのドキュメントが肥大化する
    • 一部分だけ更新したいのに巨大なドキュメントを更新する必要がでてくるためリソースに不安がある
  • 来店情報にユーザ情報を紐付ける
    • ユーザの情報が更新されたときに大量のデータを同期する必要がある
    • ユーザを探したいのに得られるのは来店情報になってしまい、複雑になる
  • indexをユーザと来店情報に分割してしまう
    • 2回Elasticsearchに対して検索することになる
    • 「人気店のA店に1回以上来店した男性」のようなケースだと、「A店に1回以上来店した」ユーザが大量になるケースがあり、リソースに不安がある
    • リクエストの実装が複雑になる ​

join datatype

​ Elasticsearchはjoin datatypeを 使用することでドキュメントに親子関係を持たせることができます。 ​ ただ、パフォーマンスに関して以下のような記載があり、ここが気になるところでした。 (僕なりの翻訳になります) ​

join datatypeはRDBのように使うべきではありません。  
Elasticsearchでは非正規化してドキュメントを作ることで高いパフォーマンスが得られています。  
​
join datatypeが意味を成すのは1つのエンティティに対して大量のデータが含まれるケースです。  
例えば製品と製品の注文です。この場合、製品を親、注文を子としてドキュメントにすることは適しています。  

​ 実際にstagingの数百万のレコードからドキュメントを構築してみたところ、十分なパフォーマンスが得られました! ​

どのように使うのか?

​ 先程のTwitterの例でjoin datatypeを構築してみます。 今回は「ユーザを検索する」のを目的に、ユーザとツイートが1対多の関係になるように構築します。 ​ 以下の形式でドキュメントを1つのindexに入れます。 ​

{
    "user" : {
        ...
    }
}

{
    "tweet" : {
        ...
    }
}

​ mapping設定です。 ​

{
    "mappings": {
        "users_tweets": {
            "properties": {
                "user": {
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": "text"
                        },
                        "screen_name": {
                            "type": "text"
                        }
                    }
                },
                "tweet": {
                    "type": "object",
                    "properties": {
                        "user_id": {
                            "type": "text"
                        },
                        "full_text": {
                            "type": "text"
                        },
                        "created_at": {
                            "type": "date"
                        }
                    }
                },
                "users_tweets_join_field": {
                    "type": "join",
                    "relations": {
                        "user": "tweet"
                    }
                }
            }
        }
    }
}

​ userとtweetの型情報とusers_tweets_join_fieldに親子関係を定義しています。 ここではuserが親で子をtweetにしています。 ​ 続いて挿入するドキュメントを見ます。 ​

{
    "user": {
        "name": "kaiba",
        "screen_name": "kaiba"
    },
    "users_tweets_join_field": {
        "name": "user"
    }
}

{
    "tweet": {
        "id": "6789",
        "user_id": "1234",
        "full_text": "こんにちは!",
        "created_at": "2019-01-01T04:00:00"
    },
    "users_tweets_join_field": {
        "name": "tweet",
        "parent": "1234"
    }
}

​ 親は自分が親であることを宣言し、子は親のIDを指定しています。 型定義をし、ドキュメントを挿入していきます。 userとtweetのIDをドキュメントのIDとしたいのですが、IDが被る場合を考慮する必要があります。 tweetのIDは tweet + tweet.id の形式にしました。 ​

curl -X PUT -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets -d @mapping.json
{"acknowledged":true,"shards_acknowledged":true,"index":"users_tweets"}%
<200b>
curl -X PUT -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/1234 -d @user.json
{"_index":"users_tweets","_type":"users_tweets","_id":"1234","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1}%
<200b>
curl -X PUT -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/tweet6789\?routing\=1234 -d @tweet.json
{"_index":"users_tweets","_type":"users_tweets","_id":"tweet6789","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1}%

​ 条件なしで検索して確認します。 ユーザとツイートが得られました。 RDBに慣れていると気持ち悪いですね。 ​

curl -X POST -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/_search\?pretty
{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "users_tweets",
        "_type" : "users_tweets",
        "_id" : "1234",
        "_score" : 1.0,
        "_source" : {
          "user" : {
            "name" : "kaiba",
            "screen_name" : "kaiba"
          },
          "users_tweets_join_field" : {
            "name" : "user"
          }
        }
      },
      {
        "_index" : "users_tweets",
        "_type" : "users_tweets",
        "_id" : "tweet6789",
        "_score" : 1.0,
        "_routing" : "1234",
        "_source" : {
          "tweet" : {
            "id" : "6789",
            "user_id" : "1234",
            "full_text" : "こんにちは!",
            "created_at" : "2019-01-01T04:00:00"
          },
          "users_tweets_join_field" : {
            "name" : "tweet",
            "parent" : "1234"
          }
        }
      }
    ]
  }
}

​ 「こんにちは」とツイートしているユーザを探してみます。
has_child クエリを使います。ドキュメント挿入は難しかったですがここはすごくわかりやすいですね。一度入れてしまえば、検索はまさにElasticです。 ​

{
  "query": {
    "bool": {
      "must": [
        {
          "has_child": {
            "type": "tweet",
            "query": {
              "match": {
                "tweet.full_text": "こんにちは"
              }
            },
            "min_children": 1
          }
        }
      ]
    }
  }
}

curl -X POST -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/_search\?pretty -d @search3.json
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "users_tweets",
        "_type" : "users_tweets",
        "_id" : "1234",
        "_score" : 1.0,
        "_source" : {
          "user" : {
            "name" : "kaiba",
            "screen_name" : "kaiba"
          },
          "users_tweets_join_field" : {
            "name" : "user"
          }
        }
      }
    ]
  }
}

​ ばっちり! 良さそうですね! ​

プログラムからの呼び出し

​ StampsのバックエンドではRailsを使用しています。
RailsはDBのテーブルと密接に関わるため、今回のようにテーブルとindexが1:1でない場合、設計の難易度が上がります。 ​ Elasticsearch-railsのテストコードが綺麗に書けているので参考にすると良いでしょう。 ​

まとめ

​ Elasticsearchのindex設計は「何を探したいか」に着目するとうまくいく気がします。
Stampsでは「来店したアプリユーザ」を様々な条件で検索したかったので、ユーザをベースに設計しました。 複雑な検索条件を柔軟に記載し、高速にできるようになりました。

意外と知らない便利なRuby/Railsメソッド5選

こんにちは、22Inc.山崎です。

この記事では、便利ではあるけども意外と知らないRuby(またはRails)のメソッドを紹介します。

以下に紹介するような便利メソッドを用いて短く書かなくても、機能開発に必要なコードを書くことはできます。 しかし、短く書く方法を知らないと、書くコード量が多くなり抜け漏れも発生しがちになります。

保守性に優れたコードを書いて 一歩上のエンジニアになるために、以下に紹介するようなコードを積極的に書いていきましょう!

Enumerable#detect

使用場面

ArrayやHashの要素の中から、任意の条件にマッチする最初の要素を取り出したい。

ary = [1, 7, 55, ... , 9893211, 9953441, 10000000 ]
# ↑のように、昇順に不規則な値が並ぶ要素を持つ配列の中から、6の倍数である値のうち、一番最初にマッチする値を取り出したい

良くない書き方

ary.select {|e| e % 6 == 0}.first

selectメソッドを用いると、配列の要素を全てチェックすることになり、配列の要素数が多くなればなるほど実行完了までの時間がかかることになります。 条件にマッチする最初の要素を見つけた時点で、要素の探索を終了するほうがパフォーマンス上望ましいですよね。

キレイな書き方

ary.detect {|e| e % 6 == 0}

detectメソッドはブロック内の条件にマッチする要素を探索するという点ではselectメソッドと同じですが、1つ目の要素にマッチした時点で探索を終了します。

find, detect (Enumerable) - Rubyリファレンス

Hash#fetch_values

使用場面

複数の要素を持つハッシュの中から、指定した複数keyに対応するvalueのみを抽出したい場合。以下のような例が挙げられます。

params = { name: "James", age: 25, gender: "man", phone_number: "333-2323", prefecture_id: 1, prefecture_name: "北海道", segment: "free" }
#↑のハッシュのkeyに対応するvalueのうちいくつかを、nilまたは空白でないことをチェックしたい。

良くない書き方

params = { name: "James", age: nil, gender: "", phone_number: "333-2323", prefecture_id: 1, prefecture_name: "北海道", segment: "free" }

# nameとage、gender、segmentのキーには値が入ってなければならない。
if params[:name].present? && params[:age].present? && params[:gender].present? && params[:segment].present?
  puts "問題ありません"
else
  puts "必須パラメータが不足しています"
end

if文の条件に params が4回も重複して登場しており、DRYでないことが一目で分かります。今後、新たなパラメータを追加したり既存のパラメータを削除したりした際は、if文の条件部分の修正も行わなければならなくなり、ヒューマンエラーによるバグの温床となってしまいます。

キレイな書き方

params = { name: "James", age: nil, gender: nil, phone_number: "333-2323", prefecture_id: 1, prefecture_name: "北海道", segment: "free" }

# nameとage、gender、segmentのキーには値が入ってなければならない。
necessary_params = [:name, :age, :gender, :segment]
if params.fetch_values(*necessary_params).all? {|e| e.present?}
  puts "問題ありません"
else
  puts "必須パラメータが不足しています"
end

fetch_valuesメソッドは、引数に渡したkeyにマッチする要素のvalueを配列で返します。今回は以下のような結果を返します。

params.fetch_values(*necessary_params)
#=> ["James", nil, nil, "free"]

コードの重複が削減され、パラメータの追加や削除などの変更に強くなりました。

docs.ruby-lang.org

Object#in?

使用場面

あるオブジェクトが、配列の要素として存在するかを確認したいとき。 Array#include? メソッドを使うことでも確認できますが、代わりにObject#in?を使用することでコードの可読性を改善できます。

良くない書き方

#ID1番のユーザーが、いくつかある契約プランのうちどれかに加入していることを確認したい。
plan = User.first.plan
contract_term = User.first.contract_term

if %w(1week 2week 1month).include?(contract_term)
  puts "短期間向けプラン加入済"
elsif %w(3month 6month a_year).include?(contract_term)
  puts "長期間向けプラン加入済"
elsif %w(free trial beginner).include?(plan)
  puts "初心者向けプラン加入済"
elsif %w(standard select medium).include?(plan)
  puts "中級者向けプラン加入済"
elsif %w( high pro super ultra great).include?(plan)
  puts "上級者向けプラン加入済"
else
  puts "プラン加入なし"
end

配列の要素数が増えれば増えるほど、include?メソッドのレシーバは巨大になるので、いったいどのオブジェクトを確認の対象としたいかが分かりにくくなってしまいます。 上記のように、配列の要素数が多かったり組み合わせパターンが多い場合には、配列を変数に格納することで可読性を上げるためには、パターンの数だけ変数を宣言することになりかえって冗長になります。

キレイな書き方

#ID1番のユーザーが、いくつかある契約プランのうちどれかに加入していることを確認したい。
plan = User.first.plan
contract_term = User.first.contract_term

if contract_term.in? %w(1week 2week 1month)
  puts "短期間向けプラン加入済"
elsif contract_term.in? %w(3month 6month a_year)
  puts "長期間向けプラン加入済"
elsif plan.in? %w(free trial beginner)
  puts "初心者向けプラン加入済"
elsif plan.in? %w(standard select medium)
  puts "中級者向けプラン加入済"
elsif plan.in? %w( high pro super ultra great)
  puts "上級者向けプラン加入済"
else
  puts "プラン加入なし"
end

in?メソッド を用いることで、レシーバと引数をinclude?の場合と逆転させることができました。(実際、in?メソッドは内部でinclude?を呼んでいます) まだこのif文は改善の余地がありますが、ひとまずどのオブジェクトが条件として用いられるかは分かりやすくなりました。

Object

ActiveRecord_Relation#find_or_create_by

使用場面

一定期間分のデータの集計をバッチで行い、その結果をデータベースに保存するという業務ロジックを組むのはよくありますよね。

集計ロジックでまず必要になるのは、データベースに保存していない時期のデータを集計して保存を行う処理でしょう(例: 毎月1日に前月の売上データを集計する) また、集計を行うタイミングによっては、過去に集計したデータと異なる数値が得られることもあり、その場合は既存データを上書きしなければならないときもあります。(例: 毎月1日に、過去1年分の月次データを集計するバッチ処理)

ここで必要になってくるのが、「データの集計をした上で過去データと値を比較し、既存データがない場合は新規レコードを作成し、既存データがある場合は取得データによる既存データの上書きを行う」という処理です。

このfind_or_create_byメソッドは、その名の通りの働きをします。 つまり、 「引数に指定した条件にマッチするレコードが存在すればそのオブジェクトを取得し、存在しなければ引数に指定した条件に従うレコードを新規作成する」 という挙動になります。

なお、データベースに保存する処理はせず、インスタンスの生成までに留めるfind_or_initialize_byというメソッドもあるので、場面によって使い分けることができます。

ActiveRecord::Relation

ActiveRecord_Relation#size

使用場面

ActiveRecord_Relation#sizeは基本的にコレクションの要素数を返しますが、ActiveRecord_Relation#groupの戻り値に対して呼んだ場合、以下のようにグルーピングした状態でハッシュを返します。

#ID1〜100番のユーザーのうち、契約プランごとにグルーピングした上で各プランの人数を抽出したい

User.where(id: 1...100).group(:plan).size
#=> {"free"=>25, "standard"=>50, "pro"=>20, "super"=>5}

このとき内部ではGROUP BYとCOUNT(*)を用いたSQLが1回投げられています。そのためcount メソッドを用いても同じくハッシュの戻り値になりますが、 lengthメソッドを用いると 要素の種類数 が戻り値となります。

User.where(id: 1...100).group(:plan).count
#=> {"free"=>25, "standard"=>50, "pro"=>20, "super"=>5}

User.where(id: 1...100).group(:plan).length
#=> 4

以上、意外と知らない便利メソッド5つを紹介しました。 複雑な処理をついつい力技で実装しそうになりますが、「もっとスマートな方法はないか?」を常に考えながらコードを書いていきましょう!

UX MILK Fest 2019に参加してきました

22Inc.のUXデザイナー ヨウソウシです。 今回はUX MILK Fest 2019に参加してきました!!フェス感がありセッションスケジュールもぎっしりで濃厚な一日でした。この記事で当時の見たこと、感じたことをまとめています。

https://uxmilkfest2019.studio.designuxmilkfest2019.studio.design

f:id:yang22:20190917083020j:plain

f:id:yang22:20190928111543j:plain 入り口からあちこちにある素敵なキャラクターミルクボイです。

f:id:yang22:20190917083152j:plain Tシャツまで作ってあって気付いたら三瓶さんが販売ブースにw

では早速、自分の参加したセッション内容をまとめてまいります。

「今まさにUX浸透させてます」新設UX組織がやっていること ベイクルーズ 佐々木さん

UXアプローチとは?

  • UIデザイン
  • ユーザー理解
    • インタビュー/アンケート
    • ペルソナ
    • ユーザーテスト(会社内部・外部両方やる)
  • 適切なステップ
    • HCDサイクル

案件

  • 01.返品体験全体 f:id:yang22:20190924075520j:plain
  • 02.商品ページ全体改善
  • 03.CRMに置けるシナリオ最適化

まとめ

  • UXデザイン以外の業務をしっかりやる →CS周りお客様と関わることなど
  • トップダウンでUXを取り入れる →社内の影響力のある方・上の方からしっかりUXについて理解し、率先して周りを巻き込んでUXの概念を社内に広げる
  • ユーザーのNPSも含めてたくさんデータ取る、そこから振り返る・ヒントが見つかる

感想

返品体験のユーザーテストはかなりしっかりしている。ユーザー体験はCSと切り離せないので継続改善はやりやすい環境と感じた。テスト→問題発見→デザイン改善といういいサイクルでサービスを作り上げていく。

オンラインとオフラインを越境するUX Payke 神谷さん

  • 経歴:YAHOO(ロッジ)→MAMORIO→Payke(全てオフラインの実体験に関わる)
  • オンラインとオフラインの横断でユーザー体験が発生
  • 理想のUXと技術面のギャップを埋める(バランスをとる)

MAMORIO

mamorio.jp

課題1:家で探したい場合音を鳴らすのは一番いい方法だが技術面ではできない

  • スマホの画面を回して検知されたら画面で知らせる
  • 歩き回ってもらうことを工夫(チュートリアルや画面上のヒントなどで)
  • 距離を示すため位置関係をマップっぽいUIにする

課題2:充電不可で電池切れたときものを無くしたがっかり体験の解消

  • 対策:プッシュ通知で(モーダル)電池切れたかもの通知を出す
  • が、電池切れと紛失はシステムで区別できない
  • →電池の寿命の一年期間を想定してもうすぐ経つタイミングでアラート出す

Payke

payke.co.jp 外国人観光客向けの多言語対応アプリ

  • ユーザーとデザインの接触するタイミングから離脱を考える

    • ユーザーとの一番の接点としては、外国人観光客が旅中に店舗にいる時。店頭にアプリインストール済みのタブレットをいかに使ってもらうのは大事。特にドンキーやビレバンなどPOPの情報量の多い店舗でどうタブレット自身を気付いてもらうのは難しいところ。
    • 自分の作っているデザインがどのタイミングでユーザーと出会うのかを常に考える(ユーザーとの接点) f:id:yang22:20190924080127j:plain f:id:yang22:20190924080242j:plain
  • 店頭のタブレットに人感センサーとか付けて調査する(使ってくれてるか)

  • 理想と現実をすり合わせていく
  • ユーザーリサーチしてストーリーを見直す。改善できるところを探す。

デザインチーム作りについて

  • デザインチームの「あり方」を可視化する
    • デザインチームのKPI(社長と一緒にすり合わせた)とKPIに基づく「やること」と「やらないこと」の明文化(工数・コストを考えて社内でやるか、社外に投げるか)
  • モックアップを作ってこうやるとユーザー体験はどう変わるかどう向上になるのかプレゼンする
  • 役割分担:プロダクトマネージャー→どう実践するか考える/UXデザイナー→全体の体験を考えて、改良していく

感想

MAMORIO、Paykeどちらでも斬新的なサービスで、今までにない体験をどうわかりやすく作るのか、色々工夫が必要。そして技術面でのバランスを取るのも難しいが大事。特にPaykeはユーザーとの接点は旅行中の店頭で、いかにビジュアルがごちゃついてる店でタブレットに気付いてもらうかは、なかなかのチャレンジと感じた。

全部話します!組織にUXデザインを導入するためにしたアレコレ mediba 岡さん

https://speakerdeck.com/masakioka/zu-zhi-niuxdezainwodao-ru-surutamenisitaarekore

  • 趣味:インタビューでありえない姿勢の写真撮られることw

f:id:yang22:20190917082830j:plain

  • お客様感謝DAY:全ての業務を止めてユーザーのことだけ考える
  • 目的を持って目標を立てる
    • 目標の為にデプロイ・リサーチをするなどなど
    • 目的→目標→手段
    • 目的を持つとモチベーションも変わる(三人のレンガ職人)
  • BTCトップ人材による継続的UX改善評価
    • トップの人に常にフィードバック・評価を繰り返す
  • モックの段階ユーザービリティをテストする
  • 会社全体をユーザー中心にする
  • 会社の方向性:会社のクレドを明確に
  • 人事制度の見直し
    • スキルをあげれば給料もあげる(売上を上げるではなく)
    • 売上を上げるのは経営陣
  • 仕事(W)=力(F)x距離(S)
    • UXデザインの摩擦を0にする(押したら動く、働きやすい環境)

感想

抽象的な「UX」はいかに組織に導入・浸透するのは、トップダウンはもちろんのことで、会社全体一丸となってしっかりユーザーのことを考える、継続的にやり続ける必要がある。

BtoBtoCサービスのデザイナーがユーザーに近づくために実施したこと ヤプリ 城さん

https://speakerdeck.com/yumijo/btobtocsabisufalsedezainagayuzanijin-dukutamenishi-shi-sitakoto

  • 周りを巻き込むUXデザインを実行
  • ユーザーが抱えている問題掴みづらい
  • ユーザーとの距離を縮めるためやっていた3つのこと
    • 小さな実践(加工された情報がデザイナーに届けてしまう)
      • 社内のユーザー像に近い社員でユーザービリティテストをする
      • 使えそうなデータとか勝手に分析してみる
      • クライアント訪問に同行してヒアリング
      • クライアント招待イベントで時間をもらってヒアリング
      • ↓これらの成果を社内で共有
      • ↓納得感がある
      • !→腑に落ちる
    • 共感者を増やす
      • 自分たちの業務と繋がりが見える
      • みんなサービスを成功させたいと思っている
    • 仕組み作り
      • 武器が増える(分析ツールができたとか、顧客アンケート実施してもらえるとか、ユーザーインタビューの予算できたとか)
  • まとめ
    • やったことを発表・共有しないのはやってないのと同じ
    • UXリサーチについてまず社内で声かけてみる、意外と興味ある人いるかも

感想

まだまだUXの概念が浸透していない組織・会社では、デザイナーが率先して地道な努力がとても大事。それの結果を可視化して社内のメンバーに説得し共感を得た上で、UXリサーチ・改善もやりやすくなる。

アジャイルUXリサーチ メルペイ 松薗さん

https://www.slideshare.net/MihoMatsuzono/agile-ux-research-171767909

  • UXリサーチチームの立ち上げ
    • 「デザイン組織のつくりかた」を参考して組織作りしている
    • 外注するか?採用して立ち上げるか
    • そもそも金融系なので外注は難しいし、毎週やるのもディレクションが大変→インハウス立ち上げ

情緒の設計 クックパッド 倉光さん

https://speakerdeck.com/transit_kix/qing-xu-falseshe-ji

  • 情緒設計することによって、情報がもっと受け入れやすくなる
  • アイデアを具現化→抽象レイヤーの霧を晴らす
  • 形が先にあり、ロジックは後から見出される
  • 具現化することによって色々見えてくる
  • 主観:感じて欲しい世界観はどのように構築?
  • ユーザーストーリー:吹き出しにユーザーの考えていることを書く(P.31)
  • UX MILKのリデザインプロセス情緒設計
    • サービスの世界観を体験する
    • ヒアリング→キーワードピックアップ→ムードボード
    • ムードボード
      • たくさんの画像を探してこれはイメージと合っているかOXで確認
      • なんか違う?と思うやつは具体的に理由をいう
    • アイデアの打ち合いが楽しい
    • いいセッションが情緒を生む
  • ユーザーに向きあってサービスを作る
  • 誰のために、誰の感情を動かすか、一番初めから情緒を決めて進めていく

感想

UX MILK BOYの誕生物語面白すぎ

未参加セッション

未参加セッションですがツイッターでスライドのシェア見かけましたのでまとめました。

全体感想

  • UXリサーチ、ユーザービリティテストが大事。やる。
  • トップダウンでやれば色々進めやすい
  • 結果、仮説検証、評価を出して説得する→UXが社内で浸透できる

終わりに

一日のセッションが多く、3会場に分けて全て参加するのは不可能でしたが、全体的にフェスのような感覚で、わいわいして楽しい一日でした。

UX大喜利もやっていて、どれも面白かったですw f:id:yang22:20190924075115j:plain

f:id:yang22:20190924075121j:plain

f:id:yang22:20190924075138j:plain

f:id:yang22:20190924075143j:plain

オフィシャルグッズはこんな感じです。 f:id:yang22:20190917083927j:plain

このようなUX中心のイベントは、日本国内でどんどん盛り上がれば嬉しいなと思います。来年もやるならぜひまた参加したいですね。

夏休みの読書感想文 : オブジェクト指向設計 実践ガイド

はじめまして。22Inc.にエンジニアとして中途入社しました藤原です。 実は、昔に営業代理店で「スタンプス」を販売していた経験があります。 そんな私が偶然、縁があり22incにエンジニアとして拾っていただきました。

さて、夏休みの読書感想文を書いていきたいと思います。

読んだ本:

本を読む前まで

ダックタイピングってなに?

もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである。

へ〜よくわからんけど、まだ自分には早い技術やな。

オブジェクト指向ってなに?

  • クラスがあるやつやろ?
  • 継承ってのが便利みたい。
  • インスタンスがつくれる(クラスは設計書みたいな感じ)

恥ずかしながらそんなレベルでした。

では、オブジェクト指向を少し身に着けた軌跡をまとめていきます。

オブジェクト指向のオブジェクトの考え方

感動した言葉があったので引用

仮にオブジェクトが人間だとして、自分の関係を説明できるとしましょう。すると、図4.5では、TripはMechanicに「私は自分が何を望んでいるかを知っているし、あなたがそれをどうやるかも知っているのよ」と伝えているはずせす。図4.6では、「私は自分がなにを望んでいるかを知っていて、あなたが何をするのかも知っているよ」、図4.7では、「私は自分が何を望んでいるかを知っているし、あなたがあなたの担当部分をやってくれると信じているよ」でしょう

オブジェクト指向とそうでないコードの対比

では、具体的にオブジェクト指向なコードとそうでないコードのを比較してみます。 本書にでてくる4章のシーケンス図の例を参考にしながらRailsで自分なりにコードにしてみました。

自転車旅行会社で、旅行の前に整備士が自転車の準備をするプログラム

オブジェクト指向でないコード

Tripクラスのprepare_bicyclesメソッドは、Mechanicクラスをメソッド内で使っており、かつMechanicクラスのメソッドの引数にどのようなデータを渡すかまで知っておりそれも、使っている。 つまり、「私は自分が何を望んでいるかを知っているし、あなたがそれをどうやるかも知っているのよ」という状況

class Trip < ApplicationRecord
  has_many :trips

  def prepare_bicycles
    self.bicycles.each do |bicycle|
      Mechanic.clean_bicycle(bicycle)
      Mechanic.pump_tires(bicycle)
      Mechanic.lube_chain(bicycle)
      Mechanic.check_brakes(bicycle)
    end
  end

end

class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
trip.prepare_bicycles

オブジェクト指向なコード

Tripクラスの内には、MechanicクラスがなくMechanicに関することを何も知らない。Mechanicクラスのprepare_trip(trip)メソッドで引数としてオブジェクトが注入され、アソシエーションのbicyclesメソッドが呼び出される。Mechanicクラスに引数として渡り、どのように処理されるかはMechanicクラスに任せている。 つまり、「私は自分が何を望んでいるかを知っているし、あなたがあなたの担当部分をやってくれると信じているよ」という状況

class Trip < ApplicationRecord
  has_many :bicycles
end

class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.prepare_trip(object)
    object.bicycles.each do |bicycle|
      self.prepare_bicycle(bicycle)
    end
  end

private

  def self.prepare_bicycle(bicycle)
    self.clean_bicycle(bicycle)
    self.pump_tires(bicycle)
    self.lube_chain(bicycle)
    self.check_brakes(bicycle)
  end

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
Mechanic.prepare_trip(trip)

オブジェクト指向ならどういうメリットがあるのか

新機能の実装を例に

e.g) 新しく、自転車レースをする事業が始まったため、整備士(Mechanic)は旅行のときだけではなく、自転車レースのときも自転車を準備する必要がでてきた。

オブジェクト指向でないコード

class Trip < ApplicationRecord
  has_many :trips

  def prepare_bicycles
    self.bicycles.each do |bicycle|
      Mechanic.clean_bicycle(bicycle)
      Mechanic.pump_tires(bicycle)
      Mechanic.lube_chain(bicycle)
      Mechanic.check_brakes(bicycle)
    end
  end

end

# >>>>>>>>>>>>> 新しく追加
class Race < ApplicationRecord
  has_many :bicyclesbicycle

  def prepare_bicycles
    self.bicycles.each do |bicycle|
      Mechanic.clean_bicycle(bicycle)
      Mechanic.pump_tires(bicycle)
      Mechanic.lube_chain(bicycle)
      Mechanic.check_brakes(bicycle)
    end
  end

end
# >>>>>>>>>>>>> 


class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
trip.prepare_bicycles

race = Race.find(1)
race.prepare_bicycles

オブジェクト指向なコード

class Trip < ApplicationRecord
  has_many :bicycles
end

# >>>>>>>>>>>>> 新しく追加
class Race < ApplicationRecord
  has_many :bicycles
end
# >>>>>>>>>>>>> 

class Bicycle < ApplicationRecord
  belongs_to :trip, optional: true
end

class Mechanic < ApplicationRecord

  def self.prepare_bicycles(object)
    object.bicycles.each do |bicycle|
      self.prepare_bicycle(bicycle)
    end
  end

private

  def self.prepare_bicycle(bicycle)
    self.clean_bicycle(bicycle)
    self.pump_tires(bicycle)
    self.lube_chain(bicycle)
    self.check_brakes(bicycle)
  end

  def self.clean_bicycle(bicycle)
    bicycle.update(is_clean: true)
  end

  def self.pump_tires(bicycle)
    bicycle.update(is_pump_tires: true)
  end

  def self.lube_chain(bicycle)
    bicycle.update(is_lube_chain: true)
  end

  def self.check_brakes(bicycle)
    bicycle.update(is_check_brakes: true)
  end

end

trip = Trip.find(1)
Mechanic.prepare_bicycles(trip)

race = Race.find(1)
Mechanic.prepare_bicycles(race)

つまり

オブジェクト指向でないコードの場合はコピペで prepare_bicycles メソッドを必要となるクラスに書く必要がある。 これは、Tripクラスのprepare_bicyclesの内容が変わると、Raceクラスのprepare_bicyclesも修正する必要がある。 オブジェクト指向なコードの場合は、Mechanicクラスのprepare_bicycleメソッドを変更するだけで大丈夫!

感想

はたして今回書いたサンプルコードはオブジェクト指向なのか? 本を読んでる最中はオブジェクト指向を「完全に理解した」が、ブログを書いていると「なにもわからない」という感じになった。 新しくGoFに関する本を買ったのでオブジェクト指向の習得していく! また、オブジェクト指向に関するブログを書いていきます。 TripクラスとRaceクラスでprepare_bicyclesの実装が違うみたいな記事を次書こうかなと思ってます。

zeplinでデザインスタイルガイドを作ってみました

22Inc.のUXデザイナー ヨウソウシです。

弊社サービススタンプスのブランディングをより向上できるように、スタイルガイドを作りましょう!という話になったので、どういう風に作っていくか、どのツールを使うか模索しながら制作していく過程を記録してみました。

ツールの選定

ネットで調べてみたら、fractal、storybook、documentCSS…など、世の中に出回っているスタイルガイドジェネレーターはたくさんあります。どれでもターミナルを使ってインストールなどの作業が必要です。ふむふむ、なるほど…若干デザイナーに優しくないですね。

と、思っているその時、frontifyというツールを見つけました。

frontifyを試してみた

f:id:yang22:20190717212135p:plain https://frontify.com/

ブラウザ上で楽々編集・作成できるツールですが、有料です。無料試用期間は14日あります。その後は29ドル/月になります。海外のサービスなので全て英語です。

ここの記事を参考してざっくり作成してみました。 www.webcreatorbox.com

操作画面はかなりシンプルでわかりやすく、英語ですが問題なく操作できました。色々いじってみて、最後の出来上がりは下記のリンクからご覧ください。 company-146839.frontify.com

UIコンポーネントもcodepenのようにhtml / css / jsが書けるので非常にいいです。

スタイルガイドの作り方はここの記事を参考にしました: uxmilk.jp

frontifyを使って作成していく中に、結局UIコンポーネントを作成するため、コードを書く必要があるのでコードに慣れていないデザイナーにとってやや大変な作業、、その時、デザイン指示書として利用しているzeplinというツールを思い出しました。

zeplinに移行

f:id:yang22:20190717212325p:plain

https://zeplin.io

sansan社がイベントでこのツールをデザイン指示書として使っていますよという話がありました。これはもしかしたらスタイルガイドとしても使えそうじゃない?と。

Sketch、Adobe XD、Figma、Adobe PhotoshopなどのUI作成ツールと連携できて、ファイルそのままアップするだけで全てのオブジェのサイズ、角丸具合、間隔などのcssコード情報が自動生成されます。

無料で利用可能ですがプロジェクトは1つのみです。コード書けなくて済むのはかなり楽です。速攻zeplinに引っ越しました。

zeplinでスタイルガイドを作る

前提としてはSketch上にスタイルガイドを整えておくことです。画面だけ作ってzeplinにアップしてもただのデザイン指示書になります。

Sketchでスタイルガイドを作成

Sketchの主力機能の「シンボル」「テキストスタイル 」「レイヤースタイル」を使ってデザインスタイルガイドを作っていきます。

シンボル

ボタンやアイコンなど作ってシンボルに登録します。登録しておくと、流用で使い回しがかなり便利になります。

テキストスタイル

見出し、本文、注釈、テキストリンクなど、テキストのカラー・サイズ・フォントの情報をテキストスタイルとして登録します。

レイヤースタイル

テーマカラー・サブカラーなどの色を登録します。色の情報だけではなく、枠線・ドロップシャドウ・半透明なども登録できます。色々な場面で活用できそうです。

zeplinにアップロード

zeplinにアップするにはzeplinのプラグインが必要なのでインストールしました。諸々作成した上で、アートボードを選択してzeplinにアップします。すると、自動的にシンボル(Components)、テキストスタイル(Text Style Catalog)、レイヤースタイル(Color Palette)が全て読み込まれます。これで一瞬でスタイルガイド完成できて最高ですね。

アートボードを選択してzeplinにアップ: f:id:yang22:20190723221416p:plain

アップしたら自動にスタイルガイドが出てくる: f:id:yang22:20190717211513p:plain

zeplinをslackと連携

zeplinはslackと連携する機能があります。連携しておくと、何かしらの操作(画面アップロードとか)があるとシステムでslackに通知が飛んできます。画面のプレビュー画像も付いててアップしたらすぐそれに対して議論することも可能で、やり取りはかなり便利です。 f:id:yang22:20190723220934p:plain

zeplin x sketchは最高

sketchでしっかりしたスタイルガイドを作ってしまえば、ワンクリックでzeplinにアップすると瞬時にエンジニアに共有できるスタイルガイドも完成されます。もうこれみたいな最強なコンビはないじゃないかと思うくらい愛用しています。

現在はプロジェクトを進めながらスタイルガイドを作り上げていく段階で、まだまだお見せできるものではないですが、デザイナーとしてzeplinとsketchの連携で本当に楽で最高です。

「Simple Rails Navigator」というVSCodeプラグインを作った話

こんにちは、22Inc.大薮です。

marketplace.visualstudio.com

タイトルのままなのですが、VSCodeでRailsを書こうとすると、どうしても自分の欲しい操作感で使えるプラグインがなかったんすよね。
試しに作ってみたら意外にも簡単に実装できたので、自分でプラグインを作って公開するまでの流れを紹介します。

f:id:oyabu22:20190604235933p:plain
Simple Rails Navigator

カンタン3Stepな手順です。

Step.1 準備

yoをインストールして、npm installします。
npmが入ってれば特に特別なことは何もしなくて大丈夫だと思います。 (試してないけど yarnでも大丈夫かな??)

➜ npm install -g yo
npm WARN deprecated cross-spawn-async@2.2.5: cross-spawn no longer requires a build toolchain, use it instead
/usr/local/bin/yo -> /usr/local/lib/node_modules/yo/lib/cli.js
/usr/local/bin/yo-complete -> /usr/local/lib/node_modules/yo/lib/completion/index.js

> spawn-sync@1.0.15 postinstall /usr/local/lib/node_modules/yo/node_modules/spawn-sync
> node postinstall


> yo@2.0.6 postinstall /usr/local/lib/node_modules/yo
> yodoctor


Yeoman Doctor
Running sanity checks on your system

✔ Global configuration file is valid
✔ NODE_PATH matches the npm root
✔ Node.js version
✔ No .bowerrc file in home directory
✔ No .yo-rc.json file in home directory
✔ npm version
✔ yo version

Everything looks all right!
+ yo@2.0.6
added 542 packages in 51.939s


   ╭─────────────────────────────────────╮
   │                                     │
   │   Update available 5.6.0 → 6.9.0    │
   │       Run npm i npm to update       │
   │                                     │
   ╰─────────────────────────────────────╯

➜  ~ npm install -g generator-code
+ generator-code@1.1.50
added 385 packages in 29.613s

Step.2 実装

ジェネレーターで雛形を生成したらあとは楽しく実装していくだけです。
今回作成した「Simple Rails Navigator」はGithubに公開してあるので、興味がある人は見てみてください。

github.com

簡単に実装できそうな気がしてきませんか?

➜ yo code
? ==========================================================================
We're constantly looking for ways to make yo better!
May we anonymously report usage statistics to improve the tool over time?
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== Yes

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? Simple Rails Navigator
? What's the identifier of your extension? simple-rails-navigator
? What's the description of your extension? Simple plugin for navigating Ruby on Rails applications.
? Initialize a git repository? Yes
? Which package manager to use? npm
   create simple-rails-navigator/.vscode/extensions.json
   create simple-rails-navigator/.vscode/launch.json
   create simple-rails-navigator/.vscode/settings.json
   create simple-rails-navigator/.vscode/tasks.json
   create simple-rails-navigator/src/test/extension.test.ts
   create simple-rails-navigator/src/test/index.ts
   create simple-rails-navigator/.vscodeignore
   create simple-rails-navigator/.gitignore
   create simple-rails-navigator/README.md
   create simple-rails-navigator/CHANGELOG.md
   create simple-rails-navigator/vsc-extension-quickstart.md
   create simple-rails-navigator/tsconfig.json
   create simple-rails-navigator/src/extension.ts
   create simple-rails-navigator/package.json
   create simple-rails-navigator/tslint.json


I'm all done. Running npm install for you to install the required dependencies. If this fails, try running the command yourself.



> simple-rails-navigator@0.0.1 postinstall /Users/hisashi/Documents/code/repository/simple-rails-navigator
> node ./node_modules/vscode/bin/install

Detected VS Code engine version: ^1.34.0
Found minimal version that qualifies engine range: 1.34.0
Fetching vscode.d.ts from: https://raw.githubusercontent.com/Microsoft/vscode/a622c65b2c713c890fcf4fbf07cf34049d5fe758/src/vs/vscode.d.ts
vscode.d.ts successfully installed!

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN simple-rails-navigator@0.0.1 No repository field.
npm WARN simple-rails-navigator@0.0.1 No license field.

added 112 packages in 7.974s

Your extension simple-rails-navigator has been created!

To start editing with Visual Studio Code, use the following commands:

     cd simple-rails-navigator
     code .

Open vsc-extension-quickstart.md inside the new extension for further instructions
on how to modify, test and publish your extension.

For more information, also visit http://code.visualstudio.com and follow us @code.


➜ cd simple-rails-navigator

Step.3 公開

基本的にはこちらの手順に沿って進めます。
Azure DevOpsへの登録が必要になります。 (個人的にはこれが一番めんどくさかった😅)

code.visualstudio.com

$ npm install -g vsce

/usr/local/bin/vsce -> /usr/local/lib/node_modules/vsce/out/vsce

> fsevents@1.2.9 install /usr/local/lib/node_modules/vsce/node_modules/fsevents
> node install

node-pre-gyp WARN Using needle for node-pre-gyp https download
[fsevents] Success: "/usr/local/lib/node_modules/vsce/node_modules/fsevents/lib/binding/Release/node-v59-darwin-x64/fse.node" is installed via remote
+ vsce@1.61.0
added 323 packages in 14.744s

$ vsce login yaboojp
Personal Access Token for publisher 'yaboojp': ****************************************************

$ vsce publish
Executing prepublish script 'npm run vscode:prepublish'...

> simple-rails-navigator@0.0.1 vscode:prepublish /Users/hisashi/Documents/code/repository/simple-rails-navigator
> npm run compile

> simple-rails-navigator@0.0.1 compile /Users/hisashi/Documents/code/repository/simple-rails-navigator
> tsc -p ./

Publishing yaboojp.simple-rails-navigator@0.0.1...
 DONE  Published yaboojp.simple-rails-navigator@0.0.1
Your extension will live at https://marketplace.visualstudio.com/items?itemName=yaboojp.simple-rails-navigator (might take a few seconds for it to show up).

しばらくすると、公開されます🎉🎉🎉
この時は、数分ほどでメール通知されました

marketplace.visualstudio.com

小並感

自分は家具などDIYするのが好きなのですが、プログラミングの道具もDIYできるって、やっぱり楽しいですね。 エディタの使い勝手は、エンジニアにとって生産性に大きく影響するのでこだわっていきたいですね。 ちなみにショートカットの使い勝手がまだ手に馴染まないのでもうちょっと改善する予定です。