コード品質のトピックに関する多くの議論、記事、ブログがあります。人々は言う-テスト駆動技術を使用してください!テストは、リファクタリングを開始するための「必須」です。それはすべて素晴らしいことですが、2016年であり、10年、15年、さらには20年前に作成された大量の製品とコードベースがまだ生産されています。それらの多くがテストカバレッジの低いレガシーコードを持っていることは周知の事実です。
私は常にテクノロジーの世界の最先端にいることを望んでいますが、新しいクールなプロジェクトやテクノロジーに取り組んでいますが、残念ながらそれが常に可能であるとは限らず、古いシステムに対処しなければならないことがよくあります。ゼロから開発するときは、クリエーターとして行動し、新しいことをマスターしていると言いたいです。しかし、レガシーコードに取り組んでいるときは、外科医のようなものです。システムが一般的にどのように機能するかは知っていますが、患者があなたの「手術」を生き残ることができるかどうかはわかりません。また、これはレガシーコードであるため、信頼できる最新のテストは多くありません。これは、非常に頻繁に、最初のステップの1つがテストでカバーすることであることを意味します。より正確には、単にカバレッジを提供するだけでなく、テストカバレッジ戦略を開発するためです。
基本的に、私が決定する必要があるのは、最初にテストでカバーする必要があるシステムの部分(クラス/パッケージ)、単体テストが必要な場所、統合テストがより役立つ場所などでした。確かに多くの方法があります。このタイプの分析にアプローチし、私が使用したものは最善ではないかもしれませんが、それは一種の自動アプローチです。私のアプローチが実装されると、実際に分析自体を実行するのに最小限の時間がかかります。さらに重要なことは、レガシーコード分析にいくつかの楽しみをもたらします。
ここでの主なアイデアは、カップリング(求心性カップリング、つまりCA)と複雑度(循環的複雑度)の2つの指標を分析することです。
最初の1つは、クラスを使用するクラスの数を測定するため、基本的に、特定のクラスがシステムの中心にどれだけ近いかを示します。クラスを使用するクラスが多いほど、テストでカバーすることが重要になります。
一方、クラスが非常に単純な場合(たとえば、定数のみが含まれている場合)、システムの他の多くの部分で使用されている場合でも、テストを作成することはそれほど重要ではありません。ここで、2番目のメトリックが役立ちます。クラスに多くのロジックが含まれている場合、循環的複雑度は高くなります。
同じロジックを逆に適用することもできます。つまり、クラスが多くのクラスで使用されておらず、特定のユースケースを1つだけ表している場合でも、内部ロジックが複雑な場合は、テストでカバーすることは理にかなっています。
ただし、注意点が1つあります。2つのクラスがあるとします。1つはCA 100で複雑度2、もう1つはCA 60で複雑度20です。最初のクラスのメトリックの合計は高くなりますが、必ずカバーする必要があります。最初に2番目のもの。これは、最初のクラスが他の多くのクラスで使用されているためですが、それほど複雑ではありません。一方、2番目のクラスは他の多くのクラスでも使用されていますが、最初のクラスよりも比較的複雑です。
要約すると、CAと循環的複雑度が高いクラスを特定する必要があります。数学的には、評価として使用できる適応度関数(f(CA、Complexity))が必要です。この関数の値は、CAおよび複雑度とともに増加します。
一般的に、2つのメトリック間の差異が最小のクラスには、テストカバレッジの最高の優先順位を与える必要があります。コードベース全体のCAと複雑さを計算し、この情報をCSV形式で抽出する簡単な方法を提供するツールを見つけることは、課題であることがわかりました。私の検索中に、無料の2つのツールに出くわしたので、それらは言うまでもなく不公平です。
ここでの主な問題は、CAと循環的複雑度の2つの基準があるため、それらを組み合わせて1つのスカラー値に変換する必要があることです。わずかに異なるタスクがある場合(たとえば、基準の組み合わせが最悪のクラスを見つける場合)、古典的な多目的最適化の問題が発生します。
いわゆるパレートフロント(上の写真の赤)でポイントを見つける必要があります。パレートセットの興味深い点は、セット内のすべてのポイントが最適化タスクのソリューションであるということです。赤い線に沿って移動するときはいつでも、基準の間で妥協する必要があります。一方が良くなると、もう一方は悪くなります。これはスカラリゼーションと呼ばれ、最終的な結果はそれをどのように行うかによって異なります。
ここで使用できるテクニックはたくさんあります。それぞれに長所と短所があります。ただし、最も人気のあるものは 線形スカラー化 とに基づくもの 基準点 。線形は最も簡単なものです。私たちの適応度関数は、CAと複雑さの線形結合のように見えます。
f(CA、複雑さ)= A×CA + B×複雑さ
ここで、AとBはいくつかの係数です。
最適化問題の解決策を表すポイントは、線上にあります(下の写真の青)。より正確には、青い線と赤いパレート正面の交点になります。私たちの元々の問題は、正確には最適化問題ではありません。むしろ、ランキング関数を作成する必要があります。ランキング関数の2つの値、基本的にはランク列の2つの値について考えてみましょう。
R1 = A ∗ CA + B ∗複雑さおよびR2 = A ∗ CA + B ∗複雑さ
上に書かれた両方の式は線の方程式であり、さらにこれらの線は平行です。より多くのランク値を考慮に入れると、より多くの線が得られるため、パレート線が(点線の)青い線と交差する点がより多くなります。これらのポイントは、特定のランク値に対応するクラスになります。
残念ながら、このアプローチには問題があります。どのライン(ランク値)でも、CAが非常に小さく、複雑さが非常に大きい(またはその逆の)ポイントがあります。これにより、メトリック値の間に大きな違いがあるポイントがリストの一番上にすぐに配置されます。これは、まさに避けたかったことです。
スカラー化を行うもう1つの方法は、参照点に基づいています。基準点は、両方の基準の最大値を持つ点です。
(max(CA)、max(Complexity))
適応度関数は、参照点とデータ点の間の距離になります。
f(CA、複雑さ)=√((CA − CA)2+(複雑さ-複雑さ)2)
この適応度関数は、中心を基準点とする円と考えることができます。この場合の半径は、ランクの値です。最適化問題の解決策は、円がパレート前面に接触する点です。元の問題の解決策は、次の図に示すように、さまざまな円の半径に対応する点のセットになります(さまざまなランクの円の一部は青い点線の曲線で示されています)。
このアプローチは極値をより適切に処理しますが、まだ2つの問題があります。1つ目–線形結合で直面した問題をよりよく克服するために、参照ポイントの近くにポイントを増やしたい。 2番目– CAと循環的複雑度は本質的に異なり、異なる値が設定されているため、それらを正規化する必要があります(たとえば、両方のメトリックのすべての値が1から100になるように)。
これは、最初の問題を解決するために適用できる小さなトリックです。CAと循環的複雑度を調べる代わりに、それらの反転値を調べることができます。この場合の基準点は(0,0)になります。 2番目の問題を解決するために、最小値を使用してメトリックを正規化することができます。外観は次のとおりです。
反転および正規化された複雑さ– NormComplexity:
(1 + min(複雑さ))/(1 +複雑さ)∗ 100
反転および正規化されたCA– NormCA:
(1 +分(CA))/(1 + CA)∗ 100
注意: 0による除算がないことを確認するために1を追加しました。
次の図は、値が反転したプロットを示しています。
私たちは今、最後のステップ、つまりランクの計算に来ています。前述のように、私は参照点メソッドを使用しているので、ベクトルの長さを計算して正規化し、クラスの単体テストを作成することの重要性を考慮して上昇させるだけです。最終的な式は次のとおりです。
ランク(NormComplexity、NormCA)= 100 −√(NormComplexity2+ NormCA2)/√2
追加したいもう1つの考えがありますが、最初にいくつかの統計を見てみましょう。カップリングメトリクスのヒストグラムは次のとおりです。
この図で興味深いのは、CAが低い(0〜2)クラスの数です。 CA 0のクラスは、まったく使用されていないか、トップレベルのサービスです。これらは 火 エンドポイントなので、たくさんあるのは問題ありません。ただし、CA 1のクラスはエンドポイントによって直接使用されるクラスであり、エンドポイントよりも多くのクラスがあります。これは、アーキテクチャ/設計の観点からどういう意味ですか?
一般に、これは一種のスクリプト指向のアプローチがあることを意味します。つまり、すべてのビジネスケースを個別にスクリプト化します(ビジネスケースが多すぎるため、コードを実際に再利用することはできません)。もしそうなら、それは間違いなく コードの臭い リファクタリングを行う必要があります。それ以外の場合は、システムの凝集度が低いことを意味します。この場合、リファクタリングも必要ですが、今回はアーキテクチャのリファクタリングが必要です。
ウェブサイトからメタマスクに接続する方法
上記のヒストグラムから得られる追加の有用な情報は、単体テストの対象となるクラスのリストから、結合度の低いクラス({0,1}のCA)を完全に除外できることです。ただし、同じクラスが統合/機能テストの良い候補です。
私が使用したすべてのスクリプトとリソースは、このGitHubリポジトリにあります。 ashalitkin / code-base-stats 。
必ずしも。まず第一に、実行時間ではなく、静的分析がすべてです。クラスが他の多くのクラスからリンクされている場合、それは頻繁に使用されていることを示している可能性がありますが、常に正しいとは限りません。たとえば、この機能がエンドユーザーによって本当に頻繁に使用されているかどうかはわかりません。次に、システムの設計と品質が十分に優れている場合、システムのさまざまな部分/レイヤーがインターフェイスを介して分離されている可能性が高いため、CAの静的分析では実際の状況を把握できません。これが、CAがSonarのようなツールでそれほど人気がない主な理由の1つだと思います。幸いなことに、これは特に古い醜いコードベースに適用することに関心があるので、私たちにとってはまったく問題ありません。
一般に、ランタイム分析の方がはるかに優れた結果が得られると思いますが、残念ながら、それははるかにコストがかかり、時間がかかり、複雑であるため、私たちのアプローチは潜在的に有用で低コストの代替手段です。
関連: 単一責任の原則:優れたコードのレシピ