スクリプト言語としてのGroovyとJavaのDSL

なんで?


たとえば、監視タスクやメンテナンスタスクなどのさまざまな種類のソフトウェアを開発するとき、特定のアクションセットを記述または実行するスクリプトをスクリプトでサポートする必要があります。 また、ソフトウェアにこのようなシナリオを追加または変更する際に、ソフトウェアの再構築や再起動を必要としないという特異性があります。

おそらく、誰もが何らかの形で遭遇したこのようなシナリオの最も単純な例は、通常のバッチファイル(batまたはsh)です。

私の実践では、XMLを使用してスクリプトを記述することがあります。 明確に定義されたアクションのセットと、分岐のない単純なフラットセットの場合、XMLの使用は悪くありません。 スクリプトファイルの検証を可能にする固定構造と、XML言語自体の単純さにより、プログラマだけでなくスクリプトファイルを使用できます。 ただし、XMLで分岐ロジックまたは条件付きロジックを実装することは、非常に費用がかかり、独自のミニ言語を作成するのに似ています。 アクションのセットの拡張は、ほとんどの場合、プログラムのソースコードが変更され、XMLスクリプトサポートの初期実装が労働集約的である場合にのみ可能です。

一般的に、シナリオを記述するためのよりシンプルで機能的なツールを探して、視線はスクリプト言語に変わりました。 プラットフォームはJavaであるため、スクリプト言語とJavaを統合する機能が必要でした。 その結果、Groovyに選択肢が選ばれました。Groovyは、Javaに簡単に統合され、シンプルで表現力があり、タスクに役立つ多くの機能を備えた動的なJVMベースの言語です。

どうやって?


スクリプト

Groovyの基本については説明しません。ロシア語であってもネットワーク上にはすでに多くの資料があります。 いくつかの重要な点のみを説明します。

Groovyでは、JavaコードからコンパイルされていないGroovyソースコードを実行できます。これにより、実行時に追加または変更されたスクリプトを実行できます。

JavaでGroovyスクリプトを実行する例を考えてみましょう。 JavaプロジェクトでGroovyをサポートするには、必要なバージョンのgroovyライブラリを1つだけ接続する必要があります。

x:\ GroovyScript.groovyファイルに次のGroovyコードを記述します。
println 「Groovyスクリプト」
multi = {
num1、num2- > num1 * num2
}
マルチ 4、4


Javaコードでこのスクリプトを実行するためのコードは次のとおりです。

GroovyShell shell = new GroovyShell ;
オブジェクトの結果=シェル。 評価 new File "x:/GroovyScript.groovy" ;
システム アウトprintln "result =" + result ;


実行の結果として、2行がコンソールに表示されます。1行目はスクリプトから、2行目はJavaから、スクリプトの結果とともに:

Groovy script
result=16


この例では、スクリプトに機能的な負荷はありませんが、動的な読み込みと実行の可能性を示しています。コードは2行のみであり、スクリプトを実行することができます。

Javaコードについて少し。 GroovyShellは、groovyスクリプトを実行するためにGroovyが提供するクラスです。 groovyスクリプトを実行する方法は他にもあります。詳細はこちらをご覧ください。

DSL

DSLは、ドメイン固有の言語またはドメイン固有の言語です。 ユーザーから実装を隠すシンプルで理解しやすい高レベル関数のセットを通じて、サブジェクト領域の基本操作を使用できるようにする言語。

上記の例では、コードは非常に単純ですが、実際のシナリオでは、非常に大きく複雑になる可能性があります。 そして、このようなスクリプトを使用できるのはgroovy開発者だけです;テストせずにエラーを回避することは困難です。 スクリプトでの既知の操作の場合、すべてのビジネスロジックをコード(javaまたはgroovy-重要ではありません)に入れることができ、一連の関数を通じて使用することができます。

小さな例を考えてみましょう。 アーカイブ、アーカイブの展開、削除、いくつかのチェックと通知を実行するスクリプトを記述する必要があります。
スクリプトの一部は次のようになります。プロセスのステータスを確認し、完了した場合は、ディレクトリをアーカイブして通知を送信します。

//インポート
...
//状態を確認します
プロセス p = getProcess ...
int state = p。 getCompleteState ...
if state == 1 {
// doSomeLogicForArchive
Zip z = 新しい Zip ...
z。 makeZip ...
} else {
// doAnotherLogic
帰る
}
// doSomeLogicForSendNotify
smtp smtp = 新しい smtp ...
メッセージm = 新しいメッセージ ...
smtp。 送信 to、m ...


コードは非常に大きいことが判明し、主にプログラマーによってのみ理解されます。 単純化して、示された3つのアクションを静的メソッドを使用してArchiveScriptクラスに入れましょう。 メソッドを作成した後のスクリプト:

ArchiveScriptを インポートする
if ArchiveScript。checkState {
ArchiveScript makeArchive ..
} else {
// doAnotherLogic
帰る
}
ArchiveScript sendNotify ...


もういい? 「より良いことですが、アーティファクトはまだあります。インポートとクラス名も削除する価値があります。」 また、Groovyでも同様の可能性があります。スクリプト自体の外部でスクリプトの基本クラスを設定する機能です。 ArchiveScriptクラスはこのためにスクリプトから継承する必要があり、メソッドは静的ではない場合があります。 スクリプトコードはまだ単純化されています-インポートとクラスプレフィックスは表示されません:

if checkState {
makeArchive ..
} else {
// doAnotherLogic
戻る
}
sendNotify ...


すでに十分です。 条件分岐ブロック内のコードが単一行の場合、中括弧を拒否できます。 また、Groovyの場合、多くの場合、メソッド名の右側に括弧が付いています。 スクリプト実行コードは少し複雑です-CompilerConfigurationオブジェクトを作成し、作成したArchiveScriptクラスの名前と同じscriptBaseClass値を設定し、このオブジェクトをGroovyShellに渡す必要があります。

CompilerConfiguration conf = new CompilerConfiguration ;
conf。 setScriptBaseClass "package.ArchiveScript" ;
GroovyShell shell = new GroovyShell conf ;


次に、呼び出されたときにスクリプト内のメソッドのパラメーターがどのように設定されるかを見てみましょう。 makeArchiveメソッドがArchiveScriptクラスで次のように定義されている場合:

def makeArchive sourcePath、destPath、deleteSource


スクリプトでは、呼び出しは次のようになります。

makeArchive "x:/ aaa /""x:/a.zip"true

//または

makeArchive "x:/ aaa /""x:/a.zip"true


可視性と利便性についても説明すると、Groovyでは、次のように名前付きパラメーターを使用してパラメーターを転送できます。

makeArchive sourcePath: 'x:/ aaa /'
destPath: 'x:/a.zip'
deleteSource: true


ただし、この場合、パラメーターはHashMap内で渡されるため、ArchiveScriptクラスのmakeArchiveでパラメーターを取得するには、次のようにします。

def makeArchive params {
makeArchiveInternalパラメーター。 sourcePath 、params。 destPathパラメーターdeleteSource
}


変換を他の呼び出しに適用すると、最終的にスクリプトは次のようになります。

if checkState 'SomeData' {
makeArchive sourcePath: 'c:/ 1 /*.*'
destPath: 'c:/testarch.zip'
deleteSource: true
} else {
// doAnotherLogic
戻る
}
sendNotify to: 'aaa@gdsl.ru' 、コンテンツ: 'message'


そして、これは複雑すぎず、読みやすいコードではありません。

したがって、タスクに固有のいくつかの定義済み機能を備えたmini-DSLを取得しました。 また、ソース言語の全機能を使用する機会もまだあります。

私はDSLの開発のほんの一部しか考慮しなかったことに注意します。 Groovyは、DSLの開発、 EclipseおよびIntelliJ Ideaの DSLサポートさらにサポートしています。

テスト中

スクリプトのテストについて少しお話したいと思います。 スクリプトがどれほど単純であっても、エラーが発生する可能性があります。 IDEでスクリプトを記述しても、完全な構文チェックを受け取れない場合があります。 これは、その実装でのみ可能です。 スクリプトの動作を確認することも必要です。
スクリプトをテストするときに実際のアクションを実行したくないため、何らかの方法で実際のロジックを模倣に置き換える必要があります。 Groovyでは、これをさまざまな方法で行うことができます。 それらのいくつかを紹介します。

基本スクリプトの置き換え

ArchiveScriptに似たインターフェイスを持ち、必要な動作を実装する(または何もしない)新しいクラスArchiveSciptMockを作成します。 CompilerConfiguration構成オブジェクトを作成するとき、元のオブジェクトの代わりにその名前を渡します。

CompilerConfiguration conf = new CompilerConfiguration ;
conf。 setScriptBaseClass "package.ArchiveScriptMock" ;


スクリプトの基本クラスのメソッドを置き換える

追加のモッククラスを作成しない別のオプションは、ArchiveScript自体のメソッドをモックに置き換えることです。 groovyでは、これは、たとえば次の方法で実行できます。

ArchiveScript metaClass{
checkState { t- > true }
makeArchive { params- > }
sendNotify { params- > }
}
runScript


1つ目と2つ目の方法の欠点は、送信されたパラメーターの正確性を検証するために重複したロジックを記述する必要があることです。 ArchiveScriptMockの場合、makeArchiveメソッドは次のようになるためです。

def makeArchive params {
// makeArchiveInternal params.sourcePath、params.destPath、params.deleteSource
}


その後、すべてのパラメーターが渡されたかどうかをチェックしません。 次のようなものを書く必要があります。

def makeArchive params {
makeArchiveInternalMockパラメーター。 sourcePath 、params。 destPathパラメーターdeleteSource
}


ArchiveScriptを少しリファクタリングして、ArchiveScriptをファサードにし、すべてのロジックを別のクラスに転送することをお勧めします。 たとえば、JavaではArchiveクラス。
リファクタリングは、テスト目的だけでなく、他の理由、たとえば、動作を実行メソッドから分離するためのものです(スクリプトに依存しません)。 その結果、変更後のArchiveScriptは次のようになります。

抽象 クラス ArchiveScript Script {
アーカイブアーク= 新しいアーカイブ
def makeArchive params {
アーク。 アーカイブパラメータ。 sourcePath 、params。 destPathパラメーターdeleteSource
}
...


これで、ロジックとスクリプトを個別にテストできます。 Archiveをモックに置き換えて、スクリプトを実行します。

//モックメソッドを定義します
def mockedMethods = {
checkState { 文字列-> true }
makeArchive { 文字列 sourcePath、 文字列 destPath、 ブール値 deleteSource- > }
sendNotify { 文字列へ、 文字列コンテンツ-> }
}

//このようなスクリプトを置き換えて実行します
アーカイブ metaClasswith mockedMethods
runScript

//または
StubForスタブ= new StubFor アーカイブ ;
スタブ。 需要 {
mockedMethods
}
スタブ。 使用 {
runScript
}


当然、Archiveの動作はJavaモックフレームワークに置き換えることができますが、現時点ではこれで十分です。

まとめ


テキストの冒頭で指摘された欠陥がなく、非常に使いやすい、かなり柔軟なスクリプトツールを手に入れたと思います。 スクリプトの正確さの制御も失われませんでした-モック動作を使用することで、実際に実行する前に十分にテストすることができます。

プロジェクトのソースコードを含むプロジェクトはgroovydslです。 ラッパーを介してコンパイルされたGradle。

いくつかのアイデアは、Groovy for Domain-Specific Languiages、Fergal Dearle、2010

お楽しみください!

Source: https://habr.com/ru/post/J145138/


All Articles