nashcft's blog

時々何か書く。

『再演 ~ 組織にテストを書く文化を根付かせる戦略と戦術 +α』メモ #t_wada

また過去に参加した勉強会のメモ。今回のは3/11ともはや半年以上前...
まあもう諦めても良かったのだけど、9月頭 (これも1ヶ月近く前...) にあったprivate methodの件だったり職場で最近あったお話だったりがこの講演で扱われた話題に関連している気がするなあと感じたので、折角だしと思って掘り出してまとめることにした。

connpass.com

togetter.com

www.slideshare.net

転送サーバおけるテストの自動化 · GitHub

メモは最後に貼るとして、講演の内容の内2点について、この記事を書いている時点までにあったことも含めて思っていることをまとめる。これらを書いていて、半年以上前の講演で、それもスライドに残されていることではなくその場で話されていたようなことやそれについてその時感じたことなのに、今でもこうやって覚えていてそれなりな量の文章を書けるのは、自分にとってこの講演がそれほど感銘を受けるもので、その後の自分の考えや行動の指針となっているのだなあとしみじみ思ったのであった。

リファクタリングとビジネス価値、日々の開発の関係

スライドにおける「リファクタリングのジレンマ」の部分。スライドにはこれだけしか書かれていないがこの会ではここを掘り下げた話があった。

リファクタリングを独立タスクにすると、そのタスクは着手されない

どういうことかというと、リファクタリングそれ自体はプロダクトに新たにビジネス価値を付与するものではなく、振る舞いを変えることなく内部を改善する「だけ」なので、「〇〇のリファクタリングを行う」とタスクとして独立してしまうと、他の新機能開発や機能追加・改修といったタスクに対して短期的な視点からエンジニアの自己満足とみなされたり早く顧客に新たな価値を提供したいというビジネス判断をされたりして後回しにされ続けてしまい、ついぞ着手されることなくTODOリストの奥底に沈み込んでしまう、ということだそうだ。
ではリファクタリングのビジネス的価値とは何かというと、中長期的に見て同様のタスクをこなすのにかかる時間を一定以内に保ち続けられるようにすることであるとのことで、つまりビジネスの進行に対するブレーキをプロダクト内に作らないようにすることと言える。

リファクタリングされないまま開発を進めてしまった場合どういうところに行き着くのか、というのは、最近だと以下の記事の「コードの乱れが生むもの」がイメージしやすく書かれていると感じた。

d.hatena.ne.jp

この記事によれば、行き着く先とは最早リファクタリングどころではなくなってまた一から同じシステムを作り直すという、開発にもビジネスにも多大なコストを支払わざるを得ない状況となる。計画された再構築でなければ実施しようとする頃にはそれをするにも心許ない、最悪実施すらできないような状況に陥ってしまっているというのは恐ろしい話である。

短期的には後回しにされがちだが、かと言ってしないままだとその内まずいことになるのでやらねばならないリファクタリングを、じゃあどうやって実施したらいいのかと言うと、日々の開発タスクの中に小さなリファクタリングをまぶして、日々の業務に改善を組み込んでいきましょう、とのことだった。

これは言われてみればなるほどその通りで、規模の小さい内に問題を潰せることができればその後起こりうる大きな問題 (大規模なリファクタリング) を未然に防げるだろうし*1、結局開発もリファクタリングもソフトウェアエンジニアとしてはどちらもコーディングタスクと見做すことができるので、あえて別タスクに分離して実施しなくても一緒にやってしまえばいいよな、と思う。
とは言うものの中々実行はされないもので、今の職場では割とカジュアルにリファクタリングを独立タスクにしていく傾向があり、見かねて一度「そういうのは良くないよ、こういう風に日々の開発タスクの中で少しずつリファクタリングしていきましょう」ということをプルリクエストで実例を紹介しつつ訴えかけたことがあった。その時はあまり注目されてないなーと思ったのだけど、2, 3人ほどそれ以降実践してくれているのを観測したので、文化を作るきっかけにはなったのかなと考えている。

この講演でも話されていたことなのだけど、本当は上記のように大勢に呼びかけるような方法というのは戦術的には筋が良くなくて、一人ずつ伝授していくようにできる人を増やしていくという方法が紹介されていた。確かに、考え方や実践のためのスキルなど習得すべきことの性質や量を考えると個別にみっちり指導していく方がよく定着するだろうなと思う。とはいえ現実問題としてはそんな気長に構えていられないような状況もあるだろうし、まだ小さくリファクタリングする習慣を会得していない人のタスクに対してもどうにか改善を適用したいという思いがないこともない。
これに関してはプルリクエストによるコードレビューを行うとそれなりにカバーできるのかなあと考えている。講演でも戦術としてコードレビューが挙げられていて、人に見られるコードを書いているという意識を植え付けることで、コードの品質を向上させられるというような話があった。そのためには、単に実施するだけではダメで、レビュイーに自分の書いたコードやどのような変更を行ったかが見られ、その内容が評価*2されるということを認識させるように、レビュアーはきちんと見なければならないし、見ているという姿を表さないといけないが。

それで、どうやってコードレビューで日々の改善をカバーしていくかと言うと、レビューの際に改善ポイントをがあったら、普段からこういうのを見つけたらこういう風に直していきましょうねー、というのを指摘・解説しつつ、改善を適用して (してもらって) からmergeするようにする感じ。はじめの内はレビュアーのコストがかかるのは仕方ない。いわゆる投資というものである。人によってはこれでレビュアーが見つけてくれるからと完全に甘えてしまって改善を行わない人も出てくるかも知れないが、そうしたら素直に張っ倒して (比喩表現)、自らやるように導いていくなりする必要がある。
これも全体に〜という話だとレビューするプルリクエストの量が多くなって大変になってしまうが、改善活動を日々の開発に取り入れるならば、それの啓蒙や教育も日々の活動の中にまぶしていくのが良いのではなかろうか。

「設計の可動域を確保した」テストについて

前半の方で品質を上げるのはテストではなく設計とプログラミングという話があって、品質改善のために再設計を、それによって実装の変更を行うとして、その際にテストが実装に寄り添ったもの (= 実装をテストしているテスト) になっていると、これらの活動の足を引っ張るようになってしまう。なので、設計や実装の変更を後押しするような、実装と距離を持ったカバー範囲に遊びのあるようなものにして、カバー範囲内で変更を行う分には落ちないようなテストを作りましょう、という話だった。

それから時間が経って9月になって、private methodのテストと設計についての話が以下の記事を発端として話題になった。

su-kun1899.hatenablog.com

private methodにまつわる設計の是非は置いておくとして、この記事に対してt_wadaさんが紹介したQ&A記事において、質問者がprivate methodも実装した瞬間に実装のテストがしたいという発想のもとテストを書いてしまいたいな、と述べており、それに対してt_wadaさんは「プライベートなメソッドのテストを書く必要は 無い と考えています」と先に結論した上で、以下のように説明している。

繰り返すと、プライベートなメソッドや関数をテストする必要は無いと考えています。プライベートなメソッドは、実装の詳細であるからです。 (中略) プライベートメソッドに対するテストは内部の実装に対するテストになってしまうことが多く、そして内部の実装に対するテストはリファクタリングの妨げになりがちです。自動テストの助けを借りて積極的にリファクタリングを行いたいのに、その自動テストがリファクタリングの妨げになる。これはとても皮肉な状況です。避けられれば避けたいものです。
テスト「できる」ことと「するべきである」ことは異なります。リフレクションを使えばプライベートなメソッドのテストは「できる」のですが、そのテストはやがて実装改善の邪魔になりかねません。

Q&Aの記事がちょうどこの講演 (再演) の3年前くらいで、その頃からリファクタリングを後押しするための実装に対して距離を保ったテストという考えがあって今日まで一貫してきてるんだなあという感動があったのはさておき、実装のテストを書いてしまうと実装を変更するたびにテストも改修しなければならないためリファクタリングのコストを無闇に上げてしまうことや、リファクタリングが振る舞いを変えずに実装を改善することだというところから、リファクタリングによって継続的にコードを改善できるようにするために実装に対して遊びのあるテストを書きましょうということについては私も同意したのだが、「実装のテストを書くべきではない」というのはちょっと違うのでは、という考えを話を聞いた当時に持った。
それについて、講演後に呟いた以下の一連のtweetが上記の話を受けての実装のテストに対する考え方を表している。

まとめると、この講演では書くべきでないとされた「実装のテスト」について、私は実装をしているまさにその時はそれぞれの部品が正常に動くことを確認するための一時的な、目的を達成したら (部品が期待通りに動くことが確認できたら) 捨ててしまう「寿命の短いテスト」としてであれば別に書いても良いと思っていて、実装後も再設計やリファクタリングできるように残すテストは設計・実装に関してカバー範囲に遊びのある「寿命の長いテスト」にする、というように使い分ければ良いのではということである。

ここでいう「寿命の短いテスト」= 「実装のテスト」は役割的には補助輪のようなものだし、書くべきではないというより書かなくてよい、言い方としては「後に残すべきではない」の方が適切かな? というのが私の意見となる。

まあ時間軸の視点を持ったところで、結局「寿命の短いテスト」は書かずに済むに越したことはないし、「寿命の長いテスト」だけ書きながら書き上げられるようなプログラムがいわゆる良い設計のプログラムと言えるのかも知れない。


以下当日のメモ (当日若干遅刻したので途中から)

組織にテストを書く文化を根付かせる戦略と戦術 +α by t_wada氏

戦略編

  • ×: テストを書く時間がない
  • ○: テストを書かないから時間がなくなる
  • ×: 誰かがテストを書く
  • ○: みんなテストを書くのが当然
  • 文化の醸成
    • だいたい2 ~ 5年かかる
  • 「動くコードに触れない」と闘う
    • 触れられないコードはゆるやかに死んでいく
  • まずすること
    • 現状の把握
    • Not To Beを定める
    • 自分、他人、プロジェクトのスピードで成長・改善していく
      • 遅くても着実に進んでいれば恥ずかしくない
  • 「みんなやってる」は最終手段、劇薬
  • 個々人の性質を知る
    • 快不快
    • 実利
    • Gamificationでスコア化による快感 -> 手段の目的化というリスク
      • うまくnavigateしよう
  • Refactoringを個別にタスクとすると、着手されなくなる
    • 短期的には自己満足に捉えられる
    • ビジネスへのvalueは中長期的
      • あるタスクをこなすのに一定の時間内で済ませられるようになる
      • Refactoringしないと同様のタスクをこなすのにどんどん時間がかかるようになる
  • "It's easier to ask forgiveness than it is to get permission." --- Grace Hopper
    • 許可を待ってやるより、日々のタスクの中に仕込む、小さくまぶす
  • 全ては変化するから、動くコードに触れ続ける
  • 技術的負い目の四象
    • 意図的・慎重 -> 皆が良く考えるやつ
    • 不注意・慎重 -> 一番厄介、後で最もダメージが来る
      • 頑張って削らないと
  • テストは品質を上げない
    • わかるようになる
    • 品質を上げるのは設計とプログラミング
      • だから再設計とrefactoringをする

戦術編

  1. どこから?
    • 一番やばいところ、不安なところ (心情的でOK)
    • 最も困っているところ、重要なところ
    • 新機能
    • Bug fixをするところ
      • Bugのあるところ -> かたよってる
        • また出ることが多い
        • Red -> Greenしたら周りにもテストを書いておく
    • 静的解析でピンポイントに
    • 『リーン開発の現場』: 18章 テスト自動化の戦略
      • テストケースを分類、以下の評価・分析を行ってトリアージする
        • リスク: 高いものほど優先される
        • 手動テストのコスト: 高いものほど自動化による効果が高い
        • 自動化するコスト: 低いものほど自動化による効果が高い
  2. 誰と?
    • できる人をじわじわ増やしたほうが良い
    • 自分で成長できるように
    • 教える側をできる人を増やす
    • まだ馴染んでないけどできる新人とか?
  3. こだわらない
    • 正常系と準正常系さえあればひとまず助かる
  4. こだわる
    • Repeatable, reproducible
      • 時間、環境、前提、後処理に依存しない
    • Independent
      • テスト間の依存
      • 並列に動くように!
    • 他 (網羅性、スピード) は後でいい
  5. レガシーコード改善ガイド
    • 何とはなくても読むべし
    • テストを書けるようにする
    • E2Eテスト: 大外からテストする
  6. 見える化
    • メトリクス
      • 小さく始める -> 分母を抑える
    • 時系列の変化を見る
  7. 静的解析
    • 動的テスト: スクリプトテスト、いわゆる「書くテスト」
    • 静的テスト: 書かなくてもできるツールがある (Linterとか)
  8. コードレビュー
    • プルリクだけでも質が上がる
      • 見られることに対する意識
  9. 設計の可動域を確保する
    • テストがない -> 設計の悪い兆候
    • 実装を変えるのを助けるテスト
      • ×: 実装に寄り添ったテスト -> テストのメンテが大変になる
      • ○: 仕様のテストを書く
        • 実装と距離を作る
        • カバー範囲に遊びを持たせる

転送サーバおけるテストの自動化 by @voluntas氏

  • ミドルウェアのテスト -> ミスると死ぬ
  • パケット転送システムのテスト
    • mock/stubはミスのもとになる
    • テストクライアントを自前で作る
    • 異常系を用意するのが非常に難しい
  • 自動テスト
    • テストを自動化したらほぼ必ずメンテコストが増大する
    • テスト自動化で守られるもの (得られるもの?) -> デグレしなくなること、のみ
    • 自動テストのメンテも開発速度・コストの要因として考慮する必要がある

*1:根本的な設計そのものみたいな問題はこの方法では対処できなさそう...?

*2:コードの評価であって、人事評価ではない。念のため