Рефакторинг C ++: условное расширение и устранение блоков

Я нахожусь в процессе рефакторинга очень большого объема кода, в основном C ++, для удаления ряда временных проверок конфигурации, которые постоянно установлены на заданные значения. Так, например, у меня был бы следующий код:

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

где вызовы value возвращают либо bool, либо int. Поскольку я знаю значения, которые эти вызовы всегда возвращают, я сделал некоторую подстановку регулярных выражений, чтобы расширить вызовы до их нормальных значений:

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

Обратите внимание, что, хотя значения являются фиксированными, они не устанавливаются во время компиляции, но читаются из общей памяти, поэтому в настоящее время компилятор не оптимизирует ничего за кадром.

Несмотря на то, что результирующий код выглядит немного глупо, этот подход регулярных выражений позволяет добиться многого, чего я хочу, поскольку он прост в применении и устраняет зависимость от вызовов, при этом не изменяя поведение кода и, вероятно, компилятор также может затем оптимизировать многие из них, зная, что блок никогда не может быть вызван, или проверка всегда вернет true. Это также позволяет довольно легко (особенно если сравнивать с контролем версий) увидеть, что изменилось, и сделать последний шаг по его очистке, чтобы код, приведенный выше, в конечном итоге выглядел следующим образом:

<code>// do something

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

// more stuff again
</code>

Проблема в том, что мне нужно внести сотни (возможно, тысячи) изменений, чтобы перейти от второго, правильного, но глупого этапа к окончательному очищенному коду.

Я задавался вопросом, знает ли кто-нибудь об инструменте рефакторинга, который мог бы справиться с этим или о любых методах, которые я мог бы применить. Основная проблема состоит в том, что синтаксис C ++ делает полное раскрытие или исключение довольно трудным для достижения, и есть много изменений в приведенном выше коде. Я чувствую, что мне почти нужен компилятор, чтобы иметь дело с вариацией синтаксиса, которую мне нужно будет охватить.

Я знаю, что были подобные вопросы, но я не могу найти какое-либо требование, подобное этому, и также удивляюсь, появились ли какие-либо инструменты или процедуры с тех пор, как их спросили?

Ответы на вопрос(2)

Ты говоришь:

Note that although the values are reasonably fixed, they are not set at compile time but are read from shared memory so the compiler is not currently optimising anything away behind the scenes.

Постоянное сворачивание значений вручную не имеет большого смысла, если они неcompletely фиксированный. Если ваш компилятор предоставляет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());
}
 Component 1011 апр. 2012 г., 12:52
Спасибо, моя формулировка была плохой: они исправлены и не изменятся, но не во время компиляции. Мне нравится идея замены так, чтобы компилятор мог оптимизировать, и я уже достиг этого автоматически с заменой регулярным выражением. Однако одна из целей состоит в том, чтобы удалить код "cruft". - есть много блоков кода, некоторые довольно большие, которые будут полностью избыточными после того, как значение будет зафиксировано, и было бы хорошо, если бы оно также могло быть автоматически удалено из источника.
 11 апр. 2012 г., 13:47
Если вам трудно оценивать выражения в своей голове, тест для бедного человека по поиску недоступного кода будет заключаться в том, чтобы следовать моему предложению, а затем использовать что-то вроде gcc.-Wunreachable-code предупреждение (которое обычно выключено, даже если-Wall). Я предполагаю, что любые бесплатные инструменты переписывания кода для C ++, которые сделают это для вас, вероятно, сложнее настроить, чем делать это самостоятельно. Если вы обеспокоены тем, что можете совершить ошибки, не заменяйте все это за один раз ... поднимайте выражения и утверждайте их равенство с вашими упрощениями.
Решение Вопроса

Похоже, у вас есть то, что я называю "зомби-кодом" ... на практике мертвым, но все еще живое, насколько это касается компилятора. Это довольно распространенная проблема с большинством систем организованных переменных конфигурации времени выполнения: в конечном итоге некоторые переменные конфигурации достигают постоянного фиксированного состояния, но повторно оцениваются во время выполнения.

Как вы уже отметили, лекарство не является регулярным выражением, поскольку регулярное выражение не анализирует надежно C ++ код. Что вам нужно, этосистема трансформации программ, Это инструмент, которыйreally анализирует исходный код и может применить набор правил переписывания кода к коду в дереве разбора, а также может восстановить исходный текст из измененного дерева.

Я понимаю, что Clang имеет некоторые возможности здесь; он может анализировать C ++ и строить дерево, но у него нет возможности преобразования источника в источник. Вы можете смоделировать эту возможность, написав преобразования AST-AST, но это намного более неудобно, IMHO. Я верю, что он может регенерировать код C ++, но я не знаю, сохранит ли он комментарии или директивы препроцессора.

нашDMS Software Reengineering Toolkit с этимиC ++ (11) интерфейс может (и использовался) выполнять масштабные преобразования исходного кода C ++ и имеет преобразования источник-источник. AFAIK, это единственный производственный инструмент, который может сделать это. Что вам нужно, так это набор преобразований, представляющих ваши знания о конечном состоянии переменных конфигурации, представляющих интерес, и некоторые простые правила упрощения кода. Следующие правила 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";

Вам также нужны правила для упрощения («сложения») константных выражений, включающих арифметику, и правил для обработкиswitch на выражения, которые теперь постоянны. Чтобы увидеть, как выглядят правила DMS для целочисленной константы, см.Алгебра как область DMS.

В отличие от регулярных выражений, правила перезаписи DMS не могут "не соответствовать" код; они представляют соответствующие AST, и это те AST, которые сопоставляются. Поскольку это соответствие AST, у них нет проблем с пробелами, переносами строк или комментариями. Вы могли бы подумать, что у них могут быть проблемы с порядком операндов («что делать, если встречается« ложный »и« x »?»); они не, как грамматические правила для&& а также|| помечены в синтаксическом анализаторе DMS C ++ как ассоциативные и коммутативные, и процесс сопоставления автоматически учитывает это.

То, что эти правила не могут сделать сами по себе, является распространением значения (в вашем случае постоянным) между назначениями. Для этого вам необходим анализ потока, чтобы вы могли отслеживать такие назначения (& quot; достижение определений & quot;). Очевидно, что если у вас нет таких заданий или их очень мало, вы можете вручную их исправить. Если вы это сделаете, вам понадобится анализ потока; увы, фронт DMS на C ++ не совсем, но мы работаем над этим; у нас есть анализ потока управления на месте. (Внешний интерфейс С DMS имеет полный анализ потока).

(РЕДАКТИРОВАТЬ, февраль 2015 г .: теперь выполняется полный C ++ 14; анализ потока внутри функций / методов).

Фактически мы применили эту технику к 1,5-метровому SLOC-приложению смешанного кода C и C ++ из IBM Tivoli почти десять лет назад с отличным успехом; нам не понадобился анализ потока: -}

 09 мая 2012 г., 15:58
Фундаментальные регулярные выражения не могут иметь дело с чем-либо, связанным с вложением. (Булевы) выражения включают в себя вложенность. QED: регулярные выражения не могут работать самостоятельно над выражениями. PS: ссылка исправлена.
 Component 1009 мая 2012 г., 15:55
Да, это именно то, что я хочу. Регексы могут получить меня совсем немного, но они неточны, и, как вы говорите, принципиально, они не «знают». язык. Спасибо большое. (Кстати, ссылка на ваш «Набор инструментов реинжиниринга программного обеспечения DMS» не работает)

Ваш ответ на вопрос