おいしい健康 開発者ブログ

株式会社おいしい健康で働くエンジニア・デザイナーが社内の様子をお伝えします。

おいしい健康のトンマナを一新したときのプロマネの話

はじめに

こんにちは、おいしい健康のWebエンジニアの二宮です。

おいしい健康のWebサイトは、去年の夏に大幅なトンマナのリニューアルを行いました。その際、サービスのトンマナ変更の主にプロマネ的な側面について、Web上になかなか参考になる記事が見当たらなかったので、おいしい健康ではどのように進めていったのか、参考までに記載しようと思います。

トンマナ変更の経緯

そもそもエンジニアチームのタスクとしては膨大なキューが溜まっている中で(おそらく読者のエンジニアのみなさまも同じような状況だとお察しします・・😅)、なぜトンマナ変更を実施しようとなったのかの経緯を記載します。

おいしい健康は、もともとはクックパッド(株)のひとつの事業部に所属していました。初期のおいしい健康はその中で生まれたサービスであったため、トンマナについてもクックパッドと似たような見た目になっていました。

その後、(株)おいしい健康として独立していく中で、事業の方向性としてレシピサービスから一歩前に進めて、総合的なヘルスケアサービスを目指していくこととなりました。その中で、これまでの UI ではユーザが受ける印象としてレシピサービスの枠から抜け切らず、ヘルスケアサービスとして制作されていたアプリ版おいしい健康( iOS, Android ) と比べて、ユーザ体験に大きな乖離ができるようになっていました。

そのため、何か新しい施策を試す際にも「Webどうしようか・・」のような声もチラホラ聞こえるようになってしまい、仕様検討やデザインにも別途工数がかかるようになってしまっていたため、大きなPJになることは想定されていたのですが、思い切ってWebのほぼ全てのページのトンマナを変更することとなりました。

トンマナ変更前のWebトップページ
トンマナ変更前のWebトップページ

進め方

PJ開始当初、Webチーム専任のエンジニアは2名しかいなかったため、1名が他サービス調査やワイヤーフレーム作成などのビジネス的なタスクを、もう1名が技術基盤周りのタスクを担当することにしました(ちなみに、おいしい健康ではディレクタ、デザイナに比べてエンジニアの数が全然多いので、施策検討やディレクションなどのビジネス関連のタスクもエンジニアが主体的に行うことが多いのも特徴かなと思います)。

その後、それらのタスクが片付いたらトンマナを変えるための画面実装は一緒に行う、という進め方にしました。

画面実装に入る前のタスクについて

ビジネス関連タスク

まずはトンマナ変更後のデザインについて、どのようなレイアウトにするか、というところから決めなくてはなりません。今回の変更では、おいしい健康が持っているコンテンツを適用しやすいことと利用ユーザが多いことから、Twitter の UI を参考にしました。

デザインチームの工数が逼迫していたため、エンジニア間で検討した UI をもとに以下のような各画面ごとのワイヤーフレームを作成し、その後デザインチームに修正してもらう、という流れを取りました。

おいしい健康Webトンマナ変更ワイヤーフレーム

ちなみに上記の小さな1つ1つが各画面のワイヤーフレームで、画面数としては、似たようなレイアウトの画面も含め約100画面弱くらいになりました。

技術基盤関連タスク

基盤関連のタスクについては実際に調査しないと見えにくいものも多かったのですが、今回は大まかに以下のようなタスクを実施しました。

  • Dockerfile の Ubuntu のイメージの更新(16.04 => 18.04
  • Ruby on Rails のバージョンアップ(5.0 => 5.1 => 5.2。おいしい健康のサーバサイドは Ruby on Rails で動いています)
  • bundle update
  • webpacker 導入
  • CoffeeSripet から TypeScript へ
  • フロントエンドの CSS 設計の調査( FLOCSS を導入することにしました)

スケジュール作成と実際にかかった工数

スケジュール作成については、非常に難儀でした。。仕様検討やワイヤーフレーム作成については、画面数はわかっていたので比較的見通しが立ったのですが、基盤関連のタスクについては手をつけてみないとどこで躓くかもわからないので、大まかなマイルストンだけを決めておいて、進捗に合わせてスケジュールやタスクを都度調整する、という進め方にしました。

ビジネス関連タスクについては、仕様検討から全画面の大まかなワイヤーフレーム作成までで約1人月、その後デザインに落とすまでに更に約1人月、と見積もっていました。基盤関連タスクについては、上に記載したそれぞれのタスクを約1.5週で終わらせるようなイメージで、約2人月程度で完了させるようなスケジュールを組んでいました。その後、膨大な量の画面実装が待ち構えているわけなのですが、それについては明確なスケジュールは作成せず、なんとなく7.5人月(3人 × 2.5ヶ月)くらいで実装しようと思っていました。(このあたりの工数感については、各サービスの状況ごとにまちまちだと思います)

最終的な工数ですが、ワイヤーフレーム作成については約1.5人月で済んだのですが、細かな詰めが甘い部分も多く、その後のデザイン作成については約2.5人月(+実装していく中で都度デザイン修正工数も発生)が必要でした。また、基盤関連タスクについては、スケジュールが間に合わず途中から基盤周りに強いエンジニアにも入ってもらい、約4人月くらいかかったかなという感じでした。

画面実装について

画面の実装については、作成されたデザインを元に、ひたすら約100画面分の実装行っていくという形です。

コードとしては、主に手を入れたのは hamlcss、JS( TypeScript )のみで、モデルにはほぼ手を入れず、コントローラもレイアウトファイルの分岐を追加するくらいにとどめるようにしました。

また、全ての画面の新しい UI が実装されるまでは、これまで通りの UI で表示をしなければなりません。ただ、修正した全てのコードを一気に master にマージするのは非常に危険なため、新旧どちらの UI を表示するかを環境変数で切り替えられるようにして、実装したコードは都度 master にマージしていくようにしました。

また、スケジュールに対して進捗が思うように進んでいない状態が継続して発生していたため、

  • 意図的にコードレビューの精度を下げる
  • 新しくWebエンジニアを採用して、実装できる手数を増やす

ということを行いました。

特に前者については、今後に技術負債を残すことは明らかで、エンジニアとしては非常に苦渋の決断だったのですが、収益基盤がまだそこまで強くないベンチャー会社でPJのスピード感を損なうことのリスクの方が大きいと判断して、このような方針にしました。

後者については、このタイミングでエンジニアの採用が行えた(しかも3人も!)というのは非常に幸運で、かつタスクとしても画面側の実装のみ(ロジックには手を入れない)、という比較的切り出しやすいタスクであったことも、追い風として働いたかなと思っています。

最終的に実装にかかった工数についてですが、上記の他に要件を削ったりなど都度スケジュールを短くする対応を取っていたものの、当初の7.5人月程度という見込みからは大きくずれ、約11人月程度の工数を要して、ようやくリリースにこぎつけました。リリースタイミングとしては、当初は8月くらいを目処にしていたものが9月半ばにずれ込んだ程度なのですが、工数としては当初の想定の1.5倍程度が必要だったという形でした。

なお、リリース後も追加の改修は発生していたのですが、ロールバックを伴うような障害は発生せず、大きなPJではあったのですが、なんとか無事にリリースできて非常にホッとしました。

トンマナ変更後のWebトップページ
トンマナ変更後のWebトップページ

KPI について

ちなみに、運用中のサービスのトンマナ変更については、一般的には利用ユーザからは使い勝手が変わってしまうので、なかなか歓迎されない施策ではあります😭(開発側は、新しい施策をリリースしやすくなってよいのですが・・)。おいしい健康のトンマナ変更も例外ではなく、多少 UU の減少が見られました。

その意味で、KPI の下げ幅をどの程度まで抑えられるかというのは、ひとつ重要なポイントとなってきます。

おいしい健康Webトンマナ変更UU推移

こちらが実際のトンマナ変更前後の UU 数の推移なのですが、約10〜15%程度 UU 数が下がっていました。ただ、この程度の下げ幅はある程度想定しており、また、この後においしい健康の有料会員化PJが控えていて、UU はいずれにしても減少してしまうことも見えていたので、結果的に KPI の減少はそこまで大きなインパクトではありませんでした。(と思っています😅)

振り返り

PJとしては(どうにかこうにか)無事リリースを迎えたものの、反省点も多く見つかりました。

その中で特にチーム内で意見の多かったのは、画面のデザインが作成された際に、UIのコンポーネントの整理・共通化が行われていない状態で画面実装に入ったため、細かな点が微妙に違うだけの UI コンポーネントをいくつも実装する事態が発生した、という点でした。

この点については、事前に多少想定はしていたものの、コンポーネントの整理・共通化をしている工数もないし、まぁ大丈夫かな・・と軽く考えていたことが最大のミスだったなと思っています。UI コンポーネントの整理については、ここまで大きな UI 変更を伴う施策でなくとも、新しいデザインを起こすことがある際にはぜひ対応しておくこととおすすめしたいと思います。

さいごに

ちなみにこのトンマナ変更PJですが、誰かから指名を受けてプロマネをやったというわけではなく、エンジニア間でWebの今後の進め方を考えていた流れで自然とプロマネをやることになった、という形でした。

おいしい健康では、エンジニア自身がどのような施策が良いか考えて自分で担当する、ということがよくあります。自分で考えて自分で進めることが好きなエンジニアの方は、ぜひお気軽にご連絡ください😊

株式会社おいしい健康 求人一覧

https://herp.careers/v1/oishikenko

ゼロから始めたASO

 こんにちは、エンジニアの國家(くにいえ)です。弊社のサービスおいしい健康はこれまでサービス開発に注力しており、広告宣伝は全く行ってきませんでしたが、3月にメンバーシップを導入し、サービスグロースの為の施策をより本格的に行っていくフェーズに突入しました。そこで今回は、AppleApp StoreにおけるASO(App Store Optimization)の活動の内、検索広告の設計についてご紹介したいと思います。

検索広告について

 検索広告は、予め設定したキーワードにマッチした単語をユーザーが検索した場合に表示され、ユーザーがその広告をタップする毎に課金されます。また、同じキーワードに対して複数のアプリが同時に広告を出している場合は、キーワードに設定された指し値を参照し、オークションの原理で広告を表示するアプリが決定されます。

f:id:oishi-kenko:20210513160047p:plain
検索結果広告

初めの一歩

 私たちは、全くノウハウを持ち合わせていなかったため、まずは予算3万円でプロダクトページに設定しているキーワードを基本に10個のキーワードを対象に広告を掲載してみることにしました。そして、週末迫る金曜日、Ad Searchの管理コンソールで広告キャンペーンの開始のボタンを期待感を持ちながら押しました。広告キャンペーンのステータスが”Running”に変わります。数時間後無事に広告が掲載され始めました。当たり前ではあるものの、掲載された広告を初めて目にした時には若干の嬉しさがありました。

 さらに、週末も終わる日曜日の夜、明日から平日ということもあり、何気なくApp Store アプリを起動して、設定したキーワードを検索しましたが、広告が表示されません。試しに、設定した10個全てのキーワードを検索しましたが、全て表示されません。そこで、状態を確認するため、広告の管理コンソールを開き、広告キャンペーのステータスを確認すると”Budget exhausted”の文字。当初の予算、3万円を消化してしまったのです。

再掲載の準備

 流石に、試行とはいえ、正味2日程度から得られるデータでは、広告計画を策定するには不十分です。予算を強化すると共に、キーワードも増やそうとしました。チームのメンバーの知恵も借りながら、ユーザーインタビューや問い合わせの内容も参照し、ユーザーがどんな状況に置かれているという事を再確認して、結果約200個のキーワードを用意しました。 また、キーワードの収集は、広告掲載の結果で各キーワードの広告パフォーマンスを評価するために、一般的な”料理”や”献立”といった”ビッグワード”とユーザー個別のシチュエーションを表すニッチな”スモールワード”に分類しながら行いました。この約200個のキーワードを使用した広告は、結果予算強化の甲斐あり10日間に渡って表示されました。

広告パフォーマンスの分析と傾向の把握

 今回の目的は、広告の”掲載”ではありません。広告の掲載計画を立てるのが最終目的なので、まず、CPA(Cost Per Acquisition)を確認しました。すると、我々が許容しうるCPAより高い値段になっていました。この原因を探るために次に、各キーワードのパフォーマンスを分析するために、以下のグラフを作成しました。横軸が、1日当たりの表示回数、縦軸がCR(Conversion Rate)です。この図では、右上に位置しているキーワードほど、パフォーマンスの良いキーワードということになります。これを見ると、パフォーマンスを上げるには、グラフにおいて右上に来る様なワードを増やす必要があることが分かります。

f:id:oishi-kenko:20210512180209p:plain
各単語のパフォーマンス(CR)

 さらに、改善の方向を明確にするために、上図の色付きの矩形の様にキーワードを6つのカテゴリに分類しました。そして、それぞれのカテゴリに下図の様に方針を決めました。

f:id:oishi-kenko:20210512181332p:plain
パフォーマンス分類の為のカテゴリ

 今後は、以下の1-4の工程を繰り返し、目標のCPAへ近づけていくためのトライ&エラーを続けます。

  1. 広告を掲載
  2. CPAの確認
  3. パフォーマンス分析を実施
  4. 上記方針に基づき広告に設定するキーワードセットを改善

 まだ、広告設計は始めたばかりですが、定量的にPDCAサイクルを回すという点は、コードのパフォーマンスチューニングの過程と同じだと気づきました。おいしい健康は、現在創業4年と新しい会社のため、幅広く様々な挑戦ができる環境があります。この環境を活かしてあなたも一緒に働きませんか。

カジュアル面談を行っています。気軽にお声掛けください。
https://herp.careers/v1/oishikenko

【Rails】ActiveModelを使ってFormObjectなるものをつくるぞ

こんにちは。 おいしい健康でwebエンジニアをしている安達です🍙

今日はRailsのForm周りをシンプルに書くためのFormObjectについてご紹介します。

accepts_nested_attributes_for だるすぎませんか

こんなことを思ったことある方多いんじゃないでしょうか?

accepts_nested_attributes_forめんどくせえ!!

僕はよく思います。

理由としては以下の3つが多いんじゃないかなと

  • Modelで同時に保存されるものがあるかどうかで書き方変えたくない

    Model層でFormの書き方(view層)に依存した書き方をしたくない

  • 独自の書き方が必要になってくる

    form.fields_forとかめんどくさい

  • validationよくわからん

    リレーションある場合のフォームで保存される時のみ…みたいなことしようとするとめんどくさい

Railsを作成したDHHもaccepts_nested_attributes_forについては以前から苦言を呈しており、廃止したいという旨のコメントがあったりします。

I'd actually like to kill accepts_nested_attributes_for in due time. Don't think we should promote it for this new API. Rather, let's just show how to do it by hand in the controller.

https://github.com/rails/rails/pull/26976#discussion_r87855694

こんな問題を考えなくて良いように今回はform_objectをActiveModelを用いて作成します!

やってみよう

ActiveModel書いてみるよ

シンプルに、今回はmodelsディレクトリに作成します。

いろいろ種類が出てくれば、form_objectsディレクトリを作成しても良いかもしれませんね。

以下のようなシンプルなモデルがあったとします。

class User < ApplicationRecord
  has_one :address, dependent: :destroy

  validates :name, presence: true
end
class Address < ApplicationRecord
  belongs_to :user

  validates :prefecture, presence: true
end

Userモデルにはname、Addressモデルにはprefectureというattributesがありますね。

今回は、これらをまとめて作成するためのform_object CreateUserFormを作成してみます。

class CreateUserForm
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::Validations

  # 個人的にはこうやってストロングパラメーターを指定しておくのが好きです
  PERMIT_PARAMS = %i[
    name
    prefecture
  ]

  attribute :name, :string, default: ''
  attribute :prefecture, :string, default: ''

  validates :name, presence: true
  validates :prefecture, presence: true

  def save!
    ActiveRecord::Base.transaction do
      user = user.create!(name: name)
      address.create!(user: user, prefecture: prefecture)
    end
  end
end

こんな感じで作成してみました。

ポイントは、 ActiveModel::Modelなどをimportしておくことで通常のModelライクに書くことができている点ですね。

attributeとして定義しておくことで、stringのnameというattributeをもちます、みたいなことが簡単にできるのが個人的にすごく好みです。 default値を指定しておくことで、initializeする(この場合だとCreateUserForm.newする)ことでdefault値を持つ、みたいなことができますね。

validationはもはやそのまま書けます。 例えば他のフォームではnameはなくてもいい、みたいなときもform_objectごとにvalidationを書いておくことで煩雑なvalidationを書く必要がない、というのがイチオシポイントです。

また、DBに保存する用のsave!メソッドを定義しておきました。 この中で、user, addressをそれぞれ保存していることがわかります。

テストも簡単だよ

このような形でform_objectを作成しておくと、テストが書きやすいというのも非常に大きなメリットなんですよね。

複数モデルを更新するテストは、accepts_nested_attributes_forで書いた場合はcontrollerに対してspecを書くことになるので、ユーザーのログイン情報が…とか返り値が…とかいう、実際の利用シーンを組み合わせたテストを書く必要があるので煩雑になりがちです。

しかし、form_objectで管理する場合だとmodelと同じような書き方ができるので、あくまでsave!メソッドが正しく動いているかを確認するためだけのテストが書けます✨

簡単にですがこんなイメージですね。

(factory_botは入っていい感じに設定している前提です)

require 'rails_helper'

RSpec.describe CreateUserForm, type: :model do
  describe 'validations' do
    subject { form.valid? }

    it 'accepts fulfilled object' do
      let(:form) { build(:create_user_form, name: '田中 太郎', prefecture: '北海道')
      is_expected.to be_truthy
    end
    
    it 'does not accept nil name object' do
      let(:form) { build(:create_user_form, name: nil, prefecture: '北海道')
      is_expected.to be_falsey
    end
    
    it 'does not accept nil prefecture object' do
      let(:form) { build(:create_user_form, name: '田中 太郎', prefecture: nil)
      is_expected.to be_falsey
    end
  end
  
  describe '.save!' do
    subject { form.save! }
    
    context 'when valid case' do
      let(:form) { build(:create_user_form, name: '田中 太郎', prefecture: '北海道')
      it 'save a user and a address' do
        is_expected.to change(User, :count).from(0).to(1)
        is_expected.to change(Address, :count).from(0).to(1)
      end
    end
    
    context 'when invalid case' do
      let(:form) { build(:create_user_form, name: nil, prefecture: '北海道')
      it 'does not save user and address' do
        is_expected.not_to change(User, :count)
        is_expected.not_to change(Address, :count)
      end
    end
  end
end

こんな感じですね。(ちゃんと書いてるわけじゃないのであくまでニュアンスとして受け取ってください🙏)

controllerまでシンプルになるよ

参考までに、このActiveModelを作成した場合のcontrollerも書いてみます。

class UsersController < ApplicationController

  def new
    @form = CreateUserForm.new
  end

  def create
    @form = CreateUserForm.new(user_params)
    if @form.save!
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def user_params
    params.permit(CreateUserForm::PERMIT_PARAMS)
  end
end

こんな感じですごくシンプルに書けますね。

ちゃんとエラーも@formに渡されるのでエラー処理も可能です。

こんな感じでスッキリシンプルに書けるので、ぜひ使ってみてください〜

====== 😇ここから宣伝です 😇======

実はこんなこと書いておきながら、おいしい健康ではほぼほぼActiveModelやform_objectはほぼほぼ使われていません😇

正確には、新しい実装箇所では取り入れられているところもありますが、古くからのコードをリファクタするというのはほとんどできていない状態です😭

スタートアップとしてどんどん新機能や改善をしていくフェーズなので、チャレンジにリソースを割くというのは当然です。

ある程度割り切りは必要だと思いつつ、一方で「なんとかしたいなー!」という気持ちを抱えながら新機能を作っている状態です🔥

こんな状態を「なんとかしたるぜー!😎🤙」という方、「リファクタは嫌だけどチャレンジするからその分リファクタの時間とってね💪」という方、どちらも大歓迎ですのでぜひ一度下記からおいしい健康の求人をご確認ください✨

https://www.wantedly.com/projects/603130

【松竹梅】リモートワーク環境を快適に。良バランスのおすすめ外部モニタ3選

おいしい健康エンジニアの濱田です。

家でも外部モニタを使いたい 🖥

昨年からの感染症の流行を受けて、リモートワークで働く機会が本当に増えました。

会社でなんとなく使っていた机や椅子、エアコンなどの什器がいかに快適だったか思い知らされる毎日です。なかでも、サブディスプレイ(外部モニタ)の有無は生産性に大きく影響を与えるので、家用に購入を検討している方も多いハズ 👀

よく選び方の相談を受けるので、2021年4月現在、バランスのいいおすすめの外部モニタを「🌲松・🎍竹・🌸梅」の3プランでご紹介したいと思います。

どれを選んだらいいのか 🤔

世の中にはモニタの候補がたくさんあって、比べるだけでも大変です。 ゲーミングやオフィス作業用、色校正を行うような DTP 用と様々あって迷ってしまうので、

一般的な開発/事務作業で使うならとりあえずこれ買っておけば失敗しないよ

というおすすめのモニタをピックアップしました

おさえておくべき条件 📝

人によっていろいろなこだわりポイントがあるとは思うのですが、業務でストレスなく使えるという観点から、以下の条件を満たすものを選びます(独断と偏見ですが、結構いいバランスなんじゃないかなと思っています)。

  • 🔍 ある程度以上の解像度がある
    • コードを書くにも、スプレッドシートをいじるのにも、相応の領域が必要。
    • WQHD (2560x1440)ドットが最低ライン。
    • フル HD ではちょっと不足している感じがする。
  • 📐サイズは 24 ~ 27 インチ程度
    • ある程度以上の解像度があると、それなりの大きさの画面でないと文字が小さく見にくくなります。
    • とはいえ設置場所の問題もあるので、大きくても 27 インチくらいまでが現実的じゃないでしょうか。
  • 👯‍♀️USB-C ケーブル一本で充電と映像出力ができる(USB-PD 60W以上)
    • 最近のモニタは、USB-C 端子で給電しながら同時に映像出力も受け付けるという機能を持ったものが増えていて、これを選んだほうがいいです。
    • ケーブル1本で机の上スッキリ!という効果もありますが、それ以上に、AC アダプタを忘れた!というミスがなくなるのが大きいです。
      • おいしい健康では、USB-C 端子(Thunderbolt 3 端子)を装備した MacBook Pro / MacBook Air を作業マシンとして支給しています。
    • 出社時/帰宅時に本体以外のことを何も考えなくていいし、重いアダプタを持ち歩く必要がなくなります。
  • 🔌電源を内蔵している
    • 本体はとてもスリムなんだけど、バカでかいACアダプタが必要なモニタ、結構あります。
    • 電源を内蔵しているモニタは取り回しが楽で、家で設置するときも場所を選びません。
    • 以外と顧みられませんが、特に自宅に設置する際、かなり使い勝手に影響する点だと思います。
  • 💓60Hz 以上のリフレッシュレート
    • 画面に表示される動画やアニメーションの滑らかさを決める数字です。だいたい 60Hz 以上が普通です。
    • 安いものだと、30Hz というのもあるのですが、マウスポインタの操作時にちらつきや残像を感じる人が結構いるみたいです。
    • 気にならない人は全然気にならないのでマストではありませんが、他の点を押さえるとこの条件も満たされていることが多いです。

おすすめ機種 👌

🌲 松. DELL U2720QM (60,000 円ほど)

公式サイト

MacBook 等の高解像度内蔵モニタと遜色ない滑らかさで画像を表示できる 4K ディスプレイで、かつ、前述の条件をすべて押さえています。 ちょっと高く感じますが、4K ディスプレイと考えるととてもお買い得ですし、今後10年戦えるよいバランスのモニタです。おすすめ。

🎍 竹. DELL U2520D (43,000 円ほど)

公式サイト

上記のポイントを押さえており、かつ細かい使い勝手を考慮すると、価格的にバランスがよいのがこのモデルです。 画面サイズも少し抑えめの 25 インチですので、省スペースに配置したい、という場合もこれがいいと思います。 Amazon では現在注文できないようですが、公式サイトからなら在庫があるようです。 いままで同僚におすすめした中で、購入実績がいちばん多いのがこのモデル。 ちなみにおいしい健康社内に多く設置してあるのもこれです。

🌸 梅. DELL P2421D (30,000 円ほど)

公式サイト

前述の🌲松/🎍竹2機種とも、コストパフォーマンスの良いモデルを選んでいるつもりではいるものの、絶対的な価格としてはそれなりに高価です 💸 もう一声!という場合に、少し条件を緩めておすすめしているのがこのモデルです。

前掲の項目のうち、USB-C 給電をあきらめ、電源ケーブルを含めてケーブルを2本使うモニタです。 この条件をはずすだけで選択肢がかなり増えるのですが、オフィスワーク向けとしてはこのモデルがバランスいいんじゃないかなあと思う次第です。

注意点 ⚠️

なお、USB-C 端子から映像出力をしたいなら、このモデルの場合以下のような映像出力接続用のケーブルを別途購入する必要があるので気をつけてください。

また、AC アダプタを会社や家においてきて作業できない!ということにならないように気をつける必要はあります。

結局どれがいいのか 🧐

以下のどれが自分の気持ちに一番近いかを考えて選びましょう

  • 今後も長く使える、すこし性能のよいやつをお得に買いたい!
    • 👉 🌲 を選んでください、後悔はしないと思います 😌
  • 難しいこと考えず、標準的なスペックでなるべく安いやつがいい!
    • 👉 🎍 が良いでしょう
  • ACアダプタを持ち歩くのは許容するので、少しでも安いやつがいい
    • 👉 🌸 が良いでしょう、家用にパソコンの AC アダプタを追加で購入することも検討してもいいと思います

なぜか全部 DELL のモデルになってしまいました。やっぱいろいろバランスよくて、おすすめしやすいんだよなあ。

プログラミング無しで「つながり」ネットワーク解析(2部グラフの隣接行列の計算)

こんにちは。データ分析チーム(仮)の花井です。最近は、社内に蓄積されたデータを分析し可視化する方法を模索しています。 過去の記事では協調フィルタリングによるレシピ類似の可視化があります。 oishi-kenko.hatenablog.com

「つながり」を解き明かすネットワーク科学は、これまで薬品設計・代謝工学などの分野で研究者の間で応用されてきましたが、最近では人事に関わる「ピープル アナリティクス」*1といった分野が急速に発展しています。弊社では、ホットなネットワーク科学を食と医療に応用して様々な「つながり」を分析・活用していこうとしています。

今回は、 弊社のSlackデータを例にして、スプレッドシートを使ったネットワークの解析を説明します。BigQueryやPythonを題材にしようと思ったのですが、エンジニアに限らず誰でもネットワークの解析ができるよう、ノンプログラミングできる表計算ソフト(MicrosoftエクセルやGoogleスプレッドシート)を使った解析方法について扱います。また、どのような見方でネットワーク構造を捉えているか解説します。

一言でいうと、メンバー集合およびチャンネル集合で構成される2部グラフの隣接行列の二乗をスプレッドシートで計算し、メンバーネットワークおよびチャンネルネットワークを隣接行列から考察します。

問題設定

「ほぼ全員が参加しているチャンネルは除いて、発言が活発なSlackチャンネルにどのメンバーが所属しているか」という情報をリスト形式のデータを持っていると仮定します。 このとき、どのメンバー同士が会話可能か、チャンネル同士の共通メンバー数を一覧で見る方法を考えます。

持っているデータは下記のような2つのリスト形式です。

リスト1(チャンネルとメンバーの所属関係)

チャンネル メンバー
A 1
A 2
A 5
A 10
... ....
E 45

リスト2(メンバー一覧)

メンバー
1
2
3
4
...
46

記事執筆時点では46名の社員がSlackに参加しています。ほぼ全員が参加しているチャンネルは除いて、発言が活発なSlackチャンネル上位5つにジョインしているメンバーを(Slack APIを使って無理やり)抽出しました。

アプローチと用語説明

今回ようなリスト1を見たとき「これは2部グラフの枝だな」と思いました。

なぜそのように思ったのか説明していきます。

まず、メンバーとチャンネルをそれぞれノードとして配置してみましょう。

f:id:oishi-kenko:20190823154022p:plain

Aチャンネルにメンバー5が所属している」という所属関係を下記のように線を結んで枝を作成します。

f:id:oishi-kenko:20190821175946p:plain

リスト1に基づいて、枝を追加していくと下記のようなグラフができます。

f:id:oishi-kenko:20190822235403p:plain

リスト1は、所属関係を示しているのでチャンネルとメンバーの要素間に枝が生成され、チャンネル同士やメンバー同士の枝はありません。

このように、要素間に枝がないノード集合が2つできるグラフを2部グラフと言います。

チャンネル集合を  {C} , メンバー集合を  {M} とすると、頂点が  {U \cup M} に属し、枝が  {(v, u), v  \in C, u \in M } となる無向グラフになっています。

隣接行列の表示

ここで、隣接行列を作ります。表計算ソフト(MicrosoftエクセルやGoogleスプレッドシート)ではピボットテーブルの作成により行います。

隣接行列とは、頂点uからvに枝があるとき(u, v)成分を1とし、枝がなければ0とする行列です。

隣接行列を作るために下記操作をしてリスト3を作ります。

  • リスト1をそのままノード1、ノード2に追加。リンクは1にする。
  • リスト1の左右を入れ替えてノード1、ノード2に追加。リンクは1にする。
  • 各メンバーをノード1、ノード2に追加。リンクは0にする。
  • 各チャンネルをノード1、ノード2に追加。リンクは0にする。

リスト3(隣接行列の準備)

ノード1 ノード2 リンク
A 1 1
A 2 1
A 5 1
A 10 1
... .... ...
E 45 1
1 A 1
2 A 1
... ... ...
45 E 1
1 1 0
2 2 0
3 3 0
... ... ...
46 46 0
A A 0
... ... ...
E E 0

このリスト3を選択してピボットテーブルを作成すると次のようになります。

f:id:oishi-kenko:20190822235246p:plain

このピボットテーブルを別シートにコピーして、空白を0に置換し、1を緑色で塗る書式設定をすると次のような見やすい表になります。

f:id:oishi-kenko:20190822235259p:plain

この大きな表から行がメンバー・列がチャンネルの部分だけ取り出して並び替えてみましょう。

f:id:oishi-kenko:20190823003203p:plain

色々なことが分かります。

  • 5つのチャンネルすべてに所属しているハブとなるメンバーがいる
  • 5つチャンネルは3つのグループに分かれている
    • (1) A,D (2) C, E (3) B
    • チャンネルA, Dは共通メンバーが多い
    • チャンネルC, Eは共通メンバーが多い

隣接行列の2乗を計算

さて、今回は次のことを明らかにしようとしています。

  • どのメンバー同士が5つのチャンネル内だけで会話可能か知りたい
  • チャンネル同士の共通メンバー数を一覧で見たい

言い換えると*2

  • ある2人のメンバー  {m_1, m_2 \in M} と任意のチャンネル  {c \in C} に対して  {m_1 c m_2 } となるパスが存在するか知りたい
  • ある2つチャンネル  {c_1, c_2 \in C}と任意のメンバー { m \in M}に対して パス  {c_1 m c_2} の数を知りたい

これを解くためにグラフ理論の定理を利用します!

あるネットワークに対する隣接行列{A}{n}{ {A}^n }{(i,j)} 成分は点{i}から点{j}への長さ{n}のパスの数となる

今回のケースに置き換えると次のようになります!

リスト3のピボットテーブルで生成した隣接行列{A}の2乗 { {A}^2 }{(i,j)} 成分はメンバー{i}からメンバー{j}もしくはチャンネル{i}からチャンネル{j}への長さ2のパスの数となる

隣接行列の二乗を計算には MMULT 関数を使います。*3

=MMULT(行列の範囲,行列の範囲)

隣接行列の隣に MMULT 関数を使った隣接行列の2乗を表示します。カラースケールで書式設定して見やすくしています。

f:id:oishi-kenko:20190823001317p:plain

それぞれの数字をどのように見ればいいのか説明します。

  • 行と列が同一メンバーのとき、所属しているチャンネル数を示しています。
    • メンバー '2' は2つのチャンネルに所属している
    • メンバー '3' は2つのチャンネルに所属している
    • メンバー '5' はすべてのチャンネルに所属している
    • メンバー '6' はどのチャンネルに所属していない
  • 行と列が異なるメンバーのとき、共通の所属チャンネル数を示しています。
    • メンバー '2', '3' で共通しているチャンネルはない
    • メンバー '2', '5' で2つ共通して所属しているチャンネルがある
  • 行と列が同一チャンネルのとき、所属メンバー数を示しています。
    • チャンネルAは19人が所属している
  • 行と列が異なるチャンネルのとき、共通の所属メンバー数を示しています。
    • チャンネルA, B で5人のメンバー共通して所属している。

目的のデータが得られましたね!

ネットワークの可視化

表計算ソフトではネットワークの可視化ができませんが、Cytoscape等のソフトウェアを使うとこのように可視化できます。

f:id:oishi-kenko:20190823013019p:plain

例えばメンバー '10' は オレンジ色で塗られたA, C チャンネルを通じて、ピンク色で塗られたメンバーとやり取りできます。

このピンク色のユーザー数が隣接行列の2乗から分かる事が本記事のポイントでした。

データ分析チームを募集しています

冒頭で「データ分析チーム(仮)の花井です」と自己紹介しましたが、現在はデータ分析チームはありません。これからデータ分析チームを作っていこうとしています!

今回はノンプログラミングのデータ分析事例を紹介しましたが、ネットワーク科学などの理論を駆使した面白いデータ分析をプログラミングによりスケールできるよう計画中です。

自社サービスの拡充と医療従事者との共同研究にチャレンジするデータ分析・可視化業務に興味ある方は下記からご応募ください。

※上記はアルバイトの募集ですが、フルタイム勤務も募集しております!

*1:https://rework.withgoogle.com/jp/subjects/people-analytics/

*2:グラフ理論に詳しい方が見たら突っ込まれそうな書き方ですが... 厳密に書くのは諦めました

*3:https://support.google.com/docs/answer/3094292?hl=ja

XcodeGenを使ったiOSプロジェクトに、XCUITestとXCTestを導入した話

はじめまして。昨年10月からiOSエンジニアとして入社しました、佐々木と申します。 子育て中のため時短で働いております。

今回はおいしい健康アプリのプロジェクトに、Apple公式のテストフレームワークであるXCUITest(UIテスト)とXCTest(Unitテスト)を導入した時の事を書きます。 XcodeGenというツールにも対応したので、そこも含めての内容となります。

XCUITestを導入

まずは、XCUITestの導入手順になります。

  • File> New> Target...> iOS UI Testing Bundle> Next
  • ProductNameにoikenUITestsと入力> Finish
  • プロジェクト配下に、ProductNameと同じディレクトリとファイルができます。
  • ファイルを開き、testExampleメソッド内にカーソルを置くと、赤丸のRecord UI Testボタンが有効になります。
    f:id:oishi-kenko:20190719144601p:plain
    Record UI Test Button
  • そのボタンを選択すると、アプリが起動します。
  • アプリ内のボタン等をタッブすると、挙動が自動でファイルに記述されていきます。スゴイ!
  • テストを実行するには、cmd+Uキーです。

XCUITestの方はこれでOKです。

XCTestを導入

次に、XCTestの導入手順になります。

  • File> New> Target...> iOS Unit Testing Bundle> Next
  • ProductNameにoikenTestsと入力> Finish
  • こちらもプロジェクト配下に、ProductNameと同じディレクトリとファイルができます。
  • ファイルを開き、試しにテスト処理を入れます。 その際アプリの処理を呼ぶには、importの並びに以下のような記述が必要となります。
@testable import oiken

しかしこのままだと、No such module ...でエラーになってしまいます。

プロジェクトの設定ファイル等を修正

エラーを解消するには、いくつか設定ファイルを変更する必要があります。 修正箇所はstack overflowの投稿をいくつか参照してを見つけました。

  • Podfileにテストのターゲットを追加します。

修正前

pod 'XXX'
pod 'XXX'

target 'oiken' do
end

use_frameworks!

修正後

def standard_pods
  pod 'XXX'
  pod 'XXX'
end

target 'oiken' do
  standard_pods
end

target 'oikenTests' do
  inherit! :search_paths
  standard_pods
end

target 'oikenUITests' do
  inherit! :search_paths
  standard_pods
end

修正後は以下のコマンドで、CocoaPodsを更新します。

bundle exec pod install

  • oiken> Product Module Name> アプリ名を変更します。 開発スキーマの末尾に追加していたDevの文字列をとり、どのスキーマoikenにしました。

  • oikenTestsとoikenUITests> Build Setting> Defines Module> Yesに変更します。

  • oikenTestsとoikenUITests> iOS Deployment Target> iOS10.3に変更します。 テストターゲットのDeployment Targetがデフォルトの最新になっていたので、変更しました。

これでビルドが通るようになります。 cmd+Uキーで、テストを実行できました!

プロジェクトの環境にもよりますが、このような手順でテスト環境が整います。 しかし今回は、XcodeGenの対応も必要となります。

XcodeGen

おいしい健康アプリのプロジェクトは、複数のエンジニアが開発しているため、以前からXcodgeGenを導入しています。 https://github.com/yonaskolb/XcodeGen

XcodeGenは、project.pbxprojのコンフリクトを防ぐためのもので、ymlに設定情報を書き、そのymlからproject.pbxprojを生成してくれるツールです。 おかげ様で最近はコンフリクトする事が少なくなり、とても生産性が上がった気がします!

しかしテストを導入する場合には、project.ymlにその変更を反映しなくてはなりません。

project.ymlに変更を反映

XcodeGenはOSSなので、GitHubにあるドキュメントやソースコードを見ながら修正箇所を見つけました。 セキュリティ上全部は載せられないのですが、部分的に紹介します。

  • プロジェクトファイルを戻します。 まずは以下のコマンドで、project.pbxprojを元に戻します。 (厳密には、project.ymlから生成するため、変更が元に戻ってしまうのです)
xcodegen & bundle exec pod install

この状態だと、cmd+Uキーを押しても何も起こりません。

  • Product Module Name追加します。
settingGroups:
  oiken:
    base:
      PRODUCT_MODULE_NAME: "$(TARGET_NAME)"
  • UIテストを追加します。
oikenUITests:
  type: bundle.ui-testing
  platform: iOS
  sources:
    - oikenUITests
  settings:
    groups:
      - oiken
    base:
      GCC_PREPROCESSOR_DEFINITIONS: "$(inherited) COCOAPODS=1"
      INFOPLIST_FILE: "$(SRCROOT)/oikenUITests/Info.plist"
      IPHONEOS_DEPLOYMENT_TARGET: "10.3"
      CODE_SIGN_STYLE: Automatic
      DEFINES_MODULE: true
      PRODUCT_BUNDLE_IDENTIFIER: "oikenUITestsのバンドルID"
    configs:
      Develop:
        TEST_HOST: "$(BUILT_PRODUCTS_DIR)/oikenDev.app/oikenDev"
      Release:
        TEST_HOST: "$(BUILT_PRODUCTS_DIR)/oiken.app/oiken"
  dependencies:
    - target: oiken
  • Unitテストを追加します。
oikenTests:
  type: bundle.unit-test
  platform: iOS
  sources:
    - oikenTests
  settings:
    groups:
      - oiken
    base:
      GCC_PREPROCESSOR_DEFINITIONS: "$(inherited) COCOAPODS=1"
      INFOPLIST_FILE: "$(SRCROOT)/oikenTests/Info.plist"
      IPHONEOS_DEPLOYMENT_TARGET: "10.3"
      CODE_SIGN_STYLE: Automatic
      DEFINES_MODULE: true
      PRODUCT_BUNDLE_IDENTIFIER: "oikenTestsのバンドルID"
      TEST_HOST: "$(BUILT_PRODUCTS_DIR)/oiken.app/oiken"
  dependencies:
    - target: oiken
  • スキーマのTestにテストターゲットを追加します。
schemes:
  Develop:
    test:
      config: Develop
      gatherCoverageData: true
        targets:
          - name: oikenUITests
          - name: oikenTests
  Release:
    test:
      config: Release
      gatherCoverageData: true
        targets:
          - name: oikenUITests
          - name: oikenTests
  • ymlからproject.pbxprojを生成します。 修正後は以下のコマンドで、再生成します。
xcodegen & bundle exec pod install

ymlに記述した設定は、階層がずれるだけでプロジェクトファイルに反映されず、、結構苦労しました。。。

  • cmd+Uキーで、テストが実行できます!

これでテスト環境が構築できました。

おわりに

今回テストを導入したきっかけは、try! Swift Tokyo 2019 でテストの重要性を再認識したからでした。 まだまだスタートラインに立っただけの状態ですが、少しずつ自動テストの環境を育てていこうと思います!

Androidで動画をサクッと撮るコマンドラインツール作った

ここ2~3年Android開発から離れていましたが、最近Androidエンジニアとして復帰を果たした @tomorrowkey です。

開発を楽にするために日頃からいくつかの工夫をしており、以前Cookpadに在籍していたときに Android開発を爆速にする10のコマンドラインスクリプト という記事を書きました。

今回Androidエンジニアとして復帰するにあたって、Androidアプリの開発をするためにそういったツールを準備をしているうちに、いつのまにか新しいスクリプトを書き始めていました。まるでラーメンを作るために畑から作っている人たちみたいですね。

コードレビュー時によくあるシーン

おいしい健康ではGitHubリポジトリとして使っており、PRを作ってマージするためには、他のエンジニアにレビューしてもらう必要があります。 モバイルアプリだとよくあると思うのですが、このときに動きが分かるものが説明に貼られていると、レビュアーにも親切です。

PRに貼られたGIFアニメーション

これはiOS版おいしい健康に対するPRに貼られたGIFファイルです*1GitHubに添付できるファイルの種類の中で、アニメーションできるのは、GIFだけです*2。なので、iOSの場合、シミュレータであれば、画面をそのままキャプチャしたり、フィジカルデバイスの場合は QuickTimeミラーリングしたものをキャプチャしたりしていました。

Androidの場合

Androidの場合にはどういった方法でキャプチャすることが可能でしょうか。

少し前でしたら AndroidTool というものがあり、なかなか手軽でおすすめしていましたが、最近息をしている様子がありません。

ApowerMirrorVysor などを使えばキャプチャできそうですが、一部有償なわりに私がそこまで使いこなすイメージも沸かなかった。デバイス側にアプリのインストールが必要などの理由で、断念しました*3

そもそもAndroidには録画機能がある

そもそもなんですが、Androidには画面を録画するためのコマンドラインが用意されています。

Android Debug Bridge  |  Android Developers

このコマンドラインを使えば録画することができます。 ですが、本来の目的である「コードレビュー時にGIFファイルを貼りたい」を達成するためには、2つの不満点があります。

  • 動画ファイルはデバイス側に保存される
  • 動画のままではGitHubに貼り付けられない

前置きはだいぶ長くなりましたが、この不満点をスクリプトを書くことで解決しました。

android-screen-record

本来の目的である、「コードレビュー時にGIFファイルを貼りたい」という欲望を満たすために、android-screen-record というコマンドラインツールを書きました。

github.com

やっていることといえば、adb shell screenrecord を実行して、動画ファイルを抜き出して、ffmpeg でGIFファイルに変換していることくらいです。 いくつか工夫したことはありますが、詳しくはコードを読んでみてください。稚拙なコードなので、きれいに書く方法をご存知であればPRお待ちしております!🤗

使い方

ただ単にコマンドを実行すれば録画が始まります。今のところオプションはありません。

$ android-screen-record

ctrl + c でインタラプトすると録画を終了します。自動的にGIFファイルに変換してデスクトップにコピーされますので、あとはGitHubにペタペタ貼るだけです。動画ファイルもそのまま残しているので、単に録画だけしたいケースでも使うことができます。

インストール方法

homebrew をお使いであれば2つのコマンドだけでインストール可能です。

$ brew tap tomorrowkey/self
$ brew install tomorrowkey/self/android-screen-record

残る問題点

ffmpeg のオプションをあまり知らないからか、GIFアニメーションがなかなか大きいファイルサイズになってしまいます。本来の目的から考えると多少フレームが落ちたり、画質が悪いのは受け入れられるので、質を落としてファイルサイズを減らせるような工夫ができるといいなあと思っています。

おわりに

今回はAndroidエンジニアとして復帰するにあたり作ったツールをご紹介しました。 おいしい健康では、Androidだけではなくサーバサイドやサービス開発など、バリエーションに富んだ開発に興味があるエンジニアを募集しております*4。 もし興味があればWantedlyよりご応募ください!

*1:GIFアニメーションを表示しているシーンをGIFアニメーションとして表示するのはなかなかシュールですね

*2:File attachments on issues and pull requests - GitHub Help https://help.github.com/en/articles/file-attachments-on-issues-and-pull-requests

*3:さんざんお世話になったadakodaさんの ASM ももう息をしてなかった…

*4:もちろんAndroidに特化した人も募集中です