私たちは何について話しているのですか
この投稿では、アプリケーション開発について抽象的に推測したいと思います。 最初はコード生成についてだけ書くことを考えていましたが、このトピックを熟考するうちに、共有したいこともたくさんありました。 したがって、DSLよりも少し広いことが判明しました。
DSL(ドメイン固有言語)とコード生成とは
DSLはドメイン固有の言語です。 つまり これは、特定の領域の概念に直接作用する言語です。 通常、汎用言語とは対照的です。 原則として、言語がコンピューターで解釈できない単なる形式的な構文であることを妨げるものは何もありませんが、そのような言語の使用はあまりありません。 通常、コンピューター言語は何らかの方法で処理を行うため、DSL用の何らかのインタープリターがあれば便利です。 したがって、2つの標準的なアプローチ-解釈とコンパイルがあります。 解釈では多かれ少なかれ明確ですが、編集では次の話があります。 もちろん、すぐにプロセッサ命令に変換することも、最悪の場合はアセンブラに変換することもできますが、なぜ高レベル言語をテキストにコンパイルするという意味で通常のコードを「書く」ことができるのであれば、コンパイラによってコンピューターで実行されないものに変換されます。 したがって、彼らはしばしばコンパイルではなく「コード生成」と言いますが、後者の用語も正しいので使用されます。
労働生産性
アプリケーション開発を行う場合、主な問題はパフォーマンスが低い、つまり 費やされた努力の「製品数量」。 原則として、同様の問題はすべての産業で見られ、一般的な解決策と特定の解決策の両方があります。 この非常に生産性を高めるために、高レベル言語、強力なIDE、継続的な統合ツール、スクラム、キャンバン、コーヒーポイント、コーヒーレディースなど、さまざまなものがあります。 それでも、製品開発には多くの時間がかかります。 これは、実行する必要があるものを数分で簡単に言葉で説明でき、実行に数週間かかる場合に特に顕著です。 「何」と「方法」の間に大きなギャップがあります。 「何をすべきか」は単純明快であり、「どのようにすべきか」は単純明快ですが、長い間です。 「方法」を迅速に行いたいが、理想的ではない。 要するに、宣言的アプローチ。
抽象化のレベル
非常に便利な概念があります-抽象化のレベル。 アプリケーションの構造化に役立ちます。 特定のサブジェクトエリアのアプリケーションがあるとします。 一方で(上記)このサブジェクトエリアからの概念がアプリケーションに何らかの形で表示され、もう一方では汎用プログラミング言語(以下)があり、そこにはバイト、型、メソッドなど何もない要素がありますサブジェクトエリアと共通です(オペレーティングシステム、電気的インパルス、トランジスタ、分子、原子、陽子、クォークなどには行きません)。 プログラマーの仕事は、これらの2つのレイヤーを正確にリンクするか、画像(左の画像)の領域を埋めることです。 アプリケーションが大きく、ドメインドメインが十分に「遠い」場合、アプリケーションにさまざまな中間レベルの抽象化が表示されます。そうでない場合、複雑さに対処できません(右図)。
レベルはもちろん発生しますが、論理的に発生します。 また、コードがレベルもサポートするようにするための努力が必要です。 言語が同じで、すべてが同じプロセスで実行されている場合、これは特に困難です。 結局のところ、レベル1からレベル3のメソッドを呼び出すことを妨げるものは何もありません。はい、そして通常、関数またはクラスは抽象化のレベルによってマークされません。 コドゲンを含むDSLはこれについて何を提供しますか? 同じエリアに入力する必要があります。 したがって、上の部分は私たちの言語で埋められ、下の部分は生成されたコードで埋められます。
前の例とは異なり、ここのレベルは突き通せません。 生成されたコードからDSL命令を呼び出すことはできません(特に存在しない場合)。 ジェネレーターが同じDSLでコードを作成する場合は考慮しません...ここでのもう1つの重要な点は、生成されたコードは、自動的に作成され、調べる必要がないという意味で、コンパイル済みと見なせることです。 ジェネレーターが既に作成されている(および十分にテストされている)場合。 つまり 言語とそのためのジェネレータを記述すると、アプリケーションの範囲が大幅に狭まる可能性があります。 これは、この分野で複数のアプリケーションを開発する場合、または絶えず変更する場合に特に役立ちます。
合併症管理
私には、非常に一般的な状況を想像してみましょう。 何らかのシステムを開発する注文を受け取ったとします。 理想的な仕様があなたにもたらされ、あなたはすべてが素晴らしい、コンポーネント、インターフェースである理想的なシステムアーキテクチャを思いつきます。 カプセル化および他の多くの同様に美しいパターン。 具体例を挙げましょう-オンライン自転車店。 あなたはオンラインストアの仕様に従って書いており、誰もが幸せです。 店は繁栄しており、ビジネスの拡大、つまりスクーターやバイクの取引を開始することを考えています。 そして、彼らはあなたのところに来て、あなたに店を変更するよう頼みます。 あなたは自転車で研ぎ澄まされた美しい建築物を持っていましたが、今は引きずる必要があります。 一方では、スクーターとオートバイは自転車に似ており、どちらもスペアパーツ、アクセサリー、関連製品を持っていますが、違いもあります。
システム全体は同じままですが、機能の一部は新しいタイプのオブジェクトをサポートする必要があります。または、新しいタイプのオブジェクト用の個別の機能が表示される必要があります。
ドメインドメインはより複雑になりました。 自転車だけでなく、自転車、スクーター、オートバイをサポートする必要があります。 システムも複雑でなければなりません。 一般に、ソフトウェアシステムの複雑さは、シミュレートされたシステムの複雑さに対応すると思います。 この場合、問題を解決することは依然として可能な最低レベルの複雑さです。 (トップレベルはありません-どんな問題に対しても無限に複雑なソリューションを思いつくことができます)。 すべての可能な解決策のうち、最も単純なものが最善であるため、最小限のレベルの複雑さで努力する必要があると思います。 要するに、コードはシンプルでなければなりません。
オンラインストアに戻ります。 自転車用に書かれたいくつかの機能があるとします。 これで、新しいタイプでも機能するはずです。
public void process(Bicycle b) {
genericCode
specificForBicycle
}
そのためには、内部に特定のForMotobikeコードが必要です。 ソリューションのオプションは何ですか?
コピー/貼り付け
public void process(Motobike b) {
genericCode
specificForMotobike
}
メソッドをコピーし、型固有のコードを置き換えただけです。 シンプルですが、問題があります。 genericCodeを変更する必要がある場合は、複数の場所で同じものを変更する必要がありますが、今回はエラー...
if / else
public void process(Object b) {
genericCode
if(b instanceof Bicycle) {
specificForBicycle
} else if(b instanceof Motobike) {
specificForMotobike
}
}
条件を設定すれば完了です。 コピー/貼り付けよりも少し良いですが、やはり問題があります。 そして明日、彼らはATVを販売したいと思うでしょう、そして、彼らはコードを通してそのような部分を探して、別のものを追加しなければなりません。
抽象メソッド
abstract void specific()
public void process(Vehicle b) {
genericCode
b.specific()
}
この時点で、各タイプに実装されている抽象メソッドが呼び出されます。 原則として、これは許容可能なオプションであることが判明するか、システムを著しく複雑にする可能性があります。 どのメソッドが呼び出されているかを把握するのが容易でない場合、多数のオーバーライドされたメソッドを含む複数階層の継承階層は珍しくありません。
DSLおよびコード生成
DSLは、すべてのタイプの機能を記述できるように設計されています。 テンプレートはコードジェネレーターで記述され、タイプの説明に適用され、コピー/貼り付けのようにコードが取得されます
テンプレート:
public void process("TYPE" b) {
genericCode
"SPECIFIC CODE"
}
DSL:
type Bicycle:
property A, ( description, value, links ...)
type Motobile:
property B,
property C,
さらに、DSLのタイプごとに、テンプレートは特定のコードに変換されます。 私の経験から、変更せずに新しいエンティティをサポートする言語をすぐに書くことは困難ですが、通常、言語とジェネレーターの変更は小さくて簡単です。 一般的に、アプローチは次のとおりです。読みやすく理解しやすい多くの単純なコードが生成されます。ファイルが多く、数千行に及ぶことは問題ではありません。 結局のところ、これはあなたの手で書いているのではありません。
最初のDSLまたは正式な仕様
ここで私は最も重要な事柄に行きます。 (その前に紹介がありました:)プロジェクトを開始するプロセスは通常どのように見えますか? 仕様が記述され、図が描かれ、アーキテクチャ、プロジェクトのステージが完成します。 そして、それがすべて完了すると、彼らはコードを書き始めます。 仕様は自由形式のドキュメントです。 仕様が形式化されないのはなぜですか? 私の主なアイデアは、最初にドメインドメインの観点からシステム記述言語を開発することです。 これは、一部はアーキテクチャの説明であり、一部は正式な仕様になります。 この場合、顧客は対象分野の条件で直接操作するため、言語を理解し、システムの開発に参加することもできます。 もちろん、アイデアは私のものではありません。 文献では、このアプローチはドメイン駆動設計(DDD)と呼ばれています。 私は、DDDアプローチがDSLおよびコード生成でうまく機能すると主張しています。
形式化とは、自動処理の可能性を意味します。 一貫性、一貫性のためにさまざまなチェックを追加できます。 一方、システム開発者は、あるべきことをすぐに正式に宣言します。 これらの同じコードジェネレーター
として 、コンバーター
を作業システム
として記述する必要があります。
すべてがスムーズではない
もちろん、すべてがそれほど単純でスムーズなわけではありません。 他のアプローチと同様に、問題と欠点があります。
- 何を生成するかは必ずしも明確ではありません。 最終的なシステムを想像する必要があります。 結局のところ、すべてのコードが生成されるわけではないので、何が生成され、何が手作業で記述され、どのようにすべてが連携するかを理解する必要があります。 最初にすべてを手動で記述し(将来の世代を念頭に置いて)、次にコードの一部をテンプレートとジェネレーターに引き出す方が簡単な場合があります。
- 2番目の問題は、生成コードと手動コードのバランスです。 実際にパラメーター化されておらず、常に同じであるテンプレートにコードを配置することは意味がありません。 上記の例のアプローチを同時に使用するのは悪い習慣です。
- 手動コードと生成コードの依存関係。 DSLを変更するときに手動でコードを中断する必要はありません。 (DSLのテキスト)
- コード生成による脳への「ダメージ」。 コードジェネレーターの作成は、通常のプログラムの作成とは多少異なります。 「間違った」スタイルを使用すると、「あまりよくない」コードを書くことになります。 レビューと「健康な」同僚を保存します。
- 私が遭遇した別のポイントは、顧客に正しいアプローチを納得させるのが難しいことです。 なんとかして、彼らはそれを何とかしてやったし、それから私たちは普通に生きて、あなたはあなたのアイデアでここにいる。 とにかく、昨日あなたがすることになっていたスクーターのサポートはどこにありますか? 仕事に行きます。
- DSL開発者の仕事を見たことがありますか? しかし、ここでは、おそらく、Haskellプログラマーを取得するのと同じです。 Javaプログラマー(C ++、Perl、Pythonなど)としてセットアップします。
Haskell DSLをクールに。 そして今、あなたはDSL開発者です。
DSLを開発し、コードジェネレーターを作成するためのツール
それまでに書いたものはすべて、通常の開発ツールがなければ実用的な意味はほとんどありません。 幸いなことに、そのような救済策があります。 手段は異なりますが、私の選択はEclipse Xtextです。 xtextの最も重要なことは、Eclipse IDEへの統合です。つまり、構文の強調表示、エラーと警告、コンテンツアシスト、クイックフィックスなど、すべての標準プロパティがあります。 これは、「箱から出してすぐに」と呼ばれるものです。 そして、どんなファンタジーでも十分です。 興味があれば、このトピックについてより実用的な投稿を行うと思います。
おわりに
私はアメリカを発見しなかったと思います。 私が書いたものの多くは当たり前です。 しかし、一方で、DSLとコード生成のトピックは十分に開示されていないと思うので、啓発に手を出すことにしました。 そして、彼らはEclipse Xtextについてあまり聞いていない、それを使ってはいない。