コードクローン検出に基づくデザイン パターン適用支援手法の提案と実現 大阪大学 吉田昌友,吉田則裕,井上克郎 コードクローン解析に基づくデザインパターン適用候補の検出手法について, 井上研究室,吉田が発表します. 2008/03/13 情報処理学会第70回全国大会
概要 デザインパターンの適用を支援する手法の提案 Factory Methodパターンの適用候補を検出する手法の提案と実現 オープンソースソフトウェアを対象とした適用実験 本研究の概要を述べます. 本研究では,デザインパターンの1つであるファクトリーメソッドパターンの適用候補を ソースコードから検出する手法を提案します. 対象となるソフトウェアに含まれるコードクローンを解析することで ファクトリーメソッドパターンの適用候補を検出します. そして,提案手法を実装したツールを試作し, オープンソースソフトウェアを対象に適用実験を行いました.
デザインパターン ソフトウェアの設計において頻繁に発生する問題に対する洗練された解決策[1] デザインパターン記述の構成要素[1] パターン名 デザインパターンを識別するための名前 問題 デザインパターンを適用すべきコンテキスト 解法 設計の要素の役割や関連についての記述 結果 デザインパターンを使用する場合の結果やトレードオフ はじめに,デザインパターンについて説明します. デザインパターンとは,ソフトウェアの設計において頻繁に発生する問題に対する, 洗練された解決策を言います. 実際のソフトウェア開発では,デザインパターンを利用することで設計が改善する部分に デザインパターンが利用されていないことがあります. そこで,既存のソフトウェアに対してデザインパターンを適用することで 設計を改善する方法が提案されています. [1] E. Gamma, et al., Design Patterns: Elements of Reusable Object-Oriented Software, Addition Wesley, 1995.
Factory Methodパターン 少ない修正で子クラスを追加可能 同じインタフェースで異なる処理をするオブジェクト コンストラクタの呼び出し文が異なり拡張しにくい Factory Method Creator Product 生成 createProduct : Product 少ない修正で子クラスを追加可能 ConcreteCreator1 ConcreteCreator2 ConcreteProduct1 ConcreteProduct2 createProduct : Product createProduct : Product 本研究ではファクトリーメソッドパターンに注目しました. ファクトリーメソッドパターンでは,プロダクトクラスのオブジェクトを クリエイタークラスが生成して使う構造になっています. オブジェクトの生成には,コンストラクタを直接使わず, クリエイトプロダクトメソッドを使って間接的に生成します. クリエイタークラスではプロダクトクラスのオブジェクトを生成するクリエイトプロダクトメソッドの インタフェースしか定義されていませんので, 実際にはクリエイタークラスの子クラスであるコンクリートクリエイターワンクラスが クリエイトプロダクトメソッドをオーバーライドしてプロダクトクラスの子クラスの オブジェクトを生成する処理を行います. この例では,クリエイトプロダクトメソッドがファクトリーメソッドと呼ばれます. ファクトリーメソッドパターンを使う利点は,例えば,プロダクトクラスに 新たに子クラスが追加され, 同様の処理をクリエイタークラスの新たな子クラスで行うように変更があっても, クリエイトプロダクトメソッドを新たにオーバーライドするだけで, プロダクトクラスの新たな子クラスを利用することができる点です. クリエイトプロダクトメソッドをそのまま使うことができますので, コンストラクタの変更に合わせて変更するコードが少なく, 少ない修正で子クラスを追加可能な設計になります. (質問が来ても応えられるか?) new ConcreteProduct1(...) new ConcreteProduct2(...) 生成 return new ConcreteProduct1(...) 生成 コンストラクタ名に依存したコードが増える return new ConcreteProduct2(...)
既存のソフトウェアに対するデザインパターンの適用 手順1. 書籍等に掲載されている適用条件に合う部分を,開発者が見つけ出す 手順2. デザインパターンを適用した結果,設計が改善するか開発者が検討する 手順3.書籍等に掲載されている手順に従い,開発者がソースコードを修正する ソフトウェアが大規模になるほど 開発者の負担は増加する 適用条件を満たす ソフトウェア デザインパターン についての書籍等 発見 実際に修正 続いて,既存のソフトウェアに対してデザインパターンを適用する手順を述べます. まず,書籍などに掲載されているデザインパターンの適用条件に合う部分を ソースコードから見つけ出します. 次に,見つけ出した部分にデザインパターンを適用するため,ソースコードを修正します. 最後に,適用した結果,ソフトウェアの設計が改善したかどうか確認します. 適用後の ソフトウェア 検討 開発者
既存のソフトウェアに対する Factory Methodパターンの適用例 DOMBuilderとXMLBuilderには共通のインタフェース(OutputBuilder)が存在する 新たにテストを行うクラスを追加する Factory Methodパターン適用前 Factory Methodパターン適用後 拡張性の向上 拡張性の問題 子クラス追加に手間がかかる junit::framework::TestCase junit::framework::TestCase AbstractBuilderTest #builder: OutputBuilder ・・・ builder = createBuilder(...); testAddAboveRoot DOMBuilderTest XMLBuilderTest HTMLBuilderTest createBuilder ・・・ builder = createBuilder(...); junit::framework::TestCase protected abstract OutputBuilder createBuilder (...); testAddAboveRoot testAddAboveRoot testAddAboveRoot 既存のソフトウェアに対するデザインパターンの適用について, ファクトリーメソッドパターンの適用例を示します. ファクトリーメソッドパターンの適用を検討すべきコードの特徴は, コードクローンであるメソッドが共通の親クラスを持つクラス対の間に存在する点と, コンストラクタの呼び出し文がコードクローンであるメソッドの間で異なる点です. このコードの構造には2つの問題点があります. 1つは拡張性の問題であり,もう1つはコードクローンの問題です. 拡張性の問題とは, 新たに子クラスを追加して同様の処理を行うように変更があった場合, テストアッドアバブルートゥメソッドを新たに定義することになり, 拡張に手間がかかります. 次にコードクローンとは コードクローンの問題とは,テストアッドアバブルートゥメソッドが増えることにより コードクローンが増えて,保守性が低下するひとつの原因となることです. 例えば,1つのメソッドにバグが存在した場合, 他の2つのメソッドも 修正を検討しなければならなくなります. では,この例に対してファクトリーメソッドパターンを適用していきます. 2つのテストアッドアバブルートゥメソッドを親クラスにまとめたいのですが, 現在の構造では, 呼び出すコンストラクタの種類が異なるためにまとめることができません. そこで,オブジェクトの生成をファクトリーメソッドで間接的に行う構造に変更します. 2つのテストアッドアバブルートゥメソッドのコンストラクタの呼び出し文を 引数,返り値の型の等しいクリエイトビルダーメソッドに置き換えます. 2つのテストアッドアバブルートゥメソッドの内容が揃いましたので, 親クラスにまとめることができます. クリエイトビルダーメソッドを親クラスに抽象メソッドとして定義し, テストアッドアバブルートゥメソッドを親クラスに移動します. これで,ファクトリーメソッドパターンの適用ができました. ファクトリーメソッドパターンを適用したことで, 新たに子クラスを追加するように変更になった場合でも, クリエイトビルダーメソッドを新たに実装するだけで済み, 拡張性が向上し,かつ,コードクローンが減少しています. コードクローン HTMLBuilderTest DOMBuilderTest XMLBuilderTest ・・・ builder = new DOMBuilder(…); ・・・ builder = new DOMBuilder(…); ・・・ builder = new XMLBuilder(…); ・・・ builder = new HTMLBuilder(…); testAddAboveRoot testAddAboveRoot createBuilder createBuilder createBuilder ・・・ builder = new XMLBuilder(…); return new HTMLBuilder(…); return new DOMBuilder(…); return new XMLBuilder(…);
本研究の動機と目的 デザインパターン適用の問題点 Factory Methodパターンの適用条件に合う部分を自動的に検出する手法の実現 デザインパターンの適用条件に合うソースコードを人手で発見することは困難である 大規模ソフトウェアが対象の場合,より困難となる Factory Methodパターンの適用条件に合う部分を自動的に検出する手法の実現 コードクローンの解析 クラスの継承関係の抽出 では,本研究の動機と目的について述べます. 既存のソフトウェアにデザインパターンを適用する上での問題点ですが, デザインパターンの適用条件に合うソースコードを人手で発見することが困難であり, 大規模ソフトウェアが対象の場合,発見はより困難になると考えられます. そこで,ファクトリーメソッドパターンの適用条件に合う部分を 自動的に検出する手法を実現することで, 既存のソフトウェアに対するファクトリーメソッドパターンの適用を支援することを考えました. 提案手法は, コードクローンの解析とクラスの継承関係の抽出, この2つを用いて実現しました.
junit::framework::TestCase 提案手法:適用条件に合う部分を検出 条件 1 兄弟クラス間にメソッド単位のコードクローンが存在する Factory Methodパターン適用前 junit::framework::TestCase 手順 1 コードクローンを検出し,コードクローンを含むクラスの継承関係を解析する DOMBuilderTest XMLBuilderTest testAddAboveRoot testAddAboveRoot 条件 2 コンストラクタ呼び出し文がメソッド間で異なる ・・・ builder = new DOMBuilder(…); ファクトリーメソッドパターンの適用条件は2つあると定めました. 1つは,共通の親クラスを持つ兄弟クラスの間にメソッド単位のコードクローンがある という条件と, もう1つは,呼び出すコンストラクタがメソッドの間で異なるという条件です. この条件を満たす部分をソースコードから検出する手順を定めました. 条件1に対しては,コードクローンを検出し,メソッド単位のコードクローンを含む クラスの継承関係を解析して判定します. 条件2に対しては,メソッドで呼び出すコンストラクタの種類を比較することで判定します. 手順 2 メソッド中のコンストラクタ呼び出し文を比較する ・・・ builder = new XMLBuilder(…);
実験用ツールの概要 入力 出力 コードクローンの検出にはCCFinder[2]を用いた 入力 出力 Javaで記述されたソースコードファイル群 出力 Factory Methodパターンの適用条件に合うメソッド群の位置 それらメソッドに共通した親クラス名 コードクローンの検出にはCCFinder[2]を用いた クラス継承関係 コンストラクタ呼出文の特定 ソースコード 解析部 解析部 デザインパターンが 適用可能な部分の 位置情報 入力 以上の提案手法を実装したツールを試作しました. ジャバで記述されたソースコードのファイル群を入力とし, ツールを使って検出したコードクローンの位置情報と ソースコードを構文解析することで得たクラス継承関係から ファクトリーメソッドパターンの適用条件に合う,メソッド群の位置情報と それらメソッドに共通した親クラス名を出力します. 今回のコードクローンの検出ツールには, シーシーファインダーを使いました. 出力 ソースコード コードクローンの 位置情報 コードクローン 検出部 [2] T. Kamiya, et al., “CCFinder: A multi-linguistic token-based code clone detection system for large scale source code”, IEEE Trans. Softw. Eng., 28(7):654-670, 2002.
実験概要 Factory Methodパターンの適用条件に合う部分の数の調査 提案手法を実装したツールを使って実験を行いました. 目的は2つあり, 1つは,ソフトウェアごとのファクトリーメソッドパターンの適用条件に合う 部分の数の調査であり, もう1つはツールが検出した部分の一例に対し,実際にファクトリーメソッドパターンが 適用可能かどうかの確認です. 8つのジャバで記述されたオープンソースソフトウェアを対象にツールを実行しました.
ツールが検出した部分の数 数万から数十万行のソフトウェア9つから最多で14個検出された Ant 198K 994 2 ANTLR 32K ソースコードの行数 クラスの数 検出部分の数 Ant 198K 994 2 ANTLR 32K 167 1 Azureus 538K 2226 14 JBoss 679K 3372 10 jEdit 168K 922 JHotDraw 90K 487 SableCC 35K 237 Soot 352K 2298 9 WALA 210K 1565 6 ツールの出力した結果です. ソフトウェアごとに,入力したソースコードの行数,抽出されたクラスの数, ツールがファクトリーメソッドパターンを適用可能と判定した部分の数を示しています. 数万から数十万行の8つのソフトウェアからそれぞれ1から14個の部分が検出されました. ツールが出力した中の,アントラーからの検出部分に対して, ファクトリーメソッドパターンの適用が実際に可能かどうか確認しました.
ANTLRへの適用 83個のテストケースを用いて,適用前後で 振る舞いが変化していないことを確認できた 共通のインタフェースが存在しないので Factory Methodパターン適用前 Factory Methodパターン適用後 CodeGenerator CodeGenerator protected abstract OOActionLexer createActionLexer(...); createActionLexer processAction ForSpecialSymbols ・・・ lexer = createActionLexer(…); 83個のテストケースを用いて,適用前後で 振る舞いが変化していないことを確認できた 共通のインタフェースが存在しないので 新たにOOActionLexerインタフェースを定義 CSharpCodeGenerator CppCodeGenerator JavaCodeGenerator CSharpCodeGenerator CppCodeGenerator JavaCodeGenerator ツールが提示した部分に対して Factory Methodパターンを適用できた ファクトリーメソッドパターン適用前は,コードクローンではあるのですが, それぞれの呼び出すコンストラクタが異なるために親クラスにまとめることが できなかった3つのメソッドを, コンストラクタの呼び出し文を, 名前,引数,返り値の型が等しいファクトリーメソッドに置き換えたことで 内容がそろい,親クラスにまとめることができました. 変更の前後でアントラーの振る舞いが変わっていないことを テストケースを使って確認できましたので, ツールが提示した部分に対してファクトリーメソッドパターンを適用できたことが 確認できました. processAction ForSpecialSymbols processAction ForSpecialSymbols processAction ForSpecialSymbols createActionLexer createActionLexer createActionLexer ・・・ lexer = new csharp. ActionLexer(…); lexer = new cpp. lexer = new java. ・・・ lexer = new csharp. ActionLexer(…); ・・・ lexer = new cpp. ActionLexer(…); ・・・ lexer = new java. ActionLexer(…); return new csharp. ActionLexer(...); return new cpp. ActionLexer(...); return new java. ActionLexer(...);
Antへの適用 振る舞いに変化がないことを確認するために JUnitのテストケースを修正する必要がある Factory Methodパターン適用前 Factory Methodパターン適用中 BaseParamFilterReader BaseParamFilterReader getLines 振る舞いに変化がないことを確認するために JUnitのテストケースを修正する必要がある HeadFilter TailFilter HeadFilter TailFilter getLines getLines ファクトリーメソッドパターン適用前は,コードクローンではあるのですが, それぞれの呼び出すコンストラクタが異なるために親クラスにまとめることが できなかった3つのメソッドを, コンストラクタの呼び出し文を, 名前,引数,返り値の型が等しいファクトリーメソッドに置き換えたことで 内容がそろい,親クラスにまとめることができました. 変更の前後でアントラーの振る舞いが変わっていないことを テストケースを使って確認できましたので, ツールが提示した部分に対してファクトリーメソッドパターンを適用できたことが 確認できました. chain chain chain chain newFilter = new HeadFilter(…); ・・・ getLines() newFilter = new TailFilter(…); ・・・ getLines()
考察 Antへの適用 ANTLRへの適用 ツールの出力情報の不足 JUnitのテストケースを修正する必要が生じた インタフェースを追加する必要が生じた コードクローンの量を減らすことができた ツールの出力情報の不足 デザインパターンを適用することで影響を受けるクラス等 テストケースを修正する必要性の有無 考察を述べます. ツールが提示した部分に実際にファクトリーメソッドパターンを適用することができ, コードクローンの量を減らすことができました. また,ファクトリーメソッドパターンの適用の前後で 対称ソフトウェアのアントラーの振る舞いが変わっていないことを テストケースを使って確認できました. 実際にファクトリーメソッドパターンを適用する作業でわかった問題としては, 減らすことのできるコードクローンの量, 編集しなければならないソースコードの量が ユーザから見てわからないという問題がありました.
まとめと今後の課題 まとめ 今後の課題 Factory Methodパターンの適用条件に合う部分を自動的に検出する手法の提案 適用実験でオープンソースソフトウェアから適用候補を検出 2個の検出結果については,実際に適用可能であることを確認 今後の課題 ツールが出力する情報を改善した更なる適用支援 デザインパターンを適用することで影響を受ける部分 テストケースを修正する必要性の有無 Factory Methodパターン以外のデザインパターンについて適用候補を検出 まとめです. 本研究では,ファクトリーメソッドパターンの適用条件に合う部分を ソースコードから自動的に検出する手法を提案しました. 今後の課題としては, 先ほど言った出力情報の改善と, ファクトリーメソッドパターン以外のデザインパターンの適用候補の検出を考えています. 以上で発表を終わります.