ソフトウェア工学研究の日々

ソフトウェア工学の学術研究を紹介しています。ソフトウェア開発に関する調査と実験が大好きです。

コードレビューでは1000回以上繰り返されている修正もある

コードレビューでは同じような修正を何度も繰り返しているしているように見えるという知見は、 if 文に関する下記の研究で判明していました。

ishiotks.hatenablog.com

この研究の続きとして、コードレビューで行われた任意の修正のうち、何度も繰り返されているものだけを取り出すパターンマイニング手法を定義し、さらに調査してみました。2019年2月に開催されたソフトウェアクローンに関するワークショップ(IWSC 2019)にて、以下のタイトルで発表しています。

Mining Source Code Improvement Patterns from Similar Code Review Works

この研究では、過去の研究と同様に、投稿されたパッチの最初の版と、レビューが完了してソースコードに取り込まれた最後の版の差分を分析しています。レビューによる差分として、たとえば以下のような情報が取得できます。

- i = dic["key"]
+ i = dic.get("key")

この研究では、この差分内容自体から、「よくある修正」のマイニングを試みています。具体的な方法としては、以下に示すように、コードレビュー差分情報を含めたトークンの系列を生成しています。

i = dic

- [

+ .get(

"key"

- ]

+ )

行の先頭に "-" が付いたものがレビューで削除されたトークン、"+" の付いたものが追加されたトークンです。i=dic、"key" という行については変更されていないことを示しています。

パッチをそれぞれこのような系列に変換した後、数値は NUMBER に置き換えるなど少し正規化操作もかけてから、系列データマイニング(Sequential Pattern Mining)のアルゴリズムを実行します。これによって、よく行われている修正と、その前後にいつも出現しているトークンを抽出しました。マイニングのアルゴリズムには、動作原理が分かりやすく、そこそこの性能を持つ PrefixSpan を使いました。

OpenStack プロジェクト群の 616,723 個の変更に対してマイニングをかけた結果から得られたパターンには色々含まれていました(詳しくは論文の Table 1参照)。たとえば、以下のパターンは、1,149個の変更に出現していました。

self.assertEqual

+ NUMBER ,

- , NUMBER

 これは、self.assertEqual(_, NUMBER) が self.assertEqual(NUMBER, _) に置き換わっているらしいことを示したパターンになっています。assertEqual の第1引数に期待された値、第2引数に実際のプログラムから計算された値を入れるので、それを守っていなかったテストコードを修正してきたことがうかがえます。このパターンを含めて、出現頻度の高いパターン群 5個について StackOverflow に対応する質問が見つかりましたので、「一度は解説を読んだことがないと分からない」というタイプの知識が、開発者にうまく広まっていない可能性があると考えています。

修正パターンのうち、修正回数が 5,000回を超えていたものは2つでした。その1つは単体テストTestCase クラスに関する関数呼び出しを置き換えるものです。

self.

+ stub_out

- stubs . Set

)

この置き換えの理由については、以下のように API ドキュメントに記載されています(レビューが行われた時期とドキュメントに書かれた時期のどちらが早いのかまでは、残念ながら分かっていません)。

stub_out(old, new)

  This should be used instead of self.stubs.Set (which is based on mox) going forward.

  The nova.test Module — nova 13.1.2 documentation

こちらはプロジェクト固有の事情によるところがありそうで、先ほどのテストの書き方よりも、さらにマイナーな知識になっていることが、この修正回数に反映されているように思います。

開発者が新しいルール(たとえば使うべきでない機能)を定義し周知できればそれで良かった可能性もありますが、なるべく手間を発生させないために、このような新しいルールの出現を自動で検知し、開発者にうまく伝える仕組みというのも、研究室で今後の課題として考えていく予定です。

( 2019年3月5日追記)本論文は、IWSC2019 において参加者の投票によって People's Choice Award に選ばれました。受賞に関するページにて、ワークショップで使用した発表スライドを公開しています。

 

OSS プロジェクトに寄付をした人に「親しみを感じる」ことの影響

オープンソースソフトウェアプロジェクトの運営において、寄付は重要な要素であると言われています。Eclipse では、寄付した人に対する特典として、バグ報告等を行うための Issue Tracking System において、寄付をした人の名前の横に "Friends of Eclipse" というバッジ(以下、寄付バッジ)が表示されるようになっています。

この寄付バッジ自体にはそれ以上の意味は用意されていないのですが、開発者がそれを見てそのバグ報告を優先して取り扱うなどの行為が起きるのではないか?という仮説を立てて調査を行いました。IEEE Software という雑誌に掲載が決まっており、既に公式サイトには著者最終版が上がっています。

Are Donation Badges Appealing? A Case Study of Developer Responses to Eclipse Bug Reports - IEEE Journals & Magazine

この調査では、統計的因果推論という手法を使っています。バグ報告者のうち、寄付バッジを持っている人たちのグループと、同じぐらい働いている人が寄付バッジを持っていない人たちを抽出し、彼らのバグレポートに対して最初に返事が返ってくるまでの時間を分析しました。以下は、寄付バッジという仕組み自体が導入される前後での2つのグループに対する返答時間を図示したものです。

f:id:ishiotakashi:20190220221932p:plain

上記論文の Fig.1 を引用。寄付バッジ導入前後での返答時間の変化。

横軸が時間で、 Before が寄付バッジ導入前、After が寄付バッジ導入後です。縦軸がバグレポートへの返答の時間で、Response trend in donors が寄付した人たちのバグレポートに関するもの、Response trend in control group が対照群です。

この図を見ると、寄付バッジ導入後、両方の群で返答時間は早くなっています。2つの群で、だいたい同じぐらい返答時間が早くなりそうなところ、寄付バッジを持っている人たちのバグレポートに対する返答のほうはさらに早くなっているので、その差が寄付バッジの効果だろうと推測されます。実際の差は2時間ぐらいなので、非常に大きいというわけではないのですが、誰かに強制されたわけでもないのに効果が出てしまうという点が面白いところです。

この結果に関連して、Eclipse の開発者の人たちは寄付バッジをどう思っているか、 Eclipse Foundation に協力していただき、 Eclipse コミュニティの皆さんにアンケートを取ることができました。この結果は、ソフトウェアエンジニアリングシンポジウム 2018 の一般論文として報告しています(著者版はこちら)。大事なところだけ要約すると、以下の通りです。

  • バグレポートを読む、バグレポートにコメントする、バグを修正するどの段階でも、自分がよく知っているコンポーネントに対するものや、深刻度が高いものを優先する。
  • 誰がそのバグレポートを書いたのか、寄付バッジを持っているかは、重要ではない。
  • 寄付バッジに対しては friendly と感じる人が多く、knowledgeable とか respected と感じる人は少ない。

開発者が「誰が言っているか」ではなく「何を言っているか」を見ていることは、開発者たちの素晴らしい姿勢だと思います。それにも関わらず寄付バッジによって実際には返答時間が早くなっているので、2時間の差は、開発者が寄付バッジを見て「親しみを感じる」ことによって無自覚に行動が早くなった影響なのかもしれません。

開発者間のコミュニケーションを円滑に進めるのに、寄付バッジという簡単な表示だけでも効果があったという点は、開発環境を取り巻く制度設計も重要であることを示唆していると思います。研究としては、今回のように「この制度が効果があった」ことを調べるのは可能ですが、「効果がありそうな制度」を考えるのは難しいところですので、今後も多くのプロジェクトでの成功例・失敗例を収集、分析していく必要がありそうです。

 

バグのあるプログラムからバグのないプログラムへの機械翻訳

機械翻訳技術は、たとえば英語の文を日本語の文へと自動的に翻訳するというのが一般的な使い方だと思います。ソフトウェア工学分野では、これを「バグのあるプログラム文からバグのないプログラム文への翻訳」とすることで、バグ修正に適用する方法を実験しています。

arxiv.org

Neural Machine Translation 技術は、対応関係のある文の組をたくさん用意してニューラルネットワークを構築し、そこに新しい文を入れると翻訳文が出てくるという手法です。そこに1文だけ命令が修正されたようなバグ修正を学習データとして投入します。1文だけの修正というのはバグ修正の中ではかなりの割合を占めていることが知られていて、たとえば以下は Apache Camel プロジェクトでの修正の事例です。

  uptime /= 24;
  long days = (long) uptime;
- long hours = (long) ((uptime - days)*60);
+ long hours = (long) ((uptime - days)*24);
  String s = fmtI.format(days) + (days > 1 ? " days" : " day");

このような事例を大量に集めて、修正パッチで削除された文(マイナス記号の付いている行)から修正パッチで追加された文(プラス記号の付いている行)への「翻訳」を学習するわけです。実験では5プロジェクトの最大10年ほどのバグ修正履歴から、 35,137件の学習データを収集して使用しています。

ソフトウェア工学における自動デバッグの研究は、「修正結果のコードのほとんどは実は対象プログラムのソースコードのどこかに既に存在していることが多い」という観測結果に基づいていて、コードを対象プログラム内部のどこかからコピーしてきて移植する形成外科(Plastic Surgery)アプローチから始まっていました。このアプローチと比較すると、「行の中をちょっとだけ直す」バグ修正に強い傾向がありそうだ、ということが分かりました。たとえば以下のような修正です。

- Set<String> knownRoles = new HashSet();
+ Set<String> knownRoles = new HashSet<>();

これは、まったく同一の文はプログラム内部に存在しないが、同じような書き換えが何度もあった事例です。このほかにも "this." の追加や式中に登場する配列の添え字の修正などをうまく実現していました。

2018年には同系統の関連論文も登場しており、アプローチ自体が発展途上の段階にあります。学習データとして大量の修正の履歴を蓄積していく必要があるため、今後どこまで発展するかは分かりませんが、よくある書き間違いに対しては、エディタがバグ修正を提案してくるようになるだろうと思います。

 

オープンソースソフトウェアプロジェクトの人口ピラミッド

オープンソースソフトウェア開発において、開発者の人数は非常に重要です。最近は企業に雇われてフルタイムで働く開発者も多くなっていますが、一時的にプロジェクトに関わってくれる人たち、単発で貢献してくれる人たちの貢献度も無視できません。

あるプロジェクトに参加している人たちの人数が今後も安定しているのか、増加あるいは減少するのか、ある程度でも予測できれば、プロジェクトの中の人にとってはプロジェクトの運営方針に、ユーザにとっては安心して利用し続けられるかどうか、考えやすくなる可能性があります。

そんな用途に、人口統計の可視化に使われる「人口ピラミッド」を使うことを試してみた研究がこちらです。

doi.org

通常の人口ピラミッドは男女別、年齢別での人数を可視化しますが、オープンソースソフトウェアプロジェクトでは、「最初にプロジェクトに関わったとき」出生という扱いにします。プロジェクトへの関わり方は色々ありますが、GitHub 上のアクティビティとして、コミットや Issue へのコメントなどを調べています。そして、最後の貢献から3か月以上経過したら、そのプロジェクトから去ったとみなして人口ピラミッドから取り除くことにします。このルールで実際に作ってみた例を以下に示します。

f:id:ishiotakashi:20190215221658p:plain

JQuery プロジェクト(2015年)の人口ピラミッド、上記論文から引用

左右は男女の別のかわりに、左側がコーディング以外で関わっている人、右側がコーディングで関わっている人という形で2つのグループに分けています。右側の白い部分は「以前はコーディング以外で貢献していた」人たち、黒は元々開発者として加わった人たちになります。

この図を見ると、少数の人たちが長期間に渡ってプロジェクトを支える一方で、多数の新しい人たちもプロジェクトに参加していることが分かります。プロジェクトによっては、1人の開発者だけがずっと頑張っていて、その人がやめた瞬間にプロジェクトが終わるというものもありますが、そういう状況を可視化できたら、誰かが(もし重要なプロジェクトであれば)終了前に引き継ぐことができるかもしれません。プロジェクトごとにそれぞれ事情もあるので、この可視化手法が常に有用であるとは主張しにくいのですが、プロジェクトがどんな状態かを考える方法の1つとして提案しています。

なお、この人口ピラミッドを使うと各年齢での人口の生存率が計算できるので、コーホート要因法という人口推計の手法が適用できます。論文では、やや単純ではありますが、「3か月後どうなっているか」をこの手法で予測してみて、「今と同じ人数がそのままいる」という単純な予測よりは、少し正確に予測ができることを確認しています。以下は、人口ピラミッドに、推計した値の線を書き入れたものです。

f:id:ishiotakashi:20190215222454p:plain

プロジェクト参加人数予測(線)と人口ピラミッドを重ねたもの、上記論文から引用

この手法ですぐに具体的な問題が何か解決できるようになるというわけではありませんが、ソフトウェア開発の社会的な側面を科学的に分析するという方法もあるのだということは示していると思います。

 

パッチ投稿経験が増えてもパッチに含まれる軽微な問題の個数は減らない

パッチを投稿してコードレビューを経験するほど、自分でパッチの問題に気付くことができるようになり、投稿パッチに含まれる間違いが減っていくのではないか。そんな仮説に基づいて、開発者のパッチ投稿回数(=経験値)と、パッチに含まれる軽微な問題の件数の関係を調べてみました。

Yuki Ueda, Akinori Ihara, Takashi Ishio and Kenichi Matsumoto: Impact of Coding Style Checker on Code Review -A case study on the OpenStack projects-

この論文では「手作業で直している軽微な問題(論文中では Manually Detected Issues)」を、Pylint のような静的解析ツールでは検出できず、レビューで直されているような修正として定義しました。いわゆるバグ修正のような大事なものは取り除きたかったので、ここでは1つのコード断片に収まっていて、しかも特定の文字種(アルファベットだけ、空白だけ、文字列だけ、など)の修正だけで直るようなもの、という便宜的な方法で抽出しています。この抽出方法は、過去の研究で目視での検査をしたときに、レイアウトの変更や変数名の変更など動作には変化がなさそうな変更をある程度の正確さ(文字種にもよりますが90%ぐらい)で取れることが分かっていたからです。

データセットには、ほかの研究と同じく、OpenStack のものを使っています。20回以上パッチを投稿したことのある人(1599人)に絞って分析しました。この人たちだけで80%ぐらいのパッチを投稿しています.

結果はというと、軽微な問題の累計数はパッチ投稿回数とともにだんだん増えていく、つまり経験に従って減っていく傾向などは出ませんでした。開発者はある一定の割合で間違いをするので、レビューを担当する人は、相手が誰であっても細かいミスがないかどうか、しっかりレビューすることが重要になります。

同時に調べた Pylint で自動的に検出できる問題 (論文中では Automatically Detected Issues)は高々1~3回でそれ以上は発生しなくなるので、こちらはツールをきちんと利用して、開発者側でうまく抑制している可能性があります。小さい誤りの混入を抑制するには、うまくツール化することが重要といえそうです。

なお、この結論は、「レビュアーはほとんど小さな誤りを見逃してない」 前提で書いてるもので、レビュアーが小さな誤りをほとんど気にせず素通しして、別途パッチを作って直しているのだと成り立たなくなります。このあたりは、レビューのデータにのみ頼っている調査の限界になります。

 

コードレビューの修正内容は開発者の勉強に役立ちそう

コードレビューはソースコード潜在的な問題を取り除き、読みやすさを向上するために重要な活動だと言われています。私たちの研究室では、将来的にコードレビューをある程度自動化したいという考えを持っていて、コードレビューがどんな修正を行っているのか(そして自動化できそうか)を調査することに取り組みました。

コードレビュー自体はあらゆるソースコードが対象ですが、プログラムの if 文の条件式はけっこうバグの原因になりがち(バグ修正時に if 文の条件式が直されていることが多い)ということが知られていたので、手始めに if 文に対するコードレビューの修正内容を調査し、論文として発表しました。

How are IF-Conditional Statements Fixed Through Peer CodeReview?

この調査でのデータセットは、Gerrit コードレビューのデータを集めることができた Qt、OpenStack プロジェクト(それぞれ C++Python)のパッチです。レビュー数で合計3000件ぐらいあります。コードレビューシステムに投稿された最初のバージョンのパッチと、レビューが終わってソースコードに取り込まれた最終版のパッチの diff (パッチ自体がそれぞれ diff 形式なので、diff の diff になっています)を取って、どんな修正をしたかを調べました。

任意の修正を調べるのは大変そうだったので、「どんな記号が追加されたか・削除されたか」という単位でグループ化し、相関ルールマイニングを使って「初期パッチに〇〇が含まれているなら、△△という修正がよく起きる」という頻出ルールを取り出しています。

結果はプロジェクト特性がけっこう出てしまった気もしますが、Qt プロジェクトでは関数の呼び出し(括弧)の追加や削除が多いという結果になりました。一番典型的だと思う例は関数呼び出しの追加・削除で、たとえば投稿されたパッチにおいて

if (qmlviewerCommand().isEmpty())

とあった条件式が、最終版のパッチでは

if (QtVersion() >= QtSupport::QtVersionNumber(4, 7, 0) &&
    qmlviewerCommand().isEmpty())

とバージョン番号の確認が挿入されていました。

OpenStack プロジェクトのほうでは、否定記号(「!」)がよく削除対象となっていて、たとえば以下のような条件式

if bytes[0] != '−' and bytes[−1] != '−':

は、then-else の内容を入れ替え、配列のインデクスに頼らない(長さ 0 の文字列でも問題ない)以下のような形式に修正されていました。

if bytes.startswith(’−’) or bytes.endswith('−'):

これらの修正は、それぞれプロジェクトのこと、プログラミング言語のことをよく知っていれば書けるけれども、パッチを作っている本人が軽くテストする程度では見逃しそうなところをうまくカバーしているように見えます。

コードレビューには可読性に関する修正もあって、OpenStack プロジェクトでは、以下のような例がありました。

if 'size' in values and values['size']:

この条件文は、辞書型の values に size という値が設定されていることを条件としていましたが、レビューで以下のように書き直されています。

if values.get('size') is not None:

 これは Python の values['size'] が値が存在しないと実行時エラーなのに対し、 values.get('size') は None を返すことを利用した書き換えになっています。

この論文の調査では、結局、レビューの修正は本当に色々あることが分かり、残念ながらすぐに応用が利くような強い結論は出ませんでしたが、コードレビューは間違いなく品質を高めるような修正を行っており、その修正の中には開発者の勉強素材になりそうな知識がたくさん詰まっていそうであるということが分かった点が収穫でした。if 文に限定しない調査も実施していますので、その結果については別途、ご紹介したいと思います。

 

手法提案系研究と調査系研究

本ブログでは、様々な研究を「手法提案系」「調査系」の2つのカテゴリに分類して紹介しています。これらは正式な分類というわけではなく、私たちの研究室でときどき使う言い回しの一種です。

手法提案系研究は、既存の何らかの問題、通常はソフトウェア開発において時間のかかる作業や難しい作業に対して、時間短縮や精度向上の方法を提案するタイプの研究です。与えられたソースコードと似ているものをすべて見つける、API の利用例を提示する、ソースコード潜在的な問題を自動検知するなど、目的は様々ですが、手法をソフトウェアの形で実装して有効性を示すという形で研究を行います。有効性の評価は、既存研究で示された何らかの評価指標を測定するか、人間が何らかの作業に適用した効果を測定することによって定量的に行うことが多く、場合によっては利用者のコメントなどから定性的に評価を行っていくこともあります。

提案手法はただ作ればよいというわけではなく、研究としての新規性を示すために、既存のツール(=競争相手)をうまく活用した場合の限界をしっかり調査する必要があります。その過程で先進的な(研究レベルの)ソフトウェア開発環境の機能の裏にある技術や実装方法を学ぶことになるので、プログラミング自体が好きな人、プログラミング環境を整えるのが好きな人に向いている研究の方向性です。アイディアが非常に新しいときは、有効性の評価もそこそこに、ツールのデモンストレーションだけで国際会議発表まで進むこともあります。私たちの研究室の場合、ある程度出来上がったところで共同研究先の企業に配布するなどといった活動もしているので、提案手法の前提条件が少なく済む(簡単な準備だけで使える)など、実用性に関する特性を比較的重視する傾向にあります。企業の状況やニーズとうまくマッチしているものが作れると、本当に現場の開発者が使用するツールとなる可能性もあります。

一方の調査系研究は、ソフトウェア開発で現在実践されている方法や開発者の活動傾向などを理解するために調査を行い、それを報告するというタイプの研究です。たとえば「開発者はソフトウェア開発作業中に Web 検索でどんな情報を参照するのか?」「開発者はどれぐらいコピー&ペーストを使っているか?」といった開発者の行動を調査するものや、「同じようなソースコードをあちこちにコピーして使いまわすのは有害か?」「1つの関数の長さはどれぐらいが適切か?」というように開発者が取るべき行動の方針を考えるための調査があります。ソフトウェアの開発データなどから開発者の活動状況を調査し、統計的な分析によってデータを整理して、開発者や研究者が今後行うべきアクションの主張を研究報告としてまとめます。ソフトウェア工学の話題に出てくるようなソフトウェア品質に関する議論は、だいたいがこちらの成果になります。

調査の設問次第で新規性は担保しやすいものの、「そんなことを調べてどうするの?」「その結果は、やる前から分かっていたのでは?」と言われやすいところに難しさもありますが、現在のソフトウェア開発技法を詳しく調べたい人や、自動化よりも人間の作業方法が大事だと考える人に向いている研究の方向性です。単にデータを調べるだけと考えられがちですが、オープンソースソフトウェアのソースコードのような大規模データに対する解析的な調査や、特定の作業における人間の動きを詳細に記録・分析するといった活動には、技術的な工夫を凝らす余地もかなりあります。

調査系研究の成果が新しい提案手法の必要性を示す土台になり、手法提案系研究の成果が新しい調査方法を生み出すなど、これら2種類の研究は相互に密接なかかわりがあり、研究室ではどちらも等しく重要であると考えています。研究室に所属する学生も、興味と得手不得手に応じて、特定のテーマについて1人で両方をこなす人、とにかく新しい手法を作り性能を改善していく人、特定の調査技術を駆使して様々な調査を行う人など、それぞれに研究の方向性を選んでいます。