dely engineering blog

レシピ動画サービス「kurashiru」を運営するdelyのテックブログ

開発ブログの大成功をお祝いして、みんなが大好きなアレを食べてきました

こんにちは!

dely, Inc.でプロダクトマネージャー兼開発部ジェネラルマネージャーをしている奥原 (@okutaku0507) といいます。気がついたら、もう3月になってしまいました。皆さんは花粉と対峙されていますか。

この記事はレシピ動画サービスであるクラシルを開発しているdely開発部が2019年1Qに開発ブログに取り組み、その一環として2018年のAdvent Calendar (dely Advent Calendar 2018) を実施した背景とその成果について書いて行きたいと思います。

目次

 

1. 開発ブログに取り組んだ背景

まず最初に、dely開発部はとても少数精鋭です。実際に人数を聞くといつも皆さんが驚かれるほどです (気になった方はこちらから僕に聞きにきてください) 。実際に会社に所属して開発ブログを運営されている方なら体感されていると思うのですが、開発ブログを運営していくことは驚くほど工数がかかります。自分たちの取り組んでいることを、社外にわかってもらえるように発信することは思っている以上に大変です。では、少数精鋭でリソースが限られている中、なぜ、僕たちがAdvent Calendarに取り組み、開発ブログを盛り上げていこうと思ったのか。その背景についてお伝えしたいと思います。

今ではどこの会社も同じ現状かと思いますが、delyにおいても「クラシルの事業拡大と新規事業立ち上げのため、圧倒的に人材が足りない」という課題を抱えています。そして、採用人事に携われている方々は痛感していると思いますが、エンジニアとデザイナーの労働市場は本当に採用が困難になっています。これらの課題を解決するために、僕らがどうすればいいのか考えました。

僕の考えでは、採用はマーケティングに似ていると思っています。

f:id:okutaku:20180405140824j:plain

だれでも使えるマーケティングファネルの考え方 (https://www.tam-tam.co.jp/tipsnote/others/post14695.html)

つまり、採用される方のUXを考えた時に、会社を認知する、興味を持つ、転職 (新卒で入社) の際に比較・検討する、入社するというようなファネルに分解できると思います。このファネルにおいて、delyの現状と照らし合わせた際にまず浮かび上がった課題が以下の通りです。

クラシルというプロダクトや社長の堀江さんは知っているけど、それを開発しているdelyの開発部のことは全く知らない。

クラシルというレシピ動画サービスは2017年に全国規模のTVCMを打ったのと、社長である堀江がTVに出たり、PRで露出する機会が多く、両者は広く知れ渡っていました。そのため、候補者や実際に入社された方にヒアリングすると上記の課題が出てきました。そもそも、クラシルを開発している僕らが認知すらされていない状態となっていました。

ファネルの上部である「認知」を獲得することを目的として、上記課題の解決策として開発部ブログを盛り上げるという方法を考えました。

以上の拝見から、2019年1Qの開発部の目標として開発ブログの活性化を入れることになりました。

 

2. KPIと実績

そもそも、dely開発ブログである本ブログは2016年から存在はしていました。しかしながら、工数もかかるためほとんど運用されていなかったのが実情です。

実際に記事を書いていくにも、目標を立てておいた方がそれを目指せると考え、2019年1Qが終わるまでに、通常の目標として42,000PV、ストレッチ目標として100,000PVをおきました。通常の投稿で、ちょっとバズったとしてもPV数は数千程度だったので、部内では目標に対する厳しさすら立ち込めていました。まさかの結果となるとはつゆ知らず...

達成できるのかという一抹の不安がこみ上げる中始まった開発ブログですが、2019年1Q中のPVランキングを紹介します。 

 

第一位は...

tech.dely.jp

弊社SREの井上さんが書いた、AWSの料金が意図せず増えてしまうのを仕組みで解決するという記事でした。AWSで意図せず予算を溶かしたというこの上なく辛い経験をした人に刺さったのか、多くのSREの人に読んでいただけたようです。

 

第二位は...

tech.dely.jp

僕が書いた、クラシルで実践しているリーンなプロダクト開発を事細かに紹介した記事です。僕のクリスマス全てを捧げたかいがありました。delyの開発部では、リーンなプロダクト開発を通して、リリースしてみないとわからないという不確実性を減らす仕組みを作り、ユーザーに価値あるプロダクトを提供することに力を注いでいます。

 

第三位は...

tech.dely.jp

弊社SEOスペシャリストである、internet_ghostさん (@ghost_inter_net) が書いたSEOの記事です。アプリの会社と思われがちですが、最近ではwebチームの発足と共にweb版クラシルもガンガン開発されており、前年比2000%の成長を遂げています。webチームにも注目が集まっています。

 

さて、開発ブログがおいていた目標に対する実績ですが...

f:id:okutaku:20190304104109p:plain

僕らの予想を大きく上回り、早々にストレッチ目標であった10万PVを超えました。開発部として、僕らがやっていたことが皆さんに注目していただけて本当に嬉しい限りです。ノウハウも多く書いたので、読んでいただけた皆さんの役に立てれば幸いです。

 

また、これは本質的ではないかもですが、はてなブックマーク数ランキングでは10位を獲得することができました。少数精鋭で行なった分、この順位は僕らの自信に繋がっています。

qiita.com

3. 開発ブログの成果

さて、目標であったPVを達成してもそれが何も繋がっていなかったら意味がありません。僕らが実感してる効果を列挙します。

  • オーガニックでの応募が増えた
  • スカウトの返信率が向上した
  • お会いする人にブログ読んだことがあると言われるようになった
  • 面接にきていただける方が事前に読んでくれるようになった
  • ブログの盛況で開発部全体が盛り上がった

dely開発部の認知が拡大することで、wantedlyなどでのオーガニックでの応募が増えたように感じますし、面接に来ていただいた方に読んだことがあると言われる機会が増えました。また、副次的な効果としてスカウトの返信率が向上したように思います。考察として、認知を獲得することは採用全てのベースになると思いました。そして、開発ブログが盛り上がると開発部全体も盛り上がって良いなと感じました。

 

4. 終わりに

開発ブログの大成功をお祝いして、みんなが大好きなアレを開発部の皆で食べました。みんなの頑張りを労い、一体感が増してよかったです。これからも引き続き、開発ブログでの発信をしていきたいと思います。お寿司目的で頑張ったのではないことだけは大事なので、明言しておきたいです

f:id:okutaku:20190302185201j:plain

そして、皆さまが気になっている実際の採用ですが、開発ブログが全てではないですが、優秀なメンバーの採用が徐々に決まってきています。一緒になってクラシルをよりよくできるメンバーが増えて、とても嬉しいです。ですが、まだまだ積極的に採用を行なっています。

こちらが現在、募集しているポジションになります。プロダクト開発に携わるポジションは全て募集しているので、是非とも話だけでも聞きにきてください。

www.wantedly.com

また、面接となるとハードルが高い方用にカジュアルに内部のことが聞ける「カジュアル1on1」という取り組みも行なっています。絶対に面接はしないので、是非とも中にいるメンバーに話を聞きにきてください。

bethesun.connpass.com

さらに、行くのもためらわれた方用に気軽に僕とチャットできる選択もオープンにしています。起きている時間全て対応しますので、気軽にチャットしてきてください。

note.mu

AWS SDK for Rubyを使ったAWS Athenaの利用

こんにちは、サーバーサイドエンジニアのjoooee0000です。 delyはデータ基盤としてAWS Athenaを使っており、ユーザーの行動ログからアプリケーションのアクセスログまで、様々なログがAWS Athena上に存在しています。AWS AthenaはS3上にあるデータソースをprestoのクエリ記法で引けるようになっていてとても便利です。

クラシルの分析基盤の歴史に興味がある方は、この記事にまとまっているので参照してみてください。クラシルの分析基盤はデータサイエンスチームが主体となって今も改善を続けています!

tech.dely.jp

今回は、AWS SDK for Rubyを使ってAthenaのクエリを実行する記事が1本もなかったので書きました。また、本記事では並列処理ではなく、シンプルな1クエリを引くsync処理の場合について紹介します。

AWS Athena SDKの特徴

AthenaのSDKは少し変わっていて、実行待ちのポーリング処理を自前で実装する必要があります。処理の流れとしては、

  1. クエリを実行するAPIを叩く
  2. クエリ実行のステータス問い合わせAPIを結果が SUCCEED になるまで叩く
  3. クエリの結果を返すAPIを叩く

このように、2の処理の間ポーリング処理を実装する必要があります。

では、工程ごとに実装例を紹介していきます。

1. クエリを実行するAPIを叩く

まずは、クエリ実行をリクエストする #start_query_execution APIを叩きます。 実装サンプルはこちらです。

client = Aws::Athena::Client.new({})
query_string = %Q{SELECT * FROM "databasename"."tablename" limit 10;}

client.start_query_execution(
  {
    query_string: query_string,
    output_location: 's3://' + S3BUCKET_NAME
  }
)
=> 
#<struct Aws::Athena::Types::StartQueryExecutionOutput
 query_execution_id="c0d4460b-xxxx-xxxx-9924-ede5c2d2b56b">

Clientのinitializeの引数は、AWSのaccess_key_id/secret_access_keyやregionを主に指定します。なにも指定しないと、他のAWS SDKの仕様と同様に

  • Aws.config[:credentials]
  • The :access_key_id, :secret_access_key, and :session_token options.
  • ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']
  • ~/.aws/credentials
  • ...

の値が順番に参照されていきます。regionも同様です。環境に合わせて設定してください。

また、#start_query_execution APIを叩く際は、クエリとresult_configurationを指定する必要があります。 result_configurationでは、クエリの実行結果の保存先としてs3のpathを指定します。APIでのクエリ実行も、AWSコンソールでのクエリ実行時と同じようにs3に実行結果を保持する仕組みになっています。指定したs3のpathに実行結果が蓄積されていきます。

また、APIのレスポンスとして、query_execution_idを取得できます。こちらのidは、実行結果の問い合わせや実行ステータスの問い合わせ、実行停止処理などに必要になります。

このAPIを叩いた時点では、処理を開始するリクエストを送っただけです。返り値としてクエリの実行結果が返ってくるわけではなくquery_execution_idという実行固有のidのみが返却されます。

2. クエリ実行のステータス問い合わせAPIを結果がSUCCEEDになるまで叩く

1で実行を開始したあと、クエリが走り終わるまで、結果を取得することはできません。しかし、1の工程ではクエリの実行完了を待たずにレスポンスがかえってきます。 そこで、現在の実行のステータスを知るための #get_query_execution というAPIが存在しています。そのAPIの返り値がSUCCEED になるまでステータスを問い合わせ続けなければなりません。つまり、ポーリング処理が必要となります。

今回はwhileを使ってポーリング処理をする代わりに、こちらのgemのwith_retriesを使用してポーリング処理を実装しました。 GitHub - ooyala/retries: A tiny Rubygem for retrying code with randomized, exponential backoff. こちらのgemは内部でexponential backoffを採用しています。exponential backoffとは、指数関数的に処理のリトライ間隔を後退させるアルゴリズムのことで、処理に時間がかかるほど再処理をする間隔が広くなっていきます。つまり、早く終わる処理には無駄な待ちがなく、時間がかかる処理には無駄なAPIのコールやCPUの負荷をかけずに済むようなアルゴリズムになっています。

実装サンプルはこちらです。

begin
  status = ''
  with_retries({ max_tries: 100, base_sleep_seconds: 0.01, max_sleep_seconds: 30, rescue: [Executing] }) do |retry_count|
    state_result = client.get_query_execution({ query_execution_id: query_execution_id })
    status = state_result.query_execution.status.state
    puts "[Athena Poling] fetching_count: #{retry_count}"
    raise Executing if ['QUEUED', 'RUNNING'].include?(status)

    case status
    when 'FAILED', 'CANCELED'
      # 処理が失敗した理由の取得
      reason = state_result.query_execution.status.state_change_reason
      raise ExecutionError, reason
    end
  end
  status
ensure
  # 成功時以外はクエリの停止リクエストを送信
  unless status.present? && status == 'SUCCEEDED'
    puts "クエリ停止処理"
    client.stop_query_execution(query_execution_id)
  end
end
class Executing < Exception
end
class ExecutionError < Exception
end

with_retriesの引数である、max_triesやmax_sleep_secondsなどは、用途に合わせて調節してください。

ポーリング処理の精度検証

試しに実行時間が100秒弱のクエリのポーリング処理を下記の条件で実行した場合のCPU使用時間やAPIコール数を比較してみます。

  • シンプルなwhileでのポーリング処理(sleepなし)
  • sleep(1)をはさんだwhileでのポーリング処理
  • with_retriesでのポーリング処理

CPU使用時間の計測には、Ruby標準ライブラリのbenchmarkを使用しました。

シンプルなwhileでのポーリング処理

APIコール数: 3470回

CPU使用時間:

# benchmark結果
user       system     total     real
12.160000   1.320000  13.480000 ( 96.931696)

sleep(1)をはさんだwhileでのポーリング処理

APIコール数: 94回

CPU使用時間:

# benchmark結果
user       system     total    real
0.350000   0.050000   0.400000 ( 97.002586)

with_retriesでのポーリング処理

APIコール数: 15回

CPU使用時間:

# benchmark結果
user       system     total    real
0.090000   0.010000   0.100000 (110.306136)

with_retriesの処理の方はwhile処理と比べぴったりに処理が終わらないため、少しreal timeが長くなっています。しかし、長い処理においてもCPUをほとんどつかっておらず、APIコール数も少ないのがわかります。 ほとんどCPUを使わないので平行で重い処理などが走っても心配ありません。(これくらいの差であればsleep(1)でも十分だと思いますが。)

停止処理について

また、2番の処理の実装においてもう一つ大事なことは、途中で強制的に処理を終了した際などにしっかりクエリの実行を中断することです。想定外の長い処理が走ってしまったとき、コードを中断しても #stop_query_execution を叩かない限り裏側ではクエリの実行が走り続けてしまいます。なので、コードを強制終了させた場合などにも後処理として実行されるensure節でクエリ停止処理を書くことをおすすめします。

3. クエリの結果を返すAPIを叩く

クエリの実行が完了したら、後は結果を引く工程のみです。 結果の量が多く、ページングが必要な処理に関してはnext_tokenを次のリクエストでなげる形で実装します。特に変わったことはしないのですが、一つあげるとしたら1ページ目の1行目にカラム行が返ってくるので、結果を返すときにそれを除外しています。 (カラム行をskipしてくれるoptionを探したのですが見つかりませんでした。。探せばあるかもしれません。)

本実装では、1ページにページに返ってくる上限を100件、すべての結果の上限を10000件に絞っています。

MAX_PAGE_RESULTS = 100
MAX_RESULTS = 10000

# next_tokenを受け取って次のページをリクエストする再帰処理
def get_all_results(query_execution_id, next_token = nil, results = [])
  rows, next_token = get_results(query_execution_id, next_token)
  results += rows
  results = results.flatten
  if results.count > MAX_RESULTS
    raise ExecutionError, '結果の上限数を超えています。'
  end
  if next_token.present?
    results = get_all_results(query_execution_id, next_token, results)
  end
  results
end

# 1ページ分の結果を取得する処理
def get_results(query_execution_id, next_token = nil)
  results = client.get_query_results({
              query_execution_id: query_execution_id,
              next_token: next_token,
              max_results: MAX_PAGE_RESULTS
            })

  next_token = results.next_token
  # クエリの実行結果の取得
  rows = results.result_set.rows

  # カラム一覧を取得
  column = results.result_set.result_set_metadata.column_info.map(&:label)

  result_rows = rows.map do |result|
                  row = result.data.map(&:var_char_value)
                  # 初回はカラム行が返ってくるので除外
                  next if row == column
                  column.zip(row).to_h
                end.compact
  [result_rows, next_token]
end

get_all_results(query_execution_id)

まとめ

今回は、AWS AthenaをAWS SDK for Rubyで引く実装について紹介しました。 ポーリング処理を自前で書くような仕様が珍しいですよね。ポーリングを書く処理は、with_retriesを使ってみてはいかがでしょうか。また、停止処理はしっかりと行いましょう!

Google Play Developer APIを活用してAPKのアップロードを自動化する(DroidKaigi 2019の発表を終えて)

こんにちは。Androidエンジニアのうめもりです。

もう終わってからだいぶ経ってしまいましたが、今年のDroidKaigiもとても面白かったですね。自分は去年から参加し始めたのですが、去年と比べても 色々な部分で改善が見られ、運営の方々には頭が下がる思いです。来年以降も続いていくといいですね。

さて、自分は今年のDroidKaigiではこんな発表をしてきました。

ちなみに去年の発表はこんな感じなので、大分毛色が違う感じでしたね。(資料のアス比が…)

正直内容としてはそこまでレベルが高い感じもしなかったので、資料を作りながら(大丈夫かな…いやでもこれCfP通りだしな…いやでもな…)みたいな葛藤がありつつも、当日はそこそこ楽しんでいただけたようで良かったです。日本語から英語への同時通訳をしていただく中での発表は初めてだったのですが、終わった後にゆっくり喋ってもらったのが良かったとコメントをいただいて安心しました。(余談ですが、DroidKaigiの同時通訳の方はAndroid Specificな内容も理解しつつ的確に翻訳してくださる素晴らしい方々だったみたいです。どれだけ事前に勉強されたのでしょうか…。事前の打ち合わせでもお話をさせていただきましたが、安心して発表に臨むことが出来ました。)

発表内容の中でGoogle PlayへのAPK、Proguardのマッピングファイルの自動アップロードを行っているという話をチラッと出したのですが、登壇後のオフィスアワーでも、Twitter上でもどうやっているのかという質問をいただいたりしたので、こちらのブログで補足しておこうと思います。ちなみにAWS Lambdaからゴリゴリやっているので、ナウい感じのCIサービス(Bitriseとか)の話は出てこないことをご了承ください。

APKのアップロードをどのように自動化したか?

タイトルでネタバレしてしまっていますが、delyのAndroidチームでは、Google Play Developer Publishing APIを使って

  • APKのアップロード
  • Proguardのマッピングファイルのアップロード
  • リリースのドラフト作成(アルファ公開チャンネルに作成しています)

のタスクを自動化しています。クラシルはRealmを使っていた関係でSplit APKを行っているので、APKのアップロードやProguardのマッピングファイルのアップロードは地味に面倒なタスクになっています。ファイルをアップロードするだけといえばアップロードするだけなのですが、リリース時の心理的な作業負担感が大分低減しているような実感があります。

Google Play Developer Publishing APIの叩き方

2019年2月21日現在でのGoogle Play Console上での説明なので、もしかしたらこの部分の説明は間違っている可能性があります。

まずはGoogle Play Developer Publishing APIを使うためのサービスアカウントを発行しましょう。今回の用途では各ユーザーの権限でAPIを呼ぶことは無いと思うので、サービスアカウントを使うのが簡単だと思います。

Google Play Developer Consoleの「設定」から、「APIアクセス」を開きます。(なお、この操作は適切な権限のあるアカウントでないと出来ませんのでご注意ください。)

まだ1回もAPIを使ったことが無いのであれば、「新しいプロジェクトを作成」からプロジェクトを作成しましょう。

作成すると、画面の下にサービスアカウントというセクションが現れるはずなので、そこから「サービスアカウントを作成」をクリックします。

基本的にはそこに書いてある通りに操作を行えばいいのですが、Google API Consoleに移動して、「サービスアカウントを作成」から要求された項目を入力し、サービスアカウントを作成します。重要なのは、キーの作成を行って(JSONがいいと思います)、それを控えておくことです。

次に、Google Play Consoleの「ユーザーと権限」から、先ほど作成したサービスアカウントのメールアドレスを指定して、権限を与えます。「リリースマネージャー」にしておくのが手っ取り早くていいと思います。

以上で、先ほど作成したキーを使ってGoogle Play Developer Publishing APIを使うことが出来るようになりました。

Google Play Developer Publishing APIを呼び出す

先程作成したキーを使ってAPIを叩くのはGoogle APIs SDKを使うのが一番簡単です。弊社ではAWS Lambda上でGoを使ってAPIを叩いていますが、以降の説明はGoogleのAPIドキュメントを使って行います。(どのプラットフォーム用のSDKでも基本的には同じようにマッピングされているはずなので、適宜読み替えて使ってください。)

さて、APKやProguardのマッピングファイルをアップロードするのにはまずEditsを作成する必要があります。Google Play Developer Publishing APIはAPIをまたいだトランザクションに対応しており、Editsはトランザクションの単位と考えれば間違えが無いと思います。

developers.google.com

なお、こちらがGoogle Play Developer Publishing APIのドキュメントなので、こちらを見て分かる方はそちらを読んでいただいた方がいいと思います。

Editsを作成する

まずは、

https://developers.google.com/android-publisher/api-ref/edits/insert

POST https://www.googleapis.com/androidpublisher/v3/applications/{packageName}/edits

こちらのAPIをコールしてEditsを作成します。{packageName}にはアプリケーションIDを入れます。

レスポンスはこのような構造になっています。

https://developers.google.com/android-publisher/api-ref/edits

重要なのはidで、こちらのidを使って以降のAPIをコールしてファイルのアップロードを行います。

APKをアップロードする

次に、APKのアップロードのやり方です。

https://developers.google.com/android-publisher/api-ref/edits/apks/upload

POST https://www.googleapis.com/upload/androidpublisher/v3/applications/{packageName}/edits/{editId}/apks?uploadType={uploadType}

こちらのAPIをコールしてAPKをアップロードします。先程作成したEditのidを{editId}に挿入します。{uploadType}は mediaresumable を設定できますが、 今回はmediaを指定した場合の説明だけをしておきます。

Content-Typeは application/octet-streamapplication/vnd.android.package-archive を設定し、ファイルのバイナリ列をリクエストボディとして送信しましょう。

なお、弊社ではAPKごとにゴルーチンを立ち上げて並列アップロードしています。

Proguardのマッピングファイルをアップロードする

次に、Proguardのマッピングファイルをアップロードします。

https://developers.google.com/android-publisher/api-ref/edits/deobfuscationfiles/upload

POST https://www.googleapis.com/upload/androidpublisher/v3/applications/{packageName}/edits/{editId}/apks/{apkVersionCode}/deobfuscationFiles/{deobfuscationFileType}?uploadType={uploadType}

基本的にはAPKと同じ要領ですが、どのAPKのマッピングファイルなのかを指定する必要があります。{apkVersionCode}には、対応するAPKのVersionCodeを挿入します。{deobfuscationFileType}には現在は proguard しか指定できませんので、それを指定しましょう。

Content-Typeには application/octet-stream を指定し、マッピングファイルのテキストをリクエストボディに入れて送信しましょう。

余談ですが、難読化を意味するobfuscationはネイティブの方でも馴染みのない単語みたいですね。事前打ち合わせでその話題が出てきて、確かに日本語でも難読化なんて言葉そんなに使わないわ…と思った記憶があります。

TracksをUpdateする

最後に、どのトラックにアップロードするかを指定します。

https://developers.google.com/android-publisher/api-ref/edits/tracks/update

PUT https://www.googleapis.com/androidpublisher/v3/applications/packageName/edits/editId/tracks/{track}

{track}には alpha, beta, production, rollout, internal を指定しましょう。弊社では毎回 alpha でアップロードしています。( internal の方がGoogle Playへの反映が早いですし、そちらでアップロードすることを検討してもいいかもしれませんね…。)

Content-Typeは application/json を指定しましょう。

リクエストボディのフォーマットは

https://developers.google.com/android-publisher/api-ref/edits/tracks#resource

こちらに書いてあります。必須なのは trackreleases[].statusreleases[].versionCodes です。versionCodesにはアップロードしたAPKのVersionCodeを指定しましょう。statusは completed, draft, halted, inProgress ですが、弊社では draft でアップロードし、リリースノート等は後で入力するという運用にしています。

Editsをcommitする

https://developers.google.com/android-publisher/api-ref/edits/commit

POST https://www.googleapis.com/androidpublisher/v3/applications/{packageName}/edits/{editId}:commit

上記APIをeditIdを指定して呼び出せば、今まで行った全ての操作がGoogle Playに反映されます。リクエストボディは指定する必要はありません。

注意点

一つだけ注意点ですが、Editsを編集している際にはGoogle Play上でリリースを操作するのはやめましょう。トランザクション外で操作が発生していると、commitする段階でAPIリクエストが失敗します。

まとめ

以上がGoogle PlayへのAPKのアップロード方法です。少しAPIに癖はありますが、Editsの操作さえ分かってしまえば他のAPIも同じように呼ぶことが出来るはずです。皆さんも良いGoogle Playライフを。

この記事の内容への質問等があればこちらまで気軽にどうぞ。

https://twitter.com/kr9ly

来年のDroidKaigiの話

ちなみに、来年のDroidKaigiでもCfPを出そうと思っているのですが、Kotlin Coroutinesで状態遷移を可視化して管理するみたいな話をしたいと思っています。Kotlin Coroutinesの無限のパワーをどう生かすかという話は来年はいっぱいCfPが出てきそうですが、今のうちにしっかり準備してめちゃくちゃ面白い発表にしたいと思っています。(通るといいな…)

クラシル・パーソナライゼーションの歩み

はじめに

こんにちは。 機械学習エンジニアの辻です。

2/6(水)AWS Loft Tokyoでイベント開催します!ご興味のある方はぜひご応募ください! bethesun.connpass.com

さて本日は「クラシル・パーソナライゼーションの歩み」ということで、クラシルをよくご利用頂いているユーザに対してよりいっそう良いコンテンツを提供していくために、パーソナライゼーションの取り組みに力をいれています。そこで、これまでに取り組んできたさまざまな施策に関して考えてきたことやフィードバックから学んだこと、そして、今後どのように進めて行こうとしているのかということについて、過去から未来への歩みとして少しご紹介したいと思っています。

目次

パーソナライゼーション以前の課題感

昨年6月までクラシルで配信しているレシピ動画として主におすすめしていたものは、いわゆるルールベースの「Most Popular推薦」という選出方法によるものだけでした。このMost Popular推薦とは、非常にシンプルなスコアリングモデルで、たとえばクリック数やお気に入り数の集計結果をベースに、いくつかの独自ルールを盛り込んでスコア化し、そのスコアの高いものから順に選出していくというもので、推薦されるレシピは全ユーザーに対して同じものとなります。この方法をざっくりいえば、「たくさんの人が好きなレシピは、たくさんの人が好きなはずでしょ?」ということなので、ある意味で理にかなっているといえます。しかしこれだけですと、クラシルをよく利用して頂いているヘビーユーザにとっては、代わり映えしない提案だったり、お気に入り済みなのに何度も勧めてきてクドいなぁと感じられることも多々あるかと思います。あるいはまた、何か苦手な食材があるユーザに対してまったく故意ではないにせよ、その苦手食材を毎回おすすめしてしまっていては、続けて使って行こうなんてきっと思って頂けないと思います。

f:id:long10:20190121111701p:plain

それから、このMost Popular推薦のルール変更についても当時は定性的な判断によるもので、例えばこのルールを追加したらCTRが0.3%向上した、あるいはこの施策によってCTRが0.5%下がったなどといったように、ユーザ行動における詳細な相関分析や因子分析を行わないまま、微細な数値の増減だけに翻弄される日々を過ごしていました。今にして思えば、これこそまさにノーフリーランチ定理だったのです。すべてのユーザ標本にとって最大極値を探索するような汎用アルゴリズムは、全ての可能なコスト関数に適用した結果を平均するのと同じ性能になってしまっていたわけです。

ノーフリーランチ定理 f:id:long10:20190121112708g:plain

この状況を打開すべく、まずは特殊用途に最適化するために全ユーザ標本に対してクラスタリングを行い、最適化すべき定義域を局所化することから始めました。これにより、分類した各クラスターの基礎統計を観察してみたところ、それぞれに特色のようなものが現れ始めたので、この特色を定量化すべく主成分分析や因子分析を行い寄与率の高い特徴量を探索して絞り込んでいくことができました。

f:id:long10:20190121113157p:plain

そして、このクラスター毎のMost Popular推薦のルールを作成しそれぞれに適用することで、全ユーザ標本に対してのMost Popular推薦と比較しても格段に高い結果を得ることができました。さらにまた、強調フィルタリングを用いてユーザと動画のスコアリングを行うことでレコメンドエンジンを作成し、パーソナライズド・レコメンドを部分的に適用することができました。(一部のクラスタではCTRが下がるという結果が得られたのですが、そのクラスタには効果がないということが判断できたので、それもまた発見でした。)
中でも、顕著な特性として出てきたのが新奇性に対する反応の違いでした。新奇性とは、目新しさや物珍しさに対する反応のことで、ヘビーユーザの中には新しいレシピを待っていて、出るとすぐにお気に入りするという使い方をされている方がいらして、その方々のパーソナライズド・レコメンドに対する反応が顕著に見られました。しかしその方々の反応は長期間継続せず、それはこのクラスタの方々にとってのレシピの鮮度というのが、配信後からお気に入りするまでの比較的短い期間であるためであることがわかりました。そのため一度お気に入りしてしまうと、おすすめレシピへの興味は急激に減少しCTRが激減するという傾向があったのです。
(以下のグラフでは、緑が各クラスタ、青が全体の平均CTRを現しています)

新奇性が高いクラスターのレコメンドに対する反応遷移

f:id:long10:20190122095358p:plain

その一方、調理を重視してクラシルを利用されている方々にとっては新奇性の影響はあまりなく、その反面、パーソナライズド・レコメンドに対する反応もそこまで高くはないという面が見られました。

調理を重視しているクラスターのレコメンドに対する反応遷移

f:id:long10:20190122095518p:plain

このような、クラスタの特色を踏まえて理想のレシピ提案を行っています。こちらについては、今後もさらなる精度向上を目指しています。

f:id:long10:20190121111853p:plain

エコシステム化

さて、ここまで課題感としてあったレシピ動画のおすすめ提案についての事例をご紹介しましたが、実はレコメンデーション自体は目的ではなく、パーソナライゼーション全体においてはほんの一部に過ぎないと考えています。それというのも、パーソナライゼーションの取り組みを進めていくことで、このおすすめ提案以外にも、様々な機能により利用して頂けば頂くほどユーザからのフィードバックを得られ、より良いサービス提供が可能になると信じているからです。ですので、ここで結果を焦り過ぎてはいけません。まずはこのフィードバックが全体に循環するエコシステム作りこそが優先で、これなくして一時の場当たり的な改善に一喜一憂しては何も得られません。

リーン開発サイクルとフィードバックエコシステム

f:id:long10:20190121184107p:plain

クラシルというブランドを理解する

f:id:long10:20190121180323p:plain

エコシステム化を進めるにあたって、その根幹にはクラシルというアプリの存在意義があります。この点でクラシルには「ブランドガイド」という指針があり、このコンセプトに反するようなイメージを受け入れることはできません。では、どうでしょうか?機械学習やAIという言葉から受ける印象と、クラシルから受ける印象とは親和性があるでしょうか?これについて定性的な判断は不可能ですが多くの人があまり親和性が高いとはいえないとお考えになるのではないでしょうか?それであれば積極的に全面に出るよりも「あたたかくて、おいしい」にそっと寄り添うようなアプローチを目指すほうが良いと判断しています。(あくまで現時点の個人的な所感に過ぎませんが。)

f:id:long10:20190121165652p:plain

データ分析に関する社内への取り組み

先程、ノーフリーランチ定理に触れましたが、やはり、なかなかそれを理解してもらえないという状況もあります。過去の経験やドメイン知識に基づく判断によってルールを場当たり的に変更していけば、いつかそのうちCTRが向上すると頑なに信じている人も中にはいます。仮に過去データに基づき統計的手法で算出した数値を根拠にいくら定量的な検定結果を共有したところで、難しいとか経験によってうまく行ったというハロー効果はなかなか覆し難いのも事実です。このような状況では、分析者側からいたずらに対立関係を作るのではなく、根気強く納得してもらえるまで取り組みに協力して、現実を検証し続けることが大切だと思っています。それでもし運良くすばらしい結果が出れば、より良いMost Popular推薦のルールが発見されたのですからそれはそれで良いことなのです。

こちらの「ファスト&スロー あなたの意思はどのように決まるか?」を読むと統計学の研究者でさえ誤りを犯すことがあるほど、意思決定がいかに曖昧なものか理解できます。

また、社内では以下のような取り組みによって、定量的なデータ分析に基づく共通認識を深めています。

  • サンプルサイズの算出方法を社内共有+推定値の自動算出
  • 分析基盤の構築+運用+改善
  • ユーザ行動ログおよびレシピデータに基づいたEDA分析
  • ダッシュボードによるビジュアライズ
  • 統計学、多変量解析の実践方法を社内にレクチャー
  • SQL勉強会の開催

レシピを考え、作るというプロセス

弊社ではクラシルシェフと呼ばれる料理人の方たちによって日々新しいレシピが考案されています。このレシピを考えるという作業はそれ自体が非常に複雑な最適化問題であるといえます。旬の食材や価格、あるいは余り物があれば優先して使いたいし、家族に子供がいる場合と高齢者がいる場合など家族構成によって様々な配慮が必要です。それに加えクラシルシェフの場合は、世間のトレンドや検索キーワードなど様々な外的要因も考慮しなければならず、また過去に作った多くのレシピともかぶらないものにしなければならないので、レシピの考案まで極めて多くの制約があります。その複雑な作業を少しでもお手伝いできないかと考えて、これらの機械学習を用いたプロセスの改善に取り組んでいます。

  • レシピ考案をお手伝い:いくつかの説明変数からレシピをヒントとして推論する
  • レシピ手順の評価:レシピの手順がネガティブ・ポジティブかを判定して手順を記述する際の判断材料にしてもらう
  • レシピの素性抽出を自動化:レシピに関する様々な素性をルールベースで導出、あるいは推論により抽出し更新・保存する

レシピ動画評価

良いコンテンツは再現したいものの、このコンテンツの良し悪しというのは外的要因に左右されることも多く、また様々なコンテキストによって目的が異なります。再生数が多いほどよいのか?より美味しそうな方が良いのか?あるいは簡単なほどよいのか?など一概に判断が難しいところです。しかしこの「良い動画」をとあるコンテキストにおいて局所的な定量評価することで、より良いコンテンツ作りのサポートができると考えて様々な角度から取り組んでいます。

f:id:long10:20190121174738p:plain

献立の最適化問題

クラシルでは、去年の11月に献立機能をリリースしました。レシピでさえ考えるのが複雑であるにもかかわらず、主菜+副菜+汁物という献立を考えるというのは本当に困難な家事と言えます。ですので、この献立についても、主菜に合う副菜、汁物が最適な組み合わせとなるように現在取り組んでいます。

f:id:long10:20190122102404j:plain

献立画面

f:id:long10:20190122102010p:plain:w300

初回のトレーニングデータのラベル付けについては専門家であるクラシルシェフの皆さんと調理栄養士の方を中心に人海戦術で行いました。現在ではそのトレーニングデータをもとに作成したモデルから最適な組み合わせを推論しています。組み合わせアルゴリズムはナップサック問題のアルゴリズムをベースにした独自実装となっています。

ナップサック問題

f:id:long10:20181105162634p:plain

今後はさらに、旬食材や冷蔵庫の余り物、あるいはアレルギー体質などにも考慮し、さらにご利用頂くユーザに寄り添う献立を様々な形で提案していきたいと考えています。

まとめ

いかがでしたでしょうか?
クラシルにおけるパーソナライゼーションの歩みについてご紹介させていただきました。機械学習やAIというとなんとなく機械的に提案されたレシピを食べるのは嫌だなぁと抵抗のある方もいらっしゃるかもしれませんが、最終的にご提案するのは、クラシルシェフの作った「あたたかくて、おいしい」レシピであって、機械学習やAIはそれにちょっとだけプラスアルファすることで、ご利用頂くユーザのライフスタイルにもっと最適なご提案ができるようなサポート的な存在として寄り添っていきたいと思っております。

さいごに

繰り返しになりますが、
2/6(水)にこのような機械学習のイベントを開催します。今回紹介しました内容以上に実践的なお話ができるかと思いますので、ご興味のある方はぜひお申込みください!
ご来場頂いた方には、弊社の取り組みの中で試行錯誤した「SageMakerの便利スニペット集」をプレゼント致します!こちらのスニペットに対する質問も随時受け付けますので奮ってお申込みください!

bethesun.connpass.com

不確実性とうまくやっていくためのプログラミング設計論

こんにちは。delyのTech Leadのうめもりです。

これはdely Advent Calendarの25日目の記事です。ほかの記事についてはこちら

qiita.com adventar.org

をご覧ください。

昨日はプロダクトマネージャー兼開発部ジェネラルマネージャーをしている奥原 (@okutaku0507)が

tech.dely.jp

という記事を書いてくれました。ご興味あればそちらも是非ご覧ください。

25日目の記事は、みんな大好き技術的負債の話をしたいと思います。

はじめに

「技術的負債」

我々プログラマーからすればうまく付き合っていく必要のある厄介な存在であり、「何故技術的負債を解消していかないといけないのか?」というトピックは定期的にプログラマー界隈でも話題になりやすいものです。

時にはビジネスサイドに技術的負債の存在やその厄介さについて説明する必要が生じることもあり、その説明の難儀さに苦労した方も多いのではないかと思います。

今日はプログラミング、プロダクト開発の不確実性というテーマから、技術的負債についての説明をしてみたいと思います。

そのためにまずは、我々は何故プロダクト開発をするのか?というところに立ち返ってみましょう。

我々は何故プロダクト開発をするのか?

BtoC、CtoC、様々なビジネス領域においてプロダクト開発という業務は存在していますが、仮にあなたがどんなプロダクトを作っていたとしても 「我々は何故プロダクト開発をするのか?」という問いに対する答えは基本的には同じはずです。

我々は、未来のマーケット、未来のユーザーに価値を提供するためにプロダクト開発をしている。 これが我々が何故プロダクト開発をするのか?ということに対する答えになると思います。

注意しなければならないのは、現在のマーケット、現在のユーザーに対してのものではないということです。何故ならばそのプロダクトが出来上がるまでには多かれ少なかれ時間がかかるはずであり、その頃にはマーケット、ユーザーは多少なりとも変化しているはずですから。

未来を確実に予測することはできないという前提

f:id:delyumemori:20181225101754p:plain

未来のマーケット、未来のユーザーに価値を提供するということを考える際に、最も重要な原則があります。それは 未来を確実に予測することはできない ということです。

どんなに注意深くマーケット、ユーザーの情報を集めたとしても、100%未来がこうなると予測することは現在の技術ではできません。つまり、未来のマーケット、未来のユーザーに対して開発する予定のプロダクトが、本当に価値を提供できるかどうかを確実に予測するすべはないということです。プロダクト開発とは本質的に不確実性を持っているものであり、我々はプロダクト開発がもたらす不確実性とどう付き合っていくか、ということを考える必要があります。

もしそのプロダクトを使うユーザーはあなたがよく知っている人間であるとしても、その不確実性を排除することはできません。プロダクトを欲しがっているユーザーと、プロダクトを前にしたユーザーはもはや他人であると考えるべきです。(多かれ少なかれ皆さんも経験があることではないでしょうか?)

そして、一部の例外を除くほとんどのプロダクトについては、今プロダクトを使っていない未知のユーザーに届ける必要性があるものだといえます。勝手知ったるユーザーにプロダクトを提供することに不確実性があるなら、今あなたが全く知らないユーザーに対してプロダクトを提供することについては言うまでもないでしょう。

不確実性を味方にするたった一つの方法

では、我々はプロダクト開発がもたらす不確実性とどう付き合っていけばいいのでしょうか?

未来を予測する最も確実な方法は、それを発明することだ - アラン・ケイ

我々は未来を確実に予測できなくても、過去のプロダクトがマーケット、ユーザーにとって価値があったかどうかを検証することはできます。 あらゆるプロダクト開発は、マーケット、ユーザーにとってそのプロダクトが価値があるかどうかを検証するために行われるものだと言っても過言ではありません。

プロダクト開発が成功した場合においても、失敗した場合においても、我々はそのプロダクトのもたらす価値という情報を得ることになります。

プロダクト開発が成功した場合に得られる情報は、それはそのプロダクトが価値がある、という情報です。
プロダクト開発が失敗した場合に得られる情報は、それはそのプロダクトが価値が無い、という情報です。

大抵の場合、そのプロダクトに価値があるかどうかは100%か0%ではなく、ある部分は価値があり、ある部分は価値がないという情報が得られるでしょう。

プロダクト開発がもたらす不確実性を味方にするたった一つの方法、それはプロダクト開発が失敗した場合に被る損失を最小にし、プロダクト開発が成功した場合に得られる利益を最大化するという方法です。

プロダクト開発の失敗とどう向き合うのか?

プロダクト開発の成功から得る利益を最大にする方法、それはプロダクトの価値のある部分を破棄せずに使い続けることです。
プロダクト開発の失敗から被る損失を最小にする方法、それはさっさとプロダクトの価値のない部分を破棄して、作り直すことです。

ここで一つ気を付けなければならないのは、コードの改修は大抵の場合はこの破棄して作り直す、ということを意味するということです。破棄する範囲が広いか狭いか、という違いだけがそこには存在します。

価値のある部分を残し、価値のない部分を捨てるためには、それらを区別することができることが必須条件です。

もし価値のある部分と価値のない部分の区別がつかなければ、価値のない部分だけを捨てるという判断が出来ません。そうやってプロダクト自体の価値のない部分が時間とともに増えていけば、いずれプロダクト全体を捨てなければならないという破綻を招くことになります。

疎結合であり、単純明快で意味のある構造を実現し続けること

プロダクトの価値のある部分を残し、価値のない部分を捨てられるようにするための基本的なアイディアとして、コードを疎結合な状態に保つということがあります。疎結合とは単純にコードが分割されている、というだけではなく、コードの意味として分割されていることで、分割されたお互いのモジュールの実装の詳細を知らなくてもそれぞれの機能が提供できる、という状態のことです。

例えばプロダクトがAという機能とBという機能を提供していたとして、Aという機能を提供している部分、Bという機能を提供している部分がコード上での構造としても明確であり、Bという機能だけを検証の結果として破棄することになったとしてもすんなり破棄できる状態であるのが疎結合になっているという状態です。これが、Aという機能がBという機能の実装を深く理解していないと提供できない、あるいはそもそも不可分である、という状態になっていると、そう簡単にBという機能だけを破棄することが出来ないということになります。Bという機能を外から不可視にすることは簡単にできるかもしれませんが、システムとしては本来必要のないBという機能の実装を理解した上で、今後の機能実装を進める羽目になるでしょう。そういった破棄できない部分が増えていけば、前述したようにいずれコードを丸ごと破棄するしかなくなるという結末が待っているでしょう。

コードを疎結合に保つ、ということはそうでない場合に比べて実装コストがかかりやすいものですし、そもそも困難なことです。大抵の場合は密結合に作る方が簡単ですし、実装コストも安く済むでしょう。ただし、それは実装した部分全てが有用であり、変更する必要が無いということが前提になっているか、そもそも一回きりの実装でコードを丸ごと破棄することに問題がない場合だけでしょう。(例えばプロトタイプ開発とか)

我々はプロダクトを実装した結果から学習し、不要な部分を破棄した上で先に進み続ける必要があります。古くからそのための様々な考え方がありますが、今回はその文脈でよく出てくるDRY原則と、SOLID原則についてもう一度振り返ってみましょう。

DRY原則をコードを破棄するという観点から振り返る

DRY(Don’t Repeat Yourself)原則とは、情報の重複を避けるという考え方です。特定の機能の実装がバラバラにコードの中に入っていると、その機能を破棄する際にコードの実装を細かく調べる必要があり、破棄することが困難になります。例えばそれらが同一の関数あるいは同一のメソッドあるいは同一のクラスで表現されていれば、破棄は容易になるはずです。一つにまとめたうえで、分かりやすい名前がついていればより簡単にコードを捨てられるようになるでしょう。

ここで気を付けなければならないのは、コードの重複を排除して一つにまとめることが、コードを破棄しにくくすることにつながってしまうことも往々にしてあるということです。一見それは重複であっても、機能上の意味として異なる場合にはそれらの機能をまとめてはいけません。

DRY原則は有用な考え方ですが、プロダクトから学習してプロダクトを改善し続ける際には、注意深く適用しなければならない考え方です。

SOLID原則をコードを破棄するという観点から振り返る

SOLID原則とは、オブジェクト指向言語(今日の実用的なプログラミング言語は多かれ少なかれオブジェクト指向的な要素を持っていますね)における、5つのプログラミング上の原則をまとめて頭文字をとったものです。

  • Single Responsibility Principle(単一責務の原則)
  • Open/closed principle(開放/閉鎖の原則)
  • Liskov substitution principle(リスコフの置換原則)
  • Interface segregation principle(インターフェース分離の原則)
  • Dependency inversion principle(依存性逆転の原則)

今回はそのすべてを振り返ることはしませんが、コードを破棄するという観点から考えるとどれも有用な考え方です。そのうちの二つ、単一責務の原則と開放/閉鎖の原則について振り返ってみます。

単一責務の原則

Single Responsibility Principle(単一責務の原則)とは、「クラスはただ一つの理由で変更すべきであり、一つの機能だけを持っているようにするべきである」という原則のことです。コードを破棄するということから考えると、とても自明なことであると言えます。機能がクラスごとに明確に分割されており一対一でマッピングされる状態であるなら、それらを破棄することはとても容易なはずです。

開放/閉鎖の原則

Open/closed principle(開放/閉鎖の原則)とは、「クラスは拡張に対して開いていなければならず、修正に対して閉じていなければならない」という原則のことです。機能を追加する際にクラス自体を修正する必要が無いようになっているということも重要ですし、その他クラス外の何か変更によって、クラスの挙動が破壊されないということが保証されているならば、安心してコードの破棄を行うことができるようになるはずです。

まとめ:「技術的負債」とは何だったのか?

さて、プロダクトから得る価値を最大化するためにコードを破棄するという観点からDRY原則、SOLID原則の一部を振り返ってみました。

「技術的負債」という言葉をここまで使いませんでしたが、この言葉をプロダクトから得る価値を最大化するためにコードを破棄するという観点から整理し直すと、

  • マーケット、ユーザーにとって価値のないコードそのもの(いずれ破棄しなければならないという債務を抱えている)
  • コードを破棄することを妨げるコード(負債への対応を先送りし、プロダクト全体の破綻を招くコード)
  • プロダクトの不確実性に耐えられない、脆い部分

が技術的負債である、ということが言えるのではないでしょうか。

常にそれは時間とのトレードオフになりますが、我々はプロダクトから学習しプロダクトを成長させ続けるために、技術的負債にいかに対応していくかということを考えていく必要があります。

参考文献

最後に

dely Advent Calendarは今年初めて行った試みですが、今日で25日分全て公開できました。(本来のアドベントカレンダーは24日分であることが多いと思うのですが、何故ブログは25日分書くことになっているのかは割と謎ですよね)

今までdelyの開発部はあまり情報を外に出していなかったのですが、今回は技術的な情報を外に出していくといういいきっかけになったと思います。今後も定期的に色々な情報を発信していきますので、dely engineering blog, dely design blogを引き続きよろしくお願いいたします。

最後に、師走で業務も忙しい中しっかり記事を書いてくれた開発部の皆さんに感謝を。どうもありがとうございました。

開発部の雰囲気

f:id:sakura818uuu:20181225144938p:plain

こんにちは: ) sakura(@818uuu)です。

本記事はdely Advent Calendar 2018の15日目の記事です。
Qiita : dely Advent Calendar 2018 - Qiita
Adventar : dely Advent Calendar 2018 - Adventar

前日は、検索エンジニアの仕事内容を紹介しつつ1年間取り組んだことをご紹介しました。

tech.dely.jp

はじめに

本記事は、delyの開発部の中からみた開発部の雰囲気をお伝えしようと思います。
技術ブログですが、現状開発部の雰囲気を知る機会や文章はあまりないのでこういう記事が一つくらいあってもいいかなと思って書かせていただきました。
あくまで個人の主観による意見が大きいですがそこはご了承願います。

自分なりに精一杯言語化したのですが伝わりにくいことがあったらすみません。
これがdelyに入社しようと考える人のご参考になれば幸いです。

変化に柔軟に対応するのが上手い

delyは会社が急成長していることもあり、どんどん環境が変化していっています。
人でも考え方でもそうですが、環境の変化にはそれ相応の対応が必要になってきます。

環境の変化に対応することは一般的にはとても難しいことだと思うのですが、delyの開発部の人は上手い人が多い気がします。

以下の記事に書いてあるプロダクト改善のプロセスは、柔軟に変化してきた一番わかりやすい好例だと思います。
とても為になると思うのでぜひご一読してみてください。

tech.dely.jp

他には、CTOの大竹さんの『越境型スキルのすゝめ』もまさに変化に柔軟に対応していくことが書かれた記事だと思います。

f:id:sakura818uuu:20181225121024p:plain

優しい人が多い

性格的に優しい人が多いです。 一見なんてこともない特徴かもしれないですが、実はとても重要な特徴だと思います。

第一に細かな気遣いをしてくれる方がとても多いです。

他には、
・わからないことを聞いたら丁寧に説明してくれる(環境構築とか特にわからないことが多いですよね)
・物腰がやわらかい
・よく感謝する(ありがとスタンプがよくslackで押されます)
・誰かがやらなきゃいけない仕事を自ら引き受けてくれる
・耳の痛いことをあえて言ってくれる
などとにかく細かなところで優しい人が多いです。

話を聞くのが上手い

話を聞くのが上手い人が多いです。
話しやすい雰囲気、というのもそうなんですが理解してくれるのがはやいし上手いです。

「一を聞いて十を知る」ということわざがありますが、まさにそんなかんじです。
私は自分の思い通りに言葉を伝えるのが少し苦手なので、開発部の人に相談させてもらうときにすごく助かっています。

f:id:sakura818uuu:20181225125039p:plain

他には
- 落ち着いている
- 和やか
- 人の意見を尊重する
- 難しいことをいいかんじに対応してくれる
- 哲学好き
などの特徴をもった人が多いと思います。

さいごに

少しでも開発部の雰囲気が伝わったでしょうか?
よく採用広報で「こんな人を求めています!」や「〇〇はこんな雰囲気です」と書かれていることがありますが、 入ってみないと正直わからないことが多いですよね。
この記事でその溝を少しでも埋めることができたら何よりです。

最後に採用情報です。クラシルを一緒に作る仲間を募集しています。
もしも就職活動や転職活動で悩んでいるならdelyを選択肢の候補にいれてみるのはどうでしょうか。また、この記事に書かれている特徴に当てはまりそうな人もぜひ検討してみてください。
よろしくお願いします!

www.wantedly.com

次回予告

明日は弊社のプロダクトデザイナーのミカサ トシキ(@acke_red)による「Fluid Interfaces実践 - なめらかなUIデザインを実現する」です。
ぜひご覧ください。

クラシルで実践しているプロダクト改善プロセスのすべて

こんにちは!

dely, Inc.でプロダクトマネージャー兼開発部ジェネラルマネージャーをしている奥原 (@okutaku0507) といいます。この記事はdely Advent Calendar 2018の24日目の投稿です。明日は待ちに待ったクリスマスですね。

先日は、弊社CTOの大竹 (@EntreGulss) から「越境型スキルのすゝめ」というタイトルで投稿がありました。自分の立ち位置を理解して、スキルにレバレッジをかけ付加価値つけながら、時代の急激な変化の中で日々戦っているたけさんの考えが書かれていて、とても面白い記事になっています。

f:id:okutaku:20181224153502p:plain

さて、Advent Calendarも終盤に差し掛かった今回は「クラシルで実践しているプロダクト改善プロセスのすべて」という題で、弊社が運営しているクラシルで実践しているリーンなプロダクト開発を一つ一つの事細かに紹介します。そのため、とても長い記事になっています。ですが、この記事を読んでいただき、不確実性に立ち向かう組織が増え、より良いサービスが世の中に増えたらいいなと思います。

また、弊社が運営しているレシピ動画サービスであるクラシルのことはよく知っているけれども、delyという会社あるいはそのエンジニアやデザイナーのことはわからないという方に、これからご紹介するプロダクト改善プロセスを通して弊社の開発部のことを少しでも知っていただけたら幸いです。

僕らは生活のインフラになるようなサービスを再現性高く世の中に提供していきます。クラシルだけではもちろんありません。クラシル規模のサービスがどんどん出てくる組織を目指して、この不確実性に向き合う開発体制を確立しています。現在、募集しているプロダクト開発部の職種です。一緒に良いプロダクトを創っていける仲間を探しています!

 

目次

 

1. リーンなプロダクト開発が必要になった経緯

クラシルでは新規のサービスや機能改善にリーンなプロダクト開発を取り入れています。業界的にそれらが必要になった経緯がまとまった素晴らしい記事があるので、リンクを貼っておきます。現在、メルペイのPMをされている川嶋一矢さん (@tsumujikaze) の記事です。

note.mu

クラシルではプロダクトの立ち上げから2017年の終わりにかけて記事の中にある、通常のアジャイル開発 (図左) を行っていました。

f:id:okutaku:20181222144944j:plain

なぜモダンなプロダクトチームによるリーンなプロダクト開発が必要なのか (https://note.mu/tsumujikaze/n/n8b5f9cfec2c9)

もちろん、ここでいう通常のアジャイル開発自体は広く一般的で、それで成功している会社も多いと思います。クラシルでも、基本的な機能でさえ不足していた初期では、この開発手法の方が上手く回っていました。しかしながら、幸いなことに多くの方にサービスを利用していただき、長期にわたり運営してきて、それらが上手くはまらなくなってきたのです。

僕らが陥った具体的な問題として大きいのは、頑張って開発した機能がほとんど使われないということが度々あったという問題でした。世の中には「この機能はリリースしてみないと使われるかわからない」という不確実性が存在します。その問題をどのように解決すればいいのか。僕らは四苦八苦していました。

f:id:okutaku:20181222145557p:plain

UXデザインへの理解を深める〜これからのデザイナーがすべきこと〜 (https://goodpatch.com/blog/about-basic-uxdesign)

Ruby on RailsやLaravelなどweb開発で使用する充実したフレームワークが枯れた技術となり、Herokuなど簡単にインフラの環境を構築することができるPaaSが普及した現代において、アイデアはすぐにコピーされ、機能的価値はすぐにコモディティ化します。何かをweb上でできることはもはや当たり前になったのです。

f:id:okutaku:20181224165541p:plain

僕らが開発しているクラシルですが、運営している身からしてこんなことを言ってはいけないと思うのですが、例えば「ハンバーグ」というレシピの作り方が知りたかったら、ユーザー視点で考えると正直なところ別にクラシルではなくても他のレシピサービスでも良いわけです。多分ですが、どのレシピサービスを利用したとしても美味しいハンバーグを作ることができるでしょう。一つ、クラシルの良いところをあげるとしたら、動画でわかり易いというのが強みであり、それがマーケットに刺さったポイントでもあります。しかしながら、クラシルをはじめとして他のレシピサービスは作りたい料理のレシピを知りたいと思った時に引く辞書的なレシピサービスとしては完全にコモディティ化しています。

では、クラシルをこれからさらに伸ばし、より多くのユーザーに使われ世の中に幸せを届けるためにはどうすればいいのか。僕らが出した答えは、噛み砕いた言い方をすれば「ユーザーが欲しいと (潜在的に) 考えているプロダクトを、しっかりと届ける」ことでした。

僕らはこれからクラシルを通してモノが買われる世界を創り、食に関わる今まで解決されてこなかった課題を解決することで、日本で一番使われる生活のインフラとなるサービスにしたいと思っています。さらには、プロダクトの再現性を高めて、クラシルを越えるようなサービスを何度も創っていき、数年後にはdelyから出るサービスは全部めちゃくちゃ流行るという世界を実現したいと考えています。その文脈において、現在どこの会社も採用には難航していると思うのですが、優秀な人材が足りていません。とりわけ、開発リソースは極限まで枯渇しています。

jp.techcrunch.com

そのため、少ない開発リソースを確実に成果に結びつけるために、リリースして使われない機能にリソースを1秒でも割くことは避けなければなりません。しかしながら、僕らが当たると確信した機能でもリリースしてみたら全く使われないという厳しい現実があります。ですが、その「この機能はリリースしてみないとわからない」という不確実性を実際にユーザーに使ってもらう前に下げる手法は存在します。それが、これから紹介するリーンなプロダクト開発なのです。

少々長くなりましたが、経緯をまとめると、

  • レシピサービスとしてのコモディティ化
  • 開発リソースの枯渇

という切実な現実を背景として、不確実性をいかに下げていき「これが欲しかった」を再現性高く実現させ、枯渇した開発リソースを着実に成果に転換していく方法として、リーンなプロダクト開発がその答えとなりました。

 

2. クラシル版リーンなプロダクト開発の概略

リーンなプロダクト開発は川嶋さんの記事にあるような形が原型ですが、クラシルで実際に取り入れていくに伴い、組織のフェーズや形態、プロダクトのフェーズや性質によってより現実に則した形に変わっていきました。それが下の図です。 

f:id:okutaku:20181222155336p:plain

Design by はしもん (@oyasumi_yayaya)

このリーンなプロダクト開発の本質は、要件定義 (何を創るべきか) のアジャイル化、デザインフェーズの導入です。図でもあるようにデザインフェーズと実装フェーズが分かれています。

今までのプロダクト開発を鑑みて、使われない機能がリリースされてしまう今までの開発プロセスの大きな原因として、ユーザーやビジネス上の課題設定は正しいけれども、解決策が間違っているということに気がつきました。これは、今までのアジャイル開発において、要件定義をPOあるいはPMとデザイナーが一気に課題選定から解決策、そしてUI作成までを行ってしまっていたから生じたのではないかという考えに至りました。この、課題と解決策が異なるというミスマッチを防ぐ方法として要件定義もアジャイル化して、課題に対する解決策として正しいのかを実際のユーザーに当てて何度もイテレーションを回すことで確かめていきます。

f:id:okutaku:20181222155309p:plain

つまり、上の図であるように、今までの開発においては紫色の線のように、機能をバーンとリリースした時に一気にその機能が課題に対する解決策なのかという不確実性の学びを得るのに対して、リーンなプロダクト開発では、要件定義をアジャイル化することで、イテレーション毎に不確実性を減らすような学びを得るため、その都度何を創れば良いのかを学ぶ方向修正を行います。こうすることで、一生懸命に開発工数を使った機能が大ハズレすることのリスクが減っていくのです。

これからさらに細かく一つ一つをみていきます。長くなりますが、どうぞお付き合いください。

 

3. どの課題を解くかで全てが決まる

f:id:okutaku:20181222160931p:plain

プロダクト開発において、最も大事になるのが解くべき課題を決めることです。

イシューからはじめよ―知的生産の「シンプルな本質」

イシューからはじめよ―知的生産の「シンプルな本質」

 

この本にもあるように、プロダクト開発においてもやりたいことが無限にあると思います。しかしながら、先ほどもあったように開発リソースが枯渇している現状では、課題を間違えてしまえば、余裕で開発者の一ヶ月くらいをムダにすることになります。競合ひしめくベンチャーにおいてそれは命取りになります。

ユーザーあるいはビジネスの課題は大きく分けて二つに大別すると考えています。

  • バケツを大きくする施策
  • バケツの穴を塞ぐ施策 

これらのどちらも欠かすことができません。

バケツを大きくする施策

バケツを大きくする施策で大切にしている考え方は、自分たちの枠組みで考えないことと、プロダクトを未来の達成したい目標から逆算して考えることです。

人間の思考はついつい自分の枠組みに束縛されてしまいます。具体的には、僕は元エンジニアなので、エンジニアリングに強みを持っていて、DAUが思うように伸びないという課題があった時に、とりあえず継続率をあげる施策を考えます。しかしながら、いくら継続率をあげたところで入っていくユーザー数にもコスト的な視点で限りがあるので、流入と離脱が釣り合う時がきます。その課題解決のアプローチは間違いではないのですが、継続率をあげることも大事だけれども、マーケコストに依存しない大きな流入経路を確立させることができれば、既存の継続率でも十分にユーザーは溜まっていくでしょう。

また、当たり前ですが事業計画上で達成しなければならない数字があります。それらをプロダクト側の人間が無視して開発を行うことは良くありません。プロダクト側の数字で達成しなければならないDAUやサブスクリプションの契約数があります。それから逆算した際に、今のCVRでは全く達成できないことがわかるかも知れません。その場合、今の方法を行っていれば確実に実現したい未来はこないでしょう。そのため、方法を変えるという意思決定をする必要があります。これは、定量的な側面ですが、プロダクトとして実現したい未来という定性的な側面でも同様のことが言えます。数年後にプロダクト上で実現したいユーザー行動があるならば、その行動変容を起こすようにプロダクトに対して変更を加えて行かなければなりません。ある機能をリリースしたからといって、既存のユーザー行動が一気に変わることは滅多にありません。

そして、バケツを大きくする施策で具体的なプロダクトへの変更を考える際には、実際のUXリサーチなどを行いながら、ユーザー行動から課題を発見してそれらに対する解決策を実装していきます。このようなアプローチが人間中心設計やジョブ理論ということだと考えています。

ジョブ理論 イノベーションを予測可能にする消費のメカニズム (ビジネスリーダー1万人が選ぶベストビジネス書トップポイント大賞第2位!  ハーパーコリンズ・ノンフィクション)

ジョブ理論 イノベーションを予測可能にする消費のメカニズム (ビジネスリーダー1万人が選ぶベストビジネス書トップポイント大賞第2位! ハーパーコリンズ・ノンフィクション)

  • 作者: クレイトン M クリステンセン,タディホール,カレンディロン,デイビッド S ダンカン,依田光江
  • 出版社/メーカー: ハーパーコリンズ・ ジャパン
  • 発売日: 2017/08/01
  • メディア: 単行本
  • この商品を含むブログ (6件) を見る
 

クラシルにおいては料理という一連のユーザー体験を考えると、料理は調理だけではないわけです。残念ながら、該当するツイートは消えてしまっているのですが、以下の記事で紹介されているツイートでは、調理という行動は料理の中では氷山の一角であることがわかります。

f:id:okutaku:20181222164157j:plain

料理をしない人に知って欲しいことを「絵」にしてみたら、想像以上に壮絶だと分かった (http://netgeek.biz/archives/76864)

今まで、クラシルはレシピが動画でわかりやすいということがユーザーに刺さり伸びてきたサービスですが、献立機能を開発した背景には、このように料理を毎日のようにしているユーザーには調理だけではなく、もっと大きな問題が山積しているという現実があり、その課題をクラシルでどう解決していくのかということを考えています。

prtimes.jp

バケツの穴を塞ぐ施策 

バケツの穴を塞ぐ施策は、基本的には既存のプロダクトの実装されている機能の改善を行うことです。そのため、溜まっているユーザーの行動データを定量的に分析して行きます。現在ではFirebase Analyticsなどを入れておけば、基本的なアプリの行動は把握することはできますが、もっと詳細に追いたい場合や自分たち用にカスタマイズしたいという要件がある場合は自社でデータ基盤を構築する必要があります。クラシルでもFirebaseなどは導入していますが、自社でデータ分析基盤を持っています。

tech.dely.jp

これらを駆使し、既存の機能に対して、その機能は本当に使われているのか、KPIを達成するためにはどのような変更が必要なのかを定量的な側面からアプローチしていきます。

 

4. 課題に対する解決策を考える

f:id:okutaku:20181222164817p:plain

定性的、定量的両軸で検討した際に、今僕らが解決すべきもっとも大切な課題を設定することができたとします。次のステップでは、設定した課題に対する解決策を考えます。アイデア出しの着想としては「SPRINT 最速仕事術」という本で紹介されている、Googleが実践しているDesign Sprintで、本来であれば5日間で実践する内容を、エッセンスだけを取り入れて、僕らで必要なことだけを行っています。

SPRINT 最速仕事術――あらゆる仕事がうまくいく最も合理的な方法

SPRINT 最速仕事術――あらゆる仕事がうまくいく最も合理的な方法

  • 作者: ジェイク・ナップ,ジョン・ゼラツキー,ブレイデン・コウィッツ,櫻井祐子
  • 出版社/メーカー: ダイヤモンド社
  • 発売日: 2017/04/13
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (3件) を見る
 

 これが実際の様子です。

f:id:okutaku:20181222165003p:plain

弊社ではこのように、会議室に数時間こもってアイデア出しをします (「仕様が決まるまで帰れまてん」と呼んでいます) 。この時大事になってくるのが、この会議に誰をアサインすべきかです。少なすぎても多すぎても会議自体が機能しなくなります。

  • POまたはPM
  • デザイナー
  • エンジニア (プロトタイプエンジニア)

絶対に意思決定者であるPOあるいはPMを参加させてください。これらの役職についている人は基本的にめちゃくちゃ忙しい身かと思いますが、プロダクトの明るい未来のために時間を取ってもらいましょう。意思決定者が不在だと、この会議で決まったことが後々になって覆ることがあるからです。プロダクトに関わるみんなで行った民主的な意思決定だったとしても、プロダクトに変更を加える最終的な意思決定を行うためにPOやPMが存在しているわけで、そこにいるメンバーが知るよしもない施策が走っていたりするので、プロダクト開発における組織全体の情報を持っている者には判断を仰ぐことを怠ってはいけません。たとえ、権限が完全に委譲されていたとしてもちょっと確認するくらいはした方がいいと個人的には思っています。

そして、この会議ではメンバーががっつり時間を使って議論できることに価値があります。これを短縮しては良いアイデアは出てきません。なぜなら、限られたメンバーで情報を瞬時に共有して知識レベルを揃え、過学習した結果として、良いアイデアが生まれやすいからです。アイデアはいくつもあれば良いわけではありません。考え尽くされた一つでも十分に成果を上げることは可能です。大事なのはちゃんとあらゆることを考え尽くした結果として出てきたアイデアなのかです。

また、この段階で次のステップに必要な全てを決めてしまいます。具体的には、どこまで精密なプロトタイプを作るかと、どのように機能的価値を検証し、ユーザービリティテストを行うかです。

 

5. プロトタイピング

f:id:okutaku:20181222170512p:plain

次にプロトタイプを作成します。この時に大事になってくるのが Fidelity (忠実度) という概念です。忠実度とは、ざっくりいうとどれだけ現実に則したプロトタイプを作るかの尺度です。High-FidelityとLow-Fidelityのプロトタイプが存在します。

  • High-Fidelity = コーディングによるプロトタイプ (Xcode)

  • Low-Fidelity = ペーパープロトタイプ or ワイヤーフレーム (Sketch)

が具体的な産物になります。

当たり前ですが、High-Fidelityなプロトタイプほど実装の難易度と工数が嵩みます。しかしながら、とても大事な考え方として「考えたアイデアが本当に課題に対する解決策として適しているのかという不確実性を最も下げることができるプロトタイプ」かどうかがあります。工数が嵩むからと言って、中途半端なプロトタイプでは、実際にその不確実性を下げることができず、本末転倒ということはよくあります。

なぜなら、中途半端なプロトタイピでは、動かない所などが多々ありユーザーの体験を阻害し、開発サイドの説明なしには動かすこともままならなくなってしまうからです。それでは、本当に欲しかった機能的価値検証やユーザービリティテストを僕らから誘導する形で期待した答えを言うようにユーザーを導いてしまうこともあるのです。

弊社のプロトタイピングのことが書かれた記事を貼っておきます。時間がある際に読んでいただけると幸いです。

f:id:okutaku:20181224153648p:plain 

6. アイデア検証

f:id:okutaku:20181222172123p:plain

プロトタイプができたら、次はそれが表現するアイデアを検証します。

f:id:okutaku:20181222172311p:plain

このように社内のクラシルユーザーを招いて、上から録画しつつ設問を当てて行きます。検証することは以下の二つです。

  • ユーザービリティ
  • 機能的価値

まずは、機能的価値の検証では、本当にそのアイデアが課題を解決できる方法なのかを検証します。機能的価値とユーザビリティを一度に試験することができる時もあれば、そうではない時もあります。それは、機能的価値がその場で検証しにくい性質を持っているからです。なぜなら、その機能は必要な時が来ないとなんの役にも立たないからです。例えば、被験者に対して「ここはキッチンです。あなたはこれから料理をしようとしています。それらを想像しながら以下の設問に答えてください。」という設定は、あくまでも想像でしかないので、本当に機能的価値を検証できるとは限らないのです。そのため、機能の特性によって機能的価値を検証する方法は異なりますが、それが現実的に可能ではない場合は、想像で答えてもらう場合もあります。

次にユーザービリティテストでは、使いやすさのテストを行います。実際のユーザーは様々なバックグラウンドを持っていて、持っている認知バイアスはそれぞれ異なります。そのため、プロトタイプを使っている際に手が止まるポイントや理由が異なるのです。それが、その個人による局所的なものなのか、それともユーザー全般的に影響を及ぼすものなのかを見極める必要があります。設問を当てながらそれらを瞬時に見抜くことは人間の脳には困難なため、被験者の承諾の上でテスト内容を録画をしています。その認知バイアスの話で弊社CTOのいい感じのスライドがあるので、引用しておきます。

speakerdeck.com

 

7. 学びを整理する

f:id:okutaku:20181222173810p:plain

機能的価値の検証およびユーザビリティのテストが済んだら、デザインフェーズの最後に得られた知見を共有し、整理します。この時に大事になってくるのが以下のことです。

  • 必ずデザインフェーズに関わる全員が参加する
  • 検証を実施した日に行う
  • 小さな問題も見逃さない
  • 得られたインサイトを深く掘って本質を見抜く
  • 次のイテレーション時のことを全て決める

まず大事なのは、検証を実施した日に、デザインフェーズに関わった全員がこの学習の場に参加することです。人間の記憶は短命です。数分前のことでさえ忘れてしまう場合があります。後日にしては、せっかく検証の際に色々考えていたことが揮発してしまうので、絶対にその日に知識を整理してください。また、デザインフェーズに関わる人全てが参加する必要があるのは、次のイテレーション時のことを全て決めておく必要があるからです。そもそも、これでデザインフェーズを終えるのか、まだイテレーションを回す必要があるのかを議論して決定する必要があります。

この学びを得るステップでは、必要あらば録画した動画を見直し、ユーザーが手を止めてしまった原因、それを解決するための情報アーキテクチャやUIの改善をどうするかを決めます。また、今回のアイデアは課題の解決策としてどこまで作り込むべきかもここで話す必要があります。

プロトタイプへの改善策が決まれば、再度デザインフェーズを回し、もう必要ないと判断したならば次のステップへ進んでいきます。

 

8. 意思決定者の決定を仰ぐ

f:id:okutaku:20181222180154p:plain

プロダクト開発において、様々な組織形態があると思いますが、弊社では意思決定者として、POあるいはPMを設置しています。現在では、僕が兼務している形ですが、会社の未来を左右するような機能の場合はCEOである堀江 (@santamariaHORI) の決定を仰ぐ場合もあります。

基本的に、意思決定と責任はセットである必要があると考えています。全員が意思決定者となり、プロダクトに変更を加えて行くことがベンチャーあるいはスタートアップの醍醐味と思われているかも知れません。それで使われるプロダクトが創れるならばそれが良いと思うのですが、不幸なのは使われない機能が世に出てしまうことと、会社としての方向性が開発部だけ異なる方向に向かっていることです。会社を成長させ、社員全員を盛り上げて行くためにも、プロダクトの成功は絶対に欠かせません。それに対して、責任を負っているのに、意思決定権がメンバー全員に分散しているのは組織構造としてよくありません。そのため、デザインフェーズの締めくくりとして、これが課題に対しての最適な解決策で、世に出して良いものかを意思決定者に確認を仰ぐのです。実質的に一つ前のステップである「学習」にも意思決定者が参加しているはずなので、そこで判断されることが多いですが、とても重要なことなので明確なステップとして切り出しています。

 

9. 要件定義を詰め、実装仕様書を作成する

f:id:okutaku:20181222180218p:plain

デザインフェーズを終えてブラッシュアップされたアイデアが世に出して良いと判断されたら、次は今までの要件定義を整理して実装仕様書を作成します。

f:id:okutaku:20181222180307p:plain

実装仕様書とは、1px単位で詰められたUIと、どのAPIを叩くのか、どのようなレスポンスが返ってくるのか、必要なパラメータは何かなどを事細かに書いたドキュメントです。もちろん、ボタンを押した際にどのようなインタラクションを行うかなども記述します。この実装仕様書を読めば誰でも同じ品質で実装を行うことができることを目指します。

この実装仕様書が必要になった背景として、アプリを開発している会社で問題になりがちなのが、iOSとAndroidで意図した挙動が異なるように実装されてしまうことがあります。それぞれ、Material DesignHuman Interface Guidelinesにあるようにデザイン原則が異なるので、意図した差異であれば良いのですが、仕様上では一緒のはずなのに実装の際にそれらの意図が明確に伝わらずに、確認漏れもあってリリースされてしまったというケースが頻発していました。そのため、誰が見ても均質な実装が行えるようにソースを一つとして、このドキュメントさえ読めば大丈夫というように仕組みで解決しました。

 

10. ゴリゴリ実装f:id:okutaku:20181222181137p:plain

さて、要件定義も煮詰まったら実装フェーズに突入します。この時に大切になるのは、以下のことです。

  • どのように実装するかはエンジニアの裁量に任せる
  • 実装仕様書は常に最新状態を維持する
  • 原則としてデザインフェーズまで回帰することはない

今までの文脈では、実装フェーズのエンジニアは「何を創るべきか」という所には参加していませんでした。それらは全てデザインフェーズにいる人たちで決めています。何を創るか決まったものを実装するなんてつまらないという人も、もちろんいると思います。先ほどもありましたが、全ての人が意思決定者となってプロダクトに変更していくことは組織が拡大していくことでカオスになり、必要ではない機能がリリースされる、その結果として枯渇状態の開発リソースが割かれてしまうという問題が生じることがよくあります。何度も伝えたいのですが、プロダクトが成長しないことが最も不幸なことです。このリーンなプロダクト開発は、より確実に確度が高い機能をリリースさせ、プロダクトを成長させるためにとってる開発プロセスなので「何を創るべきか」に関心があるならば自分自身の組織における役割を変え、デザインフェーズに入っていくことで開発できます。もちろん、リソースに余裕があれば、デザインフェーズで固まった要件をそのまま自分で実装するケースもあると思います。そこに垣根はありません。

実装フェーズにおいて、エンジニアはどのように実装するかと期日に間に合わせるためにはどうするかという所に責任を負います。どのように実装するかを考えることは、非常に難易度が高いことです。プロダクトが成熟し、組織が拡大すると、基本的にはソースコードは複雑性を増していきます。複雑性を増すことは、開発におけるスピードを落とす原因にもなりかねません。

f:id:okutaku:20181222182358j:plain

そのメタファとして、ジェンガが面白いです。開発初期では、ジェンガの中から一つを取り出すことは簡単です。しかしながら、開発が進んでくると一つのブロックを取り除き、どこに置くかはとても難しくなります。これが開発でも起こりうるのです。そのため、成熟したプロダクトの開発こそ、より良いアーキテクチャや読みやすく、理解しやすく、変更に強いコードを書くことはとても重要です。長期的な生産性の高さやテストのしやすさなどに必ず効いてきます。より良いコードを実現するために、コードの全体像を理解して、メンテナンス性が高い設計をすることに実装フェーズのエンジニアは責務があるわけです。

また、実装上にも不確実性が存在します。実装を進めていく上で、難しい実装や意図してなかった仕様の穴が潜んでいるわけです。それが発覚した時点で、先ほど作成した実装仕様書に変更を加えて、関係者に伝達する必要があります。それに応じて、リリース日の再検討などが発生するため、PMはステークホルダーに伝達する必要があります。

そして、原則としてその機能を開発すべきということはデザインフェーズで議論し尽くされたという前提があるため、全てを覆す事実がない限りはデザインフェーズまで立ち返ることはありません。その機能の必要性が納得できていないメンバーいるならば、それはPMに納得いくまで説明する責任があります。ここで大事なのは、全員が納得した状態で開発に挑むことです。僕らエンジニアは決まったものを実装するためにいるのではありませんし、ユーザーに価値あるプロダクトを届けるために、役割を分担して良いサービスを創っているので、全員が同じ方向を向いていることはとても大切なことです。

 

11. デバックテストを行う

f:id:okutaku:20181222183432p:plain

実装フェーズの最後のステップとして、デバックテストがあります。ちゃんとQAエンジニアがいれば良いのですが、そのような充実した開発体制が整っているところも多くはないと思います。

f:id:okutaku:20181222183549p:plain

弊社では、検証可能なバージョンをDeployGateで配布して、各々が持っている端末あるいは検証用に用意された端末でデバックを行なっています。その際に出た小さい実装漏れやクラッシュを引きこすようなバグをその再現方法などを専用のチャンネルでやりとりしています。大きな機能によっては、会議室を抑えてみんなでデバックしています。この時大事になことは、どれだけ小さなことでも発言することです。小さいことが実は大きなバグの引き金となることは歴史が証明してくれています。誰が実装したかなどは関係なく、発言することが大事です。

ここで修正すべき問題があれば、そのフィードバックをもとに実装仕様書を変更して、実装フェーズのイテレーションを回します。問題ないことが確かめられたら、晴れてリリース🎉となるわけです。

 

12. まとめ

今まで読んできていただいたように、このリーンな開発プロセスはとても長いですし、デザインフェーズに関わるメンバーも工数がかなりかかっています。そのため、全ての課題に対してこのリーンなプロダクト開発を適用することはおすすめしません。この開発プロセスを適用する判断の軸として「そのアイデアは課題に対して本当に最適な解決策となっているという不確実性が高いか、あるいはその課題が本当に今取り組むべき課題なのか」を持っておくと良いと思います。極論をいえば、文言をちょっと変えるだけの実装にデザインフェーズは不要な場合が多いです。少ない開発リソースでデザインフェーズを適用するかどうかも、そのメンバーで実装できたことのトレードオフの上に成り立っているということを忘れてはなりません

クラシルの開発プロセスにこのリーンなプロダクト開発を取りいれたら、世にでる施策の精度は一気に高まりました。全て当たったと思うほどの威力を発揮します。裏を返せば、今までリリースされてきた不要な機能のように確度が低いものが世に出なくなったということでもあります。

プロダクト開発はマラソンです。リズムが大切です。一見、煩雑に見れるこの開発プロセスを体験してみると、実は今まで以上に開発スピードが上がっていることに気がつくと思います。それは、今まで要件定義がバーンとなされていたことで、仕様の雑さなどから、手戻りなどが多く発生し、仕様の共有ができてなかったり様々な要因で開発が阻害されていたからです。それらが積み重なれば、エンジニアの心理的な余裕がなくなり、バグが発生してしまう要因にもなり得ます。

リーンなプロダクト開発のように、確度が高い施策を常に世に出せる開発体制は、成功体験を積みやすく、モチベーションにも還元されてきます。結局はプロダクトの成功が全てを癒してくれるのです。

もちろん、僕らもまだまだ完璧ではないし、多くのミスを犯してしまいます。しかしながら、リーンなプロダクト開発は僕らに不確実性に向き合うための勇気をくれたと信じています。

 

13. 終わりに

長い長い記事を読んでいただき、本当にありがとうございます。

明日は弊社Androidのエンジニア兼テックリード兼エンジニアリングマネージャーをしている梅森より「不確実性とうまくやっていくためのプログラミング設計論」というタイトルで記事が出ます。是非お楽しみにしてください。

このように僕らは、不確実性に向き合う組織を作って、ユーザーから「これが欲しかった!」をいただけるように日々開発を行なっています。自分たちが開発しているプロダクトを通して、世の中がより良くなっていくことはエンジニアの冥利に尽きます。しかしながら、現在開発部を含めてすべてのポジションで人が足りていません。僕らと一緒に世の中を変えるプロダクト創りに全力を出せる人を探しています。

www.wantedly.com

全てのプロダクト開発に幸あれ!

良いクリスマスを!