PHPで循環的複雑度をコミッタ毎に可視化する

## 目的

- コードの複雑度は人に依存するのか、タスクに依存するのかを調べたい
- 誰が書いたかという点も重要視するが、誰が直せるかに着目したい
- 直してもらいたい箇所にアサインできるといいなを自動で抽出したい

## まずは誰が関数を書いたかという定義を策定する

- 関数の中身を一番変更している人間
    - できなくもないだろうけど、ただただメンドくさい
- 関数の宣言箇所をコミットした人
    - これならイケそう(Git前提)
    - 最初に書いてメンテしていれば複雑度に影響しないはず
    - そもそも、誰かのメンテが必要なものを最初に書いている
    - シグニチャを変えればコミッタは変わるが、それって複雑度にきっと影響するよね
- ということで、勝手に宣言部を書いた人単位で割り当てしてみる(こういうのは勢いが大事)

## 実装を考える

- phpmdがある
    - これで循環的複雑度はわかる
- 誰がそれをもたらしたかの定義もできた
- Gitを使っていれば、blameすることで誰が書いたかわかる
    - 作れる！！

## 材料

- phpmd
- Git
- Bash(連想配列が使えるVer)
- 諸々のPATHが通っていること

## 得た知見

- `git blame` は Lオプションで行指定できる
- `git blame` はwオプションでホワイトスペースの変更を無視した最終更新者を出してくれる
- Macは*NIXコマンド使えるって思ったら、案外とGNUじゃないので苦労する

## とりあえず、ルールを用意する

- 複雑度1以上(つまり、すべて)とってくるXMLを準備する
    - [レガシーコードは生き続ける！？](http://tech.voyagegroup.com/archives/7282086.html) 参照
- あとはいい感じにシェルを書いていく

## 実際に書いたシェルスクリプト(Mac対応版)

- カレントディレクトリが起点になります
- XMLのPATHはいい感じに指定してください
- 細かい関数をいっぱい作成していると見逃しがちなので数字を読む能力は問われます
    - アクセサ/ミューテータをいっぱい作ってるとか
    - ライブラリ系をいっぱい作ってるとか
    - 平均は問題ないけど、TOPが高いとか
    - 平均的だけど、TOPもなぜか高くないとか

```sh
#!/usr/local/bin/bash

set -ue

declare -A method_count
declare -A total_complexity

:> /tmp/cc.tmp
find "$(pwd)" -type f -name '*.php' | while read file_name ; do
    phpmd "${file_name}" text ~/bin/check_cc.xml | tr '\t' ' ' | cut -d' ' -f1,4,10 | sed 's/[\\(\\)\.]//g' | while read result ; do
        if [[ "${result}" = '' ]] ; then
            continue
        fi

        linum="$(echo "${result}" | cut -d' ' -f1 | cut -d':' -f2)"
        method_name="$(echo "${result}" | cut -d' ' -f2)"
        complexity="$(echo "${result}" | cut -d' ' -f3)"
        author="$(git blame ${file_name} -L${linum},+1 | head -n1 | sed 's/.*(\(.* \)201.*/\1/g' | cut -d' ' -f1,2)"

        echo "${complexity} ${file_name} ${method_name} ${author}" >> /tmp/cc.tmp
    done
done

while read line ; do
    complexity="$(echo "${line}" | sed 's/  */ /g' | cut -d' ' -f1)"
    author="$(echo "${line}" | sed 's/  */ /g' | cut -d' ' -f4- | tr ' ' '.')"

    if [ ! ${method_count["${author}"]+'abc'} ] ; then
        method_count["${author}"]=0
        total_complexity["${author}"]=0
    fi

    method_count["${author}"]=$((${method_count["${author}"]} + 1))
    total_complexity["${author}"]=$((${total_complexity["${author}"]} + $complexity))
done < <(cat /tmp/cc.tmp)

for author in ${!total_complexity[@]}; do
    avg=$(echo "scale=2; ${total_complexity[${author}]} / ${method_count[${author}]}" | bc)
    echo -e "\e[33m${author}\e[m ${avg} (${method_count[${author}]} methods)"
    grep -r "${author}" /tmp/cc.tmp | sort -nr | head -n5 | while read worst5 ; do
        worst5="$(echo $worst5 | cut -d: -f2)"
        tmp=($worst5)
        echo -e "    \e[40m${tmp[0]} ${tmp[2]}@${tmp[1]}\e[m"
    done
done

rm -f /tmp/cc.tmp
```

## 実行結果

- サンプルにはLaravelのコードを利用しました

```
Roman.Kinyakin 1.00 (2 methods)
    1 after@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Gate.php
    1 __construct@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Gate.php
Taylor.Otwell 2.09 (21 methods)
    5 resolvePolicyCallback@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Gate.php
    4 raw@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Gate.php
    4 firstArgumentCorrespondsToPolicy@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Gate.php
    4 define@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Gate.php
    3 resolveAuthCallback@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Gate.php
Joseph.Silber 1.00 (4 methods)
    1 message@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Response.php
    1 __construct@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/Response.php
    1 deny@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/HandlesAuthorization.php
    1 allow@/Users/sage0196/gitrepos/framework/src/Illuminate/Auth/Access/HandlesAuthorization.php
```

## 今後の展望

- このコードが複雑度高いから○○さんにやってもらおうとか
- 定期的に観測して最近、コード荒れてるなみたいのをチェックするとか