リファクタリングの実践。 En望の機能





ある日、私たちのチームは、かなり単純なSQLクエリを実行すると、システムパフォーマンスが大幅に低下することを発見しました。



select count(*) n from products where category_id = ?
      
      







もちろん、それを最適化する方法についての疑問が生じました。



知識のある読者は、DBMSインデックス、ヒント、およびその他の機能についてすぐに考えることができます。 しかし、今日の話は彼らについてではありません。 とにかく、SQLクエリの最適化のトピックには影響しません。



今日は、非常に単純なリファクタリング方法についてお話します。この特定のケースでは、システムパフォーマンスが大幅に向上しました。







このクエリは、他のSQLクエリの中でも特に、SQLConstsクラスで、数年間誰も登ってこなかった古いコードにありました。



 public class SQLConsts { public static final String PRODUCTS_SQL = "select count(*) n from products where category_id = ?"; ...
      
      







そして、それは別のクラスで使用されました-CategoryRepository:



 public class CategoryRepository { ... private boolean isCategoryVisible(int categoryID) { ResultSet resultSet = executeQuery(SQLConsts.PRODUCTS_SQL, categoryID); int n = resultSet.getIntegerFieldByName("n"); return n > 0; } ...
      
      







経験の浅いプログラマーであっても、クエリの行数を計算する必要がないことに気付くでしょう。この数を単純にゼロと比較した場合。



この明白なエピカールはどのように現れましたか? Gitログの分析により、最初にisCategoryVisibleメソッドに、計算で行数を使用するより複雑なロジックが存在することが示されました。 しかし、その後、彼らは複雑な論理を拒否し、 n > 0



のみが残りました。 どうやら、これらの変更を行ったプログラマーは、 n



正確に何であるかについて疑問を持たず、特に完全に異なるファイルにあるため、SQLクエリ自体を確認しませんでした。



これらの2つのコードが隣接しているため、最適化が明らかになります。 その結果、isCategoryVisibleメソッドが書き直されました。selectcount select count(*)



where exists



構造select count(*)



置き換えられ、大量のデータボリュームで明確なパフォーマンスの向上select count(*)



られました。 SQLConstsクラスは後で破棄されました。



 public class CategoryRepository { ... private boolean isCategoryVisible(int categoryID) { ResultSet resultSet = executeQuery( "select null from dual where exists (select null from products where category_id = ?)", categoryID ); return !resultSet.isEmpty(); } ...
      
      







したがって、ルールは次のとおりです。「同時に変化するものは1か所に保存する必要があります。 このデータを使用するデータと関数は、通常一緒に変更されます」と、Martin Fowlerは著書Refactoringに書いています。 10年以上前の既存のコードの改善。



この場合、データ(SQLクエリ)は1つのクラス(SQLConsts)に保存され、関数isCategoryVisible(このデータを使用)は別のCategoryRepositoryに保存されました。 ファウラーはそのような関数をうらやましいと呼びます。なぜなら、それらが置かれているクラスではなく、他のクラスに興味があるからです。 そして、私たちの場合のように、ほとんどの場合、our望はデータです:isCategoryVisibleは、別のSQLConstsクラスが、このクラスが必要としないがisCategoryVisibleが必要なSQLクエリを格納する環境です。



繰り返しますが、同時に変化するものは1か所に保存する必要があります。このルールが習慣になるまで、それをマントラとして繰り返します。 あなたがそれについて考えるのをやめ、潜在意識レベルでそれに従うと、あなた自身はあなたのコードがどのようにきれいになるかに気付かないでしょう。



機能的en望



この記事では、vious関数の非古典的な例を示していることに注意してください。 オリジナルでは、うらやましい機能は「機能”望」と呼ばれ、文字通り「機能”望」を意味します。 したがって、ファウラー自身は例としてメソッドのen望だけを挙げていますが、ファウラーによる機能的functional望は関数/メソッドだけに限定されず、クラス全体にも拡張できると信じています。



したがって、実際にはこのクラスのすべてのメソッドがSQLConstsからのデータを使用し、SQLConsts自体はこれらのデータを使用しなかったため、en望のCategoryRepositoryクラス全体について話す必要があります。



詳細については、この質問に対する私のビジョンがここに表明されています。

habrahabr.ru/post/220883/#comment_7547819



PS



しかし、変数n



呼び出された場合、たとえばproductCount



、およびPRODUCTS_SQL



定数がPRODUCT_COUNT_IN_CATEGORY



だった場合はどうでしょうか? 次に、 productCount > 0



すると、開発者はリクエストで数量を計算する必要があるかどうかを検討するように求められます。



したがって、2番目のルール: 変数、定数、メソッド、クラスに明確な名前を付けます 。 おそらく、このルールは最初のルールよりもさらに重要です。



更新



嫌いな人のための小さな教育プログラムexists



ます。



exists



演算子は、サブクエリの少なくとも1つのレコードがcategory_id =?を満たす場合にtrue



を返しtrue





したがって、DBMSはサブクエリからすべての行を選択するわけではありません。条件を満たす最初のレコードを見つけるだけで十分です。



したがって、これらの2つのオプションは同等に効果的です。



 select null from dual where exists (select null from products where category_id = ?)
      
      







 select null from products where category_id = ? and rownum = 1
      
      







* Oracleのrownum = 1



は、MySQLのlimit 1



と同じです。



しかしwhere NOT exists



実際にはすべての適切なレコードが列挙されます。 ただし、この場合、 rownum = 1



を使用することはできません。



更新2



インデックスがcategory_id列に使用されたかどうかに関心がある人のために、YESが使用されたと報告します。



SQL最適化の観点から見ると、元のクエリでは操作のロジックを変更しないと何もできません。



All Articles