Abstract

ソフトウェア開発の分野でモデルベース設計が広く用いられるに従って,上流の概念設計だけでなく,中流のソースコードや下流のテストコードまでを自動生成することが一般的になってきました。

このような開発プロセス全体にモデルが影響する場合には,上流から下流までの各段階でモデル変換を行いながら,詳細化,ならびにソースコード等成果物の出力を行います。 このようなモデルを軸とする開発の流れはモデリングワークフローと呼ばれます。 このようなモデル変換のフローを制御するソフトウェアはワークフローエンジンと呼ばれます。

従来,ワークフローエンジンは,Java や Ruby など汎用のプログラミング言語を用いて記述されてきました。 しかしながら,汎用プログラミング言語では,その記述の自由度が裏目に出てしまい,再利用性や記述の容易性が損なわれてしまいます。

そこで,大規模なモデルワークフローにおいては,独自の言語(DSL - Domain Specific Language)を定義し,再利用性や記述の容易性を担保しようという取組みが進められています。

本稿では,大規模なモデルワークフローを前提としている仕様である AUTOSAR を題材に,そのモデルワークフローエンジンを Pure Java で記述している実装例を分析し,課題を抽出します。 また,モデルワークフロー記述のための MWE2 言語を用いることで,課題が適切に解決されうることを示します。

前提

AUTOSAR の特徴

AUTOSAR は,欧州の自動車関連企業が中心となって仕様策定を進めている,車載ソフトウェアのための標準仕様です。AUTOSARは,下記のような特徴を持っています。

  • (補助的な外部モデルは存在するものの,概ね) 単一のメタモデルにより成立します。
  • OEM の設計から始まる V 字開発フローの大半が,メタモデルの中でモデル変換のワークフローとして形式化できます。

モデリングワークフロー構築時の留意点

モデリングワークフローは,開発プロセスの全体を串刺しするように用いられます。 開発プロセスの各段階で,特定の箇所が別の箇所の開発進捗を阻害するようなことがあると,プロセス全体が停滞するダウンリスクがあります。 そこで,ワークフローエンジンは,下記のような点に注意しなければなりません。

留意点A) ワークフローのコピーは,可能な限り減らすべきです。あるワークフローが変更された時の,他のワークフローへの影響分析コストが上昇します。

留意点B) ワークフローは分岐を可能な限り減らすべきです。分岐の多さ,すなわち高い循環的複雑度はテストケースの増大を招きます。

留意点C) ワークフローの各ワークノードが参照するリソースのスコープは,適切に設定されるべきです。ワークノード間の参照透明性が低いと,ワークフロー変更時の影響分析コストが上昇します。

留意点D) ワークフローエンジン,エンジンが処理するワークフローの実装,含まれるワークノードの実装は,それぞれ独立であるべきです。これらを分割統治することで,並列開発が可能となり,開発の規模に応じて開発者数をスケールさせることが可能となります。

Pure Java 実装例の分析

Pure Java での AUTOSAR 対応のモデリングワークフローエンジンの例示として,本稿では A-RTEGEN を用いることとします。

A-RTEGEN は名古屋大学大学院情報科学研究科付属 組込みシステム研究センター(NCES)が中心となって開発しており,ソースコードが公開されています。

A-RTEGEN の内部構造のうち,モデルワークフローを管理している部分のみを抽出すると,概ね図の通りになります。

GENERATE, CONTRACT 各フェーズの具体的なソースコードは https://github.com/PizzaFactory/a_rtegen/blob/1.3.0/src/jp.ac.nagoya_u.is.nces.a_rte.app/src/jp/ac/nagoya_u/is/nces/a_rte/app/internal/GeneratePhaseRteGenerator.java および https://github.com/PizzaFactory/a_rtegen/blob/1.3.0/src/jp.ac.nagoya_u.is.nces.a_rte.app/src/jp/ac/nagoya_u/is/nces/a_rte/app/internal/ContractPhaseRteGenerator.java にあります。

これらのコードは,先に一般論で挙げた留意点をことごとく満たしません。

留意点Aについて

まず,留意点A について,ソースコードの目視,また上掲の図を見ても理解可能なように CONTRACT / GENERATE の多くの箇所でソースクローンが存在しており満たせません。 クローン部分のみのテストが不可能な状態で,信頼性の担保が難しい状態になっています。

留意点Bについて

留意点B については,return 文による分岐,例外脱出による分岐,if 文による分岐が混合しており,また関数呼び出しのネストも存在します。 これらは Java 言語での記述としては自然です。しかし,ワークフローを Java で記述するという最上流の判断が間違えているといえます。

留意点Cについて

留意点C については,ソースコードのコンストラクタをご覧いただくと,満たせていないことが解ります。 使用するほぼすべてのオブジェクトがコンストラクタで生成されており,クラス内の処理から参照/変更が可能となっています。 車載ソフトウェアでもグローバル変数の使用は問題視されますが,同様の問題が,このコードには存在しています。

なお,使用するオブジェクトをコンストラクタで用意するのは,Java 言語での設計戦略としては悪くはありません。依存性注入のテクニックとしてコンストラクタインジェクションという技法があり,Java 言語の世界,特にエンタープライズ分野では広く使われています。 留意点Cを満たせない理由は,Java の単一クラスでワークフローを記述しようとした最上流の設計の失敗といえます。

留意点Dについて

留意点D については,A-RTEGEN が単一のアプリケーションとして提供されていることから,明らかに満たしていません。

MWE2 にすると何が変わるのか?

MWE2 は,最も狭義には,ワークフローエンジンです。広めに捉えると,ワークフローエンジンへの入力となる言語と,各ワークノードを実装するための API のセットと言えます。

A-RTEGEN の CONTRACT フェーズと等価な MWE2 言語記述は,下記のようになります。(イメージです。この記述は厳密には正しくなく,MWE2 は処理できません)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var diagnostics = BasicDiagnostic {}
var loader = AutosarModelLoader {}
var GeneratorInitOptions initOptions 

/** 
 * CONTRACTフェーズ向けのRTEを生成する。
 */
Workflow {
	component = SystemOutPrintln {
		message = "Checking input AUTOSAR XMLs..."
	}
	component = InitResource {}
	component = LoadM2 {
		loader = loader
	}
	component = ValidateM2 {
		rteValidatorM2 = ModelValidator.forRteContractPhaseM2
		bswmValidatorM2 = ModelValidator.forBswmContractPhaseM2
		commonValidatorM2 = ModelValidator.forCommonContractPhaseM2
		diagnostics = diagnostics
	}
	component = LoadInstance {
		loader = loader
	}
	component = ValidateInstance {
		rteValidatorInstance = ModelValidator.forRteContractPhaseInstance
		bswmValidatorInstance = ModelValidator.forBswmContractPhaseInstance
		commonValidatorInstance = ModelValidator.forCommonContractPhaseInstance
		diagnostics = diagnostics
	}
	component = CheckDiagnostics {
		diagnostics = diagnostics
	}
	component = PrintPreBuildMessage {}
	component = BuildModuleModel {
		moduleModelBuilder = RteModuleModelBuilder {}
	}
	component = GenerateRte {
		codeGenerator = RteCodeGenerator {
			codeFormatter = UncrustifyCodeFormatter {
				executableFile = generatorInitOptions.uncrustifyExecutableFile
				configFile = generatorInitOptions.uncrustifyConfigFile
			}
		}
	}
	component = SystemOutPrintln {
		message = "Generation done." 
	}
	component = DumpModel {
		ignoreAbort = true
		modelFileName = "modeldump.xmi"
		serializer = ModelSerializer {}
	}
}

この記述には,component(ワークノード)の実行順序と,実行に際し必要となるオブジェクトの値設定が含まれます。

LoadInstance, ValidateInstance 等は,ワークノードです。これらは MWE2 自身では提供されず,MWE2 が提供する API を基に,実装する必要があります。 たとえば,LoadInstance は下記のように記述されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package jp.ac.nagoya_u.is.nces.a_rte.app.internal.workflow.contract

import org.eclipse.emf.mwe2.runtime.workflow.IWorkflowContext
import org.eclipse.xtend.lib.annotations.Accessors
import jp.ac.nagoya_u.is.nces.a_rte.persist.AutosarModelLoader
import jp.ac.nagoya_u.is.nces.a_rte.app.internal.workflow.ARteBuildAction

/**
 * AUTOSAR Instanceモデルの読み込み
 */
class LoadInstance extends ARteBuildAction {
	@Accessors var AutosarModelLoader loader

	override action(IWorkflowContext ctx) {
		this.loader.loadInstance(ctx.resource)
	}	
}

最終的には汎用言語で処理を記述する必要はありますが,処理の粒度を細かくすることで再利用性が高まります。 またワークフローの作成者とワークノードの作成者が分離することで,留意点Dが解決されます。

MWE2 のソースコードに戻ります。MWE2 スクリプトの初めに幾つかの大域変数が定義されていますが,これらは component から直接に参照することはできず,中括弧内で代入されることでのみ可視になります。 つまり留意点Cが解決されています。

MWE2 スクリプトには,例外脱出の処理記述がありません。これではエラー中断ができないように思われるかもしれません。 この問題は component に一枚の薄皮を被せることで解決可能です。

1
2
3
4
5
6
7
8
9
10
11
12
abstract class AutosarBuildAction implements IWorkflowComponent {
	static val ABORT_RETURN = "a-rtegen.abortReturn"
	@Accessors var boolean ignoreAbort = false
 
	/**
	 * Use `action` method instead of this.
	 */
	override final invoke(IWorkflowContext ctx) {
		if (ctx.get(ABORT_RETURN) == null || ignoreAbort) {
			action(ctx)
		}
	}

ワークノード中で例外が発生した場合,以降のすべてのワークノードは実行されない状態にし,かつエラー処理を行いたい場合は MWE2 ファイルに ignoreAbort=true と明示することで,強制的にワークノード内処理を行うようにします。 分岐のないワークフローであればこれで十分です。留意点 B が解決されます。

残るは 留意点 A についてです。 MWE2 を採用しても,CONTRACT / GENERATE それぞれのフェーズは,似てはいますが微妙に違います。つまり,似通っている 2つの mwe2 ファイルが必要になります。これは AUTOSAR 仕様に依存する事柄なのですが,同様のことは他のモデリングワークフローでも起こり得ることです。 ここは MWE2 のみを採用している範囲では解決できません。解決の方法は2通りありえます。

  • MWE2 に制御構文に相当する機能を付加する Viatra というツールを,Eclipse プロジェクトからダウンロードします。
  • CONTRACT / GENERAGE の両方の情報を内包するモデルから MWE2 を生成するようにします。

どちらかができたならば,留意点 A も解決します。

もし読者が最新版の AUTOSAR 仕様の愛読者であったならば,後者の方法でこの問題を解決する方法が暗に仕様記述されていることに気づくでしょう。

ここまでで,留意点AからDまですべての課題が解決されました。

まとめ

近年開発プロセスに浸透してきているモデリングワークフローを支えるツールを作成する際に,Pure Java で作成する場合の課題を実例を基に紹介し,ワークフロー記述用の DSL である MWE2 を用いることで課題を解消できることを紹介いたしました。

A-RTEGEN の実装者各位の名誉のために申し添えますが,Java アプリケーションとしての A-RTEGEN の品質は悪くはありません。目視によるレビューや静的解析ツールの結果も,それを裏付けています。 しかしながら,「Pure Java アプリケーションとしてワークフローを実装する」という最上流の方針決定に失敗しているとは言えます。 これは他の言語(Ruby を使った言語内 DSL など)を選んだとしても,汎用言語である限り同じ結果になりがちです。