2012-04-11 4 views
5

Ich bin dabei, eine sehr große Menge an Code, hauptsächlich C++, zu refactorieren, um eine Anzahl von temporären Konfigurationsprüfungen zu entfernen, die dauerhaft auf gegebene Werte gesetzt wurden. So zum Beispiel, würde ich den folgenden Code hat:C++ - Refactoring: bedingte Erweiterung und Blockeliminierung

#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 
} 

wo die Anrufe an Rückgabewert entweder ein bool oder ein int. Da ich die Werte wissen, dass diese Anrufe immer wieder zurückkehren, habe ich einige regex Substitution getan, um die Anrufe zu ihren normalen Werten zu erweitern:

// 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 
} 

Beachten Sie, dass, obwohl die Werte festgelegt sind, werden sie bei der Kompilierung nicht festgelegt, aber werden aus dem gemeinsamen Speicher gelesen, so dass der Compiler im Hintergrund derzeit nichts optimiert.

Obwohl der resultierende Code ein bisschen doof aussieht, erreicht dieser Regex-Ansatz viel von dem, was ich will, da es einfach anzuwenden ist und die Abhängigkeit von den Aufrufen entfernt, während das Verhalten des Codes und wahrscheinlich auch der Compiler nicht verändert wird kann dann viel davon optimieren, wenn man weiß, dass ein Block niemals aufgerufen werden kann oder eine Überprüfung immer wahr zurückgibt. Es macht es auch relativ einfach (vor allem, wenn sie gegen die Versionskontrolle diffing) um zu sehen, was sich verändert hat und nehmen Sie den letzten Schritt der es nach oben, so dass die obige Code Code Reinigung sieht schließlich wie folgt:

// do something 

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

// more stuff again 

Das Problem, dass ich ist hunderte (möglicherweise tausende) von Änderungen zu machen, um von der zweiten, richtigen, aber doof, Stufe zu kommen, um zum letzten bereinigten Code zu kommen.

Ich fragte mich, ob jemand von einem Refactoring-Tool wusste, das damit umgehen könnte oder mit irgendwelchen Techniken, die ich anwenden könnte. Das Hauptproblem ist, dass die C++ - Syntax eine vollständige Erweiterung oder Eliminierung ziemlich schwierig zu erreichen ist, und es gibt viele Permutationen zu dem obigen Code. Ich glaube, ich brauche fast einen Compiler, um mit der Variation der Syntax umzugehen, die ich abdecken müsste.

Ich weiß, es gab ähnliche Fragen, aber ich kann keine Anforderung ganz so finden und fragte mich auch, ob irgendwelche Werkzeuge oder Verfahren entstanden waren, seit sie gefragt wurden?

Antwort

5

Es klingt wie Sie haben, was ich „Zombie-Code“ ... tot in der Praxis nennen, aber immer noch so weit leben, wie der Compiler geht.Dies ist bei den meisten Systemen mit organisierten Laufzeitkonfigurationsvariablen ein ziemlich häufiges Problem: Eventuell kommen einige Konfigurationsvariablen zu einem permanenten festen Zustand, werden jedoch zur Laufzeit wiederholt ausgewertet.

Die Heilung ist nicht Regex, wie Sie bemerkt haben, weil Regex C++ - Code nicht zuverlässig analysiert. Was Sie brauchen, ist ein program transformation system. Dies ist ein Werkzeug, wirklich analysiert Quellcode, und kann eine Reihe von Code-zu-Code-Rewriting-Regeln auf die Parse-Struktur anwenden, und kann Quelltext aus der geänderten Struktur neu generieren.

Ich verstehe, dass Clang hier einige Fähigkeiten hat; Es kann C++ analysieren und eine Baumstruktur erstellen, es verfügt jedoch nicht über eine Quelle-zu-Quell-Umwandlungsfunktion. Sie können diese Fähigkeit simulieren, indem Sie AST-zu-AST-Transformationen schreiben, aber das ist viel unbequemer IMHO. Ich glaube, es kann C++ - Code neu generieren, aber ich weiß nicht, ob es Kommentare oder Präprozessordirektiven erhalten wird.

Unsere DMS Software Reengineering Toolkit mit seiner C++(11) front end kann (und wurde verwendet) massive Transformationen auf C + + - Quellcode durchführen, und hat Quell-zu-Quell-Transformationen. AFAIK, es ist das einzige Produktionswerkzeug, das dies tun kann. Was Sie brauchen, ist eine Reihe von Transformationen, die Ihr Wissen über den Endzustand der Konfigurationsvariablen von Interesse darstellen, und einige einfache Regeln zur Vereinfachung des Codes. Folgende DMS-Regeln sind in der Nähe zu dem, was Sie wahrscheinlich wollen:

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"; 

Sie müssen auch Regeln („fold“) konstante Ausdrücke vereinfachen Arithmetik Einbeziehung und Regeln Schalter auf Ausdrücke behandeln, die jetzt konstant sind. Um zu sehen, welche DMS-Regeln für die Ganzzahlkonstante Faltung aussehen, siehe .

Im Gegensatz zu Regexen können DMS-Rewrite-Regeln Code nicht "vertauschen"; sie repräsentieren die entsprechenden ASTs und es sind die ASTs, die übereinstimmen. Da es sich um einen AST-Abgleich handelt, haben sie keine Probleme mit Leerzeichen, Zeilenumbrüchen oder Kommentaren. Sie könnten denken, sie könnten Probleme mit der Reihenfolge der Operanden haben ("was wäre wenn" falsch & & x "ist aufgetreten?"); sie nicht, wie die Grammatikregeln für & & und || werden im DMS C++ - Parser als assoziativ und kommutativ markiert und der Matching-Prozess berücksichtigt dies automatisch.

Was diese Regeln allein nicht können, ist die (in Ihrem Fall konstante) Weitergabe über Zuweisungen. Dazu benötigen Sie eine Flow-Analyse, damit Sie solche Zuordnungen verfolgen können ("Definitionen erreichen"). Wenn Sie solche Zuordnungen nicht oder nur sehr wenige haben, können Sie diese natürlich manuell patchen. Wenn Sie dies tun, benötigen Sie die Flow-Analyse. Leider ist DMS C++ Front nicht ganz da, aber wir arbeiten daran; Wir haben Kontrollflussanalyse an Ort und Stelle. (Das C-Frontend von DMS hat eine vollständige Flussanalyse).

(EDIT Februar 2015: Jetzt tut voll C++ 14; Flow-Analyse innerhalb Funktionen/Methoden).

Wir haben diese Technik vor fast einem Jahrzehnt mit exzellentem Erfolg auf die 1.5M SLOC-Anwendung von gemischtem C- und C++ - Code von IBM Tivoli angewendet; wir brauchten die Flussanalyse nicht: -}

+0

Ja, genau das möchte ich. Regexes können mir einiges abverlangen, aber sie sind ungenau und wie du sagst, "wissen" sie die Sprache nicht. Vielen Dank. (Übrigens, Ihr "DMS Software Reengineering Toolkit" -Link ist kaputt) –

+0

Grundsätzlich können Regexes nichts mit Verschachtelung umgehen. (Boolesche) Ausdrücke beinhalten Verschachtelung. QED: Regexes können für Ausdrücke nicht selbstständig arbeiten. PS: Link behoben. –

1

Sie sagen:

Beachten Sie, dass, obwohl die Werte einigermaßen festgelegt sind, werden sie bei der Kompilierung nicht festgelegt, sondern werden aus dem gemeinsamen Speicher lesen, so dass der Compiler nicht gerade hinter den Kulissen etwas zu optimieren entfernt.

Constant-Falten der Werte von Hand nicht viel Sinn machen, es sei denn, sie sind vollständig fixiert. Wenn Ihr Compiler bietet constexpr könnten Sie verwenden, oder Sie können in Präprozessormakros wie folgt ersetzen:

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

Der Optimierer würde von dort um Sie kümmern. Ohne Beispiele zu sehen, was genau in Ihren <valueX.h> Kopfzeilen ist, oder zu wissen, wie Ihr Prozess zum Abrufen dieser Werte aus dem geteilten Speicher funktioniert, werde ich einfach wegwerfen, dass es nützlich sein könnte, die vorhandenen valueX() -Funktionen umzubenennen und eine Laufzeitprüfung durchzuführen Fall sie in der Zukunft wieder ändern:

// 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()); 
} 
+0

Danke, mein Wortlaut war schlecht: Sie sind behoben und werden sich nicht ändern, aber nicht zur Kompilierzeit. Ich mag die Idee der Substitution, so dass der Compiler optimieren kann und ich habe das bereits automatisch mit Regex-Substitution erreicht. Eines der Ziele ist es jedoch, den Code "cruft" zu entfernen - es gibt eine Menge Codeblöcke, von denen einige ziemlich groß sind, die vollständig redundant sind, sobald der Wert festgelegt ist, und es wäre schön, wenn sie automatisch aus der Quelle entfernt werden könnten auch. –

+0

Wenn es Ihnen schwer fällt, Ausdrücke in Ihrem Kopf zu bewerten, würde ein Test für unerreichbare Codes meinem Vorschlag folgen und dann etwas wie die Warnung "-Wunreachable-Code" von gcc verwenden (die normalerweise ausgeschaltet ist) mit '-Wall'). Meine Vermutung ist, dass alle kostenlosen Code-Umschreibe-Tools für C++, die dies für Sie tun, wahrscheinlich schwieriger einzurichten sind, als es selbst tun würde. Wenn Sie besorgt sind, könnten Sie Fehler machen, dann ersetzen Sie nicht alles auf einmal ... heben Sie die Ausdrücke und behaupten Sie ihre Gleichheit zu Ihren Vereinfachungen. – HostileFork