
シェア:
シナはVonageのJavaデベロッパー・アドボケイト。アカデミックなバックグラウンドを持ち、自動車、コンピューター、プログラミング、テクノロジー、人間性など、あらゆることに好奇心旺盛。余暇には散歩をしたり、対戦型ビデオゲームをしたりしている。
モデル駆動エンジニアリングで生産性を高める(第3回)
はじめに
モデル・ドリブン・エンジニアリングのシリーズ最終回へようこそ。その パート2に新しいAPIのサポートを追加する際に、これらの技術をどのように使って多くの時間を節約したかを説明した。 Vonage Java SDK.この記事では、この技術とアプローチを最大限に活用できるように、学んだ教訓に基づくいくつかの重要な原則を紹介します。
自分の "なぜ "を知る
これ以上先に進む前に、まず念頭に置くべきことは、これがあなたのユースケースに適しているかどうかを確認することである。諺にあるように、「ハンマーしかないときは、すべてが釘に見える」。テクノロジーに流行があるのは自然なことで、これは「ガートナー・ハイプ・サイクル」として知られている。 「ガートナーのハイプ・サイクル".ある種の技術や開発方法論は魅力的に見えるが、結局は道具である。多様なツールボックスを持つことは、仕事に適したツールやアプローチを選択できるという意味で素晴らしいことだ。あるユースケースに適したものが、別のケースに適しているとは限らない。では、MDEはどのような場合に適しており、どのような場合に適していないのでしょうか?以下は、検討すべきいくつかの質問です。
ドメインはモデル化しやすいか?
すべての課題が、定義しやすいメタモデルにきれいに収まるわけではありません。多くの場合、ドメイン内の概念間の関係が予測可能なオブジェクト指向的なドメインの見方をすることは可能ですが、これらの概念の性質や、それらが互いにどのように関係するかさえも、あまりにも動的な場合があります。モデル駆動型アプローチを最大限に活用するためには、ドメインのメタモデルは少なくとも高い確実性で定義可能でなければならない。概念は、定義可能な属性と関係を持つクラスにマッピングされるべきである。定義すべきドメイン要素が多すぎ、関係が複雑すぎる場合、メタモデルにはそれが反映されます。このような場合、メタモデルやモデルを定義することはあまりにも大きな作業となり、直接実装することに比べてメリットを上回る可能性があります。
ジェネレーターはどの程度複雑なものになるのでしょうか?
ドメインがメタモデルを使用して明確に定義できる場合、これらの概念が生成したいコードやビジネスロジックにどのようにマッピングされるかを検討する必要もあります。ロジックが密である場合(すなわち、モデルへの依存度が高い場合)、自動生成はエラーの可能性を減らします。一方、ジェネレータのメンテナンスと作業が難しくなります。特に手書きのコードと比較した場合、これらのテンプレートを呼び出すための調整ロジックの全体的な複雑さと同様に、あなたが開発し、維持する必要があるテンプレートの数を尋ねることも価値があります。
手作業によるコーディングはどのくらい節約できるのか?
最も重要な、そしておそらく最も単純な疑問の1つは、ボイラープレートとロジックの比率です。モデル駆動型のテンプレートベースのコード生成アプローチは、生成されたコードがほとんど定型文であり、出力を最小限の修正、あるいはまったく修正することなくコードベースに貼り付けることができる場合、非常にうまく機能します。しかし、生成されたコードに手作業による大幅な追加コーディングが必要な場合、特に、新しいメソッドの追加などとは対照的に、生成されたコードを修正する場合、ジェネレーターの価値は低下します。なぜなら、ジェネレーターの開発とメンテナンスのオーバーヘッドや、生成されたコードを手作業でリファクタリングしたり修正したりすることは、コードのこれらの部分をゼロから書くことに比べて、開発者によっては負担になるからです。
使用頻度は?
モデル駆動型アプローチが実現可能であるとしても、つまり、ドメインがメタモデルにきちんと適合し、コード生成が有益であるとしても、それを一度しか使う予定がないのであれば、労力に見合わないかもしれない。もちろん、これは生成するコードの量など他の要因に依存します。大規模なプロジェクトであれば、1回限りであっても投資する価値があるかもしれません。大規模なプロジェクトであれば、1回限りでも投資する価値があるかもしれない。その他のケースでは、「節約」はささやかなものかもしれないが、頻繁に使用することで時間の経過とともに積み重なっていく。この質問について考える別の方法は、「いくつのモデルを持つか」です。チャンスは、作成するモデルが多ければ多いほど(そして、モデルを変更する頻度が高ければ高いほど)、ジェネレーターから得られる使用量は多くなります。
モデルのアップデートは簡単ですか?
前のポイントでは、より多くのモデルや頻繁に変更されるモデルを持つことで、コード生成アプローチの利点をより活用できることを述べました。しかし、モデルがこの演習のためだけに構築され、ビジネス要件や環境の変化を反映するために常に更新されなければならない場合、これは摩擦を生む可能性がある。例えば私の場合、API仕様に変更があればモデルを更新する必要がある。モデル更新の負担は、仕様の変更の程度に正比例する。モデルを急速に進化させる必要がある場合、以前に生成されたコードは無効となり、再度生成する必要があることを意味する。前のポイントで説明したように、生成されたコードの一部が手作業で修正された(あるいは修正する必要がある)かもしれないという事実と一緒に考えてみてください。
メタモデルは頻繁に変更する必要があるのか?
モデルは構造化されたデータであるため、時には急速に変化することも予想される。モデルの更新が自動化されていれば、これはほとんど問題なく、摩擦も比較的少ない。しかし、頻繁に変更すべきでないのはスキーマ(メタモデル)です。ドメインの概念が頻繁に変化し、安定したメタモデルに釘付けにすることが難しい場合、モデル駆動型アプローチに従うことがより難しくなります。理想的には、メタモデルはプロトタイピングの段階で修正されることがほとんどで、安定して「本番」(すなわち、アウトプットが使用可能)になったら修正されることはほとんどありません。メタモデルにフィールドを追加しても、それらはオプションである可能性があるため、通常は破壊的な変更にはなりませんが、クラス、リレーション、属性を削除すると、既存のモデルやワークフローが壊れる可能性があります(そして通常は壊れます)。
バグ修正の他に、ジェネレーターはどれくらいの頻度で変更されるのですか?
コード・ジェネレーター・テンプレートと調整ロジックの最初の開発中、多くのバグや漏れがあるのは自然なことです。いったんこれらがほとんど取り除かれ、生成された出力が望んだとおりになれば、ジェネレーターは安定していると考えることができます。しかし、この後でも、新しいビジネス要件、スタイルガイド、またはコード構造による 変更は避けられません。どのような理由であれ、安定したジェネレーターに変更を加えると、新たなバグが発生する リスクがあり、そのバグを取り除く必要があることを考慮する価値があります。これらは通常、出力コードを検査するときにのみ検出できるので、ジェネレーターが 頻繁な変更を受ける必要がなければ理想的です。なぜなら、ジェネレーターは実際に生成されるコードよりもテストしにくい(もしくはしやすい)ので、テンプレート化ロジックに反映される必要があるビジネス要件が急速に変化すると、さらなる摩擦が生じるからです。
あなたのチームはこれに賛同しているだろうか?
最後になりますが、モデル駆動型アプローチがユースケースに完璧に適合しているように見えても、上司や同僚がこのワークフローに対応することに消極的であれば、成功する可能性は低いでしょう。結局のところ、メタモデル、ジェネレーター、モデルを構築し、それらを維持することは、複雑さを増し、使用されるテクノロジーを理解することを要求する追加的な負担となります。組織的な要因も一役買います。例えば、モデルがどこから来るのか、誰がどのように作成または更新するのか、ジェネレーターの責任者は誰か、生成された出力を抽出して既存のコードベースに追加するためにどのようなプロセスを使用するのか、などです。コンプライアンスや監査は言うに及ばず、このアプローチに関して考慮すべき多くのロジスティックな課題がある!ボイラープレートの生成を助けるためにこのアプローチを使うが、それを使う全員が、生成されたコードをメインのコードベースにコミットする前に手作業で検査し、レビューすることを約束するのであれば、比較的摩擦は少ないはずだ。一方、これが自動化された開発プロセスの不可欠で必須の部分である場合は、より深い考えと十分な注意が必要だ。いずれにせよ、モデル、ジェネレーター、または結果の出力を使用する人々は、そのアプローチについて認識し、賛同する必要がある。
反復開発
上記のすべての質問について考え、そのアプローチがあなたのユースケースに適していると判断したとします。それをどのように構築していけばいいのでしょうか?これまでご紹介してきたことから、モデル駆動開発は「ウォーターフォール」プロセスであるという印象をお持ちかもしれません。メタモデルは安定しているべきであり、当然出発点であることは事実ですが、実際には、文献やツールに書かれているよりも柔軟性があります。
私の場合、確かに完璧なメタモデルから始めたわけではなく、今でもまだ改善の余地がかなりあります。例えば、モデルを作成する際に私が発見したことの1つは、それらを参照できるようにするために組み込みのJava型を作成しなければならないということですが、これらはメタモデルの一部である可能性があります。モデルの属性の多くは、特に以下のようなブール値属性です。 isRequest, isResponse, isHal, isQueryParamsなどのブール型属性は、ジェネレーターで必要になることが分かってから生まれたものです。追加的な変更は壊れるものではありません。通常は、オプショナルであるか、賢明なデフォルト値を持っている限り、新しい型や属性でメタモデルを拡張することができます。
モデル駆動開発もソフトウェア開発であることに変わりはないため、反復プロセスにより、情報が少ないときに先行投資しすぎるよりも、より早くフィードバックを得ることができます。したがって実際には、メタモデル、ジェネレーター、サンプルモデルを並行して開発することをお勧めします。そうすれば、ワークフローをエンドツーエンドで見ることができ、紙の上では「完成」しているように見えるものを設計した後ではなく、早い段階でメタモデルに欠けているパラメーターや生成されたテキストのエラーを発見することができます。
"ワンショット "か "純粋な "MDEか?
パート2で紹介したユースケースは、非常に「一発勝負」のアプローチだった。つまり、いったんコードが生成されたら、もうジェネレーターやモデルを使うことはない。例えば、APIの仕様が更新されたり、出力されたコードにバグが見つかったりしても、大きな問題や修正がない限り、モデルを更新してコードを再生成することはない。なぜなら、生成されたコードはすでにリファクタリングされ、進化しているからだ。結局のところ、アウトプットこそが私にとって重要であり、メンテナンスされるものなのだ。その意味で、私はMDEを "本来 "やるべきこととして実践していたわけではない。生成されたコードはビルドするための出発点であって、完成品ではない。したがって、精神的にはモデル・ドリブンではないと主張する人もいるかもしれない。しかし、それが役に立たないという意味ではない。
伝統的なモデル駆動アプローチでは、コードベースとモデルが常に同期するように、モデルが更新されるたびにモデルからコードが再生成されるのが理想的だ。JVM言語(Java、Kotlin、Groovy、Scalaなど)でプログラムを書くとき、何をソース・コントロールにコミットしますか?何をメンテナンスする?何を気にする?それはソースコードだ。しかし、そのソースコードが実行時に重要なのではない。 .classファイル)である。Javaバイトコードにコンパイルされるのに、なぜ私たちはコードを気にするのでしょうか?なぜなら、バイトコードは 決定論的にソースコードから決定論的に導かれるからだ。これらの言語の主な資産はコンパイラーだと主張することもできる。ソース・コードがモデル(言語のメタモデルに準拠)であり、コンパイラーがジェネレーター、そしてバイトコードがジェネレーターからの出力だ。興味のある方は、以前このことについて書きました。).同様に、"純粋なMDE "で生成されたコードは、コンパイルされたコードと同じように扱われるべきです。ジェネレータテンプレートは、本質的にはMDEにおける「ソースコード」です。
モデルとソースコードの間の同期を手動で維持することは、摩擦や潜在的なエラーの原因になるため、先に提示した質問と並行してこのことを考えることは重要です。もしあなたが、いくつかの初期コードを得たいだけで、同じモデルでジェネレーターを再利用するつもりがないような、より実際的なアプローチを採用するのであれば、これはあまり心配することではありません。さらに、生成されたコードがあまり(あるいはまったく)手動で修正する必要がないとします。その場合、出力コードにエラーを発見したときにモデルやジェネレーターを更新し、最小限の手作業で再生成することができます。一方、生成後に出力コードを手作業で大幅に修正する必要がある場合、手作業による変更と生成されたコードを再度照合しなければならないので、モデルを更新して再生成する価値はおそらくないでしょう。
ゴミを入れる、ゴミを出す
それが次のポイントだ。このアプローチから得られるのは、そこに投入したものだけだ。これはモデルとジェネレーターの両方に当てはまります。モデルを構築するときに、必要でないフィールドを空白のままにして手を抜くと、生成されたコードは望ましい出力を持つことができません。私の例では、どうせ微調整が必要だろうと思い、テストに使用するドキュメントや値に手を抜いてしまうことがありました。しかし、より多くの労力を費やすと、より少ない修正で済むことがわかった。前述したように、追加機能が必要であったり、フォーマットのエラーがあったりする場合は、いつでもジェネレーター・テンプレートに追加したり、微調整したりすることができます。自分のモデルが正しく、自分のニーズに対して可能な限り完全であることを確認するために時間をかけることは、エラーを減らし、ジェネレーターを再実行する必要性を減らします。これは、ワンショットのアプローチや、生成されたコードが最初の生成の後に手動で編集される場合に特に重要です。
パレートの原則が適用される
80/20の法則 80/20ルールは、私の経験上、モデル駆動型コード生成に非常によく当てはまり、反復開発のケースを再強化する。20%の労力で80%の利益を得ることができる。単純なテンプレートから始めて、必要なクラスの基本的なスケルトンだけでも生成するのであれば、すでにコードのかなりの部分を書いていることになる。正しいパッケージの正しい名前のクラスがあり、コピーライトヘッダ、典型的なインポート、メソッド、フィールド宣言、コンストラクタなどがあります。そして、より複雑な手書きのロジックがジェネレーターに繰り返し含まれていることがわかれば、それを徐々に取り除くことができます。
一度に多くのことをやろうとしないこと。テストコードを生成するジェネレーターを書いて、アサーションを含むテストクラスとメソッドのスタブを作成するだけで、多くの時間を節約できました。そして、定型文を気にすることなく、重要なテストロジックを手書きすることに集中できる。また、ワークフローに構造を持たせることで、細かいことに追われることなく、何をすべきかが明確になります。これだけで、モチベーションと生産性がどれほど上がるか、驚くことだろう!
裏を返せば、コードベースの保守と進化の実用性とのバランスをとることが重要です。ジェネレーターやモデルよりも、生成されたコードを維持することにほとんど関心があるのなら、ジェネレーターを過剰にエンジニアリングしないでください。これは、ジェネレーターをより威圧的にし、一緒に働くことを難しくするので、他の人をオンボーディングすることが困難になります。経験則として、各ジェネレーター・テンプレートは、生成された出力と同じように見えるようにします。あなたのテンプレートが、静的なテキストとは対照的に、重い分岐ロジックを持つ動的なセクションで構成されているとします。その場合、おそらくそのロジックをユーティリティメソッドに分割するか、テンプレートをより読みやすくするために単純化する価値があります。
いずれにせよ、ここに「魔法」は存在しないことを心に留めておく価値があります。MDEワークフローとそのツールの作成と維持は、追加のオーバーヘッドです。MDEワークフローとそのツールの作成と維持は、追加的なオーバーヘッドとなります。そしてそれは、私たちを一周させて、そもそもの根拠を正当化することにつながる。
結論
これで、実用的なモデル駆動エンジニアリングに関するこのブログ・ミニシリーズは終わりです。新しいツールや開発アプローチを学んだだけでなく、その限界やこれらの技術を最大限に活用する方法についても知っていただけたと思います。最終的には、このモデル駆動エンジニアリングの紹介とケーススタディが有用な例となり、あなたがこのルートを選択した場合に、ソフトウェア・プロジェクトでより良い意思決定をするのに役立つことを願っています。
ご意見、ご感想がありましたら、お気軽にX(旧名 ツイッターまたは コミュニティ・スラック.この記事がお役に立てば幸いです。もしこの記事を楽しんでいただけたなら、私の他の Java記事.


