#book #r2020 ![image](https://gyazo.com/a78ddd26e55afa2bbf7cfdbfed5658d3/thumb/1000) [[Refactoring at Scale]] で紹介されていたバージョン管理システムからコードを解析していくという本 [CodeScene](https://codescene.io/) というサービスが随時出てくるので、このサービスの宣伝も兼ねていそう > It's not techinical debt unless we have to pay interest on it, and intereset rate is a function of time > 読むのに注意を払わないといけない場合だけ秘術的負債と呼ぶ。注意を払う行為は時間が密接に関係してくる > the root cause is often social and organizational problems > 真の原因は、人間や組織的な問題であることが多い # Behavioral Code Analysis - コード自体ではなく、バージョン管理に保存されているデータからコードを時間軸も含めて解析する方法。 - 主に負債になってる箇所や原因、また負債の重み付けなどを行うための解析。 ## Interest Rates - Code Frequency - ほとんどのソースコードは、縦軸を変更の回数としたときに以下のような long tail グラフになる - ![image](https://gyazo.com/579df48ca3be78b0d9d50046609f70f8/thumb/1000) proxy interest rate ``` # commitの回数が多いファイル一覧を出力 git log --format=format: --name-only | egrep -v '^ | sort | uniq -c | sort -r | head -5 # commitの回数が多いファイル一覧をファイルに出力 git log --format=format: --name-only | egrep -v '^ | sort | uniq -c | sort -r > all_frequesncies.txt ``` - Complexity Dimension - コードが多く変更されるというだけではそこが読むのが難しいとまでは言えない、変更の頻度が多くて複雑なコードを探したい - [[McCabe cyclomatic complexity]] や [[Halstead Complexity]] を使えれば良いがそれぞれは言語依存の解析になってしまう - ここでは、それほど科学的ではないにしろ、[[Lines of Code]] は複雑度の参考にはなる - [[Indentation-based complexity]] も良いアプローチと言える - ifやelseを数える代わりにインデントの数を数えるという方法 ## Function Level Analysis - Interest Ratesの測定により着目すべきファイルを特定したあと、そのファイルの詳細についてさらに調べていきたい - XRay Analysis 1. 着目すべきファイルの履歴を git から持ってくる 2. 全ての連続するバージョン間で git diff を取る 3. diffで変更された関数を取り出す 4. 関数ごとに change fequency をまとめて関数ごとに出す ## Change Coupling - 同一のcommit内で一緒に変更したファイルを調査することも発見がある - 同一のcommitの中で変更しないといけないということは、それらのファイルは密接に関係していると言える - この本の中では、以下の条件を満たしてるものを change coupling としている - 少なくとも20回のコミットで一緒に変わる - ファイルは、いずれかのファイルに対して行われた合計コミットの少なくとも50%で一緒に変更される ## Software Clones - 類似度が高くコピペによるコードを探すこともリファクタリングの最初の一歩になる - 一般的にどんなソフトウェアにも5-20%の割合でコードの複製が存在しているとも言われてる - Code Similarity によって測定されることが多い ## Future Hot Spot Analysis bash ``` # 二ヶ月前 git log --before="two months ago" --format=format: --name-only | egrep -v '^ | sort | uniq -c | sort -r > two_months_ago.txt # 今 git log --format=format: --name-only | egrep -v '^ | sort | uniq -c | sort -r > now.txt # two_months_ago.txt と now.txt を比較して急上昇中のhot spotをみる ``` # Refactoring Strategies ## Principle of Proximity - 近いものは、グループとしてまとめて捉えるというゲシュタルト心理学に基づく - 同じようなものは、近い場所に移動するというリファクタリング戦略 - また逆にChange Coupling functionであっても、距離が遠い場合には抽象化できないかもしれないことを示す ## Splinter Pattern - 1つのファイルが巨大でリファクタリング対象になってる場合には細かくファイルを分ける - コンフリクトを避ける - 意味のあるまとまりとしてコードを分割する - アルゴリズム 1. テストが十分あるか?(なければE2Eテストを一時的に作る) - わざわざ自動テストを書かなくても、手動でも問題ないかもしれない (一時的なテストはどうせ寿命が短いので) 2. コードを理解して役割によって分類をする 3. Proximity Refactoringを行い、ファイル内で意味のあるまとまりに移動していく 4. 別のモジュールに移動する 5. 新しいモジュールの呼び出しに変更する (Delegate する) 6. 1で用意していたリグレッションテストを実行して動作が変わっていないことを確かめる 7. 次の対象を選び4からやり直す ## Stabilize Code by Age - コードには、アクティブに更新されている箇所とstableになってしばらく変更がない部分がある - stable - コードの役割が落ち着いている - ブラックボックスとしてみなすことが開発者が考えることを減らす - 長い間、稼働していて研究からもバグの確率が小さいことが知られている - コードは、very recent, old, middle があり、middleなコードが最も開発者にとって読むのが難しい - アルゴリズム 1. 直近変更されていないファイルとアクティブに開発されている箇所で分類する 2. stableになっているものは別のパッケージやライブラリにして物理的に分離する 3. stablizeに失敗しているコードを移動したり、リファクタリングする - calculate the age og code 1. git ls-files でリポジトリの全てのファイルを取得する 2. git log -1 --format="%ad" --date=short -- FILEPATH で最後にcommitがあった日付を取得する 3. どれくらい最後のcommitから経ってるかを計算する ## Devide and Conquer - これまでファイル単位、関数単位で解析を行ってきたが、これをモジュール単位、サブシステム単位で行うようにしていく - アルゴリズム 1. Architectural boundaries を見つける 2. 1のレベルでhot spot analysisを行う 3. hot spotに含まれるファイルそれぞれをまた詳細解析する - 観点 - commit frequency - complexity - Lines of code - Similalirty - Coupling - 軸 - 時間軸 # 組織的観点 - コードを見ただけでは、生産性のボトルネックになってるかどうかはわからない - アーキテクチャの成功は、新しいフィーチャーをフィーチャーを開発している人たちだけで完結して開発できること - モジュールの境界をチームの責任に合わせていく - モジュールを分けるだけでは不十分 - いわゆる コンウェイの法則 - [The Influence of Organizational Structure on Software Quality](https://www.microsoft.com/en-us/research/publication/the-influence-of-organizational-structure-on-software-quality-an-empirical-case-study/) from MS Research - 組織のメトリクスがソフトウェアの品質を測定する上でコードのメトリクスよりも有用であるという観測 - authorの数、ex-authorの数など ## Fractal Value Analysis - Authorが多いほどバグの可能性が増えるという観測 - [Don't Touch My Code! Examining the Effects of Ownership on Software Quality](https://www.microsoft.com/en-us/research/publication/dont-touch-my-code-examining-the-effects-of-ownership-on-software-quality/) from MS Research $1- \sum_{a_{i} \in \mathrm{A}} (\frac{nc(a_{i})}{NC})^2$ $\mathrm{A}$ : represents all authors that worked on the module $nc(a_{i})$ : The number of commits by author a $NC$ : total number of commits for the module - どれくらいの人数がどれくらいのコードを書いてるかという指標 - Fractal Value とバグの報告は強い相関があるという研究がある - [Fractal Figures: Visualizing Development Effort for CVS Entities](https://ieeexplore.ieee.org/document/1684303) # Use Case and Feature-Centric - Package by Component vs Package by Feature - Componentごとにまとめると、一貫性が保てるが機能によって規模が異なるので小さい機能なのにも関わらず多くのファイルを要することもある - Featureごとにまとめると、そのFeatureの中ではある程度の自由を得ることができる - ただしFeatureを跨いでコードをシェアするということが難しくなってしまうというトレードオフが存在する - DCI (data, context and interaction) という戦略もある - [Lean Architecture for Agile Software Development](https://www.oreilly.com/library/view/lean-architecture-for/9780470684207/) を参照 # 感想 - コード自体の解析ではなく、開発者のsocial activity を解析対象にするのは面白い視点だった - 結局結論としては、 - Hot Spot Analysis でコード中でアクティブに開発されていて、負債が溜まってる箇所を優先的にリファクタリングする - Code Frequency、Loc、Coupling で Hot Spotを見つける - そのファイルの Loc と Complexity を時間軸で追う - メソッドについても Code Frequency、Loc、Coupling が使える - Ageも意識することでコードのアクティブな部分とStableな部分を認識する - コンウェイの法則にしたがってコードの境界を設けていって、チームでオーナーシップを持つようにして Operational Boundariesを狭くする (Knowledge Boundariesは広いままを保つ) - いろんな人がみんなでコードを書くとバグの確率が増えちゃうよ (Fractal Valueは小さい値を保てるようにしよう) - Code SceneやCode Maatの宣伝にも見える笑 [[Code Matt を用いた Behavioral Code Analysis]] で実際に解析したりもしてみた ## 追記 本業で使った行数に着目したスクリプト sh ``` # 過去1年間に編集した Swift および Objective-C ファイルを add, del の合計が大きい順に300件出力する # 最初の数字3つはそれぞれ add+del, add, del を表す git log --since="1 year ago" --pretty=tformat: --numstat | \ awk ' { if ($3 !~ /^Pods\// && # Pod についてはこちらで編集していないので除く $3 ~ /\.swift$|\.h$|\.m$/) # Swift および Objective-C (ヘッダーと実装) のみを対象としたい { total[$3] += ($1 + $2); add[$3] += $1; del[$3] += $2; } } END { for (file in add) { printf "%d,%d,%d,%s\n", total[file], add[file], del[file], file; } }' | \ sort -t, -k1,1nr | \ head -n 300 ``` sh ``` # 過去1年間に編集した Swift および Objective-C ファイルを commit が多い順に100件出力 git log --since="1 year ago" --format=format: --name-only | \ egrep -v '^ | \ egrep -v '^Pods/' | \ egrep '\.swift$|\.m$|\.h | \ sort | \ uniq -c | \ sort -r | \ head -n 100 | \ awk '{print $1 "," $2}' ```