どこでも見れるメモ帳

とあるSEの備忘録。何かあれば気軽にコメントください〜

git blameによるSRP(単一責任原則)の定量化

はじめに

ソースコードを静的解析することでSRP(単一責任原則)を定量的に算出します.*1
svn blameによるSRP算出*2を参考に、git blameによる算出をshで行ってみました.
このSRP値が最大のモジュールが王様モジュールに相当します.

http://f.st-hatena.com/images/fotolife/n/ni66ling/20150625/20150625000750.png?1435158531?changed=1435158531

# 単一責務性の違反指数(SRP)
#   SRP=R+U+((L/100)-5)
#     R:修正リビジョンのユニーク数
#     U:修正ユーザのユニーク数
#     L:モジュールのライン数
function get_SRP() {
  local target_filepath=$1
  echo $(( \
    $(git --no-pager blame --line-porcelain $target_filepath | sed -n 's/^summary //p' | sort | uniq -c | sort -rn | wc -l) + \
    $(git --no-pager blame --line-porcelain $target_filepath | sed -n 's/^author //p' | sort | uniq -c | sort -rn | wc -l) + \
    ( $(cat $target_filepath | wc -l) / 100 - 5) \
  )) $target_filepath
}

# SRPが酷い順(大きい順)に "SRP ファイル名" を標準出力
for file in `git ls-files app lib config vendor script | grep -E '\.rb$'`; do
  get_SRP $file
done | sort -k1,1 -nr 

※ここではrailsリポジトリを対象にしたため,rbファイルを対象にしていますが,別言語でも全く同様に扱えます.

関連する過去記事

ni66ling.hatenadiary.jp

*1:静的解析とかいうとかっこよかったりしますが,やってることはとても単純です.

*2:第8回 Perlによる大規模システム開発・設計のツボ(3):Perl Hackers Hub|gihyo.jp … 技術評論社 - http://gihyo.jp/dev/serial/01/perl-hackers-hub/000803

LDAの各変数の意味と幾何的解釈について

はじめに

LDAの仕組みについて,時間をあけるとすぐに記憶が飛んでしまうためメモ.
ここでは以下についてまとめます*1

  • LDAのグラフィカルモデルにおける各変数の意味とは?
  • LDAは幾何的に何をやってるのか?

LDAのグラフィカルモデル

まず,各文書についてBag of Words(BoW)表現に変換する*2
そして,次の仮定をおく.

  • 文書は複数のトピック*3から構成され,その構成比を離散分布としてもつ
  • トピックは語彙の出現確率分布で表現される
  • 単語ごとに潜在トピックが存在する*4

これをグラフィカルモデルに落としこむと下図になる*5

Smoothed LDAのグラフィカルモデル

すると,グラフィカルモデルにおける各変数の意味は次のようになる.*6

  • M:文書数
  • K:文書集合全体におけるトピック数
  • V:文書集合全体における語彙数*7
  • n_d:文書dにおける単語数*8
  • w_{d,i}\in\left\{1,..,V\right\}:文書dにおけるi番目の単語の語彙インデックス*9
  • z_{d,i} \in \left\{1,..,K\right\}:文書dにおけるi番目の単語の潜在トピック
  • \vec{\theta_d} = \left( \theta_{d,1},..,\theta_{d,K} \right):文書dにおけるトピックの出現確率ベクトル.\sum_{k=1}^{K}\theta_{d,k}=1
  • \vec{\alpha} = \left( \alpha_1,..,\alpha_K \right):トピックの出現頻度の偏りを表すパラメータ*10
  • \vec{\phi_k} = \left( \phi_{k,1},..,\phi_{k,V} \right):トピックkにおける語彙の出現確率ベクトル.\sum_{v=1}^{V}\phi_{k,v}=1
  • \vec{\beta} = \left( \beta_1,..,\beta_V \right):語彙の出現頻度の偏りを表すパラメータ*11

LDAの生成過程

  • \vec{\theta_d} \sim {\rm Dir}(\vec{\alpha})\ \ \ \ \ \ \ (d=1,..,M)
  • \vec{\phi_k} \sim {\rm Dir}(\vec{\beta})\ \ \ \ \ \ \ (k=1,..,K)
  • z_{d,i} \sim {\rm Multi}(\vec{\theta_{d}})\ \ \ \ \ \ \ (i=1,..,n_d)
  • w_{d,i} \sim {\rm Multi}(\vec{\theta_{z_{d,i}}})\ \ \ \ \ \ \ (i=1,..,n_d)

ただし,{\rm Dir}はディリクレ分布,{\rm Multi}は多項分布を表す.*12
また,\vec{\alpha}\vec{\beta}はパラメータとして与える.

補足:パラメータ\vec{\alpha}\vec{\beta}の意味

多項分布による生成過程x_i \sim {\rm Multi}(x_i|\vec{\pi}),(i=1,..,n)を考えて,
\vec{\pi}の事前分布としてディリクレ分布p(\vec{\pi}|\vec{\alpha})={\rm Dir}(\vec{\pi}|\vec{\alpha}),ただし\vec{\alpha}=(\alpha_1,..,\alpha_K)を考える.

すると,\vec{\pi}の事後分布は,p(\vec{\pi}|\vec{x},\vec{\alpha})={\rm Dir}(\vec{\pi}|\vec{\alpha}+\vec{n})となる.
ただし,\vec{n}=(n_1,..,n_K),n_kn回試行の中でkが出現した回数.
すなわち,事前分布における\vec{\alpha}は,事後分布では\vec{n}に加算される.
したがって,\vec{\alpha}はデータを観測する前のkごとの仮想的頻度を表す.

よって,LDAにおける\vec{\alpha}\vec{\beta}についていえば,次のようになる.

  • \vec{\alpha}は,トピックの出現頻度の偏りを表すパラメータ
  • \vec{\beta}は,語彙の出現頻度の偏りを表すパラメータ

LDAの幾何的解釈

LDAの幾何的解釈 簡単のため,全文書集合における語彙数Vが3の場合を考える*13
このとき,各文書をBoW表現にすることで,上図の語彙座標単体*14に射影できる.

各文書は語彙座標単体空間に偏りをもって分布していると想定*15され,より低次元に射影できるはずである.
この低次元空間(単体)を潜在トピック座標単体と呼び,その基底ベクトルがトピックとなり,トピックは語彙の出現確率分布で表現される.そして,各文書のトピック分布は,潜在トピック座標単体上の点となる.

つまり,LDAのは,文書集合の潜在トピック座標単体上への射影とみることができ,
潜在トピック座標単体は,単語座標単体よりも低次元であるため次元圧縮と見ることができる.

参考文献

トピックモデルによる統計的潜在意味解析 (自然言語処理シリーズ)

トピックモデルによる統計的潜在意味解析 (自然言語処理シリーズ)

*1:ほとんどの内容を本ページ末尾の参考文献から引っ張ってきています

*2:文書を形態素解析し,形態素ヒストグラムに変換する.例えば,文書が「もももすももももものうち」であれば,分かち書き「もも/も/すもも/も/もも/の/うち」に対して,形態素ごとにカウントした「もも:2 も:2 すもも:1 の:1 うち:1」がBoW表現である.

*3:話題.音楽,スポーツ,政治…など

*4:出現箇所が異なればその単語のトピックは異なる.例えば,「アップルが新製品を発表しました」と「バナナ,アップル,キウイのミックスジュースがうまい」の「アップル」のトピックは異なる.前者は「企業」トピックとしてのアップルであり,後者は「フルーツ」トピックとしてのアップルである.

*5:これは正確にはSmoothed LDAのグラフィカルモデル.無印LDAの場合はβが存在しない.多くの場合,LDAといえばSmoothed LDAを指すみたい.

*6:ベクトル表記に関して,上図では太字で記し,下では矢印で表記する.…はてなtex記法で太字を表現できなかった…

*7:ここでは「語彙」はユニークな形態素として呼び,一方「単語」はユニークでない形態素として呼ぶ.例えば,「もももすももももものうち」の語彙数は「もも」「も」「すもも」「の」「うち」の5つとするのに対して,単語数は「もも」「も」「すもも」「も」「もも」「の」「うち」の7つとする.

*8:語彙数とは異なるので注意

*9:語彙インデックスは語彙へのマッピング情報を持つ.例えば,語彙インデックス「3」は語彙「すもも」を表すなど

*10:要素値\alpha_kは大きくなるに連れて,文書集合全体においてトピックkが出現しやすくなる

*11:要素値\beta_vは大きくなるに連れて,文書集合全体において語彙インデックスvの語彙が出現しやすくなる

*12:各分布の意味はこちらのスライドがわかりやすいです → 3分でわかる多項分布とディリクレ分布 - http://www.slideshare.net/stjunya/ss-29629644/4

*13:すべての文書において,登場する語彙がgame,play,musicの3つしか存在しない状況

*14:各語彙の出現確率の総和が1となるような空間

*15:単語には共起性があるから

gitで変更ファイルの差分行番号を取得するには?

はじめに

gitで変更ファイルの差分行内容とその行番号を取得したい状況が生じたためメモ(下画像は実行結果) Gyazo

やりかた

git --no-pager diff --no-ext-diff -U1000000 \
  | diff-lines.sh \
  | grep -E "^[^\"].*\:[0-9]+\:[\+|\-]"
  • 1行目について,git diffをファイル全行について標準出力*1.具体的には以下.
    • --no-pagerはgit diffを標準出力するオプション.デフォルトだとpagerにlessが設定されてしまうため,パイプに渡せるように指定
    • --no-ext-diffは外部diffを無効化するオプション.git diffをvimdiffで見るように設定していたため指定
    • -Uは行数を表示するオプション.例えば10を設定すると差分行まわりで10行が出力される.ファイル内容について全行出力したいため大きな値1000000を指定
  • 2行目について,diff-lines.shは以下とし*2,pathを通しておく.
  • 3行目について,変更のある行の取得.
#!/bin/sh
# diff-lines.sh
numbers
path=
line=
while read; do
  esc=$'\033'
  if [[ "$REPLY" =~ ---\ (a/)?.* ]]; then
    continue
  elif [[ "$REPLY" =~ \+\+\+\ (b/)?([^[:blank:]]+).* ]]; then
    path=${BASH_REMATCH[2]}
  elif [[ "$REPLY" =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then
    line=${BASH_REMATCH[2]}
  elif [[ "$REPLY" =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
    echo "$path:$line:$REPLY"
    if [[ "${BASH_REMATCH[2]}" != - ]]; then
      ((line++))
    fi
  fi
done

補足

ファイル名と行番号だけで良い場合は以下のようにすればOK

git --no-pager diff --no-ext-diff -U1000000 \
  | diff-lines.sh \
  | grep -E "^[^\"].*\:[0-9]+\:[\+|\-]" \
  | ruby -nle 'print $_.split(":").slice(0..1).join(" ")' \ 
  | uniq

Gyazo

*1:Git - git-diff Documentation - http://git-scm.com/docs/git-diff

*2:Using git diff, how can I get added and modified lines numbers? - Stack Overflow - http://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers

SVN/GITリビジョン区間の更新ファイルをパッチ化する

はじめに

f:id:ni66ling:20160611151644p:plain

SVNもしくはGITにおけるリビジョン区間*1の更新ファイルをパッチ化*2する作業が生じたためシェル化メモ。

やりかた(SVNの場合)

# リビジョン区間(r6000-r6100)における更新ファイルについて、
# ディレクトリ構造を維持したままtar.gzに固める
svn diff -r6000:6100 --summarize \
  | awk '{print $2}' \
  | (mkdir patch_dir; \
     xargs -I{} cp --parents {} patch_dir; \
     tar czvf patch.tar.gz patch_dir; \
     rm -Rf patch_dir)

補足すると,以下の様な流れ

  • 1行目で、リビジョン区間(r6000からr6100)の更新ファイル一覧を取得
  • 2行目で、更新ファイル名のみを抽出
  • 3行目で、作業パッチディレクトリを作成
  • 4行目で、更新ファイルそれぞれについて、ディレクトリ構造を維持たままパッチディレクトリにコピー
  • 5行目で、パッチディレクトリをtar.gzに固める
  • 6行目で、作業パッチディレクトリを削除

やりかた(Gitの場合)

# gitでリビジョン区間(リビジョンハッシュA-リビジョンハッシュB)における更新ファイルについて、
# ディレクトリ構造を維持したままtar.gzに固める
git diff --name-only リビジョンハッシュA..リビジョンハッシュB \
  | (mkdir patch_dir; \
     xargs -I{} cp --parents {} patch_dir; \
     tar czvf patch.tar.gz patch_dir; \
     rm -Rf patch_dir)

補足:SVNでリビジョン区間の更新ファイル一覧を最終更新リビジョンとともに取得

# リビジョン区間(r6000-r6100)の更新ファイル一覧を最終更新リビジョンとともに取得
svn diff -r6000:6100 --summarize \
  | awk '{print $2}' \
  | xargs -I{} bash -c "( svn ls -v -R {} | awk '{printf \$1}'; echo ' '{} )"

*1:SVNなら例えばr6000からr6100、GITなら例えばリビジョンハッシュXからリビジョンハッシュY

*2:ディレクトリ構造を維持したままtar.gzに固める

技術的負債をいかに減らすか

はじめに

技術的負債が大きくなってしまったコード(=保守しにくい品質が低いコード)をいかに改善するか?という話です.
対処策として,リファクタリングやテスト充実化(★)すればいいじゃないか?という意見が出てくると思いますが,
これを漠然と実行に移すのは,意外と簡単なことではないのではないかなと思っています.

実行に移しにくい理由として,大きく以下を挙げます.
 [理由1] ★は短期的に見ると製品価値を生む行為ではないため,関係者(経営者)に対して説得力しにくい
 [理由2] 実施後のモチベーションが続きにくい

ここでは,世間的にこの問題に対してどのように対処しているのかを簡単にまとめ,
その上で,どのように対処すべきか考えをまとめたいと思います.

事例

  1. mixi alpha.mixi.co.jp
    ソースコード静的解析による技術的負債の見える化(定量化)
    ②①の指標改善度により開発者を評価

  2. ドワンゴ doda.jp
    ①技術的負債を短期間返済するための部署立ち上げ
    GitHub/IntelliJなどの開発ツール導入

  3. GMOペパボ engineer.typemag.jp
    ①技術的負債返済のための部署を社長直轄チームとして立ち上げ
    ②CI導入
    ③技術的負債返済に関して開発者を評価

各事例における対処策の分類

1.~3.の対処策をまとめると,以下の5点に分類できると思います.

  1. 技術的負債返済のための部署立ち上げ
  2. ソースコード静的解析による技術的負債の見える化
  3. CIによる技術的負債の継続的改善
  4. 技術的負債返済に連動した開発者評価
  5. GitHub/IntelliJなどの開発ツール導入

どのように対処するか

はじめの「実行に移しにくい理由」に対して対処できるのではないかなと思います.
 [理由1] → 2.による現状把握と,この指標によるデメリットの説明
 [理由2] → 3.,4.によるモチベーション維持

対処策を具体化すると,次のようになるかなと思います.

  • 2.について
    Rubocop*1による各種指標*2を取得
    取得した指標について,Rubocop規定値および世間的な標準値をもとに現状を評価し,本問題の重要度を明文化
  • 3.について
    2.の指標をJenkinsで管理.具体的には,RSpecの自動テストと同様に,コミット時にRubocopで各種指標の取得を実施
  • 4.について
    3.で取得した値について,各種指標の改善度/改悪度をチーム全体およびコミッター別に計上
    技術的負債返済の進捗状況を透明化

以上を実際に試してみて,技術的負債の返済をトライしたいと思います.

*1:対象の実装がRailsによるため

*2:Cyclomatic complexity, Assignment branch condition size metric, Perceived complexityなど.http://www.rubydoc.info/gems/rubocop/0.27.0/RuboCop/Cop/Metrics