Парадигма программирования; интересно, требуется ли переписывание/рефакторинг

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

Позвольте мне объяснить проблему и как я ее решил в настоящее время. В основном у меня есть данные, и я позволяю вещам происходить по этим данным (хорошо, я описал каждую программу, не так ли?). Что происходит:

Данные → запрашивает просмотрщик → просмотрщик отображает данные на основе фактических данных viewer возвращает пользовательский ввод → data → запрашивает "исполнитель" для его выполнения → новые данные

enter image description here

Теперь это работало очень хорошо, и я думал, что изначально "эй, я мог бы, например, сменить командную строку на qt или windows - или даже взять этот внешний (С#) и просто вызвать эту программу".

Однако по мере роста программы она становилась все более утомительной. Самое главное, что данные отображаются по-разному, в зависимости от того, что данные и что еще важнее, где они расположены. Поэтому я вернулся к дереву и добавил, чтобы "отслеживать" то, что у родительской линии ". Тогда общий просмотрщик будет искать наиболее конкретный фактический виджет. Он использует список с [location; виджета] и находит наилучшее совпадающее местоположение.

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

Теперь я могу полностью поменять это. И вместо дерева datastructure вызова общего зрителя. Я использовал бы внутренние возможности дерева OO. Узлы были бы childs (&, когда требуется новый просмотрщик или механизм сохранения, формируется новый ребенок).

Это позволит удалить сложный механизм проверки, где я проверяю местоположение в дереве. Однако это может открыть целую другую червь червей. И мне хотелось бы получить некоторые комментарии по этому поводу? Должен ли я держать зрителя совершенно отдельным - с трудностью проверки данных? Или новый подход лучше, но он объединяет данные и выполнение в один node. (Так что, если я хочу перейти с qt на cli/С#, это становится практически невозможным)

enter image description here

Какой метод следует использовать в конце? Также есть что-то еще, что я могу сделать? Чтобы зритель не раздельный, все же не нужно делать проверки, чтобы увидеть, какой виджет должен отображаться?

EDIT, просто чтобы показать некоторый "код" и как работает моя программа. Не уверен, что это хорошо, как я уже сказал, это стало довольно сложной методологией.

Предполагается объединить несколько проектов "gamemaker" вместе (как GM: студии странно не хватает этой функции). Файлы проектов Gamemaker - это просто наборы xml файлов. (Основной файл xml с единственными ссылками на другие файлы xml и файл xml для каждого ресурса -объекта, спрайт, звук, комнату и т.д.). Однако есть некоторые "причуды", из-за которых невозможно прочитать что-то вроде деревьев свойств boost или qt: 1) порядок атрибутов/дочерних узлов очень важен в определенных частях файлов. и 2) пустое пространство часто игнорируется, однако в других точках очень важно его сохранить.

Считается, что есть также много точек, где node точно такой же. Как то, как фон может иметь <width>200</width>, и комната тоже может это иметь. Тем не менее для пользователя очень важно, о какой ширине он говорит.

В любом случае, "общий просмотрщик" (AskGUIFn) имеет следующие типы typedef для этого:

    typedef int (AskGUIFn::*MemberFn)(const GMProject::pTree& tOut, const GMProject::pTree& tIn, int) const;
    typedef std::vector<std::pair<boost::regex, MemberFn> > DisplaySubMap_Ty;
    typedef std::map<RESOURCE_TYPES, std::pair<DisplaySubMap_Ty, MemberFn> > DisplayMap_Ty;

Где "GMProject:: pTree" является деревом node, RESOURCE_TYPES является константой для отслеживания того, каким ресурсом я на данный момент (спрайт, объект и т.д.). "MemberFn" будет просто просто загружать виджет. (Хотя AskGUIFn, конечно, не единственный общий зритель, этот только открывается, если другие "автоматические", "перезаписать", "пропустить", переименовать обработчики не удались).

Теперь, чтобы показать, как эти карты инициализируются (все в пространстве имен "MW" является виджем qt):

AskGUIFn::DisplayMap_Ty AskGUIFn::DisplayFunctionMap_INIT() {
    DisplayMap_Ty t;
        DisplaySubMap_Ty tmp;

        tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^instances "), &AskGUIFn::ExecuteFn<MW::RoomInstanceDialog>));
        tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^code $"), &AskGUIFn::ExecuteFn<MW::RoomStringDialog>));
        tmp.push_back(std::pair<boost::regex, AskGUIFn::MemberFn> (boost::regex("^(isometric|persistent|showcolour|enableViews|clearViewBackground) $"), &AskGUIFn::ExecuteFn<MW::ResourceBoolDialog>));
        //etc etc etc
    t[RT_ROOM] = std::pair<DisplaySubMap_Ty, MemberFn> (tmp, &AskGUIFn::ExecuteFn<MW::RoomStdDialog>);

        tmp.clear();
        //repeat above
    t[RT_SPRITE] = std::pair<DisplaySubMap_Ty, MemberFn>(tmp, &AskGUIFn::ExecuteFn<MW::RoomStdDialog>);
    //for each resource type.

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

AskGUIFn::MemberFn AskGUIFn::FindFirstMatch() const {
    auto map_loc(DisplayFunctionMap.find(res_type));
    if (map_loc != DisplayFunctionMap.end()) {
        std::string stack(CallStackSerialize());
        for (auto iter(map_loc->second.first.begin()); iter != map_loc->second.first.end(); ++iter) {
            if (boost::regex_search(stack, iter->first)) {
                return iter->second;
            }
        }
        return map_loc->second.second;
    }

    return BackupScreen;
}

И тут проблемы стали откровенными.Функция CallStackSerialize() зависит от стека вызовов. Однако этот call_stack хранится внутри "обработчика". Я сохранил его там, потому что все начинается с обработчика. Я не совсем уверен, где я должен хранить этот "call_stack". Ввести еще один объект, который отслеживает, что происходит? Я попытался перейти по маршруту, где я храню родительский элемент с самим node. (Предотвращение необходимости в стеке вызовов). Однако это не так хорошо, как хотелось бы: каждый node просто имеет вектор, содержащий его дочерние узлы. Поэтому использование указателей не может быть указано на родительскую заметку... (PS: может быть, я должен реформировать это в другом вопросе..)

Ответ 1

Рефакторинг/переписывание этого сложного механизма проверки местоположения из зрителя в выделенный класс имеет смысл, поэтому вы можете улучшить свое решение, не затрагивая остальную часть вашей программы. Позволяет называть это NodeToWidgetMap.

Архитектура
Кажется, ваш заголовок к Model-View-Controller, что является хорошей идеей IMO. Ваша древовидная структура и ее узлы являются моделями, где, поскольку зрители и "виджеты" являются видами, а логические выборки виджетов в зависимости от node будут частью контроллера.

Основной вопрос остается, когда и как вы выбираете виджет w N для данного node N и как сохранить этот выбор.

NodeToWidgetMap: когда выбрать
Если вы можете предположить, что w N не изменяется во время его жизни, хотя перемещаются узлы, вы можете выбрать его правильно при создании node. В противном случае вам нужно знать местоположение (или путь через XML) и, следовательно, найти родителя node при его запросе.

Поиск родительских узлов
Моим решением было бы сохранить указатели вместо самих экземпляров node, возможно, используя boost::shared_ptr. Это имеет недостатки, например, копирование узлов заставляет вас реализовать свои собственные конструкторы-копии, которые используют рекурсию для создания глубокой копии вашего поддерева. (Однако перемещение не влияет на дочерние узлы.)

Существуют альтернативы, такие как сохранение дочерних узлов uptodate, когда прикосновение к родительскому node соответствует вектору дедушек. Или вы можете определить функцию Node::findParentOf(node), зная, что определенные узлы могут (или часто) быть найдены как дочерние узлы определенных узлов. Это грубо, но будет хорошо работать для небольших деревьев, просто не очень хорошо масштабируется.

NodeToWidgetMap: как выбрать
Попробуйте записать правила, как выбрать w N на листе бумаги, возможно, только частично. Затем попробуйте перевести эти правила на С++. Это может быть немного длиннее с точки зрения кода, но будет легче понимать и поддерживать.

Ваш текущий подход заключается в использовании регулярных выражений для сопоставления пути (стека) XML.

Моей идеей было бы создать граф поиска, чьи ребра помечены именами XML-элементов и узлы которых указывают, какой виджет будет использоваться. Таким образом, ваш XML-путь (стек) описывает маршрут по графику. Тогда возникает вопрос, следует ли явно моделировать график или можно ли использовать группу вызовов функций для зеркалирования этого графика.

NodeToWidgetMap: где хранить выбор
Связывая уникальный, числовой идентификатор с каждым node, записывайте выбор виджетов, используя карту из node id в виджет внутри NodeToWidgetMap.

Переписывание против рефакторинга
Если вы переписываете, вы можете получить хороший рычаг привязки к существующей структуре, такой как Qt, чтобы сосредоточиться на своей программе, а не на переписывание колес. Легче переносить хорошо написанную программу из рамки в другую, а не абстрагироваться от особенностей каждой платформы. Qt - хорошая структура для получения опыта и хорошего понимания MVC-архитектур.

Полное переписывание дает вам возможность переосмыслить все, но подразумевает риск, который вы начинаете с нуля, и будет без новой версии на достаточное количество времени. И кто знает, у вас будет достаточно времени, чтобы закончить? Если вы решите реорганизовать существующие структуры, вы будете совершенствовать его шаг за шагом, имея полезную версию после каждого шага. Но есть небольшой риск остаться в ловушке старых способов мышления, где, поскольку переписывание почти заставляет вас переосмыслить все. Поэтому оба подхода имеют свои достоинства, если вам нравится программирование, я бы переписал. Больше программирования, больше радости.

Ответ 2

Добро пожаловать в мир программирования!
То, что вы описываете, является типичным жизненным циклом приложения, начинается как небольшое простое приложение, тогда оно получает все больше и больше функций, пока оно больше не будет поддерживаться. Вы не можете себе представить, сколько проектов я видел на этом последнем этапе разрушения!
Вам нужно рефакторинг? Конечно, вы делаете! Все время! Вам нужно переписать все? Не так уверен. На самом деле хорошим решением является работа по циклам: вы разрабатываете то, что вам нужно закодировать, вы его кодируете, вам нужно больше функциональности, вы разрабатываете эту новую функциональность, вы реорганизуете код, чтобы вы могли интегрировать новый код и т.д. Если вы не делайте этого так, тогда вы прибудете к точке, где ее менее дорого переписать затем на рефакторинг. Получить эту книгу: Рефакторинг - Мартин Фаулер. Если вам это нравится, то получите это: Рефакторинг для шаблонов.

Ответ 3

Как сказал Pedro NF, Мартин Фаулер "Рефакторинг" - это прекрасное место для знакомства с ним.

Ответ 4

Я рекомендую купить копию Роберта Мартинса "Agile Principles, Patterns and Practices in С#". Он рассказывает о некоторых очень практических примерах, которые показывают, как преодолеть проблемы обслуживания, подобные этому.