satoryuの日記

忘れっぽいから覚えてるうちに書いておかないと。

はてなブログからGitHub Pagesに引っ越します。

特に理由はありませんが、はてなブログからGitHub Pagesへ引っ越すことにしました。

satoryu.github.io

今の所、はてなブログのアカウント自体は消すつもりはありません。 ですが、念の為、ここで書いてきた記事すべてをGitHub Pageへ移行してあります。

移行のために作ったスクリプトを公開しました。 forkしてご自由にお使いください。

github.com

スクラムフェス大阪2020でLTセッションを担当した。

f:id:satoryu:20200628194153j:plain

6/26、27に開催されたオンラインカンファレンス「Scrum Fest Osaka」で、ハッカーライフラボとしてLTセッションを担当しました。

confengine.com

オンライン開催決定後、ハッカーライフラボを運営している岩切さん*1から

旅するAgile本箱で何か一緒にやりませんか?

というお誘いを頂き、今回の企画に至りました。 たしか今年のデブサミの頃からこういうイベントをしたいという話をしていたのですが、具体的にやる機会を今回得られたのでとても良かったです。

旅するAgile本箱は、翔泳社の岩切さんが始めた活動です。 スクラムを実践する人たちが読むと良い本を、実践者の人たちが選んで集め、それをスーツケースに詰めて一定期間貸し出すというものです。 この本箱に入っている本それぞれには、その本を選んだ人が「勧めたい!」と感じたストーリーがあると思いました。 特に、それは「本から勇気づけられた」といった話なのではないかと私は想像しました。 そこで、本に後押ししてもらったストーリーを話すLTセッションを企画しました。

8人のトーカーと6つのストーリー

こんなLTセッションで話をしたい人なんて誰もいないんじゃないか、と思いつつも、「やってみなければわからない」ということで募集しました。 その結果、8名のエントリー!中には現在のAgile本箱に入ってない本の話もあって、非常にありがたい収穫でした。

セッションの動画は後日ハッカーライフラボで公開するので、是非チャンネル登録をお願いします。

www.youtube.com

トーカーとタイトルについてはこちら。

  1. 「『SCRUM BOOT CAMP THE BOOK 増補改訂版』コラムニスト3人分の学び」, 森 一樹(@viva_tweet_x), 飯田意己(@ysk_118), Koji Sudo(@su_kun_1899)
  2. 「一万年の旅路 ネイティブアメリカンの口承史」, 懸田 剛 (@kkd)
  3. カイゼン・ジャーニー」, 井内 聡(@superSatoshiKun)
  4. 「旅するAgile本箱をシェアして会社の仲間と読んだ体験から得たこと」, 常松祐一 (@tunepolo)
  5. 「チームが「サイロ化」しないための仕掛け(増補版)」, ふかやみわ(@miwa719)
  6. 「未来を変えるためにほんとうに必要なこと――最善の道を見出す技術」, 石井智康

トップバッターは長年愛され続けている『SCRUM BOOT CAMP THE BOOK』。

出版されて7年目の今年に増補改訂版が出版されました。 そのコラムを書いたコラムニストのうち3名が、それぞれの経験から生まれたコラムについて紹介していただきました。この本を読んで感銘を受けた3人が、増補改訂版にあたり、それぞれが歩んできた経験をコラムに込めているのが最高にエモい話でした。

2番手は、アジャイル459トラックでも登壇していた懸田さんが、「一万年の旅路 ネイティブアメリカンの口承史」を紹介してくださいました。

一万年の旅路 ネイティヴ・アメリカンの口承史

一万年の旅路 ネイティヴ・アメリカンの口承史

人類が生まれてどのように移動していったのか、その中で迫られる決断。 1万年前のイロコイ族の人たち生死を伴う判断といった、今我々がおかれている状況と近いようでもあります。懸田さんの発表から一万年前の人々から普段の仕事と対比すると、かなりエクストリームな感じもありますが、とても興味をそそられました。 Amazonだと在庫数が残り僅かだったのですが、このLT直後にごっそり消えて、今確認したら入荷待ち状態。 是非読んだ方々はレポートをお願いします。 懸田さんがこの書籍を紹介しているブログがあるので、こちらもオススメです。 medium.com

3番手のいっちーさんからは、「カイゼン・ジャーニー」から始まるいっちーさんのジャーニーのお話。

いまさら紹介する必要ないくらいに、多くの方が手にとっている本です。 その中の言葉の1つに、

それで、あなたは何をしている人なんですか?

という言葉に考えさせられた人はたくさんいるのではないでしょうか。いっちーさんもその一人。 何もできていなかった自分自身に気づき、社内でスクラムの勉強会を開催したり、部署を超えた活動も始めるなどの行動を起こしています。 さらっと「人生を変えた一冊」とおっしゃっていて、これは著者やこの本に関わっった人たちを泣かせる言葉ですね。

4番手の常松さんのLT「旅するAgile本箱をシェアして会社の仲間と読んだ体験から得たこと」では、旅するAgile本箱を実際に利用したRettyさんで起きたことの話です。 Agile本箱を借りると何ができて、何が起こるのか、ということを話していただきました。 LTで話していただいた内容の一部は、Retty Tech Blogで読むことができます。

engineer.retty.me

本当は弊社はRettyさんの次に借りる予定だったのですが、非常事態宣言などもありキャンセルしました。Rettyさんで行われた社内ビブリオバトルなどの話をきいて、やっぱりまた借りてみようと思えました。

5番手のみわさんのLT「チームが「サイロ化」しないための仕掛け(増補版)」の話は、下記ブログの内容の増補版です。動画を視聴する前に読んでおくとより楽しめます。

miwa719.hatenablog.com

このLTの中で触れている本はこちら。 leanpub.com

「仕掛けの仕掛け」、「心理的安全性はやってこない」や「チームは概念上のもの」など、刺さる話がたくさんありました。最後のオチも素敵でした。「チーム間の情報共有ができてないー」とか言ってるマネージャーの人はじっくりこの話を聞いたらいいんじゃないでしょうか。 いや、ほんと。

トリは石井さんによる「未来を変えるためにほんとうに必要なこと――最善の道を見出す技術」。

誰もがお世話になった石井食品の社長に登壇していただきました。セッションの翌日が株主総会というお忙しい中、ご参加いただけて本当に感謝しています。 この本は民族紛争解決の本です。石井さんは、心が折れそうなくらいに辛い状況になったときの心の処方箋として、この本を使っているそうです。「もっと酷い状況でも頑張っている人がいる」ことを知ることで、ちょっと前まで辛いと思っていた状況が「なんだそんな大変じゃないや」といった気持ちに切り替えられるということです。どんなつらい状況であれ、アパルトヘイト後の南アフリカやゲリラのリーダーが参加するワークショップなんかに比べたら楽勝ですよね。 そして、この本のタイトルにある「未来を変えるために必要なこと」は、「力(Power)と愛(Love)」であると書かれています。実現するために行動する力とバラバラなものを統一させようとする力を表しています。 組織運営においてはどちらも重要という話をされていました。

どの方の話も濃厚な内容で、一人ずつ45分のセッションが組めたんじゃなかったのかな。 でも、「もっと聞きたい」と思わせられたらLTとしては成功だと思うので、このセッションは個人的には成功でした。 だって、少なくとも自分はもっと聞きたいと思ったので。

当日ご参加いただいた皆様、そして何よりもここでストーリーを紹介してくれたトーカーの皆様、大変ありがとうございました。

Scrum Fest Osakaを終えて。

この2ヶ月くらいは、Scrum Fest Osakaに対しては、よく思ったりそうでもなかったりと悶々としていました。 最初にScrum Fest Osakaがオンライン開催すると聞いたときは、とても嬉しかったのを覚えています。 ですが、開催場所のDiscordを覗いてみると、大阪だけでなく様々な地方のトラックが乱立していて、一体何の会なのかわからなくて、混乱しました。 その趣旨が見えず、正直、「うっ」と思いました。 ただ、今後、こういうオンライン開催でのカンファレンスは増えていくだろうし、新しいあり方を受け入れなければいけないのかもしれない、と思ってもいました。 そんな時にちょうどよく声をかけてくれたのが岩切さんでした。 これまでイベントに参加する際は、単独での講演か、普段一緒に仕事しているチームメンバーと出ることが殆どでした。 今回はハッカーライフラボの皆さんと一緒に準備から当日運営までやらせていただきました。 前に進めるためにワイワイとできて良いチームだな、って思いました。 そこに混ぜてもらえて、大変感謝しています。

と、色々書きましたが、要するに楽しかったということです。

LTセッションの動画は、ハッカーライフラボのYoutubeチャネルで公開される予定ですので、ぜひともチャンネルの登録をよろしくお願いします。

www.youtube.com

*1:デブサミのおっかさんでもあり、翔泳社の中の人でもある

「みんなでアジャイル」を読んだ。

みんなでアジャイル ―変化に対応できる顧客中心組織のつくりかた

みんなでアジャイル ―変化に対応できる顧客中心組織のつくりかた

  • 作者:Matt LeMay
  • 発売日: 2020/03/19
  • メディア: 単行本(ソフトカバー)

開発のプラクティスに特化しているわけではないので、エンジニア以外の人たちでも十分にわかるような内容だと思う。 むしろ、そういった人たちがアジャイル開発というのに関わる場合に始めてに手に取るといいのかな。 「アジャイルって何?」という人にどういうことをやるかという話をしても頭に疑問符が残りそうだ。 そういったときに、この本に書いてあるように「何のために」「なぜやるのか」といった説明があると理解してもらいやすそうだ。

色々プラクティスを試してみてるけど何かモヤモヤっとしている人にも良さそうな本だと思う。 アジャイル開発を導入すると決まってプラクティスを始めてみたが、何か腑に落ちないといった場合に、各章の最後にある「良い方向に進んでいる兆候・悪い方向に進んでいる兆候」を読んで振り返ってみると改善策が出てきそう。 もちろん、これは導入し始めたチームだけでなく、その周りにいる経営層含めた関係者も同じように兆候を理解しておくために、良い本だと思う。

「リモートワーク時代のモブプログラミング」勉強会でLTしてきた

もうそろそろ一週間経ってしまいそうなのですが、「リモートワーク時代のモブプログラミング」という勉強会(?)でLTをしてきました。

最初はただのネタっぽい感じの話にしようかと思っていたのですが、わりと真面目に考えても良い話とも思っています。 このLTをした翌日にSlackがダウンしました。 いくつかの開発の現場ではチームでのコミュニケーションが途絶えたのではないでしょうか。 幸いなことに、短時間であったし、そんなに頻繁に起こることでは無いのかもしれないです。 けれど、zoomなどで常時接続していなければ開発ができない、という状態は問題ないのだろうか。ネットワークが常に安定していることは望ましいけれど、そうでなければリモートでのモブワークはできないのだろうか。低速なネットワーク環境で無理してモブを続けるだけでなく、諦めて別々のタスクに分かれて仕事をする方が良い場合があるのではないだろうか。

このようなことを少し考えてみることも意味があるのではないかと思いました。 上の疑問に対する自分の解答ははっきりとありません。悶々としています。

捕捉できなかった例外を勝手にググってくれるUndine をリリースしました

f:id:satoryu:20200429164857j:plain unsplash-logoFabian Grohs

開発中のコードを試しに実行してみたところ、思いがけない例外が出てしまった。 そういう時はどうするだろうか。 出てきたエラーメッセージを読み、何が原因なのかを探るのではないでしょうか。 自分もgoogleなどで検索して、例外に関するドキュメントを探したりします。

その手間を助けるために、コード内で捉えられなかった例外が出た場合に自動でブラウザを起動してエラーメッセージをキーワードとして検索してくれるgemを作りました。

github.com

使い方

インストール

下記のようにgemコマンドでインストールできます。

gem install undine

bundlerをお使いの方はGemfileに下記の一行を追加し、bundle install を実行するとインストールできます。

gem 'undine', group: :development

使い方

お使いのRubyスクリプトのはじめの方に以下のコードを追加します。

require 'undine'
Undine.load

これだけで、どこにも捕捉されなかった例外が出たときに、例外メッセージを検索するgoogle のページがブラウザで開かれます。

既存のスクリプトに手を入れたくない場合は、undine/autoload を実行時に指定してください。

ruby -r'undine/autoload' your_script.rb

bundlerを利用している場合は、Gemfile中のgemメソッドにrequireオプションを追加してください。

gem `undine`, require: 'undine/autoload', group: :development

バグ報告、ご意見はこちら

利用してくださった皆様からの感想、ご意見、バグ報告なんでもお待ちしております。 何かございましたらGitHub Issueから連絡いただけると助かります。

Typetalkに通知を送るGitHub Actionを作った: Typetalk Notify

無料プランがあるということでなんとなくTypetalkを使い始めてみた。 Slackなんかと同じようにAPIやWebhookなどがありカスタマイズが可能で、GitHubとの連携もサポートされている。

GitHubと連携することで、ブランチのプッシュ、Issueやプルリクエストの作成・変更が起きる度に通知がTypetalkのトピック(Slackのチャネルに相当)に届くようになる。

しかし、リリースなどどうやらサポートされていないイベントが一部あり、またメッセージをカスタマイズしたいことがある。 ということで、GitHub Actionsを作ってみた。

github.com

使い方

オプションについてはREADMEを参照してほしい。 同じREADMEに記載されているworkflowファイルの記述例を載せる。

name: Release announce

on:
  release:
    types: [published]

jobs:
  steps:
    - uses: satoryu/typetalk-notify@v0.0.1
      with:
        token: ${{ secrets.TYPETALK_TOKEN }}
        topic_id: 165952  # Replace your topic's id
        message: "${{ github.repository }} ${{ github.ref }} has been released :tada: :white_flower:"

この設定ファイルをリポジトリ.github/workflows/ ディレクトリ以下にrelease_announce.yml などのようにして保存する。 その後に、release tagを作ると、以下のような通知がTypetalkのトピックに投稿されるようになる。

f:id:satoryu:20200331104506p:plain

上に書いた設定を変更すれば、push時やissue作成時など別のGitHub上のイベントが発生した時に好みのメッセージを投稿することができる。

おまけ: GitHub Hackathon にエントリー

ちなみに、3/31までGitHub Hackathonが開催されていて、GitHub Actionsがテーマになっている。 ちょうど良かったので今回作成したActionでエントリーしてみた。

githubhackathon.com

Rails のGeneratorのデフォルト値はどこから来るのか

ことの発端

Rails のController generator で不要なrouting が書き込まれてしまうので、毎度--skip-routes オプションを付けるようにしている。 けれど、オプションを付け忘れることもあって、面倒なことになることがよくあった。 調べてみると、 config/application.rb などでconfig.generators を以下のように指定することでデフォルトのオプションとして指定できることがわかった。

config.generators do |g|
  g.skip_routes = true
end

github.com

該当の変更だけを見ても、仕組みが理解できなかったので調べてみた。

TL;DR

controller generator にオプションの渡し方は、以下の3通り。

  1. config.generators.controller = { skip_routes: true }
  2. config.generators.rails = { skip_routes: true }
  3. config.generators.skip_routes = true

個別のgeneratorのためのオプションとして最優先されるのは1の設定の仕方。 2と3は同等の扱いで、他のgeneratorとも共有される。

探求編

コードを読んでいく方針として次の順で行った。

  1. 参照側: ControllerGeneratoroptions[:skip_routes] はどこを見ているのか
  2. 設定側: Rails::Application.config.generators に指定した値はどこに入るのか
  3. 上記2つはどこでつながるのか

参照側

module Rails
  module Generators
    class ControllerGenerator < NamedBase # :nodoc:
      argument :actions, type: :array, default: [], banner: "action action"
      class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb."
     # 
     # 中略
     # 
      def add_routes
        return if options[:skip_routes]
        return if actions.empty?
        routing_code = actions.map { |action| "get '#{file_name}/#{action}'" }.join("\n")
        route routing_code, namespace: regular_class_path
      end

add_routes メソッド内で、options[:skip_routes] を見ているので、options がどこから来るのかを探る。 ControllerGenerator 自身は持っていないので、継承しているNamedBaseを見てみるがこれもoptionsを持っていない。更に上位クラスのRails::Generators::Baseを見ると、これがThor::Group を継承していることがわかる。 RailsのgeneratorなどCLIは、CLIを作成するDSLを提供するthorを使用している。 Thorのドキュメントを見ると、Thor::Group がincludeしているモジュールThor::Baseoptionsメソッドが存在していることがわかる。

ControllerGeneratorのコードを再度見ていくと、skip_routesオプションをclass_option で定義している。 これもまたthorに存在するメソッドである。しかし、デフォルト値を指定するためのdefaultをキーに持つハッシュが引数に渡されていない。 Rails::Generators::Base を見ると、class_option メソッドをオーバーライドしている箇所がある。

      def self.class_option(name, options = {}) #:nodoc:
        options[:desc]    = "Indicates when to generate #{name.to_s.humanize.downcase}" unless options.key?(:desc)
        options[:aliases] = default_aliases_for_option(name, options)
        options[:default] = default_value_for_option(name, options)
        super(name, options)
      end

これは、Thor::Base.class_optionsuperで呼ぶ前に、デフォルト値を設定している。 default_value_for_optionは以下のようになっている。

        def self.default_value_for_option(name, options) # :doc:
          default_for_option(Rails::Generators.options, name, options, options[:default])
        end

さらにdefault_for_optionを呼び出している。

        def self.default_for_option(config, name, options, default) # :doc:
          if generator_name && (c = config[generator_name.to_sym]) && c.key?(name)
            c[name]
          elsif base_name && (c = config[base_name.to_sym]) && c.key?(name)
            c[name]
          elsif config[:rails].key?(name)
            config[:rails][name]
          else
            default
          end
        end

これは以下のようにデフォルト値を決めている。

  1. config(ここだとRails::Generators.options)の中にgenerator名(ここだとcontroller)をキーとして持っていて、なおかつその値がハッシュでname(ここだとskip_routes)をキーとして持っていると、その値をデフォルト値とする
  2. config の中にbase_name(ここだとrails)をキーとして持っていて、なおかつその値がハッシュでnameをキーとして持っていると、その値をデフォルト値とする
  3. configの中にシンボルrailsをキーとして持っていて、なおかつその値がハッシュでありそのハッシュがnameをキーとして持っている場合、その値をデフォルト値とする
  4. 以上のどれにも当てはまらない場合、引数のdefaultをデフォルト値とする

つまり、Rails::Generators.options がどのようになっているのかで決まる。 この順番で先に見つかった値が使われる。

設定編

Rails Guideに、Generatorの設定についての記述があり、そこに下記のサンプルコードがある。

config.generators do |g|
  g.orm :active_record
  g.test_framework :test_unit
end

Rails::Engine::Configuration#generatorsを見ると、

      def generators
        @generators ||= Rails::Configuration::Generators.new
        yield(@generators) if block_given?
        @generators
      end

Rails::Configuration::Generatorsをブロックに渡している。 Rails::Configuration::Generatorsmethod_missing が定義されていて、任意の名前で設定値を与えることができる。 下記のように4通りの記述が可能である。

  1. config.generators.controller = { skip_routes: true }
  2. config.generators.controller skip_routes: true
  3. config.generators.rails = { skip_routes: true }
  4. config.generators.skip_routes = true

1、2は同じ扱いで、controller generator固有のオプションを設定している。 3、4もまた同じ扱いで、controller generatorのオプションのデフォルト値だけでなく他のgeneratorからも参照される可能性がある。 「可能性がある」というのは、先述のようにgenerator固有の設定(つまり上の1、2で指定された値)を優先するためである。

どこでつながるのか

これで一件落着のように感じるが、Rails::Generators.optionsの中を見ると、以下のようにDEFAULT_OPTIONSにあるハッシュを返しているだけである。

      def options #:nodoc:
        @options ||= DEFAULT_OPTIONS.dup
      end

rails generatorコマンド実行時にRails::Configuration::Generators に設定された内容をRails::Generators.options に入れている。

rails/generate_command.rb at c0d91a4f9da10094ccdb80e34d1be42ce1016c9a · rails/rails · GitHub

このload_generatorsRails.application.load_generatorsを呼んでいるだけのメソッドである。 Rails.application.load_generators の実体はRails::Engine#load_generators で、この中で下記を実行している。

Rails::Generators.configure!(app.config.generators)

引数のapp.config.generators は、設定編で解説した設定で生成したRails::Generatorsオブジェクトである。 レシーバーのconfigure!の中で、Rails::Generators.optionsの値に、Rails::Configuration::Generators.optionsの値をmergeしている。

ということで、このように設定した値がController Generatorから参照されていることがわかった。 Generatorを自分で作成する必要がありそのGenerator固有のオプションを持つ場合、そのオプションのデフォルト値はconfig.generators.my_generator = { my_option: 'foobar' } といったように作成したgenerator名に値を与えることで設定できる。