Javaのテンプレート「メタプログラミング」は良いアイデアですか?


29

非常にパフォーマンスに敏感ないくつかの関数(1秒あたり数百万回と呼ばれる)を持つかなり大きなプロジェクトにソースファイルがあります。実際、以前のメンテナーは、1つの関数で条件のチェックに費やす時間を節約するために、それぞれわずかに異なる関数のコピーを12個書くことにしました。

残念ながら、これはコードが維持するPITAであることを意味します。重複するコードをすべて削除し、テンプレートを1つだけ作成したいと思います。ただし、Java言語はテンプレートをサポートしていないため、ジェネリックがこれに適しているかどうかはわかりません。

私の現在の計画は、代わりに関数の12個のコピーを生成するファイル(実際には1回限りのテンプレート拡張機能)を書くことです。もちろん、ファイルをプログラムで生成する必要がある理由については、豊富な説明を提供します。

私の懸念は、これが将来のメンテナーの混乱につながり、変更後にファイルを再生成するのを忘れると厄介なバグを引き起こす可能性があることです。残念ながら、C ++ですべてを書き直すまで、これを修正する方法はありません。

このアプローチの利点は欠点を上回っていますか?代わりに:

  • パフォーマンスに打撃を与え、単一の保守可能な機能を使用します。
  • 関数を12回複製する必要がある理由の説明を追加し、保守の負担を丁寧に負担します。
  • ジェネリックをテンプレートとして使用することを試みます(それらはおそらくそのようには動作しません)。
  • 単一の関数にコードをパフォーマンスに依存させるという古いメンテナーに大声で叫ぶ。
  • パフォーマンスと保守性を維持する他の方法は?

PSプロジェクトの設計が貧弱であるため、関数のプロファイリングはかなりトリッキーです...しかし、前のメンテナーは、パフォーマンスの低下は許容できないと私に確信させました。これは彼が5%以上を意味すると思いますが、それは私の側の完全な推測です。


おそらく少し詳しく説明する必要があります。12個のコピーは非常によく似たタスクを実行しますが、わずかな違いがあります。違いは関数全体のさまざまな場所にあるため、残念ながら多くの条件文があります。事実上、6つの操作の「モード」と2つの操作の「パラダイム」があります(自分で作成した単語)。関数を使用するには、操作の「モード」と「パラダイム」を指定します。これは決して動的ではありません。コードの各部分は、厳密に1つのモードとパラダイムを使用します。12のモードパラダイムペアはすべて、アプリケーションのどこかで使用されます。これらの関数には、func1〜func12という適切な名前が付けられ、偶数が2番目のパラダイムを表し、奇数が最初のパラダイムを表します。

保守性が目標であれば、これは最悪の設計であることを認識しています。しかし、それは「十分に速い」と思われ、このコードはしばらく変更を必要としませんでした...元の関数が削除されていないことにも注意する価値があります(ただし、私が知る限り、それはデッドコードです) 、したがって、リファクタリングは簡単になります。


パフォーマンスヒットが12のバージョンの関数を保証するのに十分なほど深刻な場合は、それで行き詰まっている可能性があります。単一の機能にリファクタリングする(またはジェネリックを使用する)場合、パフォーマンスヒットは顧客やビジネスを失うほどひどくなりますか?
FrustratedWithFormsDesigner

12
次に、独自のテストを実行する必要があると思います(プロファイリングは難しいと言いましたが、システム全体の大まかな「ブラックボックス」パフォーマンステストを実行できますよね?)です。そして、それが注目に値する場合、コードジェネレーターにこだわっている可能性があると思います。
FrustratedWithFormsDesigner

1
これは、各関数を異なる名前で呼び出すのではなく、パラメトリック多型(またはジェネリック)の恩恵を受けているように思えます。
ロバートハーヴェイ14年

2
関数にfunc1 ... func12という名前を付けると、異常に思えます。少なくともmode1Par2など...またはmyFuncM3P2と名前を付けます。
user949300 14年

2
"プログラムで生成されたファイルを変更する場合" ...... Makefile(または使用するシステム)からビルドするときにのみファイルを作成し、コンパイルを終了してすぐに削除します。このようにして、彼らは間違ったソースファイルを変更する機会がありません
バクリウ14年

回答:


23

これは非常に悪い状況です。できるだけ早くリファクタリングする必要があります-これは最悪の技術的負債です- コードが実際にどれほど重要かさえわかりません-それが重要であると推測するだけです。

ソリューションについてできるだけ早く:
できることは、カスタムコンパイルステップを追加することです。実際に非常に簡単なMavenを使用している場合、他の自動化されたビルドシステムもこれに対処する可能性があります。.javaとは異なる拡張子のファイルを作成し、そのようなファイルをソースで検索し、実際の.javaを再生成するカスタムステップを追加します。また、自動生成されたファイルに、変更しないことを説明する大きな免責事項を追加することもできます。

長所と一度生成されたファイルの使用:開発者は.javaへの変更を機能させません。コミットする前に実際にマシンでコードを実行すると、変更が効果がないことがわかります(ハハ)。そして、おそらく彼らは免責事項を読むでしょう。この特定のファイルを別の方法で変更する必要があることを覚えて、チームメイトや将来の自分を信頼しないことは絶対に正しいことです。JUnitはテストを実行する前にプログラムをコンパイルするため(また、ファイルを再生成するため)、自動テストも可能です。

編集

コメントから判断すると、これはあたかもこれを無期限に機能させる方法であり、プロジェクトの他のパフォーマンスクリティカルな部分に展開してもよいかのように答えが出ました。

簡単に言えば、そうではありません。

将来のメンテナーに教えることは言うまでもなく、独自のミニ言語を作成し、コードジェネレーターを作成して保守するという余分な負担は、長い目で見れば地獄のようです。上記は、長期的なソリューションに取り組んでいる間に問題を処理するためのより安全な方法のみを許可します。それは私の上にあります。


19
申し訳ありませんが、私は同意する必要があります。あなたは彼のためにこの決定を下すOPのコードについて十分に知りません。これをコード生成すると、元のソリューションよりも技術的な負債が多くなるように思えます。
ロバートハーヴェイ14年

6
ロバートのコメントは誇張することはできません。「ファイルを変更しないことを説明する免責事項」があるときはいつでも、「シャーク」と呼ばれる不法なブッキーが経営する小切手換金店に相当する「技術的負債」に相当します。
corsiKa 14年

8
もちろん、自動生成されたファイルはソースレポジトリに属していません(ソースではないため、コンパイルされたコードです)ant clean。そこにないファイルに免責事項を入れる必要はありません!
ヨルグWミットタグ14年

2
@RobertHarvey OPについては何も決定していません。OPは、そのようなテンプレートを彼が持っている方法で持っておくのは良い考えかと尋ね、私はそれらを維持する(より良い)方法を提案しました。これは理想的な解決策ではありません。実際、最初の文で述べたように、状況全体が悪いです。そして、前進するためのはるかに良い方法は、問題を揺るがす解決策を作らずに取り除くことです。これには、このコードがどれほど重要であるか、パフォーマンスヒットがどれほど悪いか、多くの悪いコードを書かずに機能させる方法があるかどうかを適切に評価することが含まれます。
オーダス14年

2
@corsiKaこれが永続的なソリューションだとは決して言いませんでした。これは元の状況と同じくらい借金になるでしょう。唯一のことは、体系的な解決策が見つかるまでボラティリティを下げることです。Javaでこのような問題に直面している場合、非常に複雑なことや非常に間違っていることをしているため、新しいプラットフォーム/フレームワーク/言語への完全な移行が含まれる可能性があります。
オーダス14年

16

メンテナンスヒットは本物ですか、それとも単に気になりますか?気になるだけなら、そのままにしておきます。

パフォーマンスの問題は本当ですか、それとも前の開発者はそれだけだと思っていましたか?多くの場合、パフォーマンスの問題は、プロファイラーが使用されている場合でも、考えられる場所ではありません。(この手法を使用し、確実にそれらを見つけます。)

したがって、問題のないtoい解決策がある可能性はありますが、実際の問題のい解決策になる可能性もあります。疑わしい場合は、そのままにしておきます。


10

実際の生産、条件(現実的なメモリ消費、ガベージコレクションを現実的にトリガーするなど)よりも、これらの個別のメソッドすべてが実際にパフォーマンスの違いを生み出すことを確認してください(1つのメソッドだけではなく)。これには時間がかかりますが、これから作業するコードを簡素化することで数週間節約できます。

あなたはまた、もしあなたはすべての機能が必要であることを発見し、あなたが使用することをお勧めしますJavassistのをプログラム的にコードを生成します。他の人が指摘したように、Mavenで自動化できます。


2
また、パフォーマンスの問題が現実的であることが判明した場合でも、それらを調査したことにより、コードで何が可能かをよりよく知ることができます。
カールマナスター14年

6

このシステムに何らかのテンプレートプリプロセッサ\コード生成を含めないのはなぜですか?残りのコードをコンパイルする前に追加のJavaソースファイルを実行および出力するカスタムの追加ビルドステップ。これは、wsdlおよびxsdファイルからWebクライアントをインクルードすることがよく機能する方法です。

もちろん、そのプリプロセッサ/コードジェネレーターのメンテナンスはありますが、重複するコアコードのメンテナンスを心配する必要はありません。

コンパイル時にJavaコードが発行されるため、余分なコードに対してパフォーマンスが低下することはありません。ただし、複製されたすべてのコードの代わりにテンプレートを使用することで、メンテナンスが簡素化されます。

Javaジェネリックは、言語の型の消去によるパフォーマンスの向上をもたらさず、代わりに単純なキャストが使用されます。

私のC ++テンプレートの理解から、それらは各テンプレート呼び出しごとにいくつかの関数にコンパイルされます。たとえば、std :: vectorに保存するタイプごとに、コンパイル中のタイムコードが複製されます。


2

正統なJavaメソッドは、12のバリアントを持つほど長くなることはできません...そして、JITCは長いメソッドを嫌います-それらを適切に最適化することを単に拒否します。メソッドを2つの短いメソッドに単純に分割することで、2倍のスピードアップが見られました。たぶんこれが道です。

複数のコピーがあるOTOHは、同一であっても意味があります。それぞれが異なる場所で使用されると、それらは異なるケースに最適化されます(JITCはそれらをプロファイリングして、まれなケースを例外的なパスに配置します。

正当な理由があると仮定して、コードを生成することは大したことではないと思います。オーバーヘッドはかなり低く、適切な名前のファイルはすぐにソースにつながります。かなり前にソースコードを生成していたので// DO NOT EDIT、すべての行を追加しました...これで十分だと思います。


1

あなたが言及した「問題」には何の問題もありません。私が知っていることから、これはDBサーバーが良好なパフォーマンスを得るために使用する設計とアプローチの正確な種類です。

特定の条件が適用される場合、結合、選択、集計など、あらゆる種類の操作のパフォーマンスを最大化できるようにするための特別な方法がたくさんあります。

要するに、あなたが思うようなコード生成は悪い考えです。おそらく、この図を見て、DBがあなたと同様の問題をどのように解決するかを確認する必要があります。ここに画像の説明を入力してください


1

あなたはまだいくつかの賢明な抽象化を使用して、例えば使用することができるかどうかを確認することがありますテンプレートメソッドパターンを 12に共通の機能について理解しやすいコードを書くと、(パターンdescritionに応じて)「基本操作」への方法の違いを移動するにはサブクラス。これにより、保守性とテスト容易性が大幅に向上し、しばらくするとJVMがプリミティブ操作のメソッド呼び出しをインライン化できるため、実際には現在のコードと同じパフォーマンスになる可能性があります。もちろん、パフォーマンステストでこれを確認する必要があります。


0

Scala言語を使用して、特殊なメソッドの問題を解決できます。

Scalaはメソッドをインライン化することができ、これは(簡単な高階関数の使用と組み合わせて)コードの重複を無料で回避することを可能にします。これはあなたの答えで述べた主な問題のように見えます。

しかし、Scalaには構文マクロもあり、これにより、コンパイル時にコードで多くのことをタイプセーフな方法で行うことができます。

また、ジェネリックで使用される場合のプリミティブ型のボクシングの一般的な問題は、Scalaでも解決できます。@specializedアノテーションを使用して自動的にボクシングを回避するために、プリミティブの汎用特殊化を行うことができます。したがって、基本的に、Scalaで1つのジェネリックメソッドを記述すると、専用メソッドの速度で実行されます。また、高速で汎用的な算術演算も必要な場合は、異なる数値型の演算と値を注入するためにTypeclass "pattern"を使用することで簡単に実行できます。

また、Scalaは他の多くの点でも優れています。また、Scala <-> Java相互運用性が優れているため、すべてのコードをScalaに書き直す必要はありません。プロジェクトのビルドには必ずSBT(scala build tool)を使用してください。


-1

問題の関数が大きい場合、「モード/パラダイム」ビットをインターフェースに変換し、そのインターフェースを実装するオブジェクトをパラメーターとして関数に渡すと機能します。GoFはこれを「戦略」パターン、iircと呼びます。関数が小さい場合、オーバーヘッドの増加はかなり大きくなる可能性があります...誰かがまだプロファイリングに言及しましたか?...より多くの人々がプロファイリングに言及する必要があります。


これはより多くの答えよりもコメントのように読み込む
ブヨ

-2

プログラムは何歳ですか?ほとんどの場合、新しいハードウェアがボトルネックを解消し、より保守しやすいバージョンに切り替えることができます。しかし、メンテナンスの理由が必要であるため、タスクがコードベースを改善することでない限り、作業はそのままにしておきます。


1
これは単に繰り返しポイントがで作られたと説明しているようだ前に答えが数時間前に投稿
ブヨ

1
ボトルネックが実際にどこにあるかを判断する唯一の方法は、他の答えで述べたように、それをプロファイルすることです。その重要な情報がなければ、パフォーマンスへの推奨される修正は純粋な推測です。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.