5

私は、非常に大量のコード (主に C++) をリファクタリングして、特定の値に永続的に設定されている多数の一時的な構成チェックを削除しようとしています。たとえば、次のコードがあります。

#include <value1.h>
#include <value2.h>
#include <value3.h>

...

if ( value1() )
{
    // do something
}

bool b = value2();

if ( b && anotherCondition )
{
    // do more stuff
}

if ( value3() < 10 )
{
    // more stuff again
}

value の呼び出しは bool または int を返します。これらの呼び出しが常に返す値を知っているので、呼び出しを通常の値に展開するためにいくつかの正規表現置換を行いました。

// where:
//   value1() == true
//   value2() == false
//   value3() == 4

// TODO: Remove expanded config (value1)
if ( true )
{
    // do something
}

// TODO: Remove expanded config (value2)
bool b = false;

if ( b && anotherCondition )
{
    // do more stuff
}

// TODO: Remove expanded config (value3)
if ( 4 < 10 )
{
    // more stuff again
}

値は固定されていますが、コンパイル時に設定されるのではなく、共有メモリから読み取られるため、コンパイラは現在、舞台裏で何も最適化していないことに注意してください。

結果のコードは少し間抜けに見えますが、この正規表現アプローチは、コードの動作を変更せずに適用が簡単で呼び出しへの依存を削除するため、私が望む多くのことを達成します。ブロックが呼び出されないか、チェックが常に true を返すことを知っているため、多くのことを行います。また、(特にバージョン管理と比較する場合) 何が変更されたかを確認し、それをクリーンアップする最終ステップを実行するのがかなり簡単になり、コードの上のコードは最終的に次のようになります。

// do something

// DONT do more stuff (b being false always prevented this)

// more stuff again

問題は、2 番目の、正しいが間抜けな段階から、最終的にクリーンなコードに到達するまでに、何百 (場合によっては何千) もの変更を加えなければならないことです。

これを処理できるリファクタリング ツールや、私が適用できるテクニックを誰かが知っているかどうか疑問に思いました。主な問題は、C++ 構文が完全な展開または削除を達成するのを非常に困難にし、上記のコードに多くの順列があることです。カバーする必要のあるさまざまな構文に対処するには、ほとんどコンパイラが必要だと感じています。

同様の質問があったことは知っていますが、このような要件を見つけることができず、質問されてからツールや手順が登場したかどうか疑問に思いましたか?

4

2 に答える 2

5

私が「ゾンビコード」と呼んでいるように思えます...実際には死んでいますが、コンパイラに関する限り、まだ生きています。これは、組織化されたランタイム構成変数のほとんどのシステムで非常に一般的な問題です。最終的に、一部の構成変数は永続的な固定状態に到達しますが、実行時に繰り返し再評価されます。

あなたが指摘したように、正規表現はC++コードを確実に解析しないため、治療法は正規表現ではありません。必要なのはプログラム変換システムです。これは、ソース コードを実際に解析し、一連のコード間の書き換え規則を解析ツリーに適用して、変更されたツリーからソース テキストを再生成できるツールです。

Clang にはいくつかの機能があることを理解しています。C++ を解析してツリーを構築できますが、ソースからソースへの変換機能はありません。AST から AST への変換を記述することでその機能をシミュレートできますが、それははるかに不便です。C++ コードを再生成できると思いますが、コメントやプリプロセッサ ディレクティブが保持されるかどうかはわかりません。

C++(11) フロント エンドを備えた当社のDMS ソフトウェア リエンジニアリング ツールキットは、 C++ ソース コードに対して大規模な変換を実行できます (また、これまでに使用されてきました)。私の知る限り、これができる唯一の制作ツールです。必要なのは、対象の構成変数の最終状態に関する知識を表す一連の変換と、単純なコード単純化ルールです。次の DMS ルールは、希望するものに近いものです。

  rule fix_value1():expression->expression
    "value1()" -> "true";
  rule fix_value2():expression->expression
    "value2()" -> "false";
  rule fix_value3():expression->expression
    "value3()" -> "4";

  rule simplify_boolean_and_true(r:relation):condition->condition
     "r && true" -> "r".
  rule simplify_boolean_or_ture(r:relation):condition->condition
     "r || true" -> "true".
  rule simplify_boolean_and_false(r:relation):condition->condition
     "r && false" -> "false".
  ...
  rule simplify_boolean_not_true(r:relation):condition->condition
     "!true" -> "false".
  ...

  rule simplify_if_then_false(s:statement): statement->statement
      " if (false) \s" -> ";";
  rule simplify_if_then_true(s:statement): statement->statement
      " if (true) \s" -> "\s";
  rule simplify_if_then_else_false(s1:statement, s2:statement): statement->statement
      " if (false) \s1 else \s2" -> "\s2";
  rule simplify_if_then_else_true(s1:statement, s2: statement): statement->statement
      " if (true) \s1 else \s2" -> "\s2";

また、算術演算を含む定数式を単純化 (「折り畳み」) するためのルールと、定数になった式のスイッチを処理するためのルールも必要です。整数定数フォールディングの DMS ルールがどのように見えるかを確認するには、Algebra as DMS domainを参照してください。

正規表現とは異なり、DMS 書き換えルールはコードを「不一致」にすることはできません。それらは対応する AST を表し、一致するのはその AST です。ASTマッチングなので、空白や改行、コメントは問題ありません。オペランドの順序に問題があるのではないかと思われるかもしれません (「"false && x" が検出された場合は?」)。&&||の文法規則のように、そうではありません。は、DMS C++ パーサーで連想および可換としてマークされ、マッチング プロセスで自動的に考慮されます。

これらのルールだけではできないことは、代入全体での値 (この場合は定数) の伝播です。このためには、そのような割り当てを追跡できるようにフロー分析が必要です (「到達定義」)。明らかに、そのような割り当てがない場合、または非常に少ない場合は、手動でパッチを当てることができます。その場合は、フロー分析が必要になります。悲しいかな、DMS の C++ のフロントは完全ではありませんが、私たちはそれに取り組んでいます。制御フロー分析が実施されています。(DMS の C フロント エンドには完全なフロー解析があります)。

(2015 年 2 月編集: 完全な C++14 が実行されるようになりました。関数/メソッド内のフロー分析)。

ほぼ 10 年前に IBM Tivoli の C と C++ が混在するコードの 1.5M SLOC アプリケーションにこの手法を実際に適用し、優れた成功を収めました。フロー分析は必要ありませんでした:-}

于 2012-05-02T10:07:49.907 に答える
1

あなたは言う:

値は適切に固定されていますが、コンパイル時に設定されるのではなく、共有メモリから読み取られるため、コンパイラは現在、舞台裏で何も最適化していないことに注意してください。

値を手動でコンスタントに折りたたむことは、それらが完全に固定されていない限りあまり意味がありません。コンパイラがconstexprを提供している場合は、それを使用するか、次のようにプリプロセッサ マクロで置き換えることができます。

#define value1() true
#define value2() false
#define value3() 4

そこからはオプティマイザーが面倒を見てくれます。ヘッダーの内容の正確な例を見<valueX.h>たり、共有メモリからこれらの値を取得するプロセスがどのように機能しているかを知らずに、既存の valueX() 関数の名前を変更して実行時チェックを行うと役立つ可能性があることを捨てます。それらは将来再び変更されます。

// call this at startup to make sure our agreed on values haven't changed
void check_values() {
    assert(value1() == get_value1_from_shared_memory());
    assert(value2() == get_value2_from_shared_memory());
    assert(value3() == get_value3_from_shared_memory());
}
于 2012-04-11T09:26:18.707 に答える