クラシル開発ブログ

クラシル開発ブログ

~OSSから学ぶ~ MVCフレームワークの保守性がモリモリ上がるクラス設計

こんにちは、delyコマース事業部エンジニアの小川です。

先月11月に入社し、エキサイティングな毎日を過ごしています。

この記事はdely Advent Calendar 2019 - Qiitaの24日目の記事です。

昨日はSREの松嶋さんが「AWS RunCommandを使ってEC2上に監視ダッシュボードをサクッと作る(Ansible+Terraform+Grafana編)」という記事を書いてくれましたので是非そちらも読んでみてください!

tech.dely.jp

コマース事業部では、現在「事業開発」と「ソフトウェア開発」がほぼ同時に進行しており、プロジェクトにおける確定要素と不確定要素が複雑に絡み合っています。 スピード重視でゴリゴリ実装していくのも興奮しますが、変化に耐えづらい実装をしてしまうと、その後の開発スピードに影響していまい、事業のスピードが落ちるなんて事にもなりかねません。

そこで、プロジェクトの保守性や拡張性をあげるには、どういった設計をしたら良いかを、OSSであるGitLabを一例として見ていきたいと思います。

今回は、model view controller 以外のどんなディレクトリ(=クラス)を導入しているか、それがどんな役割を担っているかを見ていきましょう。

それではいってみましょう。

GitLabが導入しているディレクトリ

GitLabはRailsで実装されています。app配下のディレクトリで、デフォルトで作成されないものを抽出すると以下のようになりました。

  • finders

さまざまな条件に基づいたコレクションを取得するクラス

  • graphql

Graphqlに関するクラス

  • policies

権限確認系に使われるクラス。独自のDSLで実装されている。

  • presenters

viewに関わるロジックやデータを持つオブジェクトをviewに提供するクラス

  • serializers

フロントで使われるJSONを構築するためのビジネスルールをカプセル化しているクラス

  • uploaders

CarrierWaveに依存しているUploader

  • services

ビジネスロジックが取りまとめられているクラス

  • validators

Activerecordが提供しているカスタムバリデーター

  • workers

SidekiqのWorkerクラス

今回はこの中から、個人的にあまり導入したことがない、finderspresentersあたりを見ていきます。

finders

finderクラスは「さまざまな条件に基づいたコレクションを取得する」責務を持つクラスになっています。

例えば、プロジェクトモデルの中でこのようなイシューを取得するメソッドを実装するより、

class Project
  def issues_for_user_filtered_by(user, filter)
    # たくさんのロジック...
  end
end

issues = project.issues_for_user_filtered_by(user, params)

下記のように実装すると、よりモデルを薄く保つことができるよ!っていうイメージですね。

issues = IssuesFinder.new(project, user, filter).execute

GitLabのFinderクラスは、基本的に#executeのみをパブリックメソッドとして持っているみたいです。

実装を見ていく

では実際にProjectsFinderを例に挙げて見ていきましょう。

まずはどこで#executeが実行されているか探してみます。 ありました、Admin::ProjectsControllerで以下のように実行されています。

ProjectsFinder.new(params: finder_params, current_user: current_user)
              .execute
              .includes(:route, :creator, :group, namespace: [:route, :owner])
              .preload(:project_feature)
              .page(finder_params[:page])

paramsにfinder_paramsを、current_userにはcurrent_userを指定しています。 find_paramsは、取得するプロジェクトの条件、つまりフィルタリングするパラメーターや、ソートの条件などのパラメーターを含めています。

では、ProjectsFinderの実装はどうなっているのでしょうか。

#initializeには、paramsとcurrent_userとproject_ids_relationをキーワード引数で渡します。

def initialize(params: {}, current_user: nil, project_ids_relation: nil)
    @params = params
    @current_user = current_user
    @project_ids_relation = project_ids_relation
end

#excuteの実装は以下のような形です。

def execute
    user = params.delete(:user)
    collection =
      if user
        PersonalProjectsFinder.new(user, finder_params).execute(current_user) # rubocop: disable CodeReuse/Finder
      else
        init_collection
      end

    collection = filter_projects(collection)
    sort(collection)
end

変数collectionに、フィルタリングのベースになる、プロジェクトのコレクションを代入しています。 その後、#filter_projectsでプロジェクトのフィルタリングをおこなった後、#sortにて結果のソートをおこなっていました。

フィルタリングとソートの条件は、#initializeの時に渡したparamsで指定しています。

メリットになりそうな事

パブリックメソッドである#executeが見通しがよく、コードリーディングしやすいと感じました。 デルメルの法則にも違反していなく、依存も少ない(浅い?)と言えそうです。

このコレクションの取得を、modelに#filterのようなメソッドで実装したらどうなるでしょうか? ProjectsFinderに実装されている、多くのprivateメソッドがmodelにも実装されることになります。しかもそのメソッド達は、#filterの結果を達成するために切り出されている(特に他のメソッドでは使われない)ロジックなので、そのメソッド達はfat modelになってしまう要因の一つだと思います。

取得のロジックが単純なうちは良いですが、上記まで複雑になってきたり、必要なパラメーターが増えてきたら、Finderクラスの実装を考えていいかもしれません。

ですが、プロジェクトの初期段階でも、取得系の実装が大きくなることが分かっている、かつ不確定要素がたくさんありそうならば、取得系のロジックをFinderに集約し、呼び出し側が変更に影響しないように実装するのもありだと思いました。

presenters

presenterは、viewに関わるロジックやデータを持つオブジェクトをviewに提供するクラスとなっています。 viewに直接書いてあるロジックや、modelにviewに関連するロジックやデータのメソッドは、presenterに実装します。

実装をみていく

使われ方を見てみます。今回はProjectPresenterを見ていきます。 presenterはviewで下記のように呼び出されていました。

-# @projectはProjectPresenterのオブジェクト
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)

パーシャルのreder時に、引数として ProjectPresenter#statistics_anchors の返り値を渡しています。 renderされているパーシャルは、

- anchors = local_assigns.fetch(:anchors, [])

- return unless anchors.any?
%ul.nav
  - anchors.each do |anchor|
    %li.nav-item
      = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.is_link ? 'nav-link stat-link d-flex align-items-center' : "nav-link btn btn-#{anchor.class_modifier || 'missing'} d-flex align-items-center" do
        .stat-text.d-flex.align-items-center= anchor.label

のようになっています。

anchorsで渡されたデータを使って、リンクを生成していますね。 このパーシャルは、#link, #label, #is_link, #class_modifierのメソッドもつオブジェクトに依存しています。

では次に、ProjectPresenter#statistics_anchorsの実装見てみます。 下記はProjectPresenterの実装の一部です。

class ProjectPresenter < Gitlab::View::Presenter::Delegated  
  presents :project

  AnchorData = Struct.new(:is_link, :label, :link, :class_modifier, :icon)
  def statistics_anchors(show_auto_devops_callout:)
    [
      commits_anchor_data,
      branches_anchor_data,
      tags_anchor_data,
      files_anchor_data
    ].compact.select(&:is_link)
  end

  def commits_anchor_data
    AnchorData.new(true,
                   statistic_icon('commit') +
                   n_('%{strong_start}%{commit_count}%{strong_end} Commit', '%{strong_start}%{commit_count}%{strong_end} Commits', statistics.commit_count).html_safe % {
                     commit_count: number_with_delimiter(statistics.commit_count),
                     strong_start: '<strong class="project-stat-value">'.html_safe,
                     strong_end: '</strong>'.html_safe
                   },
                   empty_repo? ? nil : project_commits_path(project, repository.root_ref))
  end
end

ProjectPresenter#statistics_anchorsは#commits_anchor_dataや、#branches_anchor_dataを集めて返しています。

#commits_anchor_dataは、構造体であるAnchorDataを生成・返却しています。ProjectPresenterは、Gitlab::View::Presenter::Delegated(Rubyの標準のSimpleDelegatorを使用)を継承しているので、Projectのインスタンスメソッドである、#statisticsにもアクセスできます。

ちなみに、#branches_anchor_dataなど、#*_anchor_dataという命名のメソッドは全てAnchorDataを返却していました。

他にもオブジェクトの生成の仕方や、URL生成のヘルパーなど、しっかり作り込まれているのですが、長くなりそうなので割愛します。 オブジェクト生成の部分を端的に紹介すると、直接ProjectPresenter.newすることを禁止しており、下記のようなパターンで生成するようにしています。

# presentメソッドで生成するパターン
@project.present 

# Factoryクラスを使って生成するパターン
Gitlab::View::Presenter::Factory.new(@project).fabricate!

メリットになりそうな事

viewの表示のルールがpresenterに集約されていて読みやすいと思いました。 表示してはいけないものを表示してしまったなどの事故が起きにくくなりそうです。viewがコンフリクトした時の恐怖からバイバイできるのも個人的に好きです。

また、viewに比べかなりテストしやすくなっています。

導入するときには、presenterがどういった粒度で実装されるかをチームで決めておいた方が良いかもしれません。 GitLabはmodelに対してpresenterが実装されている風に見受けられました。(models/project.rbに対して、presenters/project_presenter.rbのイメージ)

プロジェクトによって、違う粒度での実装もありえると思います。例えば、viewで関連データが複雑かつ、多くのところで使われているパーシャルがあったら、それと1対1で対応させるようにすれば幸せになれそうですね。コントローラーにも同じロジックを書かなくて済むし、変更に対する影響範囲も明確です。もしコマース事業部で導入するとしたら、draperを使ったDecoratorとどんな住み分けするか(そもそも導入しない)とか、チーム内で議論してみたいです。

最後に

今回はこんな風にGitLabのクラス設計を見ていきました。 プロダクトの特性によって設計の仕方も変わってくると思うので、違うOSSも読んでみるのも楽しいと思います。

「保守性がモリモリ上がるクラス設計」と題しましたが、クラス設計に銀の弾丸はない気がするので、プロダクトの変化しやすい部分はどこか、設計することでどんな問題を解決したいかを、チーム内で議論を重ねて実装・検証することが良さそうです。

コマース事業部では事業も開発も挑戦することが多く、エンジニア・デザイナーを強く募集しています。 もし興味がある方がいましたら、お気軽にご連絡ください!

www.wantedly.com www.wantedly.com

AWS RunCommandを使ってEC2上に監視ダッシュボードをサクッと作る(Ansible+Terraform+Grafana編)

こちらは、dely advent calender 2019の23日目の記事です。
qiita.com
adventar.org
昨日は、サーバーサイドエンジニアのyamanoiさんが「画像管理をActiveStorageからCarrierWaveへ乗り換えた話」という記事を書きました。興味を持った方は、是非読んでみてください!
tech.dely.jp

こんにちは!
今年11月からdelyに入社しました開発部SREの松嶋です。
本記事では、Systems ManagerのRunCommand (Ansible-playbook)を使うことでより簡単に監視ダッシュボードを作ることができたので、その手順について紹介したいと思います。

はじめに

今年9月にGitHubまたはS3に保存しているAnsible-playbookを直接実行する機能がSystems Managerに導入されたのを覚えていますか。
この新機能によって、Ansibleを使うためにEC2にssh接続用の公開鍵の作成や管理をしなくてもplaybookを実行可能になりました。Ansibleのplaybookを作成するだけでサーバー設定ができるのは、設定や管理面で楽になりますよね。
ちょうど弊社でも運用やセキュリティの観点からSystems ManagerのRunCommandやAutomationを使用することで、sshしなくてもデプロイできる手順に順次置き換えている最中です。
aws.amazon.com

また今年の11月には、Grafana6.5がリリースされました。このアップデートによって、AWS Cloudwatchのメトリクスをより効率的に監視できる機能が新しく追加されました。例えば、ワイルドカードを使って動的なクエリを書くことが可能になったり、事前構築されたダッシュボードが用意されるようになったため素早くモニタリング開始することができるようになっています。
aws.amazon.com

そこで、今回はこれらの新機能を試すために、Systems Managerの公式ドキュメント「AWS-ApplyAnsiblePlaybooks」を使ってRunCommandでGrafanaの監視ダッシュボードを構築してみたいと思います。

今回使ったものは、以下の通りです。

  • Grafana 6.5.2
  • Nginx 1.16.1
  • Terraform 0.12.12
  • AWS-ApplyAnsiblePlaybooks (Systems Managerドキュメント)

Grafanaプロビジョニング用設定ファイルの用意

現在のGrafanaでは、データソースやダッシュボードをファイル管理することが可能となっています。そのため、事前に設定ファイルを用意しておけば、Grafanaの起動と同時にモニタリングを開始することができます。

まずは、データソース設定用ymlファイルを用意します。ここでは、データソースのタイプと認証方法の設定を記載します。セキュアな情報が必要となるのでAWSのパラメータストア等を使用して安全に管理してください。

  • cloudwatch-datasource.yml
apiVersion: 1

datasources:
  - name: cloudwatch
    type: cloudwatch
    jsonData:
      authType: keys
      defaultRegion: ap-northeast-1
    secureJsonData:
      accessKey: $AWS_ACCESSKEY
      secretKey: $AWS_SECRETKEY

続いて、ダッシュボード設定用ymlファイルを用意します。optionsのpathはダッシュボードのjsonファイル置き場を指定しています。

  • cloudwatch-dashboard.yml
# # config file version
apiVersion: 1

providers:
 - name: 'cloudwatch'
   orgId: 1
   folder: ''
   folderUid: ''
   type: file
   options:
     path: /var/lib/grafana/dashboards #dashboard jsonファイル置き場

監視ダッシュボード用jsonファイルは、公式サイトからダウンロードしました。今回は、以下3つのダッシュボードjsonファイル(EC2,EBS,Billding)を用意しました。

  • amazon-ebs_rev1.json
  • amazon-ec2_rev1.json
  • aws-billing_rev13.json

公式のダッシュボードは、他にもLambdaやCloudwatchLogs、RDSがあります。ここはお好みのものをどうぞ。
grafana.com

Ansible-playbookの作成

RunCommandで使用するplaybookは以下のような構成にしました。playbookの中身は、grafana及びnginxのインストール、設定をするタスクを記載しています。これらをzipファイルにまとめて後ほどs3にアップロードします。

.
├── README.md
├── main-ansible.yml
└── roles
    ├── grafana
    │   ├── files
    │   │   ├── amazon-ebs_rev1.json
    │   │   ├── amazon-ec2_rev1.json
    │   │   ├── aws-billing_rev13.json
    │   │   ├── cloudwatch-dashboard.yml
    │   │   └── cloudwatch-datasource.yml
    │   ├── handlers
    │   │   └── main.yml
    │   ├── tasks
    │   │   └── main.yml
    │   └── templates
    │       └── grafana.ini.j2
    └── nginx
        ├── files
        │   └── grafana.conf
        ├── handlers
        │   └── main.yml
        └── tasks
            └── main.yml

10 directories, 13 files

使用したplaybookのサンプルをgithubにあげていますので、参考までに。
github.com

terraformで環境構築

ここまで準備ができたら、terraformで必要なものを構築していきます。
今回Grafanaサーバー用に構築したのは、以下の通りです。VPCやサブネットは既存のものを使用しました。

  • EC2インスタンス
  • セキュリティグループ
  • IAMロール
  • IAMポリシー
  • S3バケット
  • S3オブジェクトのアップロード
  • Route53のAレコード

EC2インスタンスに付与するIAMロールに関しては、RunCommandの実行やGrafanaがCloudwatchのメトリクスを取得できるように以下のIAMポリシーをアタッチしておく必要があります。

  • Cloudwatchのメトリクス取得
  • 作成したS3バケットのアクセス権限
  • AmazonEC2RoleforSSM (Amazon管理ポリシー)

IAMロールとアタッチしたポリシーの例は、github上に置いているので参考にしてみてください。
github.com


Terraformの実行は、GithubActionを使うとGithub上でterrraform initからvalidateやshow, applyまで完結するのでおすすめです。GithubActionで実施したplanやapply履歴もgithubで確認することができます。
github.com

Ansible-playbookの実行

Systems Manager > Run CommandからAWS公式コマンドドキュメントの「AWS-ApplyAnsiblePlaybook」を選択します。
コマンドのパラメータは、以下のように設定しました。Source InfoはS3バケットに保管しているオブジェクトURLを記載してください。

Source Type: S3
Source Info: {"path":"object-url"} 
Install Dependencies: True
Playbook File: grafana-ansible/main-ansible.yml
Extra Variables: SSM=True
Check: False
Verbose: -v

後はRunCommand先のインスタンスを選択し、ログが必要な場合はCloudwatchLogsまたはS3に出力するように設定して実行すればOKです。
成功すれば以下のように表示されます。

f:id:akngo22:20191222210405j:plain
RunCommand実行結果

S3にアップロードしておくだけで、ssh経由せずにAnsible-playbookを実行することができるのはかなり便利だと思いました。ただ、デバックオプションを使ってログ出力していてもplaybookのどのタスクで失敗したのか表示されずデバッグしづらかったので、事前にplyabookが想定通りに動作することを確認の上で使う必要があると思います。この点は、RunCommandの実行結果で見れるようになると良いですね。

Grafana確認

Ansible-playbookの実行が成功したら、Grafanaにログインしダッシュボードを見てみましょう。ダッシュボード一覧にプロビジョニングしたダッシュボードが表示されていることが確認できると思います。
Grafana6.5では、ワイルドカードを使えるようになったため動的なクエリに対応できるようになり、AutoScalingでEC2インスタンスが増減しても自動でダッシュボードに反映されるようになっています。

f:id:akngo22:20191222205423j:plain
EC2インスタンスのダッシュボード

また、グラフをクリックすると「View in Cloudwatch console」というCloudwatchコンソール画面に遷移するためのディープリンクがコンテキストメニューに追加されていることが確認できます。このリンクをクリックすれば、Cloudwatchコンソール画面に飛び、対象メトリクスを表示させることも可能となっています。

f:id:akngo22:20191222205024j:plain
ディープリンクが表示される
f:id:akngo22:20191222205106j:plain
Cloudwatchコンソール画面に遷移できる


まだ、発展途上な感じではありますが今後よりAWS Cloudwatchと親和性が高くなる予感がするので、さらに使いやすくなるのではと思います。

最後に

delyではSREを大募集しています!興味ある人は気軽にまずはオフィスに遊びにきてください!
www.wantedly.com

delyの開発部について知りたい方はこちらをご覧ください!
speakerdeck.com

画像管理をActiveStorageからCarrierWaveへ乗り換えた話

本記事は dely Advent Calendar 2019 22日目の記事です。

qiita.com
adventar.org


昨日はiOSエンジニアのknchstが「“ダーク“な2019年」という記事を書きました。
tech.dely.jp


こんにちは、delyでサーバーサイドエンジニアをやっているyamanoiです。

弊社のとあるプロダクトにて画像アップロード処理周りに、ActiveStorageを使用していたのですが、使いづらい点がいくつかあったため、採用実績があったCarrierWaveへ乗り換えました。
この記事ではなぜ乗り換えたのかと、乗り換える手順を書いていきたいと思います。

なぜActiveStorageから乗り換えたのか

1. CDNとの相性が悪い

ActiveStorageはアタッチされたモデルのurlメソッドを使用するとActiveStorageが定義した /rails/active_storage/blobs/* へのパスを生成します。
このパスへアクセスすると、各クラウドストレージ上のオブジェクトに対する一時的な認証コード付きURLへリダイレクトし、画像を取得することができます。
このURLをキャッシュしてしまうと、URLが期限切れになってしまうと画像が表示されなくなってしまいます。

ActiveStorageとCDNを併用するにはActiveStorageの機能を独自に拡張することで利用はできるのですが、拡張を行うことによってシステムが複雑になってしまい、アップデートの障壁になったりすることが容易に想像できるため今回は避けました。

2. 画像のリクエストがRailsに向いてしまう

ActiveStorageを有効にするとActiveStorage用のroutingが新しく追加されます。
ActiveStorageを用いて画像を取得する際はすべてこのルーティングを経由する必要があります。
静的ファイルはパフォーマンスの観点からアプリケーションサーバーを通さずnginxやs3等のバケットから直接配信したいですよね。

また追加されるルーティングは自分で定義しているconfig/routes.rbの後にロードされるため、以下の様なルーティングを定義しているとActiveStorage側のルーティングにマッチする前にルーティングが解決されてしまい、画像が正しく表示されない問題に直面しました。

  get "*path", controller: 'front', action: 'spa', via: :all

3. DBへのリクエストが頻繁に走る

ActiveStorageはactive_storage_attachmentsactive_storage_blobsの2つのテーブルを作成し、そこに画像のメタ情報やモデルとの関連を保持します。
ActiveStorageを使う場合は少なくともアタッチするモデルと1対1の関連が発生します。
何も考えず使用すると容易にN+1を誘発します。そのためActiveStorageではN+1を回避するためのメソッドが用意されています。
また上記の2つのテーブルですべてのモデルに対しての画像を扱うため、レコード数の多いテーブルが複数存在するとレコード数が増加し、パフォーマンスに影響が出てしまう可能性もありそうです。


CarrierWaveへ乗り換える手順

1. 設定ファイルを消す

config/application.rb
でrails/allしている場合は不要なファイルもロードしてしまうので必要なもののみをロードするように変更します

デフォルトだとconfig/application.rbに以下の様な記述があると思いますが、これだとActiveStorageもロード対象になってしまうので使うものだけをロードするように変更します。
Before

require 'rails/all'

After

require "rails"
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"

2.CarrierWave gemの追加と書き換え

CarrierWave gemを入れて実際にコードを置き換えていきます。
置き換える点で厄介になりそうなところはActiveStorageのvariantを使用している場合です。

ActiveStorageではアタッチされている画像に対してvariantメソッドを使うことでリサイズ処理を手軽に実現することができます。

    <%= image_tag @post.thumbnail.variant(resize:'50x50').processed %>

様々なサイズを気軽に生成することができるので便利な機能なのですが、CarrierWaveではUploaderクラスに予めversionとして画像のパターンを定義しておく必要があります。
今回のプロダクトではこのvariantの機能をフル活用している場所はあまり無かったのでそこまで問題にはなりませんでした。

3. 移行スクリプト作る

弊社の場合、ActiveStorageで保存された画像はs3に置いてあり、移行するにあたってファイルの保存場所を変更する必要があったため移行するスクリプトを作りました。
ActiveStorageは1つのバケットにフラットに画像を保存するため、パスを特定し、置き換えていきます

↓サンプルコード

class FileDataStringIo < StringIO
  attr_accessor :original_filename, :cnotent_type

  def initialize(*args)
    super(*args[2..-1])
    @original_filename = args[0]
    @content_type = args[1]
  end
end

class ActiveStorageBlob < ActiveRecord::Base; end

class ActiveStorageAttachment < ActiveRecord::Base
  belongs_to :blob, class_name: 'ActiveStorageBlob'
  belongs_to :record, polymorphic: true
end

ActiveStorageAttachment.all.each do |attachment|
  blob = attachment.blob
  key = blob.key
  filename = blob.filename
  record = attachment.record
  name = attachment.name
  content_type = blob.content_type

  s3 = Aws::S3::Resource.new(region: 'ap-northeast-1')
  obj = s3.bucket(ENV["ACTIVE_STORAGE_S3_BUCKET"]).object(key).get
  data = obj.body.read

  io = FileDataStringIo.new(filename, content_type, data)
  record.send("#{name}=".to_sym, io)
  record.save!
end

4. テーブルの削除

最後にActiveStorageの有効時に生成されたテーブルを削除します。

class DestroyActiveStorageTables < ActiveRecord::Migration
  drop_table :active_storage_blobs
  drop_table :active_storage_attachments
end

まとめ

Rails備え付けの機能だからと言ってすぐ取っつかず、開発しているプロダクトの要件をしっかり満たせるかを検討しながら、Gemの選定を行いましょう

最後に

delyではサーバーサイドエンジニアを募集中です。ご興味ありましたらぜひこちらから!
note.com
www.wantedly.com


delyの開発について知りたい方はこちらもあわせてご覧ください!
speakerdeck.com

“ダーク“な2019年

こんにちは!クラシルiOSアプリを開発しているknchstです。

この記事は「dely Advent Calendar 2019」の 21 日目の記事になります。

qiita.com

adventar.org

昨日は Android チームの tummy による「何もわからない状態からいち早く脱するためのコードリーディング法(Android 版)」という記事でした。

tech.dely.jp

ダークモードな2019年

WWDC2018で発表された macOS Mojave にて実装されたダークモードを皮切りに世界的にダークモードへの対応が進んできました。しかしダークモードについては様々な意見があり、その評価や必要性についても賛否両論です。

iOSやAndroidのような主要なモバイルプラットフォームがOSレベルでダークモードをサポートしたことにより、Twitter, Instagram, Slackなどメジャーなデベロッパーたちは早々にダークモードに対応しました。

Dark Mode Listというサイトではダークモードに対応しているアプリの一部が確認できます。

darkmodelist.com

ダークモードがもたらした功罪

ダークモードに対する一般的な評判はとてもいいように感じます。iOSやAndroidがダークモードに対応する前からLINEが黒のテーマをリリースした時や、SpotifyのアプリのUIがかっこいいという声はよく聞きました。

さらにウォールストリートジャーナルは、ダークモードを強く推奨している記事を書いていました。そしてその記事を今月のはじめに再びツイートしていました。しかしながら面白いことに当のWSJのアプリはこの時点ではダークモードに対応していなかったのです。ダークモードが追加されたのは最近の話です。

この動きはメディアに限らず個人レベルでも起きつつあります。そう、ダークモードハラスメントが起こっているのです。以下は著名なeスポーツプレイヤーのツイートです。

なぜダークモードはここまで人気なのでしょうか。

いち早く一般ユーザー向けのサービスでダークな外観を提供していたSpotifyのディレクターだったMichelle Kadir氏はインタビューで以下のように述べています。

www.fastcompany.com

We believe that when you have music or art that’s very colorful and very artistic, and you have beautiful cover art for music, that it really shows more clearly visible in a product like this, when it’s about entertainment. Everything else settles in and isn’t as much in the way when you have a white background

Spotifyがダークな外観をアプリに採用したのは多くの背景パターンをユーザーに対してテストした結果ダークな外観がユーザーに好まれることが分かったからです。

さらにダークモードの人気は近年の人々の日常のニーズを反映していると言えます。 おそらく誰もが照明が暗い空間でスマホをいじる人にイラついたことがあると思います。ダークモードがあるおかげで他人の邪魔をせずに暗闇でスマートフォンを使用することができます。

例えば映画館・美術館などの照明が灯っていないもしくは非常に暗い状況下での使用です。あるいは、パートナーが近くで寝ている間にスマートフォンを操作したい場合などもです。

さらにダークモードにすることにより憎っくきブルーライトを軽減し、安眠に貢献してくれることでしょう。

また、スマートフォンのディズプレイへの様々な工学的理由によって採用されている有機EL(OLED)との相性の良さもダークモードの普及をさらに促すことになると思います。今年のGoogle I / Oでのセッションによると、Android Qのダークモードは、「有機EL(OLED)下での利用でバッテリー消費を最大60%削減できる」と述べていました。

しかしながらダークモードが全てのユーザーに適しているかは疑問があり現在でも議論がつづけられています。以下のQuoraの質問ではメガネをかけている人や乱視など状態にある人にとってはダークモードが眼精疲労を助長する恐れがあると主張しています。

www.quora.com

以下の2008年のブログで引用された論文では、黒地に白のテキストが白地に黒よりも26%読みやすいと主張しています。ただ、その論文は1980年代のスクリーンを使用しているため、今の私たちが利用しているディズプレイなどで再度研究してみる必要があるかもしれません。

tatham.blog

ダークモードとレガシー

ダークモードに対応するということは、既存のサービスのUIと闘う必要があります。ただ単に背景を黒に変更すればいい、という訳ではありません。

以下記事に書かれているAlibabaアプリのダークモード対応では、色の最適化の重要性について主張しています。

www.uisdc.com

Alibabaのブランドカラーである明るいオレンジを使用する時、暗い背景に適しているか考慮する必要があります。あるいはブランドカラーを拡張させた補助色を利用することも検討できます。これにより、コントラストを維持し、より疲れにくく美しさを保ったUIを提供することができます。

f:id:knchst:20191221175537p:plain

また別の例としてロシアの検索エンジンを提供しているYandexが開発しているメールクライアントではダークモードに対応する際に大きな課題にぶつかりました。

habr.com

Eメールは古くからあるコンテンツ形式であるため、ほとんどのメールクライアントは背景色が白であるという前提で設計されているため、メール本文ないに利用される画像は背景が白で作られていることが多くあります。また、一部ではメールテンプレートの背景色を調整している可能性もあります。

このためメールクライアントがダークモードに対応すると以下のような問題が発生します。Amazonのロゴ部分は背景が白い画像であるためダークモードにしたときに不自然に見えます。

f:id:knchst:20191221175654p:plain

おわりに

ダークモードについて話してきましたが、サービスに本当にダークモードが必要かも検討する必要があるかもしれません。Spotifyの事例ではそれがユーザーに最適であるということがテストによってわかったのに加え、サービスのコンテンツがダークモードとの相性が良かったので採用されました。自分はなんでも間でもダークにすればいいとは思いませんし、ダークモードもあまり使っていません。

しかしながら、Appleにプラットフォームの開発者である以上Appleの決定には従わざるおえないのも事実です。来年もいろんな意味でダークな一年になりそうですね。。

delyでは一緒に食の課題を解決してくれるエンジニアを募集しています!興味ある人は気軽にまずはオフィスに遊びにきてください!

www.wantedly.com

speakerdeck.com

何もわからない状態からいち早く脱するためのコードリーディング法(Android 版)

f:id:rnitame:20191218093616p:plain

こんにちは。

dely の開発部でクラシルの Android を担当している tummy です。 2019 年 12 月から dely に入社して、たくさんキャッチアップしながら初めての施策を実装しています。ついていくので精一杯です。。笑

この記事は「dely Advent Calendar 2019」の 20 日目の記事になります。

qiita.com

adventar.org

昨日は同じ Android チームの kenzo による「エンジニアは体が資本でしょ。と思って始めた習慣とその続け方」という記事でした。 自分は三日坊主になることが多いので、次になにか継続しようと思ったときはアドバイスをもらおうと思います。

tech.dely.jp

今回は、新しいプロジェクトに入った際のキャッチアップ時に行っている、担当する Android アプリの中身を「なるべく早く」「ざっくりと」把握するためのコードリーディング方法を紹介できればと思います :)

目的

何がどこにあってどう使われているか、を粗方把握している 状態を目指します。この状態になっていれば、とりあえず該当箇所を見に行けるため一次調査が自分で行えると考えているからです。

まずやること

  1. build.gradle で何が入っているのか見る
  2. デバッグ周りを調べる
  3. package 構成を見る

以上の 3 つです。

まず、build.gradle を見ます。ライブラリの一覧が記述されているので通信ライブラリや Android SDK まわりで使っているものを確認できます。また、flavor や Lint の設定なども記述されているため、手元で開発するときの環境が把握できます。

その後、デバッグまわりを調べてデバッグメニューが実装されているかどうか、Stetho やこのあとも紹介する Hyperion などのデバッグツールが入っていないか見ます。

そして、package 構成を見に行きます。 ここまでやったあとにコードを読みに行きます。

コードを読む方法

PR を見に行く

コードを読めばなんとなくやっていることはわかりますが、「なぜこういう実装になっているのか?」という箇所を発見した場合、プルリクに書いてあればそれを元に理解することができる可能性があります。Find Pull Request という Intellij プラグインがあるのでそれを活用します。

plugins.jetbrains.com

f:id:rnitame:20191218113909p:plain
List Pull Requests にチェックを入れて Annotate すると Pull Request の番号が表示される

GitHub 等を使ってプルリクベースで開発していることが前提になりますが、こういった情報も概要を掴む上で助けになってくれるはずです。

Android Studio の機能フル活用

ショートカットをたくさん使うのはもちろんですが、他に特に便利なものを紹介します。

Breakpoint にひっかけて Frames を見る

ブレークポイントを任意の場所(例えばどこから遷移してくるのか知りたい Activity の onCreate)に置き、Debugger を起動してブレークポイントの場所を通ると Debug のビューが開くと思います。 そこに Frames という欄があり、アプリ起動からそのブレークポイントに至るまでが表示されています。

f:id:rnitame:20191216160948p:plain

上記の場合、「MainActivity の initListener メソッドで onClick が呼ばれ、SecondActivity の Companion Object 内にある createIntent が呼ばれた」と読むことができます。 このことから、MainActivity から SecondActivity に飛ぶ際の Intent が SecondActivity 内のメソッドで作られている事がわかります。

Layout Inspector

実行中のアプリのレイアウトがどういった構造になっているか確認することができます。 Tools > Layout Inspector から起動できます。

f:id:rnitame:20191216160431p:plain
Layout Inspector を起動したところ

2 つ以上 Activity を開いていれば、起動時にダイアログが表示されて Activity のクラス名が判明します。実機で触ってみるフェーズにおいて、こちらを併用しながらコードを読んでいくと捗るかと思います。

f:id:rnitame:20191216160301p:plain
スタックに溜まっている Activity のリストが表示される

※ Android Studio 4.0 から使える Live Layout Inspector もあるとより便利ですね!

Hyperion-android の活用

hyperion-android というデバッグライブラリがあり、Timber の中身やクラッシュログを確認することができます。またこのライブラリのサードパーティで、任意のアイテムを追加できるものがあり、今回はそちらを活用します。

github.com

どのクラスにいるかを Toast で出す

Application クラス内で以下のように実装します。

   override fun onCreate() {
        super.onCreate()

        val listener = object : ActivityLifecycleCallbacks {
            private var activityName: String? = null

            override fun onActivityPaused(activity: Activity) {
            }

            override fun onActivityStarted(activity: Activity) {
            }

            override fun onActivityDestroyed(activity: Activity) {
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            }

            override fun onActivityStopped(activity: Activity) {
            }

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            }

            override fun onActivityResumed(activity: Activity) {
                activityName = activity.javaClass.simpleName
            }

            fun getCurrentActivityName() = activityName
        }
        registerActivityLifecycleCallbacks(listener)

        val item = SimpleItem.Builder("debug")
            .image(R.drawable.ic_launcher_foreground)
            .text("Show class name")
            .clickListener {
                Toast.makeText(this, listener.getCurrentActivityName(), Toast.LENGTH_LONG).show()
            }
            .build()

        SimpleItemHyperionPlugin.addItem(item)
    }

f:id:rnitame:20191216151312g:plain
実際にうごかすとこのようになります

FragmentLifecycleCallbacks を使えば Fragment でも同様に取得できると思います。

まとめ

最初からすべてを把握するのは大変難しく、時間を要します。そのため、基本的な設計とメインループだけわかっていれば最初の入りとしては上出来で、他の画面等については施策やレビュー等でいじった際に理解していけば良いと考えています。 自分は Android Studio やちょっとした実装をすることでカバーしていましたが、他にこういうツールも便利ですよっていうのあったらぜひ教えて下さい!

明日は iOS エンジニアのふくさんが、「“ダーク“な2019年」というタイトルでお届けします。

さいごに

dely では Android エンジニアを絶賛募集中です、ご興味あればこちらのリンクからお気軽にエントリーください!

www.wantedly.com

開発チームについて詳しく知りたい方はこちらから 🙆

wevox.io

note.com

『Deep Neural Networks for YouTube Recommendations』を紹介します

www.youtube.com

はじめまして。
dely, Inc. の @sakura です。

この記事は Google Products Advent Calendar 2019 - Adventar の20日目の記事です。 昨日は新坂さんのGoogle Homeの記事でした。Google Homeに関する様々な実体験が書かれており、とてもほっこりするエントリでした。

polasleep.hateblo.jp


本記事では、YouTubeのレコメンドの仕組みについて書かれている論文『Deep Neural Networks for YouTube Recommendations』を紹介します。

なぜ読んだのか

YouTubeは普段から利用するサイトの一つであり、興味があったからです。
また、社内では「サーベイチャレンジ」という試みがありせっかくの機会なので読んでみました。

tech.dely.jp

本論文について

f:id:sakura818uuu:20191219173446p:plain
本論文の1ページ目

論文へのリンク https://static.googleusercontent.com/media/research.google.com/ja//pubs/archive/45530.pdf

タイトル: Deep Neural Networks for YouTube Recommendations
著者:Covington, Jay Adams, Emre Sargin

これは2016年に発表された論文で、YouTubeのレコメンドに関する技術について書かれています。

イントロダクション

さっそく本論文の内容に入っていきます。

※ここから先は書き言葉になります。そうしないと〇〇らしいです。〇〇そうです。の文章のオンパレードになってしまい読みくいため

f:id:sakura818uuu:20191219173222p:plain:w200
イントロダクションには、YouTubeのレコメンドは3つの大きな挑戦があると書いている。

1.スケールの問題

既存のレコメンドアルゴリズムの多くは小さな問題ではうまく機能することが証明されているが、YouTubeの規模では機能しない
YouTubeの規模になるとユーザーデータも莫大にあるからそこをどう対処していくか。

2.新鮮さの問題

毎秒何時間もの大きな動画がアップロードされるのでレコメンドシステムはこれに耐えうる性能でなければならない。
また、ユーザーは新しいコンテンツを好むことはわかっているがそれと引き換えに適合度(関連性)や既存のコンテンツとのバランスは慎重に考慮する必要がある。

3.ノイズの問題

YouTubeでの過去のユーザー行動は、スパース性(まばらという意味)と色んな観測不能な外部要因があるから予測がかなり困難。
加えて、各ビデオのメタデータの構造が不十分である。
従って、アルゴリズムを作るにあたってこのような特性を十分に加味しておかなければならない。

わかったこと

論文を読んでわかった部分のいくつかを紹介します。
全てについては解説していないのでご了承ください。

レコメンドシステムの2層のニューラルネットワーク

f:id:sakura818uuu:20191219174303p:plain
Figure 2: Recommendation system architecture demonstrating the “funnel” where candidate videos are retrieved and ranked before presenting only a few to the user.

レコメンドシステムには2層ニューラルネットワークを使用している。
1つはレコメンド群の候補たちを生成するもの、もう1つはそれらをランキングのように並び替えるものである

前者には特徴量行列としてユーザーのYouTubeアクティビティ履歴を使用しているとのこと。
また、前者には協調フィルタリングも使用していてパーソナライズを実現している。協調フィルタリングにはアイテムの類似度のみならず、ユーザー同士の関係性も必要になってくるがそこはどんなビデオを見ているか、どんな検索クエリを入力しているか、あとは一般的な人口統計などの情報を使っているらしい。

暗黙的フィードバック

f:id:sakura818uuu:20191219174447p:plain
YouTubeのGood/Badボタン

YouTubeにはGood/Badボタンなど様々な明示的フィードバックがあるが、モデルをトレーニングする時は暗黙的なフィードバックを使う。

暗黙的なフィードバックとはなにかの説明については以下のブログがわかりやすかったです。

この記事では,ユーザの行動履歴をフィードバックと呼びます.一言にユーザからのフィードバックと言っても様々な種類があります.例えば,ユーザがアイテムに対して点数(rating)をつけたものや,like・dislikeのようにそのアイテムを好きか嫌いかを表すものがあります.このタイプは,ユーザがそのアイテムを好きかどうかがはっきりとわかるので,明示的フィードバック(explicit feedback)と呼ばれます.一方で,単なる閲覧・購入履歴などのデータも存在します.このデータからは,閲覧したけど好きだったかどうかはわかりませんし,購入したけど満足したかどうかはわかりません.したがって,このタイプは暗黙的フィードバック(implicit feedback)と呼ばれます.

「推薦」の定式化から推薦システムを理解する | カメリオ開発者ブログ

アルゴリズムに使われている情報

論文中に記載されていただけでもこれだけの情報がレコメンドアルゴリズムに使用されている可能性があることがわかった。
3.3 Heterogeneous Signalsなどに記載されている。

・ユーザーのYouTubeアクティビティ履歴
・ユーザー間の類似度
・ビデオ間の類似度
・ユーザーの視聴履歴
・ユーザーの地域
・ユーザーの性別
・ユーザーのログイン情報
・ユーザーの年齢
・ユーザーはいくつこのチャンネルの動画を見ているか
・ユーザーがこのトピックに関するビデオを最後に見たときはいつか
(・もしレコメンドした動画をユーザーがみなかったらその動画は降格させる

など

CTRより視聴時間

Ranking by click-through rate often promotes deceptive videos that the user does not complete (“clickbait”) whereas watch time better captures engagement [13, 25].

[13] E. Meyerson. Youtube now: Why we focus on watch time. http://youtubecreator.blogspot.com/2012/08/ youtube-now-why-we-focus-on-watch-time.html. Accessed: 2016-04-20.

[25] X. Yi, L. Hong, E. Zhong, N. N. Liu, and S. Rajan. Beyond clicks: Dwell time for personalization. In Proceedings of the 8th ACM Conference on Recommender Systems, RecSys ’14, pages 113–120, New York, NY, USA, 2014. ACM.

「CTRで測定するランキングはしばしばdeceptive(こすい、詐欺的)なビデオを促進するものだ。それよりも視聴時間を見るほうがユーザーのエンゲージメントをよく捉えているといえるだろう。[13,25]」

と引用して記載されている部分がある。

レコメンド群の候補たちを生成するモデル

f:id:sakura818uuu:20191219174638p:plain
Figure 3: Deep candidate generation model architecture showing embedded sparse features concatenated with dense features. Embeddings are averaged before concatenation to transform variable sized bags of sparse IDs into fixed-width vectors suitable for input to the hidden layers. All hidden layers are fully connected. In training, a cross-entropy loss is minimized with gradient descent on the output of the sampled softmax. At serving, an approximate nearest neighbor lookup is performed to generate hundreds of candidate video recommendations.

図はレコメンド群の候補たちを生成するモデルを説明しているものだ。

視聴履歴や検索履歴をベクトル化して、そこにユーザーの地理的な情報や年齢、性別などの情報をベクトル化してくっつけている。
それをReLU関数に3回かけてsoftmaxでトレーニングしてクラスの確率を推定してる。(多クラス分類なので
最後に、上記でやった結果を最近傍探索して上位N件を取り出している。

まとめ

YouTubeのレコメンドに関する論文
Deep Neural Networks for YouTube Recommendations』を紹介しました。

YouTubeってついつい見て時間経っちゃってる時ありますよね。
その裏ではこんなことが起こっているんだな、と一端を知ることが出来ました。

機械学習に詳しいわけではないので理解できない部分も多々ありましたが、
YouTubeのレコメンドの仕組みが少しでも理解できて感動しました。

もし、YouTubeに興味がある方がいれば読んでみると面白いかもしれません。

さいごに

最後に告知です。delyではエンジニアを絶賛募集中です。
ぜひお気軽にご連絡ください。

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

エンジニアは体が資本でしょ。と思って始めた習慣とその続け方

f:id:kenzo_aiue:20191219102803p:plain

本記事は dely Advent Calendar 2019 19日目の記事です。

昨日はWebフロントエンドエンジニアのしらりんくんが「Vue.jsでカスタムディレクティブを使ってユーザーの「見てる」を可視化する」という記事を書きました。ぜひご一読を。表示されて1秒経ったら色が変わる動画が見ていて気持ちいいです。

こんにちは。継続の鬼、kenzoです。
冬ですね。寒いですね。みなさまにおかれましてはますますご健勝のこととおよろこび申し上げます。

。。本当にご健勝でしょうか。風邪をひいていたり、なんとなく体調悪い日が続いている、なんてことはないでしょうか。

「日々開発に打ち込むためにはまず健康でなければならない」
そのような思いからこの1年、自分の体のためにいくつか続けてきたことがあります。
この記事ではその続けてきたこと、それを始めてから続けられた理由、そこから見えてきた新しいことを始めて習慣化させる方法についてお伝えします。

続けてきたこと

ごはんログ

健康は食事から。ということで、自分の食生活を見直し、改善するために1年間ほぼ全ての食事や間食の写真を撮ってTwitterに投稿してきました。
以前、弊社の管理栄養士の同僚が趣味で食生活を見てくれるという取り組みがあり、それに手を上げてお願いしたのが始まりでした。

食事を投稿することで、自分が普段食べているものをきちんと把握したり、人の目を意識することでヤバい食べ方を減らすことを期待してのことでした。
一部の例外(家で飲む水や薬、サプリメント等)を除き、朝昼晩の食事に加えて仕事中に食べるおやつ、筋トレ後のプロテイン、デパートでの試食、果ては駅伝中にもらった水まで撮影してTwitterに投稿してきました。

この取り組みは1年間続いたところで卒業?という形で終了することになりました。

体組成記録

日々の自分の体の変化を認識できるよう、毎朝体組成計で自分の体組成を計測しています。その内容はTwitterにも投稿しています。
これも前述の同僚の取り組みに合わせて始めましたが、ごはんログを卒業?した今でも続けられています。

タニタの体組成計を使っているので、Twitter連携で自動でこのような投稿をしてくれます。

家トレ

毎日家で筋トレをしています。こればかりは色々な理由(けが等)により1年間は続いてはいませんが、いろんなパターンをやってみて、ここ3ヶ月ほどは継続中です。

筋トレアプリ

筋トレ用のアプリを使って日々家トレをしていました。様々な部位の筋トレメニューを日替わりで提供してくれるアプリでした。けがをして一度途絶えた後、再開時の強度が高めだったためか、復帰したものの長続きさせることはできませんでした。

筋トレグループ

家で腹筋をした回数を報告し合うLINEグループを友人と作って競い合っていました(二人でしたが)。当時はその回数をスプレッドシートにメモして月々の記録をしていました。こちらは数年間続きましたが、他の競合筋トレ台頭の憂き目に遭い、今ではそのグループも静かになってしまいました。

f:id:kenzo_aiue:20191218200740p:plain

プランクアプリ

これは今でも続いている習慣です。上記の筋トレアプリと同様にメニューを提供してくれるアプリでプランクに特化したものを使っています。こちらも1度けがによる離脱ピンチがありましたが、前回(上記の筋トレアプリ)の反省を踏まえ、復帰時には強度を弱くしてハードルを下げた状態で再開したためか、再度習慣化させることができています。

f:id:kenzo_aiue:20191219095807p:plain

続けられた理由

以上の習慣について振り返ってみると、やってこれた(やめてしまった)のにはいくつかの理由がありました。

ごはんログ

やります宣言・人に見てもらう

そもそもの始まりが食事を見てもらうためだったということもあるのですが、きちんと「やります!」と宣言し、Twitterに投稿して見てもらうことで、続けざるを得ない環境となりました。

褒めてもらう・定期的なリアクション

ランチや飲み会の場でも摂取する全てのものを撮影してきたので、同席した方から「え、インスタ?インスタ?ww」などと聞かれることが度々ありました。
その度にこういう取り組みをしていますと伝えてきましたが、その際によく「へーよく続けられるね」のようなお褒めの言葉をいただくことができ、続けるモチベーションになっていました。
また、Twitterをフォローしてくれている同僚からも、たまに自分の投稿した食事を話題に上げてもらえることがあり、ちゃんと見てもらえているし続けようという気持ちになりました。

体組成記録

起きたら計る

毎朝起きたら必ず体組成計に乗る。というやり方で実施してきました。
起きたら目覚ましを止めて毎回同じところに向かうというルーティーンの動作ができ、忘れずに実施することができていました。
「起きたら◯◯する」は他の習慣づくりにも良さそうです。ただ、朝起きたら家トレも並行して実施していた時期には、体組成計に乗るのを忘れてしまうこともありました。同時にいくつものことを忘れずにやるのは難しいですね。

仕組みづくり

毎日体組成計に乗っていると、自分の組成がどんな変化をしているか知りたくなります。
そこで、GASでいくつかの項目の実測値と移動平均のグラフを作成し、その画像をTwitterに投稿するスクリプトを作成しました。詳しくは後述します。
ちょっと手間をかけてスクリプトを作ったので途中でやめたらもったいない。何より自分でスクリプト作って投稿されるグラフが見たい。という気持ちで続けることができました。

家トレ

カレンダーに○

使用していたアプリではトレーニングが完了するとカレンダーに◯がつきました。 f:id:kenzo_aiue:20191219094050p:plain ただやった日に○がつくだけなのに、それが並んで増えてくるとどうしても途絶えさせたくなくて続けていたところがありました。

成果を報告

友人と毎日成果を報告し合うことで、やらないといけない雰囲気になっていました。 時に褒め合い、時に煽り合うことで互いにモチベーションを高められていました。 ただし、これについては双方のモチベーションや回数に偏りがある場合にはうまく噛み合わないこともありました。

充実感・体の変化

他の2つと比べて時間もかかり負荷も高い家トレでしたが、その分をこなしたときの充実感は高く、毎日やることで「自分、めっちゃやってる」感を得られていました。
これは意外と大事で、休みの日にこれ以外なにもしなくても、「今日は筋トレしたから充実した日だった。 」と思えるくらいのパワーがありました。
また、やっているうちにお腹周りがすっきりしてくる等の体の変化を感じられるようになってきました。

起きたらやる

これも上記の体組成記録と同様、朝目が覚めたらやる形式でやっていました。
筋トレ → プロテイン → シャワーという流れができ、ちゃんと目も覚めるため、うまく続けられていました。
しかし、上記にもあるように、他の朝起きたらシリーズと被ってしまい、その習慣の実施を忘れてしまうこともありました。

高負荷(ダメだった理由)

一度は習慣となっていた筋トレアプリでしたが、一旦離れてしまった後に再開する際にきつめのトレーニングから始めてしまったため、「これからこんなんやってくの、、無理」という気持ちになってしまいました。こうなってしまうと再び習慣化するのは難しく、数日で終わってしまいました。

習慣化させるには

人を巻き込む

一人だけで新しいこと*1を始め、習慣化させて実施し続けるのはかなり難しいことだと思います。
しかし、上記の理由にいくつかある

  • 人に宣言する
  • 人に見てもらう
  • 人と競う

のように他の人を巻き込むことで、「見られているしやらないと」だったり「あいつには負けてられない」というように、始めることや続けることに対するモチベーションを高めることができます。
私はこれによってハードルをかなり下げることができました。ぜひ何かを始める際には身近な方を巻き込んでやってみてください。

負荷を減らす

しんどいものはなかなか続きません。
負荷の高いものでも一度や二度なら我慢できるかもしれませんが、それを長期的に継続していくのはかなり難しいと思います。*2
逆に言うと、初めのうちはかなり簡単なこと(こんなんでいいの?くらい)から始めると、比較的簡単に続けることができると思います。
負荷を高めるのはそうして簡単なことを続けられた後でよいと思います。

もったいない

「もったいない」という気持ちも習慣を続けていく上では役に立ちました。
上記の例であげた「せっかくスクリプトを作ったし使わないと」「せっかくここまで◯をつけたし途切れさせたくない」といった、せっかくやってきたのにもったいないという気持ちによって続けられた部分はありました。
一見するとサンクコストの説明で悪い例として使われそうなこの心の動きも、良い行動の習慣化にはうまく利用できました。
習慣化のためにはちょっとひと手間かけたり、自分のやってきた積み重ねが見えるようになっていると、離脱の防止になるのでおすすめです。

まとめ

習慣化することは気持ちの勝負のようなところがあります。ですので、いかに自分がそれをやる気持ちを高められるか、やりたくない気持ちを抑えられるかが決め手となります。上記の内容もほぼそのどちらかに当てはまるものとなっています。
今回は私が習慣化してきた内容を振り返り、その要素をご紹介いたしました。これがみなさまの習慣化に少しでも役立てばと思います。
また、これらは個人の経験ベースの話なので、心理学等に基づいた習慣化の話とは同じところも異なるところもあり、見比べてみても面白いと思います。

おまけ

体組成のTwitterへのグラフ投稿の自動化

体組成計記録において作成した、Twitterに投稿した毎日の体組成データを元にグラフを描画し、その画像をTwitterに毎週投稿するスクリプトの詳細です。 基本的にはGASで下記のことをやっている感じです。

  • Twitter APIの使用準備
  • 毎日の投稿を蓄積

    • 自分のTwitterの投稿から特定のタグをついたものを取得 https://api.twitter.com/1.1/statuses/user_timeline.json?user_id=TwitterのID&count=20&trim_user=t

    • パースして項目毎にスプレッドシートに保存 f:id:kenzo_aiue:20191219094353p:plain

    • ついでに平均値を計算してそれも保存 f:id:kenzo_aiue:20191219094410p:plain
  • 毎週グラフをTwitterに投稿

実際に使っているコードに近いものがこちらです。

TANITAの体組成計連携Tweetを元に作成したグラフを投稿 · GitHub

おわりに

明日はAndroidエンジニアのtummyさんによる「何もわからない状態からいち早く脱するためのコードリーディング法(Android 版)」です。お楽しみに!

delyではエンジニアをめちゃめちゃ募集中です。ご興味ありましたらぜひこちらから!

delyの開発について知りたい方はこちらもあわせてご覧ください!

*1:難しかったり苦痛を伴うものです。ゲームとか楽しいから習慣化するのめちゃ簡単ですもんね。

*2:もちろん他のやる理由があればその限りではありません(お金を払って通うジム、業務上やらないといけないことなど)