2017年の私とYAGNI

2017年は YAGNI でした。

古い話でいえば Ron Jeffries さんのこれになるわけです。

ronjeffries.com

ここには、

  • 考えがずれちゃってるよ、クラスがどうなるべきじゃなくて、どうなりそうか考えるなんて。集中しろよ
  • コードをいじくりまわしてないで、実際の問題を解決して前に進んでる感覚高めていこうぜ
  • 結局いらないかもよ。いらないとしたら書いた時間、読んだ人の時間、保存してた場所、全部無駄になるぜ

などと書いてあります。

これはこれでエモーショナルないい話なのですが、個人的には大好き Martin Fowler さんが書いた

martinfowler.com

こっちのが好きです。こっちには、要求ではなくて推測で作られたコードのもたらすコストってのが書かれていて

  • Cost of Building (作っても誰も使わないメソッドやクラスと、そのテストと、ドキュメントなんかに使うコスト)
  • Cost of Delay (推測で作ったもののために他が遅れた分のコスト)
  • Cost of Repair (半年後に必要になった時、すでに負債となってしまったコードを修正するコスト)
  • Cost of Carry (結局使われたかもしれないが、それまでの期間使われないコードのために増していた複雑性)

なんてふうにまとめられていて、これらが「推測が外れていた」「推測は当たっていたが、間違った実装をした」「推測は当たっていて、正しい実装をした」という状況ごとに適用されます。推測も実装方法も正しかったとしてもコストはあるよ、というのが一番のポイントです。

しかし、なんでもかんでもめんどくさがってヤグニャればいいかということそんなことはなくて

Yagni only applies to capabilities built into the software to support a presumptive feature, it does not apply to effort to make the software easier to modify.
YAGNI は、推測された特徴のために作られるソフトウェアの能力に対して適用されるものであり、ソフトェアを変更しやすくする努力については適用されない。

と注意も書いてあります。変更可能性を高めておけば、というか高めておくことで「予め用意しておかなかったコスト」を小さくでき、天秤を効率的な方に傾けることができてビジネス的に正当化されます。わかりやすい。ここまでは復習です。

組織のYAGNI

僕これほんと思ったんですけど、これってチームにも適用されますよね。

僕の尊敬する人事の人が「新しい人は『いい人を見つけたから何かやってほしい』とか『他所と比べて少ない気がするから採っておきたい』ではなくて、帰宅途中にふと『これ以上は工夫のしようがない。もう無理だ』って思ったくらいから探し始めればいいよ」って言ってて、なんだか納得感があって、まあ極端ですけど、これ YAGNI ですね。

つまり、実際の要件ではなく推測した要件にもとづいて採用したメンバーというのは、 Martin Fowler が言う4つのコストをチームに対して持ってしまうわけです。

  • Cost of Building (採用活動にかけたコスト)
  • Cost of Delay (採用活動で遅れた制作のコスト)
  • Cost of Repair (まだ仕事がないのに、半年後に仕事があるからといって採用した人が半年間暇してる間にアル中になってしまったので矯正病院に入れたり、解雇したら裁判沙汰になったときのコスト)
  • Cost of Carry (本当に必要になるまでのお給料、人が増えたことによる意見のばらつき、会議室が取りづらくなるとか、WIP・仕掛りが増えてチームが集中できなくなるとか)

うーん、これは渋い。

ちなみに、チームを修正・変更しやすくする努力は最初からずっとやっていていいということです。コードだと主に自動テスト、継続的デリバリーです。つまり人だと…あんまり考えたくないです。

つまり、変更可能性を担保するためにはコードよりもっとずっとリスク回避志向にならないと駄目だよということですが、リスク志向ではなく、リスク回避志向になった結果、上記のコストが目に入りにくくなってとりあえず採用しておこう、みたいな意思決定をしてしまったりするのですから、リスクやらコストの見積もりってつくづく難しいなあって思います。

見送るにせよ見逃すにせよ、失った(と思い込んだ)ことによるコストと、手にしたものが手にした後に生むコストとリターン、そして推測した要件は必ずずれるということを正しく天秤にかけられる人・場面はそうそうなさそうです。もはやよく寝る、とか幸せでいる、とかが大事なんじゃないかなあ。

Firebase Weekly 第4・5号をまとめました

www.getrevue.co

Firebase Dev Summit 2017 があって Predictions が発表されたりなどしました。

個人的には Puffelen おじさんの

stackoverflow.com

Android 版の Cloud Firestore の listener にはライフサイクル感じてもらえるよ、っていう話がいちばん衝撃でした。 Firestore なんとかして仕事で使って深掘ろうと思いました。

まとめてるたびにたくさん出てくるから、影響されて Google アシスタント開発始めちゃいました。

Firebase Weekly 第3号をまとめました

www.getrevue.co

週に1度のブログ更新です。 Firebase Weekly 3号をまとめました。個人的には、メルカリさんがメルカリチャンネルで Realtime Database 使ってるよ。ってお話が、これからの自分の仕事の仕方を早めに変えてくれて嬉しいなと思いました。ちょっと説得力が違います。

個人的には、 jQueryRails のときのような……、食器洗い洗浄機のような……、ルンバのような……、そういう種類の、業界だけでなく自分のライフスタイルや考え方への強い変化を感じています。

Firebase Weekly 第2号をまとめました

www.getrevue.co

ちゃんと2号も出しました。 Firestore と DevFest Tokyo のおかげです。

DevFest Tokyo は招待いただいてたのに( Firebase の話が全然ねえ)と思って申し込みすらしなかったんですが、結構トークがあったみたいで…。ないと思った僕は何を見ていたんだろうか。何も見ていなかったんでしょう。

まとめてて思ったことは、みんな iOS かブラウザだなってことです。

Firebase Weekly を作ってみました

Firebase の日本語の情報ってなかなか集めるの大変だなーと思っていたので、 iOSAndroid や Swift や Kotlin みたいに Firebase Weekly みたいなのないかなあと検索したら、なかったので、えーって思って作りました。

www.getrevue.co

サービスは Swift Weekly の使ってるところですが、中身は日本語のものを集めました。とりあえず1週間分。

次回は英語と動画の追加を予定してます。自分以外に一人でもお求めの方がいれば…と思ってます。誰もみてなくても3号までは必ずやります! よろしくお願いいたします。

近況

最後の記事からほぼ3年経ちまして、その間にすっかりインフラや Rails には縁が遠くなり、今は Firebase と Android アプリと iOS アプリを同時に全部やって作るというこれまた楽しいお仕事をさせていただいています。

その辺りのことは、試験的に Medium に書いてみているのですが、いやーこれが自分が SNS に投げた URL 以外からの流入がびっくりするほどない! Google.co.jp のランクなんかびっくりするほど上がらないです。クロール間隔もすごくゆっくり。

medium.com

日本におけるはてなと Qiita のエコシステムは素晴らしいものであるなあと実感する日々です。乗っかるだけである程度の流入があります。

Medium も英語で書けばかなりいいんですよね。ということは逆もまた然りなのか……。

自分で検索していても、日本語の Firebase Database の話とか、自分みたいなふわっとしたレイヤーからリーチする Rx の話とか、もうちょっとあってもいいなと思うので、ある程度やってダメならまたここに戻ってこようと思います。

stop したり init スクリプトを削除したりすると apache2 cookbook なんかが仕込む delayed な reload アクションがこけて converge 全体が失敗する

また Chef のしょうもない話で恐縮なのですが、表題のようなことに悩んでおりました。

実はこれ、単に次の起動時に立ち上がってきて欲しくないという要求から始まったもので、それだけなら stop も削除もせず、立ち上げたまま chkconfig off でも service リソースで止めるでもしておくだけでいいわけですが、なんかこー…勝手に上がって欲しくないときって、あらかじめ明示的に止めたいじゃないですか、私の手で。特に docker なんかだと supervisord 使いたいじゃないですか。使わない(使って欲しくない)のが明らかなら upstart の config ファイルや init ファイルも消したい……はやり過ぎかもしれませんが、そういうときにどうするかっていうのを

delayed されたものをキャンセルしてえー

とか結構長いこと悩んで放置していたのですが、どこだったか誰かのレシピ見てて

あ…… web_app リソースの後に stop する delayed なアクションをセットするんだ……

という学びがあった次第です。

web_app 'oogatta' do
  docroot   /var/www/oogatta
  server_name    'oogatta'
end

bash 'cleanup_services' do
  code <<-EOH
    if [[ -e /etc/init.d/httpd ]]; then
      service httpd stop
      chkconfig httpd off
      pkill httpd
    fi
  EOH
  action :nothing
end

log 'set cleanup timer' do
  level    :info
  notifies :run, 'bash[cleanup_services]', :delayed
end

気がついてみると実に当たり前ですね。

EC2 の Amazon Linux を launch するとき authorized_keys に公開鍵が注入されるユーザを変更したい

変更したくないですか? 私はしたかった。いい加減 recipe 作り込んだし、 ec2-user じゃねえだろって思ってた。例えば oogatta とかにしたかった。でもわからなかった。

今日、 @ainame と話してたら「それは CloudInit です」と教わった。 CloudInit …!

その通りだった。 /etc/cloud/cloud.cfg.d/00_defaults.cfg を開くと

# A set of users which may be applied and/or used by various modules
# when a 'default' entry is found it will reference the 'default_user'
# from the distro configuration specified below
users:
  - default

と書いてあり、その通り system_info の中を見ると default_user がいた。

# System and/or distro specific settings
# (not accessible to handlers/transforms)
system_info:
  # This will affect which distro class gets used
  distro: amazon
  distro_short: amzn
  # Default user name + that default users groups (if added/used)
  default_user:
    name: ec2-user
    lock_passwd: true
    gecos: EC2 Default User
    groups: [ wheel ]
    sudo: [ "ALL=(ALL) NOPASSWD:ALL" ]
    shell: /bin/bash

めっちゃ ec2-user って書いてある……。

さっそくこの一歩前の recipe で

if platform?('amazon')
  bash 'switch default user' do
    code <<-EOH
  sed -i -e '{s/name: ec2-user/name: oogatta/;}' /etc/cloud/cloud.cfg.d/00_defaults.cfg
    EOH
  end
end

ぶち込んでやりましたら、望んでいたとおりになった……。 lock_passwd とか passwordless sudo とかそのままでいいのかってのはあとで調整として、これで packer とか test-kitchen とか ssh 接続をラップするツールを横断している時に(今はどのユーザとどの鍵で入れるんだっけ……?)といちいち認知負荷を強いられることがなくなり、今夜は健やかな夜を過ごせそうです。 @ainame サンタさん、ありがとう。

これでミッチェル先生が packer の ssh 鍵交換周りで本気出してない理由わかったす。

Rails の n.months って Date の加減演算の引数になると月ごとの日数考慮するんだなあ

知らなかったというか、それについて真面目に考えたことが無かった。

コード

class HogeTest < Test::Unit::TestCase
  def test_hoge
    Timecop.freeze Date.new(2014, 11, 1) do
      assert_equal Date.new(2014, 11, 1), Date.today
      assert_equal Date.new(2014, 12, 1), Date.today >> 1
      assert_equal Date.today + 1.months, Date.today >> 1
      assert_equal Date.today, Date.today + 1.months - 30.days
      assert_equal 30.days, 1.months
    end

    Timecop.travel Date.new(2014, 12, 1) do
      assert_equal Date.new(2014, 12, 1), Date.today
      assert_equal Date.new(2015, 1, 1),  Date.today >> 1
      assert_equal Date.today + 1.months, Date.today >> 1
      assert_equal Date.today, Date.today + 1.months - 31.days
      assert_equal 30.days, 1.months
    end

    assert_equal Date.new(2015, 1, 1),  Date.new(2015, 1, 1) + 2.months - 59.days
    assert_equal Date.new(2015, 2, 1),  Date.new(2015, 2, 1) + 3.months - 89.days
  end
end
$ RAILS_ENV=test rbbe rails r hoge.rb

ぽてっと 1.months って書いた場合はいつも固定で30日が来るんですが、加算の左辺が11月の場合30日になって、12月の場合31日になる。2015年の1-2月の2ヶ月だと59日で、2-4月の3ヶ月だと89日。

いつも定数的に使ってるけど、同じスコープ内で同じ表記して違う値が戻ってくるんだなあ。31日から1ヶ月後とやって次の月に31日が無い場合30日に戻るので、「次の月の同じ日に行く!(その日が無かったら有効なとこまで戻る!)」指向なのでありました。

ActiveSupport::Duration 同士の加減算では年月日の情報が保持されている

ちなみに 1.month + 1.days こんな感じに左辺としても使うと呼ばれるのは Duration#+ でこれはもちろん30日固定…なんだけど、この加算で生成された Durationインスタンスは内部的に「月が1で日が1です」って情報を抱えていて、これを Date に対して加算しようとするとやっぱり30日になったり31日になったり28日になったりする。

    duration = 1.months + 1.days
    assert_equal Date.new(2014,11,1) + duration, Date.new(2014,11,1) + 31.days
    assert_equal Date.new(2014,12,1) + duration, Date.new(2014,12,1) + 32.days

これは、自分アホなんで、間違いなく定数として使ってバグらすパターンですね。

実装

Date#+ はその実 Rails が拡張した Date#plus_with_duration で、これが最終的にネイティブの Date>> を使っておられる。

TimeDate はいかにも Ruby ネイティブの少女っぽいふりして Rails によってすっかり女に変えられているので、裏表あって怖い。