構文解析でテキスト全置換によるリファクタリングからおさらばしたい
2024-04-04
azblob://2024/04/04/eyecatch/2024-04-04-AST-Text-Replacement-Refactoring-000.jpg

2024年度入社の玉井 陽博です。

大学院ではJavaScriptにおけるソースコードのリファクタリングに関する研究を行っていました。

また、学生時代はゲーム等の開発をしていた他、いろいろ旅行に行ったりとかもしていました。

今回は、自分の大学での研究に関連する話で、リファクタリングにおいてテキスト全置換ではなく構文解析を利用したい、という話をしていきたいと思います。

テキスト全置換によるリファクタリング

プログラミングにおいて、既存のメソッドの名前が変更され、それを利用している箇所の修正が必要になる場合がよくあるかと思います。

その場合、単純なプログラムの変更であればソースコードに単純なテキスト全置換をかけることでリファクタリングが完了する場合があります。

たとえば、以下のようなソースコードがあったとします。

Javascriptlet HOGE = {
	FUGA = 100,
	PIYO = 20
};


HOGE.FUGA = 100;
HOGE.FUGA += HOGE.PIYO;
console.log(HOGE.FUGA - 10);

ここで、オブジェクトHOGEの設計が以下のように変更された場合のリファクタリングについて考えてみます。

Javascriptlet HOGE = {
	FOO = 100,
	PIYO = 20
}

HOGE内のFUGAがFOOにリネームされています。

ここで、先ほどのプログラムの7~9行目でFUGAが参照されていますね。

今回は、これを修正するには「HOGE.FUGA」を「HOGE.FOO」に全置換するすることで7~9行目でこのようにリネームに対応することが出来ました。

JavascriptHOGE.FOO = 100;
HOGE.FOO += HOGE.PIYO;
console.log(HOGE.FOO - 10);

テキスト全置換で対応出来ないケース

では、例えば以下のようなソースコードがあったとして、

Javascriptlet PointCounter = {
	Count = 100,
	Name = "NewCounter"
};

let OtherPointCounter = {
	CountedNumber = 10
};


PointCounter.Count = -999;
PointCounter.Count += OtherPointCounter.CountedNumber;
console.log(PointCounter.Count - 10);

このソースコードのPointCounter内のCountがNumberにリネームされる場合を考えてみましょう。

Javascriptlet PointCounter = {
	Number = 100,
	Name = "NewCounter"
};

ここで、11~13行目の修正について、先ほどと同じように「PointCounter.Count」を「PointCounter.Number」に全置換してみます。

Javascriptlet PointCounter = {
	Number = 100,
	Name = "NewCounter"
};

let OtherPointCounter = {
	CountedNumber = 10
};


PointCounter.Number = -999;
PointCounter.Number += OtherPointCounter.NumberedNumber;
console.log(PointCounter.Number - 10);

12行目で、OtherPointCounter.CountedNumberがOtherPointCounter.NumberedNumberになっておかしくなっています。

この場合はただ単に全置換では修正に対応できません。

構文解析の利用

では、ここでプログラムの構文解析を利用した置換を行う場合を考えてみましょう。

ソースコードの構造を木構造にしたものを、抽象構文木(AST)と言います。

プロパティとしてソースコードに登場するPointCounter.Numberを構文解析し、結果を抽象構文木に表すと以下のような木構造になります。

AST1

また、同様にOtherPointCounter.CountedNumberの抽象構文木はこのような構造になります。

AST2

この2つの木構造を比べると、「OtherPointCounter.CountedNumber」というテキストの中にある「PointCounter.Count」というテキストと「PointCounter.Count」というプロパティを区別することが出来ます。

リファクタリングを行いたいプログラム全体を構文解析し、このような木構造を持つ部分を対象にして置換すると、11~13行目を以下のように修正出来ます。

JavascriptPointCounter.Number = -999;
PointCounter.Number += OtherPointCounter.CountedNumber;
console.log(PointCounter.Number - 10);

このように、単純なテキストの全置換よりも複雑なリファクタリングを行うことが出来ました。

まとめ

今回は、構文解析を用いるとテキストベースの置換よりも高い精度でリファクタリングが行える例を記事にしました。

今後、構文解析を使ったリファクタリングを行うための実装についても記事にしていけたらと思います。