やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

修正コスト最小化するためにどうすればいいか

今の私における状況下で、私なりに考えてみた。

前回まで

http://ytyaru.hatenablog.com/entry/2017/12/21/000000

PythonGitHubアップローダを作ってきたが、単体テストを先送りして後戻り作業が大きくなっていることに気づく。プログラマが知るべき97のことの第1項、分別のある行動読んで自分の現状と照らしあわせてみた。私はすでに技術的負債を抱えていると感じた。

方針

楽しいプログラミング生活を実現したい。そのために負債を明確にし解消する。

作業 価値 方針
新規開発 楽しい 楽しいことに多くの時間を割けるようにしたい
修正作業 つまらない 修正作業は最小化したい

技術的負債があることに気づいた

http://ytyaru.hatenablog.com/entry/2017/12/14/000000で言うように、先送りしてしまうと後々で余計な苦労をすることになる。今まさにその状況であり実感している。思い立ったが吉日。気づいた時が改善のチャンス。面倒だが考えてみる。面倒だが。

現状確認

状況を改善するためにはまず確認が必要である。次に理想を打ち立てる。実現できる方法を考える。

  • 何が負債になっているのか
  • どうすればいち早く返済できるか

これを考える必要がある。

  • 何が負債になっているのか
    • それが負債であると証明するにどうすればいいか
    • 証明を試みる
  • どうすればいち早く返済できるか
    • いつ負債が生じているか
      • 負債の発生にいち早く気づくためにどうすればいいか
    • 何をいつ行えば修正コストを最小化できるか
      • 実行コスト < 修正コスト のときである
        • 単体テスト」は「コードの結合前」に行うと修正コスト最小化できる
        • Python言語仕様の学習」は「案件のコードを書く前」に行うと修正コスト最小化できる
        • オブジェクト指向デザインパターンの学習」は「コード設計する前」に行うと修正コスト最小化できる

現状確認

私はすでに技術的負債を抱えていると感じた。負債とは修正作業を生み出してしまう「先送りした作業」のことである。まずは負債となる「先送りした作業」が何かを明確にする必要がある。

  • 何が負債になっているのか
  • 何が負債になっているのか

  • 何が負債になっているのか

    • 負債と思うものを列挙する
    • 負債と思うものを列挙する(そのうちの1つを仮に"負債A"とする)
      • 負債Aが負債であると証明せねば負債とは言えない
        • 負債Aが負債であると証明するにどうすればいいか
          • 負債Aが負債である証明を試みる
            • 負債といえる根拠は何か
              • 修正作業が1つ以上あること(そのうちの1つを仮に"修正作業A"とする)
                • “修正作業A"は修正作業といえること
                  • 案件達成に必要な作業であると証明する
                • 修正作業Aは「負債Aを抱えたため生じる」といえること
                  • 修正作業Aが生じた経緯を明らかにする
                    • 修正作業Aが生じる原因を推論する(そのうちの1つを仮に"原因A"とする)
                      • 原因Aは負債Aを行うと生じることを確認する
                        • 再現確認するための準備や手順を明らかにする
                          • 再現確認できたら「原因Aは負債Aを行うと生じる」の証明とする
              • 修正作業にはコストがあること
                • コストとは何か
                  • 何をすることか
                  • 何が消費されるか
                  • どのくらい消費されるか
    • 証明済みの負債を列挙する

負債が明らかになれば、やっと本題を検討できる。

  • 修正コスト最小化するためにどうすればいいか
    • 負債を発生させないよう努める
      • 負債の発生を最小限に抑えるよう努める
        • 負債の発生をいち早く察知する
          • 負債の返済を早めに行う
            • 負債の返済を忘れず行う
              • 負債の発生を記録する
                • 負債の返済をスケジュールに組み込む

具体的な負債ごとに最適な対処は異なる。

さらに、学習以前のことについては以下。

  • 何を行えばいいか
    • 動作確認
    • 学習の記録
  • いつ行えばいいか
    • 「動作確認」は以下の場合に行う
      • 「未学習の構文を案件コードに実装する前」に行う
      • 「未学習のライブラリを案件コードに実装する前」に行う
      • 「未学習の言語仕様を案件コードに実装する前」に行う
      • 「未学習のデザインパターンを案件コードに実装する前」に行う
      • 「未学習の実装構造を案件コードに実装する前」に行う
    • 「学習の記録」は「動作確認した後、案件コードに実装する前」に行う
目的の確認→目標の設定
→調査→動作確認→学習の記録
→案件実装→単体テスト
→コード結合→結合テスト
→公開→拡散

どうやって行えばいいか。

何が負債になっているのか

今の私にとっての技術的負債とは何か。

単体テストをサボったことによる負債

単体テストを先送りしたときに生じるコストとは何か。「呼出側の修正に要した作業時間」だろうか。最初にテストしておけば呼出側の実装は1回で済んだはずだ。

項目 すぐテストした場合 先送りした場合
単体テスト
結合後の呼出側修正
負債 負債と思う根拠
呼出側の修正に要した作業時間 最初にテストしておけば呼出側の実装は1回で済んだはず

Pythonの言語仕様について未学習による負債

Pythonの言語仕様をせずにやってきたことで生じた修正コストとは何か。「どうすればいいか」を調べる時間と労力がコストである。

  • 行きあたりばったりでググっている
    • 自分で資料を作成し、情報源の場所を確定させれば素早く検索できるはず
  • 何度も同じことをググっている
    • 学習し身に付けば検索する時間をゼロにするか頻度を減らせるはず
負債 負債と思う根拠
行きあたりばったりで調べる時間と労力 自分で資料を作成し、情報源の場所を確定させれば素早く検索できるはず
何度も同じことを調べている時間と労力 学習し身に付けば検索する時間をゼロにするか頻度を減らせるはず

Pythonのライブラリについて未学習による負債

Pythonのライブラリについて学習せずに実装を進めてしまうことによる修正コストは何か?リファレンス調査とライブラリの動作確認する時間と労力である。しかも忘れるたびに毎回生じる。

  • アレをするにはどのライブラリを使えばいい?
    • アレをする関数はあるか?どれか?
      • 引数には何がある?名前は?順序は?型は?
      • 戻り値は何?型は?メンバ変数は?

一度まとめてどこかに記録しておけば以下のように解決できる。

  • すぐに探せる。調査時間の短縮。
    • ドキュメントは関数や引数などが網羅的に書いてある
      • その中から、よく使うもの、特定の用途に使うもの、を抽出せねばならない
        • 用途ごとの引数組合せなどをコードの形で記録しておけば、網羅的なドキュメントから探す時間を減らせる
  • すぐに書ける。コーディング時間の短縮。
    • 一度どこかにまとめてコードを記録すればコピペで大枠を書ける
      • たとえばunittestコードとして書いておけばコードのコピペと動作確認までがコーディング時間ゼロでできる。
負債 負債と思う根拠
調べる時間と労力 細かすぎる引数なども含む中から、よく使う記述を抽出せねばならない。一度書けば網羅的なドキュメントから探す時間を減らせる。
書く時間と労力 一度どこかにまとめて記録すればコピペで大枠を書ける。unittestにすれば動作確認もできる。

引数の組合せなどは利用時に応じて考え、書き換えねばならないため、未学習による修正コストとは言えない。

オブジェクト指向デザインパターンなどのコード設計に必要な方法論について未学習なことによる負債

コード設計の方法論が未学習のまま実装を進めてしまうことで生じる修正コストは何か?

  • コードの総書き直し
  • 呼出元の修正
  • 再テスト
  • 動作実績

動作実績の修正コストとは何か。すでに運用されていたコードの構造を変更した場合、これまでの動作実績(信頼性)もクリアされてしまう。動作内容はおなじでもコード実装内容がまったく別物になるから。テストの確認が必要なのはもちろんだが、運用時にはほかのシステムと複雑に関わるなどの状況も考えられる。そのすべてをテストするのは難しい。これまでの動作実績で稼働できたという事実が信頼の根拠になっていたが、それがすべてクリアされてしまう。稼働実績をイチから積み直すことになる。膨大な利用パターン、回数、時間を要する。

負債 負債と思う根拠
コードの総書き直し これまでコードを書き、テストまでしてきたものが水泡に帰す。また、既存のコードとの兼ね合いも考慮せねばならないため新規作成するより難しい。新たな不具合を作りこむ可能性がある。
呼出元の修正 規模が大きい場合、呼出元も非常に多い。新たな不具合が生じる可能性がある。
再テスト 単体だけでなく結合テストなど以降の全テストをやり直すことになる。これまでのテストコードは100%無価値である。
動作実績 稼働実績をイチから積み直すことになる。これまでの実績(信頼)はパァ。サービス価値が低下する可能性あり。

最初に学習しておけば無用なコストのはずである。一体どれだけ膨大なのか。上流工程であるほど、それ以降の下流工程すべてに影響がある。負債の利息が増大してゆく。

コストをどうやって発見すればいいのか

  • いつ生じるか: 今まさにコストが生じた!
  • 何がコストか: これこそ先送りによって生じたコストの1つだ!

上記のようなことに気づけなければ発見できないだろう。しかし、そんなふうに感じたことは一度もない。今までの体験から言うと、何らかのストレスを感じたときにコストが生じているのではないか?

  • 最近、別の箇所で似たようなコードを書いたような気がする。同じコードを何度も書いて先に進めない。早く未実装を書いて先に進めたいのに!イライラ!ヽ(`Д´#)ノ ムキー!!
  • どこがどう動くのか把握していない。思い通りに動作するか確認していない。どこなら思い通りに動くか分からない不安 ((((;゜Д゜)))

コスト発生に気づく方法は、上記のようなストレスに気づくことが必要なのかもしれない。

しかしこのように感じるためには、コードを書かねば身につかない感覚のような気がする。まったくコードを書かずに察知するのは難しい。現に今までまったくできなかった。でも、書く前に察知しないと回避できない。今はまだコードを書いて芽生えた感覚を育てていくしかないのかもしれない。

コストをどうやって計測すればいいのか

どのくらいのコストがあるのか。対策をするためには動機づくりが必要。コストが明確になれば動機になるという。

  • 何がコストか?
    • 何を先送りしたことによって、どんな後戻り作業が生じたか
  • どのくらいコストを要した?
    • その後戻り作業にどれだけの時間を要したか
  • その後戻り作業は不毛か?
    • 得るものがあれば無駄とは言えない。不要な単純作業なら無駄。この区別は因果関係の把握に必要

前後の作業との因果関係を把握せねばならない点が難しい。全容を把握し、因果関係を把握できていなければ、それが無駄な後戻り作業であるかどうかすら判断できない。全容を把握するためには、少なくとも一度は失敗しないと知ることができない。

つまり、コストの発見や計測には、失敗することが必要なのではないか。

失敗することがコストを最小化する

修正コストを最小化するには、早めに失敗するのが最善かもしれない。

  1. できるだけ早く失敗する
  2. 失敗だと気づけたらそれを記録する
  3. 失敗した結果に至るまでの経緯を調べる
  4. 失敗した結果に至るまでの因果関係を推測する
  5. 失敗を回避する経緯を推論する
  6. 実践して確かめる

スタート地点である「できるだけ早く失敗する」ためには「とにかくやってみる」ことが最善か。たとえ何も知らないままだとしてもやってみる。最小限のコストでいち早く成功するためには、早めに失敗すること。

よりよい姿勢

問題解決することを「成功する」と表現するとき、以下のような姿勢で望むほうがより良いと思う。

何もしようとしない < 失敗を避けようとする < 積極的に失敗しようとする < 失敗を探す(粗さがし)

何もしないと成功も失敗もし得ない。問題解決するヒントも得られない。

何もしないより、失敗したほうがいい。失敗すれば「そのときそれをすると失敗する」と知ることができる。次の改善に繋がる情報を得たことになる。何もしなかった時には得られなかった成果である。しかし、失敗によって別の新たな問題も生じるはず。それでも良いとまで言えるのかは別途判断が必要。あくまで「問題解決のためには」という条件付き。問題解決のためには、何もしないより、失敗したほうが良い。失敗を避けるより、失敗しようとするほうが良い。

しかし「失敗を探す」のはどうか。問題解決にとって良いというよりは、将来のコスト最小化に良いと言うべきか。失敗するだけでも新たな問題を抱えかねないのに、さらに粗さがしすることまで同時にすると負荷が高すぎて前に進まないなどの問題になりかねない。たとえば粗さがしはする方もされる方も精神的、心理的、労力、時間、PCリソースなどに負荷がかかる。「問題がある」「解決しようとする」だけでも負荷が高いのに「問題解決しようとすることで生じる問題」で新たな負荷が生じてしまう。問題解決しようとすることにもコストがあることを忘れてはならないが、問題解決しようとすることで生じる新たな問題(メタ問題)についても考慮する必要がある。メタ問題について考慮するという負荷まで生じる。この連鎖ははてしなく続いていくように思える。心が折れずにどこまで続けられるだろうか。見通しを立てたり記録しておくことは現状確認やモチベーション維持に必要。

先送りする必要性

同時に多くの問題を抱えてしまうと複雑化し、解決できるものもできなくなってしまう。あえて問題を先送りする必要もある。覚えきれないため、忘れてもいいように記録する必要がある。

引き継ぎの価値

自分一人だけで解決できる問題とは限らない。その問題を解決することの意義や価値を示し、できたところまでの成果を記録、発表すれば、他の誰かがスムーズに引き継いでくれるかもしれない。どんな大きな問題でも、いつか誰かが達成するかもしれない。

問題は、そのために貢献する動機づくりだけでも大変だということ。

常に人の粗さがしをする人は嫌われがちだが、じつは「いち早く問題を探して修正コストを最小化しようとする」ことに貢献しているのではないか。ただ、自分の粗は認めず、自分の体を使って修正しようとしないなどの点が嫌われる原因だろう。さらに解決できる人材や契機を排除しようものなら目もあてられない。問題解決をする場を破壊することに尽力している。先細りの未来しかあるまい。

また、失敗しないようにするより、失敗して対処したほうがいい。失敗しないようにしても、失敗する理由を内包しているならいずれ必ず失敗する。後で複雑な状況になってからだと失敗の原因を特定するのが難しい上に時間もかかる。失敗を回避しようとするより、むしろ失敗を炙りだしてやろうと意地悪をするくらいのほうが、総合的に幸せになれる。たまたま偶然、何の失敗もないのに「失敗してやろう」と粗さがしした挙句に何も見つからなかったとしても、そのほうがよい。「これだけ粗探ししても何も出なかった」という証拠になるから。

「失敗しないこと」は「失敗することを回避すること」である。「成功すること」ではない。成功するためには、それ以外の経路をすべて確認する必要がある。すべての経路の中から取捨選択し、目的に合致した経路だけを「成功」と言う。失敗もしないと比較対象がないため成功とは言えない。

失敗を避けようとする=生存本能

逆に、失敗を避けようとすることで問題解決が遠のく。しかし、失敗を避けようとする理由があるようにも思える。

  1. 失敗することについての認識
  2. 「失敗すること=悪いこと」
  3. 「悪いこと=排除すべき」
  4. 「悪いことをする人=排除すべき人」
  5. すべての人は排除されたくない
  6. すべての人は失敗しないようにするし、失敗したら隠す
  7. 後々になって問題が肥大化してから発見される。責任のなすりつけ合い
  8. 失敗を失敗と認めず、原因分析に進めないまま事態が進む
  9. 総合的な損失は拡大の一途をたどる

「失敗することで生じるコスト」より「失敗しないことによるコスト」のほうが遥かに大きくなっていく。

  • 「良い/悪い」の二元論で捉えている
    • 状況はもっと多元的で複雑。しかし対象者の頭ではそれを把握できないため状況を二元的にして単純化することで把握しようとする 途中で「事」から「人」に変わっている。

失敗を避けようとすることの正当性

まるで失敗を避けることが「悪いこと」のようにも読める。しかしそうではない。

失敗することによって生じる問題もあるはず。それを回避するためには「失敗を避けようとすること」が必要であり重要だ。ここでは「問題解決するために最善なことは何か」という文脈であるため、「失敗を避けること=悪」になってしまう。

しかし、もし「問題解決できないせいで存在し続ける問題」より「失敗によって生じてしまう新たな問題」のほうが大きな問題だったらどうか。

問題を解決すること < 失敗によって生じる問題を回避すること

コスト最小化のためには「失敗を回避すべき」と答えるはず。

コスト最小化する方法 = コスト最小化する方法を取得する()
def コスト最小化する方法を取得する():
    if 問題を解決すること < 失敗によって生じる問題を回避すること:
        return 失敗を回避しようとする
    else 
        return 問題を解決しようとする
def 失敗を回避しようとする():
    ...
def 問題を解決しようとする():
    ...

つまり「コストを最小化する」という目的を達成するためには「どういうときに、何をするか」が重要なのである。間違っても「失敗を回避する/問題を解決する」に「善/悪」の区別をつけて分別することではない。そんな分別をつけたところで「だから何?問題解決には何をどうすればいいの?」

善悪の二元論で捉えることの正当性

まるで善悪の二元論で捉えることが「悪いこと」のようにも読める。しかしそうではない。

複雑な状況の中にいながら、少しでも理解を進めようと努力した結果である。最初から複雑な全体像の把握などできるわけがない。単純な情報から整理していく必要がある。まったく理解できていない中でも理解を進めるために、物事を単純化するという方法を取っているのではないか。少なくとも「理解しよう」と試みていることに違いない。たとえ不足でも、たとえ間違っていても、たとえ失敗だとしても、失敗すれば「これは失敗だ」と理解できる時が来くる。それだけ分別がつくようになり、やがて理解も進む。二元論で捉えることは、理解するために必要な過程なのだと思う。

善悪の二元論で捉えることの必然性

善悪の二元論で捉えることとはどういうことか。「自分にとって都合が良いか/悪いか」の分別をして、素早く取捨選択するための機能かもしれない。

人間は動物である。動物は一瞬の判断で生死が別れることがある。「自分の生存にとっての都合の良し悪し」を優先的に素早く判断することは非常に重要と思われる。生物として、その機能は否定すべきでない。

問題解決を目的とした場合、物事を二元論で捉えるだけでは不足。

問題解決の目的は「自分の生存」よりも大きい「状況の操作」だから。「状況の操作」には「自分の生存」を高めることも含まれている。より視野が広くなり、より良く、より長く、より確実に「自分の生存」に寄与する状況をつくることが目的になる。

そのほうが良いが、

二元論問題解決

問題解決をしようとするとき、状況を二元論で捉えると解決に結びつかなくなる。問題解決のゴールは「どうなれば解決といえるか?」という具体的な状況と、「どうすれば解決するか?」という具体的な行動である。

  1. 失敗したヤツ=悪いやつ

バグを潜ませたままコードを結合させていくと、後戻り作業が大きくなってしまう。

今までGitHubアップローダを作っていた時期のうち、初回DB作成のところでは、エラーを出さないよう、動作テストを避けていた。理由は、まだどう書いていいかもわからず、コードを確定できなかったから。一応ちゃんとした理由はあるのだが、そのせいで後に「正しい書き方」をしたコードで書き直すハメになった。

先送りしてしまう理由

「だって面倒なんだもん」と心の底で思っている。もう少し掘り下げて考えてみた。

「新機能を追加しよう!」とするときのワクワク感と「無駄な苦労をしないように…」とするときとではモチベーションに大差がある。「後々の苦労を回避するため(に仕方なく)」という目的志向だけでは動機が足りない。「抽象化してわかりやすいコードを書いて気持ちイイ!」という快感を意識することで、「正しいコード」を書くために必要だが大変なこと(学習など)を進んで行うことができるようになるのではないか。

行動に至るまで

行動←動機=目的+正の気持ち
動機 = 目的 + 正の気持ち
if 面倒さ < 動機:
    行動する()
else:
    行動しない() # 永久に行わない or 後で行う(無自覚な先送り)

目的意識と快感が動機となり、動機が面倒さを上回ったとき、行動に移せる。

項目 説明
動機 行動を移すために必要な心理的原動力。「意欲」とも言い換えられる。
目的 やりたいこと。すべては目的を達成するために。「問題」とも言い換えられる。
正の気持ち 楽しい、嬉しい、気持ちいいなどポジティブな気分のこと。
面倒さ やりたいことをやる前に必要なことが大きすぎて絶望する。
行動 コードを書くなど実際にとる行動。行動の結果が何らかの成果を生む。

先送りする前に行動するためには

行動 目的 心理
コードを書く 動くものを作りたい! 思ったとおりに動いて楽しい!
言語仕様やデザパタなどの学習 重複コードを避けて修正やバグが少ないコードを書きたい! 抽象化してわかりやすいコードを書けると気持ちイイ!

負の気持ち

正の気持ちを生み出すためには、背景に負の気持ちがあると思う。

負の気持ち 理性による転換期 正の気持ち
思い通りにならないことばかりでイライラ! どうにかできないか? 思ったとおりにできてスッキリ!嬉しい!楽しい!
同様のコードばかり書いて先に進まずイライラ! どうにかできないか? 抽象化で重複が解消されて解決!また、具象コードの包含関係を表しつつコード表記上も見やすく読みやすく理解しやすいコードになってスッキリ!

負の感情はすべてのスタート地点であり必要なもの。しかし、解決や改善に向かわせるためには、感情だけでなく理性との協調が必要。さらに解決に努めるための動機が必要。

しかし動機の創出には一度やってることが必要と思われる。動機は「やる前」に欲しいと思うが、じつは「やった後」で実感と分析をしないと得られない気がする。

負の感情→気付き→理性による分析→調査→学習→対処→改善

負の感情がスタート地点である。

これまで先送りしてしまった理由

最初は何も知らない。動機の創出が難しい。

行動 目的 心理
コードを書く 動くものを作りたい! どうやって書けば?調べるのメンドクセ…
言語仕様やデザパタなどの学習 類似コードが増えてきた…それを何度も修正してる… (言語仕様、デザパタなどの概念すら知らない)

所感