背景
webアプリの開発プロジェクトを前任の方から引き継いだ。
プロジェクトはrailsフレームワークを用いており、前任の方がある程度進めているようだった。
無事納品した際、依頼主より「前任の方のコントリビューションはいくらか」と聞かれた。どうやら報酬の参考にするとのこと。
これは責任重大である。適当に答えれば主観が入りまくってしまう。
何か定量的指標を設けなければ。
そうだ、行数でコントリビューションを評価しよう[^1]
以上が事の経緯である。
[^1]: 行数がコントリビューションに一致するわけではない。しかし!あまり深く考えるとキリがない。行数とコントリビューションは相関するのでヨシ!
1. 差分ファイルの比較
ここでは更新のあったファイル・ディレクトリのリストを取得する。
folder1が新しいフォルダ
folder2が古いフォルダとする。
folder1="./new_project"
folder2="./old_project"
diff -rqw $folder1 $folder2
#Output
Only in [folder]: [newly_created_file]
Files [modified_in_folder1] and [modified_in_folder2]
diff でfolder1とfolder2の間で更新あるいは新規作成されたディレクトリやファイルが表示される。
更新されたものは Files ~ differ と表示され、新規作成されたものは Only in ~~ と表示される。
新規作成されたディレクトリの子要素は表示されない。
2. diffコマンド戻り値のパス化
diffでめでたく更新フォルダ·ファイルを得られたと思いきや、そのフォーマットはパスになっていない。
パスで欲しいんじゃ。
diffの戻り値をfor文で1行ずつ取り出してパス化する。
while read -r line; do
modified_string=${line#"Only in "}
modified_string=${modified_string//: /\/}
modified_string=${modified_string#"Files "}
modified_string=${modified_string%% and*}
file="${modified_string//$folder1/}"
done <<< "$diff_output"
1行目 read -r line
は標準入力から一行ずつ読み込み変数lineに格納する。ここでは直前に実行したdiff -rqw [folder1] [folder2]
の戻り値が入力となる。
diffの戻り値を一行ずつ取り出し、フォーマットをパスの形に落とし込んでいく。
-
modified_string=${line#"remove_char"}
文字列lineよりremove_characterを削除 -
modified_string=${modified_string//pattern/replace/}
patternをreplaceに置換
modified_string はfolder1からのパス./folder1/file1
になっている。
./folder1
の部分を削除しfolder1以下からのパスにする。
file="${modified_string//$folder1/}"
3. 更新行数の出力
変数countを用意し、各ファイルの行数をcountへ加算していく。
count=$((count + [number_of_updated_lines]))
while read -r line; do
modified_string=${line#"Only in "}
modified_string=${modified_string//: /\/}
modified_string=${modified_string#"Files "}
modified_string=${modified_string%% and*}
file="${modified_string//$folder1/}"
if [ -f "$folder2$file" ]; then
diff_lines=$(diff -u "$folder1$file" "$folder2$file" | grep -E '^\+|^-|^ ' | wc -l)
echo "更新ファイル: $file, 差分行数: $diff_lines"
count=$((count + diff_lines))
else
if [ -d "$folder1$file" ]; then
# ファイルがディレクトリの場合
# ディレクトリ内の各ファイルに対して行数をカウントする
for element in "$folder1$file"/*; do
if [ -f "$element" ]; then
if echo "$element" | grep -iq "\.png$"; then
continue
fi
if echo "$element" | grep -iq "\.avif$"; then
continue
fi
if echo "$element" | grep -iq "\.jpeg$"; then
continue
fi
line_count=$(wc -l < "$element")
echo "新規ディレクトリ内ファイル: $element, 行数: $line_count"
count=$((count + line_count))
fi
done
else
line_conut=$(wc -l < "$folder1$file")
echo "新規ファイル:$file 行数: $(wc -l < "$line_count")"
count=$((count + line_count))
fi
fi
[ -f "$folder2$file" ]
で古いフォルダ folder2に"file"があることを確認する。
diff_lines=$(diff -uw "$folder1$file" "$folder2$file" | grep -E '^\+|^-|^ ' | wc -l)
で更新行数を数える。
folder2に"file"がない場合は新規作成ファイル/フォルダである。新規作成ファイルは全部の行数をカウントする。
変数file
がディレクトリならば、新規作成ディレクトリの子要素はdiffコマンドで得られてないため子要素を取得する。
if [ -d "$folder1$file" ]; then
# ファイルがディレクトリの場合
# ディレクトリ内の各ファイルに対して行数をカウントする
for element in "$folder1$file"/*; do
if [ -f "$element" ]; then
line_count=$(wc -l < "$element")
echo "新規ディレクトリ内ファイル: $element, 行数: $line_count"
count=$((count + line_count))
fi
done
else
完成系は以下のようになる。
#!/bin/bash
folder1="./new_project"
folder2="./old_project"
diff_output=$(diff -rqw $folder1 $folder2 | grep -E "controller|views|models|db|$folder1" | grep -v -e "tmp" -e "storage" -e ".git")
count=0
while read -r line; do
modified_string=${line#"Only in "}
modified_string=${modified_string//: /\/}
modified_string=${modified_string#"Files "}
modified_string=${modified_string%% and*}
file="${modified_string//$folder1/}"
echo "========"
echo $Files
if [ -f "$folder2$file" ]; then
diff_lines=$(diff -uw "$folder1$file" "$folder2$file" | grep -E '^\+|^-|^ ' | wc -l)
echo "更新ファイル: $file, 差分行数: $diff_lines"
count=$((count + diff_lines))
else
if [ -d "$folder1$file" ]; then
for element in "$folder1$file"/*; do
if [ -f "$element" ]; then
if echo "$element" | grep -iq "\.png$"; then
continue
fi
if echo "$element" | grep -iq "\.avif$"; then
continue
fi
if echo "$element" | grep -iq "\.jpeg$"; then
continue
fi
line_count=$(wc -l < "$element")
echo "新規ディレクトリ内ファイル: $element, 行数: $line_count"
count=$((count + line_count))
fi
done
else
line_conut=$(wc -l < "$folder1$file")
echo "新規ファイル:$file 行数: $(wc -l < "$line_count")"
count=$((count + line_count))
fi
fi
done <<< "$diff_output"
echo "total $count"
いろんなオプションがついたが、私が使ったのはrailsフレームワークであるため、主な編集対象であるcontroller, view, model, dbディレクトリに絞りたかった。また他の自動生成されるファイルも除外したい。
それで grepにより結果を絞っている。
diff -rqw $folder1 $folder2 | grep -E "controller|views|models|db|$folder1" | grep -v -e "tmp" -e "storage" -e ".git"
また、ディレクトリ子要素を取り出す際に画像も出てきてしまったので、
if echo "$element" | grep -iq "\.png$"; then continue fi
で除外している。
ここら辺は各プロジェクトに依存するため編集して使っていただきたい。
結果
さて、行数を数えることができた。数えたのは3つ。
- 引き継ぎ時フォルダ(前任者の成果) vs 前任者が用いたRails原型フレームワーク
- 自分の納品時フォルダ vs 前任者が用いたRails原型フレームワーク
- 自分の納品時フォルダ vs 引き継ぎ時フォルダ(前任者の成果)
結果,
- +4481行
- +4930行
- +1858行
( + は削除した行も含まれる[コメントを受け追記])
図示すると次の図になる。
前任者のコントリビューションは 4930-1858 = 3072と考えられる。 全体の62%を占める貢献度だと分かった。
ただし、初期の導入コストも考えると、前任者はもうすこし高いコントリビューションになるだろう。
一方で、私は設計を見直したので、その分コントリビューションが加算されても良いだろう。
このように、行数に関係のないコントリビューションはたくさんある。
アルゴリズムの冗長性、可読性、ロジック、複雑性、評価しようと思えば指標にキリがない。
ざっくり行数で評価してあとは依頼主側のお気持ちを加えるのが、フェアかなあ
コメントを受け追記
図について:
納品時で原型と比較し4930行の追加/削除があり、
引き継ぎ時点と納品時を比較すると、1858行の追加/削除があったらしい。
一回矢印が後退しているが、これはフォルダごと削除され比較されなかったファイルがあるためかと思われる。
(削除した行は考慮されるが、ファイルごと削除したら考慮されない。ここは要改善)
ごっそり消えた行数は 1858-(4930-4481) = 1409
行を消すことが最も生産的な作業とのコメントをいただいた。
そこで、この削除されたファイル1409行分をコントリビューションに加え計算し直すと、私のコントリビューションは 1409+1858 になる...のか?
闇に消えたコードがある分、図のような解釈へ一意に定まるとは言い難い。