$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

More Effective Agile読んだ

読みました。アジャイル開発やスクラム開発をしている時に取り入れると良いプラクティスを大量に知ることができて良かった。

印象に残ったのは以下の項目。数字はkindle location

  • スクラムの基本は、最も端的にスクラムをどうやるかを理解できる形になっていて良かった 912
  • スクラムを成功させるには、「有能なプロダクトオーナーを割り当てる」「バックログリファインメントを行う」「ストーリーを小さく保つ」「デイリースクラムを毎日行う」「スプリント期間は1~3週」「作業をバーティカルスライスにまとめる」「テスト・テスト技術者・品質保証を開発チームに組み入れる」、「完成の定義の明確化」「各スプリントでリリース可能な品質水準を達成する」「毎スプリントでレトロスペクティブ」「レトロスペクティブからの学びを活かす」「有能なスクラムマスターを割り当てる」
  • 仕事のできる職能横断的チームには、スキル以外に、拘束力のある決定を下す能力と権限の両方が必要 1350
    • 決定を下す能力 = チームに効果的な意思決定を行うのに必要な専門知識が全て揃っている
    • 決定を下す権限 = 組織の他の誰かが覆すことができない決定を下す能力。重要なステークホルダーがチームに参加している
  • 基本原則:成長マインドセットを培う
    • プロジェクトの目的は、動くソフトウェアを作るだけじゃなく、ソフトウェアを作るチームの能力を向上させることもある
  • 銀の弾丸に近いソフトウェアプラクティスは、1人1人の開発者が実際の顧客であるシステムの実際のユーザーと定期的に直接交流する 1645
    • ただし1回限りの体験では、その時に目撃したユーザーに開発者が固執する場合があるので、定期的に行うことが重要
  • 技術的負債の分類法 2413
    • 意図的な技術的負債(短期):たとえば、時間的制約のあるリリースを期限までにデプロイするなど
    • 意図的な技術的負債(長期):たとえば、最初からマルチプラットフォーム対応の設計と構築を行うのではなく、最初は1つのプラットフォームだけをサポート
    • 不慮の技術的負債(悪意):いい加減なソフトウェア開発の習慣のせいで意図せずに発生する技術的負債
    • 不慮の技術的負債(善意):ソフトウェア開発がエラーと隣り合わせの性質であるために意図せずに発生する技術的負債。たとえば「私たちの設計アプローチは思っていたほどうまくいかなかった」「プラットフォームの新しいバージョンのせいで設計の重要な部分が無効になってしまった」
    • レガシーな技術的負債:新しいチームに引き継がれた古いコードベースの技術的負債
  • チーム間で意味のある生産性の比較は、チームの生産性の向上率 4089

スケールする組織を支えるチームタイプやチームインタラクションを学ぶ - チームトポロジー読んだ

読みました。非常に良かったので、スケールする組織をどうやって作るか考えている人は一読すると良さそう。とくに4つのチームタイプ、3つのインタラクションモードについて知れたのが良かった。

  • チームトポロジーは、チームファーストアプローチを適用し、4つの基本的なチームタイプと3つのインタラクションパターンを示す。この方法に従うと、生み出されるソフトウェアのアーキテクチャが明快で持続可能になる。結果的に、チーム間の問題を有用なシグナルとして扱えるようになり、組織の自律操舵を促す
  • 4つの基本的なチームタイプ
    • ストリームアラインドチーム:ビジネスの主な変更フローに沿って配置されるチーム。職能横断型で、他のチームを待つことなく、利用可能な機能をデリバリーする能力を持つ
    • プラットフォームチーム:下位のプラットフォームを扱うチームで、ストリームアラインドチームのデリバリーを助ける。プラットフォームは、直接使うと複雑な技術をシンプルにし、利用するチームの認知負荷を減らす
    • イネイブリングチーム:転換期や学習期に、他のチームがソフトウェアを導入したり変更したりするのを助ける
    • コンプリケイテッド・サブシステムチーム:普通のストリームアラインドチーム、プラットフォームチームが扱うには複雑すぎるサブシステムを扱うためのチーム。本当に必要な場合にだけ編成
  • 3つのインタラクションモード
    • コラボレーションモード:特に新しい技術やアプローチを探索している間、2つのチームがゴールを共有して一緒に働く。学習のペースを加速する上で、このオーバーヘッドには価値がある
    • X-as-a-Serviceモード:あるチームが、別のチームが提供する何かを利用する(API、ツール、ソフトウェア製品全体など)。コラボレーションは最小限になっている
    • ファシリテーションモード:あるチーム(通常はイネイブリングチーム)が、新しいアプローチの学習と適用を促すため、他のチームをファシリテーションする
  • 4つの基本的なチームと3つのインタラクションモードの組み合わせが、速いフローで効果的にデリバリーを行うのに必要。そこから外れるなら考え直した方が良い

この知識を利用して、自分の組織のチームを分類し、より良い組織構造について考えていきたい。

意図せず外部へのネットワークアクセスをしているテストを、WebMockを使って徐々に外部アクセスを減らす話

最近担当しているRubyのプロジェクトで、テスト実行中に外部のサービスに意図せずアクセスしている(たとえばexamle.comへGETリクエストしていたなど)ケースがあった。これはまずいなと思い、WebMockを使って徐々に外部アクセスを減らしていっているので、その話を書く。

課題: テスト中に意図しない外部アクセスがある

現在のプロジェクトではWebMock gemを使って外部へアクセスしないようにモックしながらテストをしていた。しかしWebMockを、外部アクセスするメソッドのテストをする時だけWebMock.enable!し、終わったらWebMock.disable!するとしていた。つまり必要に思った時だけ以下のようなコードを使い、外部アクセスしないようにしていた。

around do |e|
  WebMock.enable!
  e.run
  WebMock.disable!
end

このやり方は非常に悪い。なぜなら自分は外部アクセスしていないと思っていたが実装上は外部アクセスしている場合、モックを忘れてしまい、テスト中に意図しない外部アクセスを作ってしまうからだ。とくに他のライブラリを使って開発をしているときに、内部実装をすべて理解していないと、このケースを作りやすい。

このような事態を防ぐため、基本的にテスト中は外部アクセスを絶対できないようにしておきつつ、本当に必要な場合だけ許可するという作戦にしておいた方が良い。WebMock + rspecの標準のやり方もこのようになっている。

解決のための作戦: まずは意図しない外部アクセスを新しく作れないようにし、その後既存の外部アクセスをなくす

解決のためにどうするか。2段階に分けて解決する。

  • 意図しない外部アクセスを新しく作れないようにする。既存の外部アクセスは通すようにする
  • 既存の外部アクセスを潰していく

意図しない外部アクセスを新しく作れないように

すべての外部アクセスを潰す手を打つ前に、意図せず外部アクセスをしてしまうという状況をこれ以上作れないようにした方が良い。そうでなければイタチごっこになり得る。とにかく素早く新しい外部アクセスを作れないようにしよう。

WebMockには、外部アクセスをできないようにするが特定のリクエストは許可するというコードが書ける。これを利用する。たとえば既存のコードに意図せずexample.comとmaps.googleapis.comへのアクセスがあったとして、そのリクエストは貫通させつつ、他のリクエストは通さないようにするにはspec/spec_helper.rbに以下のように記述する。

require 'webmock/rspec'

RSpec.configure do |config|
  config.before(:suite) do
    # テスト時にアクセスしても良い外部サイトを定義
    WebMock.disable_net_connect!(
      # localhostにはアクセスしていい
      allow_localhost: true,
      allow: [
        # capybaraがアクセスしている
        'chromedriver.storage.googleapis.com',
        # TODO(webmock): 以下は必ずstubしたい
        'example.com',
        'maps.googleapis.com',
      ],
    )
  end
end

既存のコードでどのような外部アクセスがあるかは、require 'webmock/rspec'を有効にした上で、テストのlogをファイルを出力し、以下のコマンドで抽出すると良い。

$ grep 'Unregistered request' tmp/test.log | perl -pe 's/^.+?Unregistered request: ([^ ]+ [^ ]+) .+$/$1/' | sort | uniq -c | sort -rn
  46 POST http://example.com/aiueo
  39 GET http://maps.googleapis.com/a/b/c
  ...

既存の外部アクセスを潰していく

まずいテストを新しく作れないようにしたら、あとは既存の外部アクセスを潰していくだけだ。さきほどdisable_net_connect!で許可リストを作ったわけなので、その許可リストから1つずつ外し、落ちたテストを修正していくと良い。

diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index ac49c794d8e..beaf8eed006 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -25,7 +25,6 @@ RSpec.configure do |config|
         # capybaraがアクセスしている
         'chromedriver.storage.googleapis.com',
         # TODO(webmock): 以下は必ずstubしたい
-        'example.com',
         'maps.googleapis.com',
       ],
     )

まとめ

今回はテスト実行中に外部のサービスに意図せずアクセスしているプロジェクトで、それをやめていく手順について書いてみた。新しく問題を起こせないようにする -> 既存を無くす、という流れはいろんなところで使えると思うので、参考になれば嬉しいです。