「ジョブ理論」を読んでみた。

まじでこれは良い本だった

自己啓発本ではないし、いい仕事の仕方を説明した本でもない。

純粋に「ジョブ理論」を説くことに専念した本。(ふつうに大学の講義とかでありそうなやつ)

 

「ジョブ理論」に基づかない平凡な施策が失敗していくさまを結構なページ数さいて書かれている。このあたりが、自身の過去の経験と照らし合わせることがしやすく、納得しつつ読みすすめることができた。

自分の場合は、ただ偶然にエンジニアリング組織論への招待Kindleで読んだら、ついでにレコメンドされたので読み始めたのがきっかけ(苦笑)なんだけど、普通に本屋にでかでかと並べて欲しいなって思った。

読んでいて、脳裏で想像していたこと

業績とかの数字が上がるのは、「数字を上げる施策をしたから」ではなく「熱心に使う人がいるから」

競合他社がいい感じのUIで機能を作っていて、そこそこいい感じにユーザを獲得できている。

それに対して、多くの場合には、

  • 競合他社のUIのどこがいいのかを分析する
  • 自社でもやるには何を作ればいいか、最短で実現できる方法を考える
  • ペルソナとかカスタマジャーニーマップとか作ってみて、どういう使い勝手が競合他社より理想的かを考えてみる

のような作業が発生する。

それで実装してみると、確かに使い勝手は多少良くなっているので、なんらかの数字は少し上がる。さて、次なにしよう・・・。

 

これが「ジョブ理論」からすると完全に反面教師的な流れだなと思った。

  • 競合他社が使われている理由は本当にUIなの?
    • 熱心に使われているのか、仕方なく使われているのか?
  • そもそもその提供機能周辺に関しては"競合"なの?

あたりを都度詰めていかないと、(典型的には複雑度が増してメンテしきれなくなって)失敗するパターンだ。

数値的な評価は「仮説がおそらく間違ってはいない」ということだけ実証できる

ちょうど3年くらい前に、今の会社をやめようと思ったくらいイライラしていた時期があるんだけど、そのころのチームリーダー(?)の方針は「まずは測定をしてみてユーザがどこに困っているのかを正確にみてから、次の施策を具体的に考えよう」だった。

自分は、数字にこだわることが嫌いで、"数字を上げに行く"ことはさらに嫌いだったので、これは地獄でしかなかった。当時は「ユーザは数字で動いているわけじゃない」とか「自分らが実際に当事者として使ってみて不便なものが、多様なユーザに提供して便利なはずがない」とか、いろいろ反論はしてたんだけど、まぁ聞く耳もたずの人だったので完全に暖簾に腕押し。

で、「ジョブ理論」読んでちょっと思ったのが、そもそも「見るべき数字」というのは深い洞察にもとづくストーリーや仮説(「ジョブ理論」でいうところの "ジョブ")があって、初めて測定指標になるんだということ。

"会員登録率" とか "○○画面での離脱率" とかそんな表面的な数値は、よほど洞察深く見える人じゃない限りは、眺めても混乱するか眺めて終わるだけなんだ、と。

幸いにして、直近半年くらいの業務ではこのあたりはかなり満足できる環境にあるので、何か改善をやっていかねばと思うには至らなかったけど、過去のイライラの説明が自分なりについたのは大きな収穫だった。

 

まとめ

みんな読もう。

次はAmazonのレコメンドにしたがいイノベーションのジレンマ読む。

Tカードを表示するだけのアプリを作った

https://user-images.githubusercontent.com/11763113/52181360-4efb1900-2834-11e9-921a-445474d7116e.png

きっかけ

最近、ときどきファミマでTカードを使うようになり、モバイルTカードアプリなるものがあることを知った。

しかし、TカードアプリはたんにWebViewでカードページを表示しているだけだ。その割に、無駄に操作が多く、ファミマWiFiを拾ってしまったらカード画像が表示できないこともしばしば。開発元はちゃんと試験してないだろ!!

ということで、とにかくぱっと開いてさっとカード画像を表示したいだけのアプリを作った。

github.com

以下、作っていく過程のつぶやきをただ書いているだけなので、有益なことは何も書いていない。

ということで、まずはCharles Proxyで通信内容を除いてみることにした。

カード会社のアプリなどであれば、証明書のpinningとかされてて覗けないのだけど、

(ノω・)テヘ

app_id_tkn っていうのをどうにかして手に入れれば、いくらでもカード表示のWebViewができるということがわかり、

ただ、認証トークンを取得するまでのプロセスが、ファミマアプリは

こんなかんじで、ちょっとだけめんどうだなー、ってなった。

そこで、ShufooっていうもうひとつTカードを表示する機能があるアプリを見てみることにした。

こいつはマジでアカンやつだw っておもいつつ、これをシミュレートするアプリはとてもカンタンに作れそうだったので、決行。

→3時間くらいで作れた。

github.com

実際に使えるの?

→今日(2019/02/04)ファミマで実際に使ってみました。使えました。

まとめ

自分専用だから、(カード番号をソースに埋め込んだりはしていないけども)みんな使わないでね。

Kotlinのデータクラスが便利なのでRubyでそれっぽいものを作ってみたときに勉強になったこと

いまの職場に入社した頃はPython大好きRuby大嫌いだったのだけど、3年半もRubyばっかり触っていたら流石に少し克服しつつあるので、初めてGemを作ってみることにした。

ネタはKotlinで便利なdata class

data class Point(val x: Double = 0.0, val y: Double = 0.0) {
  val norm get() = Math.sqrt(x * x + y * y)
}

つくってみると、「Rubyのこの仕組みって、こうやって実装されてるのか!」ってところがいくつか有ったので、書いておこうかなと思う。

そもそもなんでデータクラスを作ろうと思ったのか?

まずは前提の共有から。

いま検索画面を仕事でつくっていて、「検索条件」のように6つくらいの値(キーワード、ソート順、カテゴリ、etc)を保持する箱がほしいと常日頃おもっていた。

qiita.com

↑ではかなり控えめに書いたんだけど、現状だと6つくらいの値がすべてコントローラのインスタンス変数で保持されていて、無秩序にそれらをパーシャルビューやヘルパーメソッドから読んでいるような実装になっている。イメージこんなかんじ↓

<% if @keyword.present? %>
<h1><%= @keyword %>の検索結果</h1>
<% else %>
  <% if professional_search? %>
  <h1>プロに仕事を依頼する</h1>
  <% elsif ... %>
     ...
  <% end %>
<% end %>
def professional_search?
  @keyword.blank? && @only_professional_flag.present?
end

これを、

SearchQueryViewModel = ViewModel.create(:keyword, :sort_order, category_ids, ...)
@search_query_view_model = SearchQueryViewModel.new(keyword: params[:q], sort_order=params[:order], ...)

のように、コントローラの変数は1つにして整理したり、

search_result = User.search(...)

UserPartialViewModel = ViewModel.create(:display_name, :avarage_feedback_score, :num_contracts)
@users = User.where(id: search_result.user_ids).includes(:feedback_summary, :profile).map{|user|
                  UserPartialViewModel.new(
                    display_name: user.profile.display_name,
                    avarage_feedback_score: user.feedback_summary.avarage_score,
                    num_contracts: user.feedback_summary.num_contracts)
               }
<% @users.each do |user| %>
<%= render 'shared_partials/user', locals: { user: user } %>
<% end %>

のように、パーシャルビューに必要な変数たちをひとまとめにしたり、あとは、ヘルパーに生やしてる変数をビューモデル側で定義したり、

まぁそれなりにひとかたまりに値をまとめる箱がほしいと思うケースがあった。  

とくにライブラリとか使わなくてもシンプルに

class HogeViewModel
  def initialize(keyword:, hoge_flag:)
    @keyword = keyword
    @hoge_flag = hoge_flag
  end

  attr_reader :keyword, :hoge_flag
end

のようなボイラープレートコードを書きまくればいい話なんだけど、できればこれを先に書いたような HogeViewModel = ViewModel.create(:keyword, :hoge_flag) みたいにサクッと済ませられる仕組みが欲しかった。

Ruby標準のStructでいいんじゃないか?」とも思ったのだけど、こいつは多機能すぎた。そもそもImmutableではないし、#members のように属性一覧をイテレートできるような仕組みまであるのはビューモデルでは全く必要ない。

ということで、自分が欲しいものをそのまま作ってみることにした。

 

bundle gem

Nodeでいうところの npm init みたいな感じの物があるだろうと思ったら、やっぱりあった。

bundle gem kt_data_class --test=rspec

github.com

RSpecの下準備をやってくれるのは結構ありがたみある。

CIの設定

bundle gem を叩いただけで、 .travis.yml が作られるし、rakeのデフォルト動作で単体試験実行をしてくれるようになっている。なので、そのままTravis CIをONにするだけで、masterプッシュ時に自動でテストがまわるCIっぽいことは十分にできる。

ただ「CIで実行したときだけspecが落ちる」という事象が起きたときに、Travis CIだとどうにもこうにもデバッグができず、かなり格闘をすることになった。 やっぱり使い慣れてるCircle CIでSSHデバッグしたいなーーと思い、速攻で乗り換えた。

github.com

結局は require 'spec _helper' を書き忘れていただけ、というショボショボの原因だったんだけど、それでもなぜかローカルではspecが通ってしまうので結構ハマった。

大抵の場合にはTravis CIで十分なんだろうなーとは思うけど、とはいえRubyのバージョンによってはTravis CIはRubyのダウンロードとか毎度やって無駄に時間がかかるし、Circle CIのコンフィグはコピペすれば動く系なので、Circle CIを最初から使ってしまうのが楽かなぁと個人的には思った。

eql?, equal?, ==, === は何を返すべきか

これはPython信者からするとかなり意味不明とキレたくなるところだ。Rubyにはequalっぽいメソッドが多く、名前もややこしい。 same_object? same_value? とかそういうメソッド名にしなかったのはなんで??ってのがものすごく謎い。

という愚痴はここまでにして、

mickey24.hatenablog.com

を参考に実装した。あとは、HashとかStructがそれぞれどういう eql? equal? == になっているかも参考にした。

このあたりは、毎度コンソールで動作確認するのがしんどいので、

  describe 'equality' do
    let(:klass1) { KtDataClass.create(:x) }
    let(:klass2) { KtDataClass.create(:x) }

    describe '同一のクラスの2つの同値インスタンスの比較' do
      let(:instance1) { klass1.new(x: 1) }
      let(:instance2) { klass1.new(x: 1) }

      it { expect(instance1.equal?(instance2)).to eq(false) }
      it { expect(instance1 == instance2).to eq(true) }
      it { expect(instance1 <=> instance2).to eq(0) }
       it { expect(instance1.eql?(instance2)).to eq(true) }
      it { expect(instance1 === instance2).to eq(true) }
    end

    describe '同一のクラスの2つの異なる値のインスタンスの比較' do
      let(:instance1) { klass1.new(x: 1) }
      let(:instance2) { klass1.new(x: 2) }

      it { expect(instance1.equal?(instance2)).to eq(false) }
      it { expect(instance1 == instance2).to eq(false) }
      it { expect(instance1 <=> instance2).not_to eq(0) }
      it { expect(instance1.eql?(instance2)).to eq(false) }
      it { expect(instance1 === instance2).to eq(false) }
    end

    describe '定義が同じで、異なるクラスの2つのインスタンスの比較' do
      let(:instance1) { klass1.new(x: 1) }
      let(:instance2) { klass2.new(x: 1) }

      it { expect(instance1.equal?(instance2)).to eq(false) }
      it { expect(instance1 == instance2).to eq(true) }
      it { expect(instance1 <=> instance2).to eq(0) }
      it { expect(instance1.eql?(instance2)).to eq(false) }
      it { expect(instance1 === instance2).to eq(true) }
    end

のようにspecをササッと書いて、こいつが通るまで実装を繰り返すっていうTDDっぽい感じで進めると捗った。

分割代入はどうやったら実現できるのか

これは必要のない機能なんだけど、どうやって実現されてるのか知りたかったので実装した。

github.com

Structだと

Point = Struct.new(:x, :y)
p1 = Point.new(3, 4)

x1, y1 = p1

x1
# => 3

y1
# => 4

のような挙動になるやつは、何を実装すればできるようになるのか?

to_a あたりを生やせばいいのかな?と思って調べ始めたところ

Rubyの多重代入におけるto_aとto_aryの挙動 - maeharinの日記

この記事に行き着いた。 to_ary っていうメソッドを生やせばいいんだとか。(これまたわかりにくい名前だw)

初期化ブロックはどうやって実装するのか?

Structだと

Point = Struct.new(:x, :y) do
  def norm
    Math.sqrt(x * x + y * y)
  end
end

のように拡張することができる。これはどうしても欲しい。

で、何を調べていたか忘れたけど、このあたりで「あれ、既に似たようなライブラリあるじゃん??」ってのを発見して、

github.com

この実装を結構参考にさせてもらった。クラスの拡張は class_eval を使ってるんだなー、と。

github.com

少し悩んだ末に型安全を捨てた

実はこのGemを作り始めたときは

Point = KtDataClass.create(x: Fixnum, y: Fixnum)

のように、型を明示的に指定して、

p1 = Point.new(x: 3, y: 4)
# => #<Point:0x000000000233c5f0 @x=3, @y=4>

p2 = Point.new(x: 3, y: "4")
# ArgumentError: type mismatch: y must be a Fixnum, String given

のように、型に合わないものは入れられないようにするつもりだった。

特に理由はないが、なんとなく「入れられるものを制限しておいたほうが、無用な条件分岐とか is_a? とかしなくて済むかなー」くらいの軽いノリで。

ところが、実際に使うとなると、これだと結構不便だということがわかった。

一番「あれれ」と思ったのが、true/falseを入れるときの定義。 TrueClass or FalseClass みたいな謎の定義をしないといけない。それはあまり狙ったところではない。むしろ邪魔くさい。

さらに、

qiita.com

このあたりの議論を見るに、Rubyはダックタイピングなので、型で制限するのは筋がよくないということだ。

それはそのとおりだと思った。

github.com

Gemの公開

何を間違ったか、最初の方でRakefileを消してしまっていたので rake build ができなかった。

とはいえ、specをrake経由で実行したいとは思わないので、

require "bundler/gem_tasks"

の1行だけを書いたRakefileを定義して、無事にrake buildでパッケージが作れた。

github.com

あとは、 rake release でリリースしようとしたら、Gitに何かがpushされるっぽく邪魔くさかったので、

rake build
gem push pkg/kt_data_class-0.1.0.gem 

のように、明示的にプッシュ対象を指定して、シンプルにリリースした。

このあたりは手作業でやるのがだるいので、そのうちCIのgit tagトリガーのbuildに任せたいと思う。

まとめ

Gemを作ると、結構勉強になることが多かった。