BASE開発チームブログ

Eコマースプラットフォーム「BASE」( https://thebase.in )の開発チームによるブログです。開発メンバー積極募集中! https://www.wantedly.com/companies/base/projects

「Sketchを用いたデザインシステム」を作ってチームでのデザイン作業を効率化した話

f:id:itsukichitose:20181221114935j:plain

この記事は、「BASE Advent Calendar 2018」の21日目の記事です。

devblog.thebase.in

はじめまして。2018年2月にBASE株式会社へデザイナーとして入社した小山です。 技術ブログを書くのは初めてなのですが、今回は私が2月に入社してから今日までネットショップ作成サービス「BASE」で「Sketchを用いたデザインシステム」を作ってチームでのデザイン作業を効率化した話について書こうと思います。

入社当時の状況

私が入社した当時、デザインチームは人数が増え始めた時期で、大きな課題が2つありました。

デザインデータの管理

私が入社する前まではプロダクトに対してデザインデータを作るデザイナーが少なく、各デザイナーが個人のローカルでファイル管理をしていました。その為、誰がどの画面のデザインデータを持っていて、最新のデータがどれなのかを毎回メンバーに確認をして、最新のデータを送ってもらわないとデータを見ることができないという状況でした。

ファイル管理については、Abstractの導入でファイル管理とバージョン管理の問題を解決しました。この内容については12月7日の「Abstractを用いたデザイン管理システムを導入して1年経ったお話」で北村が詳しく書いているので興味のある方はぜひ読んでみてください。

デザインパーツの共通化

もう一つはデザインパーツがデザイン的にもデータ的にも共通化されていなかったことです。

当時のBASEは新旧のデザインが入り混じっており、どのパーツが最新のものでどのレイアウトに合わせて作っていいかが入社したての私には難しいものでした。また、コーディング用のスタイルガイドはあったものの、Sketch用のデータはなかった為、毎回パーツを作るかそのパーツを含むファイルを探してコピーしてくる必要がありました。

そこでSketchとAbstractのLibrary機能を使ってSketchを用いたデザインシステム(通称:BASE UI kit)を作る「UI コンポーネントプロジェクト」が立ち上がりました。

UI コンポーネントプロジェクト始動

BASE UI kit 作成にあたり、デザインチーム内の管理画面に関わるデザイナーとフロントエンドエンジニアでプロジェクトを2月末に立ち上げ、毎週定例ミーティングをしながら必要なデザインパーツの洗い出しを行いUI コンポーネントとしての仕様決めを行っています。

f:id:itsukichitose:20181220161508p:plain

はじめに作ったBASE UI kit は1ファイルでPC、SPの全てのコンポーネントを管理しており、pageもグループごとに分けていました。 しかし、BASEの管理画面ではコンポーネントの数も多く、レイアウト側のデザインもファイル数が膨大になり、次第に管理が難しくなっていきました。

BASE UI kit 大改修

今まで1つのプロジェクトですべてのファイルを管理していましたが、BASE UI kit は専用プロジェクトに分離してAbstractのLink Library 機能を使って別のプロジェクトへリンクさせ、さらに1ファイルで管理していたコンポーネントはcommon、PC、SPの3ファイルで管理するように大改修しました。

f:id:itsukichitose:20181220162624p:plain

また、BASE UI kit を大改修する一環として、コンポーネントの命名規則もAtomic Designのレベル分けでグルーピングして参照しやすいものに変更しました。(例:1 = Atoms、 2 = Molecules )

f:id:itsukichitose:20181220160523p:plain

BASE UI kit を別プロジェクトにしたメリット

  • BASE UI kit の編集をしたブランチをマージする度に他の各ブランチで変更をPullする必要がなくなった
  • 右上に出てくる「Library Updates Available」をクリックするだけで最新のコンポーネントに更新できるようになった
  • 個人の作業ブランチでBASE UI kitの編集ができなくなったことで、そのブランチがマージされるまで他のブランチで最新のコンポーネントが使えないという問題が起きなくなった(運用でカバー仕切れなかった問題が解決)

ファイル分割したメリット

  • コンポーネントを呼び出す時に階層を減らすことができた
  • 1ファイル内のコンポーネント数が減ってコンポーネントを探すのが楽になった

その他にもBASE UI kit以外のデザインデータもグループ分けをしてプロジェクトを分けて管理することで、担当外プロジェクトのAbstractのシンクを切ることができます。(これによりAbstractとSketchが軽くなったような気がします。※個人の感想です。)

大改修をする時の注意点

一度作成したBASE UI kitを別プロジェクトへ移行してファイルを分割する際に注意するべきことがいくつかありました。

  • BASE UI kitとして登録しているシンボルは一度ファイルから消してしまうと呼び出し先でリンクが切れる(つまりカット&ペーストした時点でそのシンボルはリンク切れになる)
  • 既にライブラリ化済みのBASE UI kit(旧 BASE UI kit)を別ファイルの BASE UI kit(新 BASE UI kit)にリンクする場合、呼び出し先のデザインファイルでリンクが切れる前に新 BASE UI kit のコンポーネントへシンボルを置き換える作業が必要

大改修をする時に便利だったSketch プラグイン

Symbol Organizer

レイアウトされたシンボルを別ファイルのシンボルに一括で置き換えてくれるプラグインです。 同じ名前のシンボル同士を置き換えるのでシンボルの名前を変更するタイミングには注意してください。

Rename It

コンポーネントの階層を整理したい時にシンボルの名前を一括変更できるのでとても便利なプラグインです。 今回はUI kitの階層も合わせて整理したのでとてもお世話になりました。

大改修の手順

  1. Abstract上の全てのブランチをmasterへマージする
  2. Abstractで BASE UI kit 専用プロジェクトを作る
  3. 既にライブラリ化済みのBASE UI kit(旧 BASE UI kit) をBASE UI kit専用プロジェクトへコピーする
  4. BASE UI kit専用プロジェクトへコピーした新 BASE UI kitの中身を整理する(シンボル名は絶対変えない!)
  5. BASE UI kit専用プロジェクト以外のデザインプロジェクトに新 BASE UI kitを Link Libraryで紐づける
  6. デザインデータを開き、プラグインのSymbol Organizerを使って旧 BASE UI kit を新 BASE UI kitに置き換える(すべてのデザインデータで同じことをする)
  7. 新 BASE UI kitのシンボル名をプラグインのRename Itを使って整理する

f:id:itsukichitose:20181220161239p:plain

現在、BASE UI kitの運用はこれで落ち着いています。

シンボル名の変更はシンボルを置き換える前とかでなければいつでもできるので、足りないコンポーネントが増えて階層を整理したくなったタイミングや、Atomic Design的にここじゃないよねって思ったタイミングで適宜修正しています。

まとめ

UI コンポーネントをきちんと定義したことでデザイナー間やフロントエンドエンジニア間での仕様の認識を揃えることができ、また、BASE UI kitを作ったことで、シンボルを呼び出すだけで統一された UI コンポーネントを使うことができるのでデザイン作業が格段に早くなったと思います。 途中でUI kitの大改修もありましたが、毎日使うものだからこそストレスが少ないかたちで運用できる方が楽しく仕事ができるんじゃないかなと思います。(大変だったけどやってよかった!)

明日は ボドゲ部の部長 柳川です!

クラスタリングで時系列予測はできる? 〜Twitterの株価を予測してみた〜

f:id:itokashi:20181219140010j:plain

これは「BASE Advent Calendar 2018」の20日目の記事です。

devblog.thebase.in

こんにちは。Data Strategy Group の岡です。趣味は珍しいお酒を飲むこと、将来の夢はウイスキーの蒸留所を持つことです。

私は機械学習エンジニアとしてまだ2年目なのですが、「この予測手法、本やweb上でほとんど見かけないな。」とずっと気になっている手法があります。ざっくり説明すると、時系列データをクラスタリングして同クラスタ内の平均を予測に使う、というもので、私より20くらい歳上の分析屋に教えてもらいました。下記の論文が一番これに近いと思います。

Time Series Forecasting through Clustering - A Case Study

今回は実験も兼ねてその予測手法を再現しようと思います。

使用データ

そういえば、昨日は@Toshi_Day1のFintechについての記事があり、先日の河越の鶴岡さん観察日記ではジャック・ドーシー氏の話題があったので、この流れでTwitterの株価を予測してみましょう。

ナスダックのサイトから5年分の日次株価データをダウンロードしました。

ここから直近30日分を検証データとして切り出し、それ以前のデータを学習データとして使います。

#!/usr/bin/env python3

# モジュール
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error
from matplotlib import pylab as plt

# 読み込み & 日付でソート
twtr_stock = pd.read_csv('twtr_stock.csv').sort_values('date').reset_index(drop=True)

# 末尾30件を検証データとして分割
train_df = twtr_stock[0:-30].reset_index(drop=True)
test_df = twtr_stock[-30:].reset_index(drop=True)

先頭数件は下記のようになっています。

f:id:itokashi:20181219140223p:plain:w400

この中で close(終値) の値を予測していきます。

下のグラフはcloseの値を時系列でプロットした結果です。

plt.plot(twtr_stock['close'])

f:id:itokashi:20181219140910p:plain

前半大きく下り傾向があり、後半から徐々に上がり調子になっていますね。このデータから後に続く30日分のデータを予測することが今回の目標です。

予測の手順

この予測手法を直感的に説明してしまうと、

1. 時系列データを等間隔でスライドさせながらカーブを切り出して
2. それらを形状の似ているもの同士でグルーピングし
3. 同じグループ内で末尾の株価だけ取り出し、その平均を予測値として扱う

という流れになります。もう少し詳しく説明すると

  1. 時系列データから幅(N + K)のスライド窓の作成
  2. スライド窓ごとに平均0、分散1に標準化
  3. スライド窓の手前N個を PCA + k-means でクラスタリング
  4. 同クラスタ内で、N+K時点のデータの平均値を計算
  5. cos類似度で近傍クラスタを求め、そのクラスタの平均値を予測に使う

という手順になります。

1. 時系列データからスライド窓の作成

スライド窓とは、時系列データから一定の幅で1時点ずつスライドさせながら取り出して作るカーブのことです。下図がそのイメージになります。

f:id:itokashi:20181219141118p:plain

スライド窓、だとわかりにくいので今後はカーブと呼んでしまいます。 ここで作られる複数のカーブを似ているもの同士でまとめ、末尾の値の平均で予測を行います。

今回は直近20日のデータから5日先のデータを予測するとします。(20 + 5)日分のデータで切り出したいので、幅25で1日ごとにスライドさせたデータセットを作成します。

TRAIN_SIZE = 20
TARGET_FUTURE = 5
WINDOW_SIZE =  TRAIN_SIZE + TARGET_FUTURE

def split_window_data(array, window_size):
    length = array.shape[0]
    roop_num = length - window_size + 1
    window_data = np.stack([
        np.hstack(
            np.array(array[i: i+window_size].astype('float64')).reshape(window_size, 1)
        )
        for i in range(roop_num)
    ])
    return window_data

train_close = np.array(train_df['close'])
window_data = split_window_data(train_close, WINDOW_SIZE)

2. カーブごとに標準化

カーブごとに切り出したデータは、あるものは(20 30 35 25 30)、別のものは(60 80 70 80 100)だったりとスケールが異なります。この生の値でクラスタリングを行うと、スケールの大きいもの同士でまとまってしまいますが、今回は時系列の形状や特徴が似ているもの同士でまとめる必要があります。

そのためカーブごとに「標準化」という操作を行い、平均0、分散1のスケールを統一してしまいます。

# 標準化
sc = StandardScaler()
window_sc = np.stack([
    np.hstack(sc.fit_transform(vec.reshape(WINDOW_SIZE, 1)))
    for vec in window_data
])

標準化については下記が参考になります。

統計における標準化の意味と目的

3. PCA + k-means でクラスタリング

ここからの手順を下図を使って説明します。同じ幅のカーブを冒頭で作成しましたが、今度はこれらを形の似ているもの同士でまとめていきます。

f:id:itokashi:20181219141200p:plain

k-meansとはクラスタリングの一つで、数学的に似ている(数学的に距離が近い、と言ったりします)データを任意のグループに分類します。分類後のグループを「クラスタ」、各クラスタの重心に位置するデータを「代表ベクトル」と呼びます。代表ベクトルも後の計算で使うため、ここで算出しておきます。クラスタリングの後、分類時には外していた末尾のデータの平均値を計算します。末尾というのは、今回でいうと25日分のカーブのうち末尾5日分のデータのことです。

細かい話になるので上図には記載しなかったのですが、今回の予測ではPCAという処理を通してからk-meansを実行します。なぜPCAが必要か、については記事末尾の補足にて記載しております。

クラスタリングの実行

それでは、PCA + k-means クラスタリングを行い、クラスタごとの平均値を計算していきます。

def curve_clustering(array, curve_len):
    train = np.stack([vec[0:curve_len] for vec in array])
    test = np.stack([vec[curve_len:] for vec in array])
    test_len = test.shape[1]
    
    # PCA
    pca = PCA(n_components=curve_len)
    pca.fit(train)
    
    # 共分散行列から写像
    cov_mtrx = pca.get_covariance()
    train_pca = np.dot(train, cov_mtrx)
    
    # あとで元に戻すため、共分散行列の逆行列を求めておく
    cov_mtrx_inverse = np.linalg.inv(cov_mtrx)

    # k-meansクラスタリング
    curve_clst_size = int(np.sqrt(train_pca.shape[0]/2))
    cls = KMeans(n_clusters=curve_clst_size, random_state=123)
    curve_clst = cls.fit_predict(train_pca)
    
    # クラスタ毎に、カーブのリスト、平均、分散、代表ベクトルを求めておく
    curve_df = pd.DataFrame({
        'curve_clst': curve_clst
        , 'train': [vec for vec in train]
        , 'train_pca': [vec for vec in train_pca]
    })
    for i in range(test_len):
        curve_df['test{}'.format(i)] = [vec[i] for vec in test]
    print([col for col in curve_df.columns if 'test' in col])
    curve_clst_dict = {
        cluster : {
            'test_means': [df[col].mean() for col in df.columns if 'test' in col]
            , 'test_medians': [df[col].median() for col in df.columns if 'test' in col]
            , 'test_stds': [df[col].std() for col in df.columns if 'test' in col]
            , 'train_cluster_center': np.dot(cls.cluster_centers_[cluster].flatten(), cov_mtrx_inverse)
            , 'train_pca_cluster_center': cls.cluster_centers_[cluster].flatten()
            , 'train_vectors': [vec for vec in df['train']]
            , 'train_pca_vectors': [vec for vec in df['train_pca']]
        }
        for cluster, df in curve_df.groupby('curve_clst')
    }
    return (curve_clst_dict, cls, pca)

curve_clst_dict, cls, pca = curve_clustering(window_sc, TRAIN_SIZE)

ここではクラスタ数を

curve_clst_size = int(np.sqrt(train_pca.shape[0]/2))

と決めていますが、理由は記事末尾の補足に少しだけ記載しております。

クラスタリングの結果の可視化

それらしい分類になっているか、クラスタ毎のカーブをまとめてプロットしていきます。

# グラフを行列形式で表示する
clst_size = len(curve_clst_dict)
row_size = int(clst_size/3) if clst_size%3 == 0 else int(clst_size/3) + 1
x_axis = [x for x in range(WINDOW_SIZE - 1)]

fig = plt.figure(figsize=(19,30))
for key in curve_clst_dict:
    # カーブのリストを取り出す
    clst_info = curve_clst_dict[key]
    raw_curves = clst_info['train_vectors']
    
    # subplotと行列番号の設定
    row_n = int(key/3)
    col_n = key%3
    ax = plt.subplot2grid((row_size,3), (row_n, col_n))
    
    # カーブのプロット
    for raw_curve in raw_curves:
        ax.plot(raw_curve, color='steelblue', alpha=0.3)
        
    # 区間のプロット
    upper_95 = np.hstack([np.repeat(np.nan, TRAIN_SIZE-1), np.array(clst_info['test_means']) + 2*np.array(clst_info['test_stds'])])
    lower_95 = np.hstack([np.repeat(np.nan, TRAIN_SIZE-1), np.array(clst_info['test_means']) - 2*np.array(clst_info['test_stds'])])
    ax.fill_between(x_axis, upper_95, lower_95, facecolor='gray', alpha=0.4)
    
    # 平均値のプロット
    ax.plot(list(np.repeat(np.nan, TRAIN_SIZE-1)) + list(clst_info['test_means']), color='coral')
    ax.set_title('curve cluster{}'.format(key))
plt.show()

実行結果は下記となります。

f:id:itokashi:20181219141236p:plain

(本当はクラスタ23まであるのですが、長い上に結論も変わらないのでクラスタ8まで載せています。) 青色の線がカーブの集まりです。一つのクラスタを注視すると、変動の大きいカーブと小さいカーブが混在しつつも、大まかなトレンドは同一であると分かります。

オレンジの線がクラスタリングに使わなかったデータの平均値です。グレーは (平均 ± 2 * 標準偏差)の範囲を表しています。やや分散の大きいクラスタが多い印象です。

4. cos類似度で近傍クラスタを求める

ここからの手順を下図を使って説明します。今度は、未知データがどのクラスタに近いかをcos類似度を用いて探します。

f:id:itokashi:20181219141306p:plain

cos類似度とは、2つのベクトル(ここではカーブのこと)の類似度を測る指標です。上の図中では未知データのカーブと、各クラスタの代表ベクトル(青色の点線)を比較し類似度を計算しています。クラスタ1との類似度が高いと判定され、未知データのカーブの先に、クラスタ1の平均値(オレンジの点線)を当てはめて予測値としています。

それでは実際のデータで実行していきます。

最近傍のクラスタを見つける

下記のコードでは、2018年11月以前のデータを再びスライド窓に分割し、標準化しています。各クラスタの代表ベクトルとの類似度を求めるため、切り出す幅がTRAIN_SIZEになっている点が冒頭と異なります。

# TRAIN_SIZEで分割
window_data_train = split_window_data(train_close, TRAIN_SIZE)

# 標準化
sc = StandardScaler()
window_sc_train = sc.fit_transform(window_data_train.T).T

次に切り出したカーブのベクトルと、各クラスタの代表ベクトルの類似度を計算し、もっとも類似度が高いクラスタの番号を返す関数を作ります。

# cos類似度を計算する関数
def cos_sim(v1, v2):
    return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

# もっとも類似度の高いクラスタの番号を返す関数
def calculate_neighbor_clst(window_vec, curve_dict):
    cos_sim_vec = np.array([
       cos_sim(window_vec, curve_clst_dict[clst]['train_cluster_center'])
       for clst in curve_clst_dict
   ])
   return cos_sim_vec.argmax()

最近傍クラスタから平均値の取り出し

続いて、最近傍クラスタの平均値を取り出します。取り出したらスケールを戻して予測値とします。

# もっとも近いクラスタの平均値を取り出す
scaled_means = []
for vec in window_sc_train:
    neighbor_clst = calculate_neighbor_clst(vec, curve_clst_dict)
    scaled_mean = np.array(curve_clst_dict[neighbor_clst]['test_means'][TARGET_FUTURE - 1])
    scaled_means.append(scaled_mean)
    
# 取り出した平均値を元のスケールに戻す
scaled_means = np.array(scaled_means)
pred_means = sc.inverse_transform(scaled_means)

以上で予測値を算出するところまでは完了です。次の節で予測がどれくらい当たってたのかを検証していきます。

5. 予測結果の評価

予測結果と実際の値をプロットしてみましょう。

act = train_close[WINDOW_SIZE:]
pred = pred_means[0:-TARGET_FUTURE-1].flatten().tolist()
plt.plot(act, label='act')
plt.plot(pred, label='pred')
plt.legend()

f:id:itokashi:20181219141333p:plain

青色が実測値、オレンジが予測値です。すごく当たっているように見えますが、実は数日前のラグを取ったような予測になっています。これでは予測できたと言えません。

株価の「上昇する」か「下落する」かの予測

今回は直近20日のデータから5日先のデータを予測する、というテーマだったので、20日目の株価から5日先の株価が上がっているか、下がっているかの判定をしてみましょう。

# 20日目の株価と5日後の実際の株価を比べ、上がってるor下がってるか
act_updown = [
    train_close[i + WINDOW_SIZE] > train_close[i + TRAIN_SIZE] 
    for i in range(len(train_close) - WINDOW_SIZE)
]
# 20日目の株価と5日後の予測株価を比べ、上がってるor下がってるか
pred_updown = [
    pred[i] > train_close[i + TARGET_FUTURE]
    for i in range(len(train_close) - WINDOW_SIZE)
]

最初のact_updownには20日目と比較して5日後の実際の株価が上がったか、下がったかの真偽値を格納しています。次のpred_updownには5日後の予測の株価と比較した結果が入っています。この予想結果の正答率を計算していきます。

TF = (np.array(act_updown) == np.array(pred_updown))
sum(TF)/len(TF)

結果は51.8% でした!シンプルに株価の上下を予想してもほとんど当たってないですね笑

検証データと予測値の比較

今度は冒頭のほうで検証用に残していた、2018年11月以降の30日分の株価を予測してみましょう。 30日分の予測なので、ひとつずつ予測値をカーブに追加していきながら、最終的には20個の予測値を使って30日先の株価を予測することになります。

# スライド窓から予測値を算出する関数
def predict_(window_vec, target_future):
    test_sc = StandardScaler()
    vec_sc = test_sc.fit_transform(window_vec.reshape(TRAIN_SIZE, 1)).T
    neighbor_clst = calculate_neighbor_clst(vec_sc, curve_clst_dict)
    scaled_mean = np.array(curve_clst_dict[neighbor_clst]['test_means'][target_future]).reshape(-1, 1)
    return test_sc.inverse_transform(scaled_mean.reshape(-1,1))

# 予測値を追加しながら、次点の値を予測
def predict_forward(window_vec, n_forward, target_future):
    predictions = []
    vec_len = window_vec.shape[0]
    train_vec = window_vec.copy()
    for _ in range(n_forward):
        pred = predict_(train_vec, target_future).flatten()
        predictions.append(pred[0])
        train_vec = np.append(train_vec[-vec_len + 1:], pred)
    return predictions 

# テストデータの長さ分の予測値を算出
test_len = len(test_df)
last_window = window_data_train[-1]
predictions = predict_forward(last_window, test_len, TARGET_FUTURE-1)

この予測を重ねた結果と、30日分の実測値をプロットした結果が下記となります。

plt.plot(test_df['close'], label='act')
plt.plot(predictions, label='pred')
plt.legend()

f:id:itokashi:20181219141418p:plain

青色が実測値、オレンジの線が予測値です。実測値より予測値はなだらかな線になっています。やはり予測値を平均で代用しているので、変動の大きい時系列を捉えるのは不得意そうですね。

まとめ

株価を使う時点で予想してはいましたが、さっぱりうまくいきませんでした。この手法を教えてくれた人は「これで為替レートの予測がうまくいった!」と言っていたので、万に一つの可能性があって株の予想ができたら、、、と思いましたが現実は厳しいですね。

これの使いどころとしては、クラスタ毎にまとめた平均値、つまりノイズを省いたトレンドを検出できるので、下記のケースなら有効かもしれません。

  • 局面変化の検知
  • 長期ではなく短期の予測
  • 件数の少ない時系列データに対し、傾向の似ている別の時系列データから予測値を算出

以上が、私の記憶と数少ない資料を頼りに再現した予想手法でした。これを読まれたどなたかが、「その分析知ってるけど使い方違うよ!」などご指摘くだされば何よりです。

明日はデザイナーの小山さんです!お楽しみに!

補足

PCAとk-meansの組み合わせ

PCAとk-meansを組み合わせることでより綺麗にクラスタリングできるという、イケてる分析屋の間ではよく使われるテクニックだそうです。私は最近まで知りませんでした。

PCAとk-meansの関係については、下記の資料が参考になるかと思います。

K-means Clustering via Principal Component Analysis

クラスタ数について

下記はk-meansを実行する部分のコードです。

def curve_clustering(array, curve_len):
    ~~~
    # k-meansクラスタリング
    curve_clst_size = int(np.sqrt(train_pca.shape[0]/2))
    ~~~

ここでしれっとクラスタ数を (サンプルサイズ/2)の平方根で設定していますが、適切なクラスタ数が不明なときはこれで良かろう、という先人の知恵なんだそうです。以下のやりとりから文献にたどり着いたので、これから理解を深めていこうと思います。

How can we choose a "good" K for K-means clustering?

国内2019年のFinTechトレンド予想(融資・資金調達・ファイナンス領域編)

f:id:Toshi_Day1:20181218222856j:plain

この記事は「BASE Advent Calendar 2018」19日目の記事です。

devblog.thebase.in

はじめに

こんにちは。BASEの100%子会社であるBASE BANKの矢部(@Toshi_Day1)です。BASEの金融事業という立ち位置で先日リリースした、リスクなく即時に資金調達ができる金融サービス「YELL BANK(エールバンク)」を中心に複数の金融事業を立ち上げ責任者をしています。

今回、社をあげてのアドベントカレンダーということで、テックな内容ではありませんがFinTechについて少し書き連ねていきたいと思います。

ここ1年弱、消費者と事業者の混ざり合ったようなユーザー層への金融サービスに思いをめぐらせつつ、決済をコアとした事業構造のBASEに身を置き、またFinTech企業や金融機関の方々とお話させていただく中で学んできたことをもとに、来年のファイナンス領域におけるFinTechトレンドを予想したいと思います。

より多くの人にとって金融サービスが普遍的なものになり、個々人が想像力や可能性を広げることができる世の中を作るため、日々事業に向き合っています。このブログ記事を読んで、興味が湧いた方がいらっしゃれば、ぜひ気軽にご連絡ください。ディスカッションやブレストなども誘っていただければ嬉しいです。

※Crypto、Blockchain、STOなどと、面白いと感じることも多いのですが、まだ勉強中のため割愛します。
※この記事の内容はあくまで個人的な意見・見解を示したものです。

1. 個人やスモールビジネスが持つ資産を流動化し、換金性をあげるサービスが登場

個人の資産流動化の流れ

資産の現金化という意味では2017年の「CASH」を筆頭に、それまで行われていた「メルカリ」で物を売って現金に変える、という行為を簡略化するような形で即現金化サービスがでてきました。また2018年には給与の前払いサービスなどへ多くの事業者が参入していきました。このような消費者向けの少額資金ニーズが顕在化したことについては、個人的には仮想通貨の価格高騰により我々が認知する割引現在価値が大きく変化したからであると、こちらのnoteで考えてみたこともあります。

これらのビジネスに共通しているのは、個人のBSを想定したときに流動資産科目をキャッシュに変えるような機能であることと言えます。基本的には、引き続き2019年もこのトレンドが続いていくと考えていて、ブランド品や(パート・アルバイトを含めた)給与以外にも身の回りにある高単価な物品、あるいは無形材をバーティカルにカテゴリー化して換金できるような仕組みに挑戦する企業が出てくるのではないのでしょうか。タイミングはもう少し先だと思いますが、権利や契約関係、データなどのBSに反映されにくいものもブロックチェーン上で資産管理され、換金性が高まるような変化が起こるでしょう。

オンラインファクタリングサービスの登場

一方で、類似の仕組みを事業者に当てはめたサービスが2018年の下半期からぽつぽつとリリースされ始めています。もっとも多かったケースとしては、事業者が保有する売掛債権を譲渡することで資金調達をする"ファクタリング"のビジネスをオンライン化していくケースです。

直近の事例としては、下記が挙げられます。

  • クラウドワークスが提供する「フィークル(feecle)」
  • GMOクリエイターズネットワークが提供する「FREENANCE(フリーナンス)」
  • マネーフォワード100%子会社であるMF KESSAIが提供する「MF Kessai」
  • 三菱東京UFJグループ運営のMUFJ Digital Accelerator出身のOLTAが提供する「OLTA(オルタ)」

また「YELL BANK」もスキームとしてはファクタリングを応用させたものになっています。

フリーランスや中小企業に特化しつつ、各社自社サービスの強みを生かす形での参入パターンと、全くの新規での参入との両パターンが存在していますが、ファクタリングは産業としての歴史は比較的浅いこともあり、ほとんど認知されていません。おそらく経営者や財務関係の方でもファクタリングについての知見を持ち合わせている方はごく少数なのではないでしょうか。

アメリカでは既に「Fundbox」や「BlueVine」など売掛債権を活用したファクタリングやABL(動産担保融資)等のファイナンススキームでユニコーンとなる企業も登場し始めています。今後日本のマーケットにおいてもFinTechスタートアップによって、本格的にファクタリングが流行っていくのか、とても楽しみです。

流動資産を活用したファイナンス

売掛債権を活用するファクタリングのように、流動資産(不動産に対して動産という)を活用したファイナンススキームは、ファクタリングやABLなど、アメリカでの市場形成を参考に日本では2000年代から中小企業庁を中心に推進しようとする動きが活発でした。しかし、残念ながら未だに一般的に利用されているとは言い難い現状があります。

中小企業のバランスシートをみると、動産の資産価値ベースで売掛債権で87兆、棚卸資産46兆、その他流動資産47兆円が積み上がっています。借り入れを見てみると金融機関、その他から合わせて約240兆円あり、この規模が事業性ファイナンスにおけるTAMという考えることも可能かと思います。加えて小規模事業者のマーケット、フリーランスなど統計上捉えられない市場も加味すると、マーケットのポテンシャルの凄さに気付くかと思います。細かいセグメントでの分析は別の機会にしようと思いますが、チャレンジに値するマーケットが存在することは明らかです。

f:id:Toshi_Day1:20181218222608p:plain

(中小企業白書 第2部 中小企業の稼ぐ力「第5章:中小企業の成長を支える金融」より)

事業者向けのFinTechサービスとしては、貸金業との対比における参入障壁の低さからファクタリングが先行していきますが、商流ファイナンスの流れに乗っかって在庫、POなど、売掛債権よりも更にサプライチェーンを遡った部分でのファイナンス手法を提供するインターネット企業が後々生まれてくるでしょう。一例としては、事業者の物流管理がクラウドサービスで行われるようになった際に、生産過程での商材を担保に運転資金融資をするなど、ある意味で商社金融によって担われていたようなサービスを、個人やスモールビジネスでも受けられるような時代がくるでしょう。

f:id:Toshi_Day1:20181219011347p:plain (日本銀行金融高度化センター ITを活用した金融の高度化に関するワークショップ「データを活用した金融の高度化」)

競争環境は厳しめ

このように事業者の資金調達課題、特に運転資金の課題に対しての切り口は現時点でも複数考えることができます。また、既存の市場環境をみてみると無数の中堅、小規模な事業者が激しく戦っている状態で、まだ誰が勝ち切っているとは言い難いのが現状です。

更に言うと、トランザクションレンディングの切り口でAPI型、マーケットプレイス型、会計型、決済型、それぞれの参入が続いていく中で、ファクタリングや商流ファイナンス単体でどこまでスケーラビリティをとれるかは難しいところかもしれません。 新規参入の際に既存プレイヤーとの差別化として、外部サービスとのAPI連携や銀行口座情報の取得など、多くのデータポイントを作り出していくことを戦術的に取りれる事例が増えていきますが、マーケットのマジョリティーを占めるであろう中小製造業社や卸売業者がデータソースとなるようなクラウドサービスを導入しているかといえば、現状まだまだな状況です。

米国事例との単純比較で言えば、独立的にファイナンスサービスを提供するテクノロジー企業(代表的な例で言えば「Kabbage」)の立ち上がりが、決済やマーケットプレイス企業である「PayPal」、「eBay」と同時期であったことにより、複数のユニコーン企業を産む結果に繋がっている一方で、日本ではマーケットプレイスが先行したために融資・ファクタリングのテックプレイヤーが急拡大する余地が薄くなってしまっています。

このような市場環境での新規参入に取りうる事業戦略は、他産業の事例や金融業界の歴史を振り返ると6つほどに類型できると私自身整理していますが、これについてはまた別の機会に書いてみたいと思います。

何にしても事業性融資/中小企業の資金調達という巨大マーケットで戦うプレイヤーが増えることは、資金需要者からするとファイナンスオプションの増加につながり、メリットは計り知れません。

CACの問題や、ファクタリングなどの参入障壁の低さ、債権回収のパワー関係、資本政策の難しさなど、あげようと思えばいくらでもこの市場でビジネスを生み出していく上での壁を思いつくことができます。しかし、我々はスタートアップとして課題があれば解決していかなければいけない立場にあります。BASEもBASE BANKを通してこの市場に参入しているわけなので、新規サービス、産業の発展、そして顧客の課題解決に知恵を働かせていきたいものです。

2. 既存のSaaSサービスから派生した金融サービスへの展開

クラウド会計サービスの貸金業進出は言わずもがなとして、ピンポイントなところで言うと、SmartHRが金融サービスを展開していくでしょう。代表の宮田さんご本人のブログにも、

おそらく FinTechっぽい事業になる予定です

とありますし、確度は高いのではないでしょうか。

事業展開としては、給与前払い系、あるいは会社が従業員へ貸付する場合は貸金業登録を必要としないので、従業員への貸付や債権管理をサポートする機能、給与から天引き回収などの事業展開かなと思っています。自社で「SmartHR Pay」のような機能を用意して、給与振込自体を自社サービス内に留める、といったことも妄想はできますが、現在のPayment市場では難しいのではないでしょうか。そうすると選択肢としては、やはり貸付周りや、積立や資産運用サービスを「Folio」など他社とのアライアンスで提供していくような新規事業を作っていくのではないかと考えます。

決済+ECで事業を作りあげてきたBASEは、個人的にはSquareと重なって見えることが多いのですが、彼らがPayroll領域へとプロダクトの幅を広げていることもありSmartHRとも若干被り始めているように映り、とても興味深いところです。

日本ではまだSaaS自体が立ち上がっているフェーズということもあり、金融サービスへと展開していくケースは少ないかもしれませんが、産業ごとのバーティカルSaaSの特性を生かして個々の金融課題を解決していく様子は想像するだけでワクワクするものです。

物流や医療機関など、各カテゴリーごとに立ち上がったSaaSがホワイトラベル化する銀行とアライアンスを組みながら金融事業を展開していく、というストーリーは多くの方が想像するところだと思いますし、これが実現される世の中であってほしいです。

3. 株式投資型クラウドファンディングの立ち上がりの兆し

事業としてエクイティクラウドファンディングが立ち上がりはじめたのは2012年頃に米国でJOBS Actが成立してからだと記憶していますが、欧米に引き続き日本でもようやく参入の兆しがみえはじめています。
(参考記事:JOBS ACTによる米国証券法等の改正)

またスタートアップ界隈でもエンジェル投資が一般化され始めており、この流れに乗っかりたい投資家は潜在的に多いのではないかと考えています。かくいう私も個人的な趣向としてFinTechスタートアップを中心に、人間の可能性や想像力を拡大していくような企業へ50〜300万ほどの少額投資したいと思うことが多々あります。(ほとんど自己研鑽/事業立ち上げが好きだからという理由ですが。)

もちろん調達側の企業とのマッチングが本質であるので、一般化されるのは難しいかもしれません。しかしながら特定のビジネスレイヤーに対しての拡大は非常に早いのではないかと、半分願望ですが考えています。

4. 比較レイヤーの新規参入

顧客へダイレクトにサービス提供する事業者が増えるにともなって、インターネットのことわり通り、顧客側には比較検討するニーズが発生します。しかし、融資領域では商品特性がコモディティ化されており、金融機関ごとに差別化することが難しい現状があります。ここに一歩踏み込んだ形で比較サービスを展開するプレイヤーが出てくるでしょう。

例えば融資領域ですと、現状検索結果はアフィリエイトサイトで溢れています。一方で融資の媒介(送客・広告ではなく、融資契約を仲介すること)は、貸金業法で登録済みの事業者しかできないことになっており、気軽には参入することができません。

新規の比較レイヤーは正式に貸金業登録を完了させた上で、自社サービス上で財務分析・信用調査・金融機関による審査と条件交渉などを複数同時平行で進めることができるようなプラットフォームを目指して生まれてくると思います。このように既存金融機関が融資プロセスの中でコストをかけて行なっていた一部の過程をアウトソースするという切り口での参入は、他産業ではよく見かける事例でもありますが、金融はブラックボックス化されやすい特性からか未だスタートアップが入り込めていません。

米国事例で言えば、「Fundera」がこれに近いかもしれません。Crunchbaseを見ると2015年のシリーズB以降の資金調達がないですが、金融機関の広告、オペレーションコストを考えるとマーケットは十分と言えそうです。

5. 金融リスク管理系SaaSサービスの発展

比較レイヤーの論理と同じで、プレイヤーが増えればその分事業者の課題の総量も増えていきます。 特に新規参入のスタートアップにとって、与信、債権管理、回収、ALMマネジメント、信用リスクモデルの構築、ポートフォリオ管理、統合的リスク管理、担保や保証などの各種経営管理要件を自前で準備して運用していくことは大きな負担であり、そもそも人材マーケットにこれらの経験がある人はほとんどいません。

金融におけるリスクは信用リスク、オペレーショナルリスク、市場リスクなどに分けて考えられますが、まずは信用リスクの部分を補完するサービスを展開していく事業者がでてくるかと思います。そもそも金融におけるリスク、というのは情報の非対称性と不確実性の両性質を持っています。前者についてはインターネット的な解決手段を提示しやすく、後者については機械学習を筆頭にAIがコアバリューとなったソリューション提供が増えていくでしょう。

先行する米国では、すでにいくつかのサービスが立ち上がっています。債権管理であれば「Yaypay」、債権回収領域の「TrueAccord」、Banking as a Serviceの「Plaid」などがこれにあたります。

また信用調査に関して言うと、日本では伝統的な企業信用情報サービスはTDB(帝国データバンク)やTSR(東京商工リサーチ)による独占状態です。この領域は市場規模も1000億以上あり、かつ課題が多く、上記のような切り口でのスタートアップによる参入もあるはずです。シードステージのスタートアップでは「アラームボックス」などが存在していますが、まだまだ参入企業数が少ない印象です。2019年は盛り上がりを見せることを期待しています。

おしまい

注目しているスタートアップや各市場への参入の戦略など、他にも考察したいことは多いのですが、今回のブログでは一旦ここまでとします。色々と考えを書き連ねてきましたが、まとめたり分析したり考えを書くだけならば、私個人としてはノーバリューだと思っています。

私はアナリストでもコンサルタントでもなく、あくまでも起業家/事業家としてしっかりと新しい時代の金融サービスを創り上げ、より良い未来に貢献いきたいと思います。 Be Hopeful!!

お茶・ランチも常時募集してますので、こちらのリンクTwitterからでも気軽にご連絡ください!

明日はBASE BANKチームを機械学習エンジニアとして支えてくれている岡さんです!

Data Strategy GroupのAPI開発の挫折とその後

f:id:beerbierbear:20181217150247j:plain
BASE Advent Calendar 2018 18日目

BASE Advent Calendar 2018」の18日目の記事です。

devblog.thebase.in

お久しぶりです。BASEビール部部長(& Data Strategy Group)の氏原です。 アドベントカレンダーの季節が来て今年も終わりかと実感しているところです。 年末年始、どこにビール飲みに行くか今から悩んでます。

参考:大晦日もお正月もクラフトビール! 年末年始営業日カレンダー (2018 – 2019) – クラフトビールのお店の予定がわかる

さて、今回はアドベントカレンダーということで機械学習そのもののお話は他のData Strategy Groupのメンバーが書いてくれるでしょうから、 私はちょっとインフラ寄りのお話をさせていただこうかと思います。

以前類似商品APIのお話を書きましたが、 それ以降もData Strategy Groupでは関連ワードAPIとか売上予測APIとかいろんなAPIを作ってきました。 APIはAWS API Gatewayを利用してBASE本体に提供していますが、ある日AWSさんからAPIの構成について貴重なアドバイスをいただきました。 どういう内容でご指摘いただいたか、そしてそれにどう対応したか、対応して良かったことを書こうと思います。

Data Strategy GroupのAWSのインフラ構成

Data Strategy Groupは自分たち専用のAWSアカウントを持っていまして、その内部構成は自由に決められます。 メンバーが各々で開発を好きに進められるように機能ごとにVPCを分けて、自分が担当しているVPC内については好きに作っていいよという構成にしました。

f:id:beerbierbear:20181217150430p:plain:w500
AWS環境のVPC構成

VPC間で通信が必要な場合はVPC Peeringで繋ぎます。

f:id:beerbierbear:20181217150640p:plain:w400
VPC Peering

こうして各機能を疎結合にして、各メンバーが割と好きに開発を進められるようにしてありました。 そして問題のAPI Gatewayですが、これは各VPCから生やすようにしていました。

f:id:beerbierbear:20181217150816p:plain:w400
API Gateway

API Gateway自体はVPCとは繋がっていませんので、API Gatewayに来たリクエストをVPC内のサーバにルーティングするにはVPC Linkを設定する必要がありました。

エラー

ある日新しく作ったVPCにAPI Gatewayを生やすためにVPC Linkを作成しようとするとエラーが起こりました。 どうやらAWSの制限に引っかかったようです。 1アカウント、1リージョンあたりのVPC Linkの数は初期状態では5つに制限されています。

f:id:beerbierbear:20181217151027p:plain:w600
VPC Linkの制限

まだ慌てるような時間ではありません。AWSはそもそも初期状態では結構厳し目に制限かかってますからね。 ちゃんと使い出したらすぐに制限に引っかかって緩和申請出すというのはAWS使ってる方なら経験があるでしょう。 引き上げ可能かに「はい」と書かれているわけですからいつも通り制限緩和を申請しましょう。

f:id:beerbierbear:20181217151149p:plain:w500
どれだよぅ

そのままドンピシャな選択肢が見つかりません。 制限タイプの「API Gateway」とか「API Gateway管理」とか「VPC」とかの中を探したんですが見つかりません。 だんだん嫌な予感がしてきました。

仕方ないので技術サポートに問い合わせました。 結果、

「VPC Linkの項目はない。適当な項目選んで申請理由の説明のところにVPC Link数増やして欲しい旨記述してほしい」

とのこと。

…今まで誰も申請してないんだろうか。 気を取り直してそれで申請しました。その結果がまさかのNG。

「VPC Linkの数は増やすことをあまり想定していない。利用方法について聞かせて欲しい。」

インフラ構成変更

その後AWSの担当の方とのミーティングなどを経て、VPC Linkの数を増やすことは諦めて構成を変更することにしました。

f:id:beerbierbear:20181217151307p:plain:w600
API VPC爆誕

まずAPI提供用のVPCを一つ用意してAPI GatewayとVPC Linkで繋ぎました。 VPC Linkの一方の端はNLBです。で、このNLBから直接各VPCのALBに繋ぎたかったんですが、NLBにそういった設定はできません。なので一旦HAProxyにリクエストを飛ばして、ここで各VPCのALBへ振り分けるようにしました。各VPCのALBはVPC内のマイクロサービスへリクエストをルーティングします。

このような構成にすることでVPC Linkの数は1つあればよいようになりました。 さて、こうしてみると実はこの構成のほうが良かったんじゃないかと思います。 その理由としては

  1. キャッシュをAPI VPC内でやるようにすれば各機能でキャッシュとか考えなくてもAPI VPCで統一的に扱える。
  2. APIの入れ替え、A/Bテスト、障害時の対応などがHAProxyの設定変更で行える。
  3. 複数の機能を統合したAPIの提供をAPI VPC経由で行える。例えば画像系とテキスト系の類似度を両方使った類似商品APIなど。

APIを外部に提供するときに考えなくてはいけないことと、提供する機能を綺麗に分離することができたように思います。

まとめ

実運用されていてかつ試行錯誤させてくれる環境って貴重ですよね。いろんな知見が溜まっていく。実は上で説明した今の構成もさらに改善中で来年頭くらいにはまたちょっと変わっちゃってると思います。Data Strategy Groupの環境はまだまだ手探りで構築中ですが、それがまた楽しいでところでもあります。

ではこのへんで。 明日は id:Toshi_Day1 さんがファイナンス系でなにか書いてくれます。 お楽しみに。

グロースハックとディレクションとAndroid開発を経験した1年の振り返り

f:id:metal_president:20181217102801j:plain
BASE Advent Calendar 2018 17日目

この記事は「BASE Advent Calendar 2018」17日目の記事です。

devblog.thebase.in

はじめに

はじめまして。Native Application Group の木下です。主にAndroidアプリの開発を担当しています。 今年はアプリの開発に留まらず、プロジェクトのディレクションやグロースハックといった分野にも少し手を出してきましたので、1年を振り返りながら知見などを共有できればと思います。

最近のBASEの組織

最近のBASEでは「グループ」と「プロジェクト」の二つの組織体系で業務を行なっています。

「グループ」はデザイン・バックエンド・アプリ・SREなど、スキルや分野によって分類された組織で、私の所属する Native Application Group では、日常的にスマホアプリに関する次のような業務を行なっています。

  • 他部署からの要望にもとづく機能開発
  • 不具合やOSのアップデート対応
  • 新しい技術の組み込みや技術的負債の解消
  • 開発環境の基盤づくり
  • 新バージョンのリリース

これに対して「プロジェクト」はクォータごとに目的・目標(KGI)と期限が設定され、目標を達成すべく、各「グループ」から選抜されたメンバーで構成される組織になります。同時に複数のプロジェクトに所属するケースもあります。

プロジェクトのイメージ

プロジェクト名 目的 KGI プロジェクト
マネージャ
ディレクター デザイン バックエンド アプリ
プロジェクトA
プロジェクトB
プロジェクトC

はじめてのグロースハック

2018年の各クォータで所属した「プロジェクト」では、ディレクターかAndroidアプリエンジニア、もしくは両方のポジションに就き、特に1Qと2Qでは数値改善を目的としたグロースハック系のプロジェクトを担当しました。 グロースハックはほぼ未経験でしたので、まずは基礎知識を身につけなければと思い関連する書籍を読みました。特に参考になった2つを紹介します。

「いちばんやさしいグロースハックの教本 人気講師が教える急成長マーケティング戦略」

タイトルのとおり、初心者向けでとてもわかり易い内容でした。

「データ・ドリブン・マーケティング―――最低限知っておくべき15の指標」

紹介文の “ジェフ・ベゾスが愛読! 世界最強のマーケティング企業 アマゾン社員の教科書” に惹かれて読んでみました。プロダクトの意思決定で重要となる15の指標についての説明が、実例を交えながら書かれています。

まずはデータ収集から

グロースハックでは仮説・検証を繰り返し行いますが、その前段として現状を捕らえるためにデータ集めを行いました。

ショッピングアプリ「BASE」では、Firebase Analytics でイベントログを収集して、BigQuery にインポートし、Redash を使ってモニタリングしています。社内には50インチ以上の大型ディスプレイが何枚もあり、さまざまな指標やサービスの状況をモニタリングできるようになっています。

Redashの導入・布教については過去の記事をご覧ください。

devblog.thebase.in

はじめの頃は個人的にRedashではなくData Studio(今のデータポータル)を利用していましたが、グラフのレイアウト調整などが地味に面倒だったり、社内的にRedashの布教が進んでいたこともあり、途中でRedashに切り替えました。

Data Studio はダッシュボードを Google Driveのファイルとして扱え、アカウントや権限の管理が簡単だったりと良い点もありますが、日々新たなダッシュボードを追加していくとなると、多機能すぎてちょっと扱いづらいかなと思いました。

KGIからKPI、施策から効果測定まで

前述のとおりプロジェクトごとにKGI(Key Goal Indicator : 重要目標達成指標)が設定されます。KGIには最終的なゴールとなる指標が設定されるため、直接KGIを上げようとしても具体的な施策を立てるのが困難でした。このため、まずKGIを構成するいくつかのKPI(Key Performance Indicator : 重要業績評価指標)に分解して、このKPIをあげるための施策(ユーザー体験や機能の改善)を検討して実施(開発・リリース)する流れとしました。

ダイエットに例えるとこんな感じです。

  • KGI : (3ヶ月で)体重マイナス10kg
    • KPI(1) : 1日の摂取カロリーを300kcal削減する
      • 施策(1-1) : 夕食のご飯の量を50%削減する
      • 施策(1-2) : 3時のおやつを廃止する
    • KPI(2) : 1日の消費カロリーを300kcal増加させる
      • 施策(2-1) : 通勤電車では椅子に座らない
      • 施策(2-2) : 毎日5kmジョギングする

施策を実施したら効果測定を行います。リリースと同時にRedashを使ってモニタリングを開始し、KPIとKGIの動向を追います。KPIが改善されてもKGIに影響しない(仮説が外れる)こともあるため必ず両方を見ます。

改善されない場合や不十分な場合は、施策の内容を改善したり、施策をキャンセルすることもあります。ダイエットの例であれば、1ヶ月経っても体重が減らない場合は「ジョギングの距離を10kmに変更する」といった感じになります。

プロジェクトのディレクション

続いてプロジェクトのディレクションについてです。 BASEにおけるディレクターの役割については次のように考えています。

メンバーの活動を最適化して、プロジェクトが成功する確率をできる限り高めること。

プロジェクトを成功させるのはあくまで各メンバーであって、環境づくりや交通整理をするのがディレクターの役割であると考えています。ディレクターが重要な意思決定を下したり、仕様を決めたりするのは適切でないと思います。特にBASEは数字やデータに基づきシンプルに意思決定する文化のため、この考えは適していると思います。

また、ディレクター要因でプロジェクトの進行やメンバーの活動を鈍らせることは絶対にあってはいけないと考えます。1Q=3ヶ月、という短い期間で目標を達成しなければいけないため、特にこの点は特に留意しました。

ディレクターとしてやったこと

ディレクターに就いた直後は、何を期待されているのか?何をやれば良いのか?よく分からず右往左往した時期もありましたが、いろいろと試行錯誤した結果、主に次のようなことをすることとしました。

プロジェクト開始前

  • プロダクトマネージャーからプロジェクトの目的についての背景や詳細、マストでやりたい施策についてのヒアリング
  • 施策ごとに作成するドキュメントのテンプレート作成
  • 主要KPIの設定

プロジェクトの立ち上げ

  • キックオフミーティング
    • プロジェクトの目的やKGI、プロダクトマネージャーの意向などを共有
    • 定例ミーティングの調整
  • メンバー全員で施策ブレスト
    • 施策案出し
    • 施策の規模感や実現性の確認
    • 優先順位付け
  • マスタスケジュール作成
    • BASEでは Asana を活用しています

プロジェクト中

  • ミーティングのファシリテーション
    • Dailyでスタンドアップミーティング
    • Weeklyでミーティング(進捗確認、施策の結果共有、追加施策の検討)
  • 進捗把握とフォロー
  • リリースや効果測定のサポート

プロジェクト終了後

  • KPTでの振り返り

納得感と温度感

プロジェクトはKGI/PKIを達成するために活動しますが、各施策についてメンバーが納得感を持つことが重要だと考えます。

なぜそれをやるのか?プロジェクトに貢献できる施策なのか?ユーザー体験は改善されるのか・悪化しないか?...などプロジェクトの目標は共通であったとしてもメンバーごとの視点は異なり、納得感が高ければ施策の質も高めることができ、より良い結果が期待できると思います。 この納得感を高めるため、ブレストで作成した施策案リストに次のような「いいね」欄を設けて、温度感を可視化することとしました。

ミーティング中に「この施策どう思いますか?」と聞いても、声の大きい人の意見で染まったり、分からないことがあっても今更聞きづらい空気ができてしまっている場合もあり、メンバーそれぞれの納得感を確認し高めることは難しいため、カジュアルに可視化できる仕組みとしてトライしてみました。

f:id:metal_president:20181217103531p:plain

この「いいね」で多数決することはしませんが、優先順位に多少は影響しました。中にはプロダクトマネージャーの挙げた施策でも、「いいね」数が少なく優先順位を下げることもありました。温度感の高い施策について重点的に議論を進めることができました。逆に効果が期待できそうなのに温度感の低い施策については、施策内容を掘り下げて納得感を高めにいくことができました。

測定不能

様々な施策を実施する中で、1つ大きくハマったことがありましたので紹介しておきます。

施策をリリースしたら効果測定するようにしていますが、測定できなくなる事態が度々発生しました。主な原因は2つあります。

  1. 同じKPIに影響する複数の施策を同時期にリリースしてしまった
  2. セールなどのキャンペーンやTVCMの放送と施策のリリース時期が重なった

一つ目の施策が重なる問題については、KPIが改善されてもどの施策が効いているのか特定するのが困難になります。プロジェクト内でやってしまうケースや他のプロジェクトの施策と重なるケースがあり、前者の場合はプロジェクト内で注意しながらリリースのスケジュールを策定していれば回避できるのですが、後者の場合は他プロジェクトとリリース時期を地道に調整することで回避するしかないかなと思います。

二つ目のキャンペーン等については、 KPI/KGIへの影響が非常に大きく期間も長いため、完全にお手上げとなります。社内にアンテナを張って、キャンペーンが始まる時期を早めに察知し、リリースや開発のスケジュールを調整することで防いでいくしかないかなと思っています。

Androidアプリの開発について

最後になりますが、この1年でAndroidアプリ開発もいろいろ変化しました。

CI環境の移行(Bitrise + Danger)

昨年までは Jenkins(社内のMac miniで稼働)でビルドしていましたが、今年に入ってから Bitriseへ移行しました。Bitrise を選んだのは Circle CI のようなメモリ制限(4GB)がないのと、モダンなUIでカジュアルに利用できそうで周囲でも流行っていた、という理由でした。最初の立ち上げは他のメンバーが対応していましたが、比較的スムーズに移行できたと思います。

そして、せっかくBitriseを導入したのだからもっと活用しようと思い、Dangerを導入しました。Danger を使うと、Pull Request などをトリガーとして、PRの内容をチェックしてワーニングやエラーなどのコメントを自動で表示することができます。エラーの場合はマージ不可とすることも可能です。

実際の Dangerfile は次のような内容となっていて、PR作成・更新時にチェックが走ります。

is_release = github.pr_labels.include? "[RELEASE]"

#  WIP
warn("PR is classed as Work in Progress") if github.pr_title.include? "WIP"

# PRが大きすぎる(リリース用のPRは除外)
warn("Big PR, over 1000 lines") if git.lines_of_code > 1000 && !is_release

# 誰もアサインされていない
has_assignee = github.pr_json["assignee"] != nil
warn("Please assign", sticky: false) unless has_assignee

# release以外からmasterへマージ
is_to_master = github.branch_for_base == "master"
is_from_release = github.branch_for_head.include?("release")
fail("You can only merge to the master branch from the release branch") if is_to_master && !is_from_release

# ファイルの変更検知
protected_files = ["build.gradle", "proguard-rules.pro", "version.properties", ".gitignore"]
protected_files.each do |file|
  next if git.modified_files.grep(/#{file}/).empty?
  message("Changes have been made to the #{file} file")
end

Androidチームでは、次のようなブランチ運用を行なっています。

  1. リリースの時期とスコープを決めてreleaseブランチを作成
  2. feature ブランチを作成して開発
  3. release ブランチへPull Request(WIPで早めに出していつでもレビュー可能に)
  4. レビューが完了したらマージ
  5. release ブランチでQAが完了したら masterブランチへPull Request
  6. master へマージすると同時に自動で本番ビルド開始
  7. リリース

このように develop ブランチが存在しないためシンプルな運用が可能です。しかし master をデフォルトブランチに設定していることで時々事故が起きていました。通常 Pull Request は release に向けて作成するべきですが、誤って master に向けて作成してしまうことがあり、レビューでも気づかれずそのままマージしてしまうことが何度か発生しました。本番ビルドが走ってしまったり(Publishは自動化していないのでセーフですが)、Revertする手間が発生したりと、地味に面倒な作業が発生します。これを防止するためDangerにチェック項目を追加してPR先が master の場合はマージ不可することで、このような事故は発生しなくなりました。

コードレビュー必須化

Androidチームのメンバーも増え、品質を維持したり技術的負債を増やさないためにも昨年末からコードレビューを必須とし、メンバー全員からapprovedされるまでマージできないルールとしました。

自分自身、前職くらいからコードレビューをやり始めてはいましたが、仕様を満たしているかのチェックがメインであまりコードレビューと言えるものではありませんでした。必須化した直後は要領よくレビューができずに、場合によってはほぼ「ノールックLGTM」していた時もありましたが、最近ではレビューの質もあがり主に次のような点を見るようにしています。

  • 将来負債になったり不具合の原因となりそうな実装がされていないか
  • MVVMモデルに則した実装となっているか
  • Nonnull / Nullable の区別は適切か
  • スコープ関数を利用したkotlinらしいコードになっているか
  • kotlinで Utilsクラスや Stream を利用していないか

また若いエンジニアからも鋭い指摘をもらうことがあり本当にありがたい限りです。Kotlinだとこんな書き方もできますよ、といった感じで知識を共有する場にもなったり、チーム開発&コードレビューって素晴らしいな〜と感じるようになりました。

ちなみにレビューの負荷軽減・レビューによる手戻り削減のため、作業を始めたらなるべく早く WIPで Pull Requestを出し、余裕のあるときにレビューするようにしています。突然「急ぎでレビューお願いします」と言われても、すぐに時間が取れず雑にLGTMしたり、ほんとは指摘したいけど修正しているとリリースに間に合わないから今回は指摘しないでおこう、など良くない状況が起こり得るため早めにレビューできる運用を心がけています。

TLS1.2対応とAndroid4.xサポート終了

BASEではセキュリティ対策の一環として、今年の6月にサーバーをTLS1.2へ移行する計画があり、これに合わせて Android4.x のサポートを終了することとなりました。

1ヶ月前からアプリ内のお知らせやGoogle Play のアプリページに告知を表示して、Android4.x の端末からは(TLS1.2に対応している)ChromeブラウザのWebページからご利用いただくようお願いしていました。(BASEの場合、アプリだけでなくWebページからも商品が購入できる点は強みだと感じました。)

また、移行したタイミングで4.x端末でアプリを起動すると、サポートを終了した旨のメッセージを表示して起動を中断する仕組みを、早い段階で仕込んでいました。

普通であれば要求を受けた何かしらのAPIがOSのバージョンをチェックして4.xならエラーを返す、という設計を考えると思いますが、TLS1.2未対応のクライアントからはAPIに一切接続することができなくなるため、アプリ起動後最初のAPI接続の前に上記のような仕組みを入れることで、アプリがクラッシュしたり「ネットワークエラー」のような意味不明なメッセージを表示することがないようにしました。この仕組みはFirebase RemoteConfg を利用して実現しています。 結果として、一部のユーザー様にはご迷惑をおかけしましたが、大きなトラブルもなくスムーズに移行することができました。

Kotlin化

BASEアプリのKotlin化進捗状況です。

f:id:metal_president:20181217103930p:plain

もともとオールJavaでしたが、2017年のGoogle I/OでKotlinの正式サポートが発表されたのをきっかけにKotlinの導入をはじめました。 ひとまず、

  • 新機能/クラスはKotlinで実装しよう
  • (あとは余裕あるときに徐々にやっていこう...)

なノリで移行を続けて1年半が経ちましたが、思ったほどKotlin化が進まず、2020年になってもJavaを書いてるのは嫌だなー(バージョン1.1のころからJavaに触れている身としては切実です)と思い、

  • 少しでも修正するJavaファイルはKotlinにコンバートする

というルールを最近になって追加しました。

f:id:metal_president:20181217103606p:plain

来年中にKotlin 100%を達成できるよう頑張りたいと思います。

AndroidX移行

AndroidXの移行については、このスライド を見て詳細を把握し、直後にIssueを立てAndroid Studio3.2(正式版)がリリースされたタイミングで対応する予定でした。今後公開されるドキュメントはAndroidX前提で書かれ、AndroidXでしか利用できない機能・クラスも増えてくるだろうと考えて、放置していても負債にしかならいと判断したためです。

AndroidXは皆さんお馴染み Support Library の改良版です。Support Library は9月にリリースされた Revision 28.0.0 で終了しており、今後は AndroidX へ移行する必要があります。

Android Studio 3.2 以降では移行ツールが用意されドキュメントも整備されていますので、まだ対応されていない皆さんには早めの対応をお勧めします。

実際に対応したのは他のメンバー(Android歴1年未満の若手エンジニア)で、QA含めて2週間くらいで完了し10月上旬にリリースしましたが、リリース後も特に問題はなくスムーズに移行できたと思います。もちろんコードレビューもしっかりやりました。

まとめ

2018年は、Androidアプリの開発だけでなくプロダクトやチームの成長にも熱中でき、SIer・受託開発歴の長い自分にとっては大変実りのある一年となりました。スタートアップだからこそできる貴重な経験だと思います。

来年もたくさん新しいことにチャレンジしていきたいと思います!

明日はビール部部長の氏原さんです!