2012-11-15 55 views
10

很长一段时间我一直在处理一个应用程序。由于编程只是一项业余爱好,这个项目已经走得太久了,但那不仅仅是重点。我现在处于一个每个“问题”变得非常难以解决的地步。我正在考虑重构代码,但是这会导致“完全”重写。编程范例;想知道是否需要重写/重构

让我解释一下这个问题,以及我目前如何解决它。基本上我有数据,而我让事情发生在这些数据上(我描述的每个程序都不是我?)。什么情况是:

数据 - >请求查看器中显示 - >查看器显示基于实际数据的数据 浏览器返回用户输入 - >数据 - >问“执行”来执行它 - >新数据

enter image description here

现在,这个曾经工作得很好,我本来想:“嘿,我可能例如变化的命令提示符下QT,或窗口 - 甚至采取外部(C#)和简单的调用这个程序” 。

然而随着程序的发展,它变得越来越令人厌烦。最重要的是数据以不同的方式显示,具体取决于数据是什么以及更重要的是它位于何处。所以我回到树&添加一些“跟踪”父母的线“然后,一般的观众会搜索最具体的实际小部件。 它使用了一个与[位置;小部件]值的列表,和找到最佳匹配位置

更新新“数据”时出现问题 - 我必须通过所有资产 - 查看器,保存等等。更新检查机制给了我很多错误。像“嘿为什么它现在再次显示错误的小部件?”

现在我可以完全交换这个了,而不是树数据结构调用一个普通的查看器,我会使用OO“内部”树功能。节点将是孩子(& whe ñ需要一个新的查看器或保存机制新的孩子形成)。

这将删除困难的检查机制,我检查树中的位置。然而,它可能会打开整个其他的蠕虫。 我想就此发表一些评论?我应该让观察者完全分开 - 难以检查数据吗?或者新方法更好,但它将数据执行结合到单个节点中。 (所以,如果我想从QT改变说CLI/C#变得几乎不可能)

enter image description here

我到底应该追求什么方法?还有什么我可以做的吗?为了保持观看者的独立性,同时又要阻止必须进行检查以查看应显示哪个小部件?

编辑,只是为了显示一些“代码”,以及我的程序如何工作。不知道这是否有什么好处,因为我已经说过它已经成为相当多的方法论。

它意味着合并几个“gamemaker项目”在一起(如GM:工作室奇怪地缺乏那个特点)。 Gamemaker项目文件只是一组xml文件。 (主xml文件只包含其他xml文件的链接,每个资源对象,精灵,声音,房间等都有一个xml文件)。然而,有一些'怪癖'使得用类似boost属性树或qt的东西阅读是不太可能的:1)属性/子节点的顺序在文件的某些部分非常重要。 2)白色空间往往被忽略,但在其他地方,保存它是非常重要的。

这就是说,也有很多点,其中节点是完全相同的。就像背景可以有<width>200</width>和一个房间也可以有。然而,对于用户来说,他谈论的宽度是相当重要的。

不管怎么说,因此,“一般观众”(AskGUIFn)具有以下类型定义来处理这个问题:

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:也许我应该在另一个问题中改革这个..)

+6

+1,为数字。没有阅读这个问题,它看起来很酷。 – iammilind

+2

您是否熟悉“[内部平台效果](http://en.wikipedia.org/wiki/Inner_platform_effect)”?我不明白你的问题是否足以得出任何结论(我对你的应用一无所知),但如果你不熟悉这个术语,那么值得进行一些研究。努力修改一个旨在成为超级通用框架的非常复杂的系统听起来像是这种特定反模式的一种症状... – Rook

+0

是不是它在创建数据节点时需要一种配置文件,配置文件描述了如何展现,行动并拯救他们? – armel

回答

2

将这个复杂的位置检查机制从查看器重构/重写为专用类是有意义的,因此您可以在不影响程序其余部分的情况下改进解决方案。让我们打电话给NodeToWidgetMap

架构
看来你对一个Model-View-Controller架构,它是一件好事IMO标题。您的树结构及其节点是模型,作为查看器和“小部件”是视图,并且根据节点选择小部件的逻辑将成为控制器的一部分。

主要问题仍然是何时以及如何为给定节点N选择小部件w N以及如何存储此选项。

NodeToWidgetMap:当选择
如果你可以假设w^ň不会在其使用寿命期间,即使节点移动改变,你可以创建该节点权当选择它。否则,您需要知道位置(或通过XML的路径),并因此在请求时找到节点的父节点。

寻找父节点
我的解决办法是指针存储而不是节点实例本身,可能使用boost::shared_ptr。这有缺点,例如复制节点迫使你实现自己的复制构造函数,它使用递归来创建子树的深层副本。 (然而,移动不会影响子节点。)

存在替代方法,例如每次触摸父节点相应的祖父向量时保持子节点向上。或者你可以定义一个函数,知道某些节点只能(或经常)被发现为某些节点的子节点。这是粗暴的,但对于小树木来说工作得相当好,只是不能很好地扩展。

NodeToWidgetMap:如何选择
试着写下来的规则如何选择w ň在一张纸上,也许只是部分。然后尝试将这些规则转换为C++。这可能会稍微延长一些代码,但会更容易理解和维护。

您目前的做法是使用正则表达式来匹配XML路径(堆栈)。

我的想法是创建一个查找图,其边缘由XML元素名称标记,并且其节点指示将使用哪个小部件。这样你的XML路径(堆栈)描述了一个通过图的路由。那么问题就变成了是否明确地建模一个图或者是否可以使用一组函数调用来镜像这个图。

NodeToWidgetMap:在何处存放选择
关联唯一数字ID对每个节点,记录使用从节点ID映射到NodeToWidgetMap内部小部件的窗口小部件的选择。

重写VS重构
如果你重写你可能会得到很好的杠杆作用tieing到现有的框架,如Qt的,以专注于你的程序不是重写车轮。将一篇写得好的程序从框架移植到另一个框架比绕过每个平台的大脑更容易。 Qt是获得MVC架构经验和良好理解的好框架。

完全重写使您有机会重新思考所有内容,但意味着您从头开始并且在很长一段时间内没有新版本的风险。谁知道你是否有足够的时间完成?如果您选择重构现有结构,您将逐步改进它,并在每个步骤后都有一个可用的版本。但是仍然存在被旧思维束缚的小风险,重写几乎迫使你重新思考一切。所以这两种方法都有其优点,如果你喜欢编程,我会重写。更多的节目,更多的欢乐。

+1

嗯,我很想重写我猜..这个项目真的显示了很多变化。在此之前,我只是“熟练掌握C++/stl”,但我从来没有做过大的事情。现在我学习了qt和其他许多我需要的东西(C++ 11)。我真的可以看到代码风格的差异,我感到羞愧。 – paul23

+0

恩,抱歉无数次编辑,似乎遇到了“难度守恒定律” –

+0

嗯,我实际上可以用“nodekind”方法去;节点将保持其类型(并且只添加到其他树中)。然而这 – paul23

1

欢迎来到编程的世界!
您所描述的是应用程序的典型生命周期,从一个简单的小应用程序开始,然后它获得越来越多的功能,直到它不再可维护。你无法想象在这最后的崩溃阶段我见过多少个项目!
你需要重构吗?当然你做!每时每刻!你需要重写一切吗?不太确定。实际上,最好的解决方案是按循环工作:设计需要编码的代码,编码代码,需要更多功能,设计此新功能,重构代码以便集成新代码等。如果你不这样做,那么你会到达重写较便宜而重构的地步。获取此书:重构 - Martin Fowler。如果你喜欢它,那么得到这个:重构为模式。

0

我建议购买Robert Martins的“C#敏捷原则,模式和实践”副本他会介绍一些非常实用的案例研究,说明如何克服这样的维护问题。