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

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

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

Данные -> просит средство просмотра отображать -> средство просмотра отображает данные на основе фактических данных
средство просмотра возвращает пользовательский ввод -> данные -> просит «исполнителя» выполнить его -> новые данные

введите описание изображения здесь

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

Однако по мере роста программы она становилась все более и более утомительной. Поскольку наиболее важно то, что данные отображаются по-разному, в зависимости от того, что это за данные и, что более важно, где они находятся. Итак, я вернулся к дереву & каким-то образом добавлен, чтобы «отследить», что является родительской строкой ». Затем общий зритель будет искать наиболее конкретный фактический виджет.
У него есть список с [location; widget] значения и находит лучшее совпадающее местоположение.

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

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

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

введите описание изображения здесь

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

РЕДАКТИРОВАТЬ, просто чтобы показать некоторый «код» и как моя программа работает. Не уверен, что это как-то хорошо, как я уже говорил, он превратился в кучу методологий.

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

Тем не менее, есть также много точек, где узел точно такой же, например, как фон может иметь <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» — это узел дерева, 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». Введите другой объект, который отслеживает, что происходит?
Я попытался пойти по маршруту, где я храню родителя с самим узлом. (Предотвращение необходимости в стеке вызовов). Однако это пошло не так, как я хотел: у каждого узла просто есть вектор, содержащий его дочерние узлы. Таким образом, об использовании указателей не может быть и речи, чтобы указать на родительскую заметку …
(PS: может быть, я должен изменить это в другом вопросе ..)

10

Решение

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

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

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

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

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

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

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

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

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

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

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

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

2

Другие решения

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

1

Как Pedro NF сказал Мартин Фаулер «Рефакторинг» это хорошее место, чтобы познакомиться с ним.

0

Я рекомендую купить копию Роберта Мартинса «Agile Принципы, Шаблоны и Практики в C #». Он рассматривает несколько очень практических примеров, которые показывают, как преодолеть проблемы с обслуживанием, подобные этой.

0
По вопросам рекламы [email protected]