どこでも見れるメモ帳

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

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 http://alpha.mixi.co.jp/entry/2012/11552/alpha.mixi.co.jp
    ソースコード静的解析による技術的負債の見える化定量化)
    ②①の指標改善度により開発者を評価

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

  3. GMOペパボ http://engineer.typemag.jp/article/pepabo-devengineer.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

ローカルのシェルスクリプトをリモート実行する

はじめに

f:id:ni66ling:20160611155037p:plain

ローカルのスクリプトをリモートで実行したいけどどうやれば?という話.
どうやるのが定番なのか?は結局わかっていないが,一応できるにはできたのでメモ.

やりかた

sshコマンドにパイプでローカルのスクリプトを引き渡す.*1

$ cat [local_script.sh] | ssh [user]@[host] bash

補足: ローカルにおける変数をリモートスクリプト内で使うには

プロセス置換*2を用いる.

$ param="local"
$ cat <(echo -e "param=\"${param}\"\n") [local_script.sh] | ssh [user]@[host] bash

ただし,懸念点として,リモート実行するスクリプト内容が分散し,見通しが悪くなってしまう問題がある.
もっといい方法をご存知の方がいらっしゃったらお教えいただけると嬉しいです.

補足: cronやjenkinsからの自動実行するには

パスワード認証を自動突破するためにexpectを用いる.*3
ソースコードで示すと以下のとおり.

#!/bin/sh
# ローカルのスクリプト(リモート実行する)
remote_command_file="./local_script.sh"

# expectでSSH接続 ([hoge]は適宜読み替えて)
expect -c "
  set timeout -1
  log_file $log_file
  spawn bash -c \"cat $remote_command_file | ssh [user]@[host] bash\"
  expect \"password\" ; send \"[password]\n\"
  expect eof  # :interactとしないこと
  exit        # :interactとしないこと
"

expectに関する注意として,interactとはせず,expect eof; exitとすること.
ターミナルから実行する場合はinteractで良いが,cronやjenkinsから実行する場合はexpect eof; exitとする必要がある.*4*5

*1:注意点として,パイプで渡されるsshでは,bashを引数にすること.bashを引数にしなくても実行はできるが,標準エラー「Pseudo-terminal will not be allocated because stdin is not a terminal.」が出力されてしまう.また,さらに補足すると,この標準エラーは多段接続時に「-t」パラメータを指定しなかった場合にも出力される.

*2:http://sechiro.hatenablog.com/entries/2013/08/15

*3:公開鍵認証かつパスワードなしの設定であれば,expectを使う必要はない.$ ssh -i [鍵path] [user]@[host] bash, もしくは, sshのconfigファイルを読ませるなど

*4:http://yu-write.blogspot.jp/2013/11/shell-crontabexpect.html

*5:ここでかなりハマった…

行ごとに文字数を換算する

はじめに

行ごとに文字数を計算したい状況が発生したためメモ。

やりかた

$ cat input_file | while read line; do echo $line $((`echo $line | wc -m` - 1)); done

文字数の計上は $ wc -m。これはマルチバイト文字に対応しており、日本語と英語が混ざっても、適切に文字数を計算してくれる。*1
ただし、$ wc -m では、改行文字も文字数に計上してしまう。そのため上のコードでは1文字分だけ差し引いている。

*1:補足。バイト数を計上したい場合は $ wc -c。単語数を計上したい場合は $wc -w。

before_filterをすべてスキップする


はじめに

子クラスで親クラスのbefore_filterをすべて実行させたくない状況が生じたのでメモ。

やりかた

skip_before_filter(*_process_action_callbacks.select{|filter| filter.kind == :before}.map(&:filter))

まず、skip_before_filterは、フィルタのシンボルを渡すとスキップできる。*1
フィルタのシンボルは、_process_action_callbacksから抜き出す。*2 *3
これは配列で返ってくるので、*で配列の展開をしている。*4

おまけ

子クラスのbefore_filterを先に実行し、その後に親クラスのbefore_filterを実行するには?
before_filterは、引数の順序にしたがって実行される。
よって、子の後に親を実行するには、1)親クラスのbefore_filterをすべてスキップし、2)子クラスのbefore_filterを実行し、3)親クラスのbefore_filterを実行すれば良い。
具体的には以下のとおり。

super_before_filters = _process_action_callbacks.select{|filter| filter.kind == :before}.map(&:filter)
skip_before_filter *super_before_filters
before_filter :child_before_filter, *super_before_filters

同じ考え方で、子クラスのbefore_filterを親クラスのbefore_filterの合間に実行したり、親クラスのbefore_filterを部分的に実行したりできる。