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

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

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

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

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

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

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

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

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

 

ライブラリのセキュリティ問題がアプリケーションに影響しないケースも多い

ライブラリの名前とバージョン番号を指定するだけで自動的にダウンロード等を完了してくれる依存性管理ツールのおかげで、どのライブラリを使っているかが機械的にチェックできるようになり、古いバージョンのライブラリを使っているとセキュリティ問題のあるライブラリを使わないように、と自動的に警告を出すこともできるようになりました。

一方で、セキュリティ問題があるといっても、問題があるのはライブラリの特定の機能に限られるため、すべての利用者がその影響を受けるわけではありません。たとえば画像ファイルを読み書きするライブラリに特殊な細工をしたファイルを読ませると不正な動作をすることが分かったとき、そのライブラリを画像ファイルを書き出すためだけに使っているアプリケーションは影響を受けないことになります。現在のセキュリティ警告の仕組みは、残念ながら、そこまでは考慮してくれません。

そこで、セキュリティ問題が存在することが分かっているライブラリのバージョンを使っていて、かつ、その問題の影響を受けないケースがどれだけあるか、JavaScript で npm によって依存ライブラリを管理しているプロジェクトに対して小規模なサンプル調査を行ってみました。全文は以下のようなタイトルで発表しています。

Rodrigo Elizalde, Raula Kula Gaikovina, Bodin Chinthanet, Takashi Ishio, Akinori Ihara and Kenichi Matsumoto: Towards Smoother Library Migrations: A Look at Vulnerable Dependency Migrations at Function Level for npm JavaScript Packages

 この調査では、ライブラリのバージョン更新履歴(ソースコードの差分)からセキュリティ問題がどの関数にあったかを特定し、関数の呼び出し経路を調べて、その関数を実際に呼び出す可能性があるかどうかを確認しました。

その結果、73.3% はセキュリティ問題の報告されているバージョンのライブラリを使用しているが、その問題を含む関数に対する呼び出しの経路は存在していませんでした。ライブラリのセキュリティ問題に関する話題でよく登場する「〇〇%のアプリケーションがいまだに古いライブラリを使っている」といった文言は、影響を過大に喧伝してしまっていることになります。

ただし、セキュリティ問題の影響を受けるような機能を使っていないうちにライブラリを更新しておくほうが、セキュリティ修正がアプリケーションに影響を与えないと分かっているので、更新作業自体にはコストがかからない可能性もあります。何かの拍子に開発者がその機能を使ってしまう可能性もありますので、更新を急ぐ必要はないとは言えるものの、更新がまったく必要ではないとまでは言い切れません。ライブラリの更新に伴うコストと更新しない場合のリスクをうまく見積もる(あるいは更新コストを下げる)技術の研究が今後も必要なのだと考えています。

 

Java プログラムのリアルタイムプロファイラの開発

プログラムの実行に時間がかかる、一瞬動きが止まって見えるといった、実行速度に関する問題が生じることがあります。そんなときに使えるのがプロファイラと呼ばれるツールで、Java の場合は Oracle JDK に標準で付いてくる hprof がよく知られていると思います。プロファイラを使うと、プログラムのどの部分の実行にどれだけ時間がかかっているか、計測することができます。

普通のプロファイラは、プログラムの実行が終了するまで計測を続け、最終的にどこに一番時間がかかっていたかを表示しますが、一瞬だけ動きが止まるといったような一時的な挙動は調べることができません。私たちの研究室では、 Java プログラムの実行中に起きたことをリアルタイムに可視化すると、プログラムの挙動の理解が容易になるのではと考え、2017年度の修士論文として、ツールを開発しました。

github.com

Heijo と名付けられたこのツールは、Java のパッケージおよびクラスを土台として、直近の1秒間のうちメソッドが実行されていた時間の割合を四角い棒(建物)の高さとして可視化するという可視化を採用しました。この可視化方法自体は Code City という名前で知られる既存研究に基づくものです。以下の動画は、上記プロジェクトページに動作例として掲載しているものです(音量にはご注意ください)。

www.youtube.comこの動画では、左側で Mario Like と名付けられたゲームが動作しており、右側にその実行を可視化しています。常時 2つ、青い棒が立ち上がっているので、2つのスレッドが動いているのが分かります。動画開始から14秒あたりのところで、ゲームの動きが一瞬だけ動きが止まるという動作が起き、右側の可視化結果でも、1本の棒が縮んでなくなり、代わりに別の棒が伸びてきます。つまり、特定のメソッドに多くの時間を費やしてしまいその結果プログラムの動きが停止してしまったことが分かります。Android アプリの監視バージョンも作成し、色々な用途を探索した結果を修士論文としました。

技術的には、Java プログラムの各メソッドの先頭と末尾、メソッド呼び出し命令の前後に、現在どのメソッドの実行を開始・終了したかを報告するような処理を行うよう、プログラムをロードするタイミングでバイトコードの書き換えを行っています。これにより、実行オーバーヘッドを(デバッガのステップ実行などと比べると)小さく抑えながら情報を収集し、可視化のエンジンにデータを送信しています。ソフトウェアの詳しい紹介はソフトウェア科学会の論文誌「コンピュータソフトウェア」に掲載の予定です(本記事執筆時点ではまだ公開されていません)。

 

ブロックチェーンを用いたソフトウェアビルドプロセスの記録

ソフトウェアの実行可能ファイル(いわゆるバイナリ)は、ソースコードさえあれば自由に作成することができます。プロの分析者であればバイナリに含まれた定数値などから元のソースコードをある程度は推定することができますが、多数のソースファイルのどれを組み合わせてバイナリを作ったかは、実際にビルドを実行した人以外にはわかりません。

ソフトウェアの実行可能ファイルをエンドユーザに提供するとき、「このファイルは、これらのソースファイル(といくつかのライブラリ)だけから作りました」と説明できるようになると、悪意あるコードの混入などのリスクを抑えらえるようになりますし、著作権の侵害などのトラブルにも対応しやすくなります。このコンセプトは "Reproducible Builds" として知られています。

Reproducible Builds — a set of software development practices that create an independently-verifiable path from source to binary code

ソフトウェア工学研究室では、このような取り組みを支援する技術の1つとして、ブロックチェーンを用いたビルドプロセスの記録に取り組んでいます。任意のコマンド(たとえば gcc のようなコンパイルの実行)の実行を監視し、読み書きしたファイルを記録することで、どのファイルが、どのファイルと関係があるかを記録することで、この目的を達成しようとしています。2019年1月の時点で、ビルドを監視するツールを試作し、ブロックチェーンに登録可能であることを確認した段階というところですが、構想には思ったより反響もあり、第25回ソフトウェア工学の基礎ワークショップ(FOSE 2018)では貢献賞(ショートペーパー部門)を受賞しました。以下はそのときの発表スライドです。

www.slideshare.net

ソースファイルと実行可能ファイルを結びつけることは、「その実行可能ファイルに使われたソースファイルに対する品質検査を確かに行った」というように、エンドユーザに提供されているソフトウェアの品質を説明していくためにも使えます。ソフトウェアにはバグが避けられませんが、開発者がその時点で行った様々な努力を証拠として記録することも、ソフトウェアに起因するトラブルの解消や原因の理解、再発の防止において重要な課題であると考えています。

 

ソースコードからの類似したコード断片の検索

プログラミングをしていると、似たような、少しだけ違う処理を何度か書かなければならないことがあります。うまく関数などの単位にまとめられれば良いのですが、ループ構造などをうまくまとめられない場合もありますし、開発の担当者が異なるために編集できないという場合もあります。そんなときは、1つ記述した処理の内容をコピー&ペーストで複製し、それぞれの場所に合わせて編集するという形でプログラムを書くことになります。

このような処理のコピーが、1つのバグを、複数の場所にばらまいてしまうこともあります。以下は、ある実際の企業システムにおける C# のバグ修正の例から、システム固有の変数名などをつぶしたものです。

   for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
-      break;
+      continue;
      }
      ret.Add((string)row.Cells[i].Value);
  }

この修正では、マイナスのついている行の break 文が、プラスで示された continue 文に置き換えられました。この処理では、表のセルに格納された値が null の場所を飛ばしてデータを取り出していくことが必要だったのですが、開発者が書いたコードは、最初の null を見つけた時点でループを打ち切ってしまっていました。

このようなバグが発見されたとき、開発者は、他に同じ誤りのあるコードはないかを検査し、まとめて修正を行うことが求められます。しかし、コード片の個々の単語だけで見ると、プログラムのあちこちに出現するものばかりです。grep のようなキーワード検索ツールだけでは、システム全体から、注目すべきポイントだけに絞り込むことは簡単ではありません。

そこで、コード片を入力として、ソースコードを検索するツールを開発しました。

github.com

ツールはコード片と検索対象ディレクトリ名を受け取り、ソースコード内から類似した内容のソースコードが出現する場所をすべて探し、報告します。この検索基準として、私たちは「正規圧縮距離(Normalized Compression Distance)」を採用しています。これはデータ圧縮アルゴリズムの特性を使用した技術の1つで、「テキスト X の内容をデータ圧縮した結果と、テキスト X の内容を2つ繰り返し並べたものをデータ圧縮した結果のデータの大きさが、ほとんど変わらない」という性質を活用したものです。上記のコード例を検索する場合、以下のようなテキストが入力となります。

   for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
      break;
      }
      ret.Add((string)row.Cells[i].Value);
  }

この内容を持ったテキストファイルを Windows 10 の「送る - 圧縮 (zip形式) フォルダー」 機能で圧縮すると、空白や改行込みで159バイトのデータが108バイトに圧縮されます。これを二度繰り返した以下のようなテキストを用意して圧縮すると、実際のデータ量は約2倍になったにも関わらず、圧縮結果は111バイトに収まります。

  for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
      break;
      }
      ret.Add((string)row.Cells[i].Value);
  }

  for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
      break;
      }
      ret.Add((string)row.Cells[i].Value);
  }

気になる方は、実際に手元の環境で試してみてください。zip 圧縮ではファイル名などの情報が一定量入るので、WindowsExplorer から見えるファイルサイズでは 220バイト前後になるかと思いますが、内容が2倍に増えても圧縮結果のファイルの大きさがほとんど変わらないことを確認できると思います。2回目のテキストの内容を書き換えて、情報の繰り返しを減らすと、圧縮結果のファイルが大きくなります。

この性質を使うと、入力として与えられたコード片と、ソースコードから適当に切り出してきたコード片をつないで圧縮をかけ、その大きさを確認することで、似ているソースコードの範囲を抽出することができます。上記のバグの事例では、システム全体から9個のコード片を報告し、うち2件が入力となったコード片とそのコピー(修正が必要な場所)、2件が同じ処理を既に正しく実装していた場所、残る5件は類似したループ構造だが今回のバグには無関係な場所となっていました。この検索方法は、かなり重たい計算のようにも見えますが、zip 圧縮のようなアルゴリズムは大容量のデータを高速に処理することに適しており、開発に使っているワークステーション上では Linux の600万行程度のソースコードを 20分で検索できるなど、実用性も意外と高いものとなっています(詳しくは論文PDFをご覧ください)。研究としては、「企業の開発者が日常的に使えるツール」を目指して、さらに高速な検索のための工夫に取り組んでいます。

なお、このような「類似したソースコード」を見つける技術については、コードクローン検出と呼ばれ、ソフトウェア工学でも息の長い研究分野の1つです。ソフトウェア工学研究室では、コードクローンの有無や量もソフトウェア品質の一指標として考えています。

門田 暁人, 佐藤 慎一, 神谷 年洋, 松本 健一: コードクローンに基づくレガシーソフトウェア品質の分析, 情報処理学会論文誌, vol.44, no.8, 2003. (論文PDF)

上記論文では、コードクローンを含むソースコードのほうが(おそらくは既にテストされたようなコードを流用しているために)バグが少ない、しかしあまりに多くのコードクローンを持つソフトウェアは品質が低いなど、いくつかの性質を報告しています。コピー&ペーストは非常に強力なツールの1つですが、活用しつつも度を超さないようにするというのも、開発者にとってはセンスの問われるところです。

 

ライブラリ利用例カタログの自動生成

ソフトウェアの色々な機能を作るための部品となるライブラリ。それらを初めてを使うときに頼りになるのが利用例、サンプルコードと呼ばれるものです。StackOverflow などの Q&A サイトでも、「〇〇を使って××するにはどうしたらいいか」という質問がよく行われています。

最近はオープンソースソフトウェアとして多数のプロジェクトのソースコードが公開されているのだから、そこからうまくクラスごとの利用例を収集できるのでは?と考えて、NAIST ソフトウェア工学研究室とオーストラリア・ウロンゴン大学の研究チームで協力してツールを作ってみました。

開発したツール Catalogen は、Java を対象として、クラス名を引数に、利用例のリストを HTML 形式で出力します。Apache POI という Microsoft Office のデータファイルを読み書きするライブラリのうち、XSSFWorkbook という Excel ファイルを扱うためのクラスの利用例を対象とした実行結果の例が こちら(XSSFWorkbook | Catalogen) です。

このツールは、以下のような仕組みで動作しています(著者最終版原稿PDFはこちら)。

  1. オープンソースソフトウェアのソースコードを検索するサービス Searchcode.com に接続し、クラス名をクエリとして渡し、そのクラス名を使っているソースファイルを集めます。
  2. 集めたソースファイルの中から、クラス名と変数名が連続しているところを見つけます。これが利用例の開始位置となります。
  3. 開始位置から、その変数名が出現する行だけを拾い集めます。これが利用例の本体となります。
  4. 同じ変数名が他の用途にも何度も使われる可能性があるので、"}" で変数のスコープが無効になったところで探索を終了し、見つかった行の範囲を利用例とします。
  5. 利用例それぞれに対して、Natural Machine Translation 技術で Java のメソッド → Javadoc コメントの対応関係を学習したモデルを適用し、内容を表現しそうなコメントを割り当てます。
  6. 利用例のうち、似ているもの同士をクラスタリングして、各クラスタから一番短いもの(最も単純そうなもの)を代表として、HTML に出力します。その他の利用例は、クラスタ名をクリックしたときだけ表示されるものとして扱います。

Apache POI の各クラスに対する実行結果はこちらにああります。コメントの内容等にはまだまだ改善の余地はありますが、全自動でこれだけ情報を整理できることに可能性を感じていただければ幸いです。本ツールは、ドキュメンテーション生成技術に関するワークショップ Dysdoc3 の一部として開催された DOCGEN CHALLENGE 2018 において、Usefulness 部門第2位に選ばれました。

このツールのようなドキュメント生成技術は、「ドキュメントを書いても誰も読まない、すぐに古くなってしまう」という問題に対して、「ドキュメントは必要なときに、その人の知識に合わせて作ればよい」という考えから、ここ数年、研究者が活発に議論を交わしているトピックの1つです。今後も、新しいアイディアや技術に基づいたツールが登場することになると思います。

 

開発者は使用するライブラリを更新しない

ソフトウェア開発では、画像の読み書きやネットワーク接続など、基本的な処理を提供するライブラリを土台にして、開発したいソフトウェアに固有の機能を作っていきます。

ほとんどの人は、開発を始めるとき、あるいはライブラリを必要とするような機能を実装するときに、ライブラリの名前やバージョンを調べて、その時点での最新版を開発に使います。それで何の問題なさそうにも思えますが、ライブラリの中にはセキュリティ問題などの深刻なバグが発見されることもあり、そういう場合には新しいバージョンに素早く移行することが求められます。ただ、セキュリティに関心のある一部の人々を除くと、ライブラリに新しいバージョンが出たといっても、大して気に留められることもありません。

そんな実態を、少しまじめに調べたのがこの論文です。

arxiv.orgいくつかのセキュリティ問題の報告に対して、横軸に時間、ライブラリの利用者数 (Library Usage, LU) を縦軸に取ったグラフを取ってプロットしてみました。以下は Apache Commons-beanUtils のセキュリティ問題発生時のものです(論文内 Fig.9 を引用)。

f:id:ishiotakashi:20190202221200p:plain

セキュリティ問題の報告に伴うライブラリ利用者数の変化

セキュリティ問題が2014年4月に報告され(図中の黒い縦の破線)、新しいバージョンがリリースされた後も、一定数のユーザが残っていることが分かります。利用者数のカウントは、リポジトリで更新作業をしていることを条件にしているので、「古いバージョンのライブラリを採用したまま、プロジェクトの更新が終わっている」ようなプロジェクトの数は除外しています。

調査対象全体では、 81.5%という多数のプロジェクトが古いバージョンのまま他の作業をしていました。この原因の1つは、個々のライブラリのバージョン更新内容をまじめに追いかけるのは大変である、ということにあります。幸い、2018年になって、GitHub など複数のサービスがセキュリティ警告機能(セキュリティ問題のあるバージョンのライブラリ利用者にメール等で連絡する機能)を提供するようになり、状況はだいぶ改善されました。それでも、ライブラリはバージョンごとに完全に互換性があるわけではなく、新バージョンに更新しようとして失敗する事例があることも報告されています。プロジェクト全体の他の作業で忙しい開発者にとっては、ライブラリの更新を迅速に実行するのは難しいかもしれません。

ライブラリ開発者が互換性を保ってくれれば自動更新ができるのに、という期待もあるかもしれませんが、非互換性の導入にもエラー報告の詳細化や危険な機能の削除など、相応の理由があることが知られています。そのため、ソフトウェアを新しいバージョンのライブラリでテスト実行して安全に更新できそうなときだけ自動で通知する、といった手法も提案されています。 また、本当に今すぐ更新する必要があるのか、セキュリティ問題のあるコードを実際に呼び出しうるか・実行しているかを自動的に判定するソフトウェアというのも研究レベルでは出てきています。以下がそういうプロジェクトの1つです。

github.comこの記事を執筆した時点では、すぐに実用に供するというには難しい開発段階のようですが、いずれは、このようなツールでソフトウェアの状態をチェックし、セキュリティ問題の報告に素早く応答することがソフトウェア開発の標準となるかもしれません。