2012-11-15 16 views
10

seit geraumer Zeit habe ich an einer Anwendung gearbeitet. Da Programmierung nur ein Hobby ist, geht dieses Projekt schon viel zu lange, aber das ist nebensächlich. Ich bin jetzt an einem Punkt, wo jedes "Problem" furchtbar schwer zu lösen ist. Und ich denke daran, den Code zu refactoring, aber das würde zu einem "vollständigen" Neuschreiben führen.Programmierungsparadigma; frage mich, ob Neuschreiben/Refactoring notwendig ist

Lassen Sie mich das Problem erklären, und wie ich es gerade gelöst habe. Im Grunde habe ich Daten, und ich lasse Dinge über diese Daten geschehen (gut beschrieben ich über jedes Programm nicht ich?). Was passiert ist:

Daten -> Viewer zeigt Daten auf aktuellen Daten basieren Betrachter Benutzereingabe zurück - -> Viewer angezeigt werden, fragt> Daten -> fragt „Vollstrecker“, um sie auszuführen -> neue Daten

enter image description here

Jetzt verwendet, um dieses sehr gut zu arbeiten, und ich dachte ursprünglich: „hey, ich könnte zum Beispiel ändern Eingabeaufforderung durch qt oder Fenster - oder sogar nehmen, dass externe (C#) und einfach dieses Programm aufrufen“ .

Aber als das Programm wuchs, wurde es mehr und mehr ermüdend. Am wichtigsten ist, dass die Daten auf unterschiedliche Weise angezeigt werden, je nachdem, um welche Daten es sich handelt und - was noch wichtiger ist - wo sie sich befinden. Also ging ich zurück zum Baum & hinzugefügt irgendwie zu "verfolgen", was die Elternlinie ist. "Dann würde der allgemeine Viewer nach dem spezifischsten tatsächlichen Widget suchen. Es verwendet hat eine Liste mit [Standort; Widget] Werte und findet den am besten passenden Ort

Die Probleme beginnen beim Update auf neue "Daten" - ich muss alle Assets durchsehen - Viewer, Saver etc etc .. Die Aktualisierung des Check-Mechanismus hat mir viele Fehler eingebracht wie "hey warum zeigt es das falsche Widget jetzt wieder an?".

Jetzt kann ich das komplett umtauschen. Und anstelle der Baumstruktur, die zu einem generischen Viewer aufruft, würde ich OO "interne" Baumfunktionen verwenden Knoten wären childs (& whe Wenn ein neuer Viewer oder Speichermechanismus benötigt wird, wird ein neues Kind gebildet.

Dies würde den schwierigen Prüfmechanismus entfernen, wo ich die Position in der Struktur überprüfe. Es könnte jedoch eine ganze andere Dose Würmer eröffnen. Und ich hätte gerne ein paar Kommentare dazu? Soll ich den Zuschauer komplett getrennt halten - Schwierigkeiten bei der Datenprüfung? Oder ist der neue Ansatz besser, kombiniert er doch Daten & Ausführung in einem einzigen Knoten. (Also, wenn ich wünsche von qt zu ändern cli/C# zu sagen, dass es fast unmöglich wird)

enter image description here

Welche Methode soll ich am Ende zu verfolgen? Kann ich noch etwas anderes machen? Um den Viewer getrennt zu halten und dennoch zu verhindern, dass Sie überprüfen müssen, welches Widget angezeigt werden soll?

EDIT, nur um etwas "Code" zu zeigen und wie mein Programm funktioniert. Ich bin mir nicht sicher, ob das gut ist, wie ich schon sagte, es ist zu einem Haufen von Methoden geworden.

Es ist beabsichtigt, mehrere "gamemaker projekte" zusammen zu verschmelzen (wie GM: Studio merkwürdig fehlt diese Eigenschaft). Gamemaker-Projektdateien sind einfach Gruppen von XML-Dateien. (Haupt-XML-Datei mit nur Links zu anderen XML-Dateien und eine XML-Datei für jede Ressource - Objekt, Sprite, Sound, Raum usw.).Es gibt jedoch einige "Macken", die es nicht wirklich möglich machen, mit Boost-Eigenschaften-Bäumen oder qt zu lesen: 1) Die Reihenfolge der Attribute/Kind-Knoten ist in bestimmten Teilen der Dateien sehr wichtig. und 2) Leerzeichen wird oft ignoriert, aber an anderen Stellen ist es sehr wichtig, es zu bewahren.

Das heißt, es gibt auch viele Punkte, wo der Knoten genau gleich ist. Wie ein Hintergrund <width>200</width> haben kann und ein Raum kann auch haben. Für den Benutzer ist es jedoch sehr wichtig, über welche Breite er spricht.

Anyways, so dass die "allgemeine viewer" (AskGUIFn) hat folgende typedefs zu handhaben:

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; 

Wo "GMProject :: ptree" ist ein Baumknoten, RESOURCE_TYPES ist eine konstante Spur zu halten Welche Art von Ressource bin ich im Moment (Sprite, Objekt usw.). Das "memberFn" wird hier einfach etwas sein, was ein Widget lädt. (Obwohl AskGUIFn natürlich nicht der einzige allgemeine Viewer ist, wird dieser nur geöffnet, wenn andere "automatische" Überschreib-, Überspring-, Umbenennungs-Handler fehlgeschlagen sind).

nun zu zeigen, wie diese Karten initialisiert werden (alles im Namensraum „MW“ ist eine qt-Widget):

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. 

Dann, wenn der Baum Datenstruktur des allgemeinen Betrachter sagt es der Betrachter das ausführt angezeigt werden möge folgende Funktion:

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

Und das ist, wo die Probleme offen zu sein begann. Die Funktion CallStackSerialize() hängt von einem Call-Stack ab. Dieser Call_stack wird jedoch in einem "Handler" gespeichert. Ich habe es dort gespeichert, weil alles von einem Handler beginnt. Ich bin mir nicht wirklich sicher, wo ich diesen "call_stack" speichern soll. Stellen Sie ein anderes Objekt vor, das verfolgt, was vor sich geht? Ich habe versucht, die Route zu gehen, wo ich die Eltern mit dem Knoten selbst speichern. (Verhindern der Notwendigkeit eines Call-Stack). Aber das lief nicht so gut wie ich es wollte: Jeder Knoten hat einfach einen Vektor, der seine Kindknoten enthält. Das Verwenden von Zeigern ist also nicht möglich, um auf die Eltern-Notiz zu zeigen ... (PS: vielleicht sollte ich das in einer anderen Frage reformieren ..)

+6

+1, für die Zahlen. Ohne die Frage zu lesen, sieht es einfach cool aus. – iammilind

+2

Kennen Sie den "[Inner Platform Effect] (http://en.wikipedia.org/wiki/Inner_platform_effect)"? Ich verstehe dein Problem nicht gut genug, um irgendwelche Schlüsse zu ziehen (und ich weiß natürlich nichts über deine Bewerbung!), Aber wenn du mit dem Begriff nicht vertraut bist, ist es ein bisschen Forschung wert. Schwierigkeiten, ein sehr komplexes System zu modifizieren, das ein Super-Allzweck-Framework sein soll, klingt wie ein Symptom dieses bestimmten Antipatterns ... – Rook

+0

ist es nicht, dass Ihre Datenknoten tatsächlich eine Art Profil benötigen, wenn sie erstellt werden, beschreibt das Profil wie um sie zu präsentieren, zu handeln und zu retten? – armel

Antwort

2

Das Refactoring/Umschreiben dieses komplizierten Mechanismus zur Standortüberprüfung aus dem Viewer in eine dedizierte Klasse ist sinnvoll, damit Sie Ihre Lösung verbessern können, ohne den Rest Ihres Programms zu beeinträchtigen. Lass uns das NodeToWidgetMap nennen.

Architektur
Scheint Ihre Position in Richtung einer Model-View-Controller Architektur, die IMO eine gute Sache ist. Ihre Baumstruktur und ihre Knoten sind die Modelle, wobei der Betrachter und die "Widgets" Ansichten sind und die logische Auswahl von Widgets abhängig von dem Knoten Teil eines Controllers ist.

Die wichtigste Frage bleibt, wann und wie Sie das Widget w N für einen bestimmten Knoten N und wie zu speichern, diese Wahl wählen.

NodeToWidgetMap: Wenn
wählen, ob Sie die w annehmen können N nicht während der Laufzeit auch nicht ändern, obwohl Knoten bewegt werden, man könnte es richtig wählen, wenn Sie den Knoten zu schaffen. Andernfalls müssen Sie den Speicherort (oder den Pfad durch das XML) kennen und folglich das übergeordnete Element eines Knotens finden, wenn Sie es anfordern.

Finding geordneten Knoten
Meine Lösung wäre Zeiger auf anstelle der Knoteninstanzen selbst, vielleicht boost::shared_ptr Verwendung zu speichern sein. Dies hat Nachteile, zum Beispiel das Kopieren von Knoten zwingt Sie, Ihre eigenen Kopierkonstruktoren zu implementieren, die Rekursion verwenden, um eine Tiefenkopie Ihres Unterbaums zu erstellen. (Das Verschieben wirkt sich jedoch nicht auf die untergeordneten Knoten aus.)

Es gibt Alternativen, z. B. das Beibehalten untergeordneter Knoten, wenn der Elternknoten bzw. der Großvätervektor berührt wird. Oder Sie können eine Node::findParentOf(node) Funktion definieren, die weiß, dass bestimmte Knoten nur (oder häufig) als Kind bestimmter Knoten gefunden werden können. Dies ist brutal, wird aber für kleine Bäume einigermaßen gut funktionieren, skaliert aber nicht sehr gut.

NodeToWidgetMap: Wie
Versuchen Sie, wählen die Regeln aufzuschreiben, wie w N auf Stück Papier, vielleicht nur zum Teil wählen. Versuchen Sie dann, diese Regeln in C++ zu übersetzen. Dies könnte etwas länger in Bezug auf Code sein, wird aber einfacher zu verstehen und zu pflegen sein.

Ihr aktueller Ansatz besteht darin, reguläre Ausdrücke für die Übereinstimmung mit dem XML-Pfad (Stapel) zu verwenden.

Meine Idee wäre, ein Lookup-Diagramm zu erstellen, dessen Kanten mit den XML-Elementnamen beschriftet sind und deren Knoten angeben, welches Widget verwendet werden soll. Auf diese Weise beschreibt Ihr XML-Pfad (Stack) eine Route durch das Diagramm. Dann stellt sich die Frage, ob ein Graph explizit modelliert werden soll oder ob eine Gruppe von Funktionsaufrufen verwendet werden könnte, um diesen Graphen zu spiegeln.

NodeToWidgetMap: Wo Wahl speichern
eine eindeutige numerische ID an jeden Knoten zuordnen, notieren Sie die Widget Wahl eine Karte von Knoten-ID mit im Inneren des NodeToWidgetMap Widget.

vs Refactoring Umschreiben
Wenn Sie Sie umschreiben Hebelwirkung gut bekommen könnte zu einem bestehenden Rahmen wie Qt, um auf das Programm zu konzentrieren tieing stattdessen die Räder neu zu schreiben.Es kann einfacher sein, ein gut geschriebenes Programm von einem Framework auf ein anderes zu portieren, als um die Eigenheiten jeder Plattform zu abstrahieren. Qt ist ein guter Rahmen um Erfahrungen zu sammeln und die MVC-Architekturen zu verstehen.

Eine vollständige Neuschreibung gibt Ihnen die Möglichkeit, alles zu überdenken, birgt jedoch das Risiko, dass Sie bei Null anfangen und für eine längere Zeit auf eine neue Version verzichten müssen. Und wer weiß, ob Sie genug Zeit haben, um fertig zu werden? Wenn Sie die vorhandenen Strukturen umstrukturieren, verbessern Sie diese Schritt für Schritt und haben nach jedem Schritt eine brauchbare Version. Aber es besteht ein geringes Risiko, in alten Denkgewohnheiten gefangen zu bleiben, während das Umschreiben Sie fast dazu zwingt, alles neu zu überdenken. Beide Ansätze haben ihre Vorzüge, wenn Sie gerne programmieren, würde ich es umschreiben. Mehr Programm, mehr Freude.

+1

Meh Ich werde definitiv umschreiben, um zu schreiben, denke ich .. Dieses Projekt zeigt wirklich eine Menge Veränderungen. Vorher war ich nur "C++/stl", aber ich habe nie etwas Großes gemacht. Jetzt lernte ich qt und viele andere Dinge, die ich brauchte (C++ 11). Ich kann den Unterschied im Codestil wirklich so sehen, dass ich mich dafür schäme. – paul23

+0

meh, sorry für zahlreiche Änderungen, scheint man in "Das Gesetz der Erhaltung der Schwierigkeit" zu finden –

+0

Hmm gut könnte ich eigentlich mit dem "nodekind" -Ansatz gehen; Knoten behalten ihren Typ (und werden nur zu anderen Bäumen hinzugefügt). Aber diese – paul23

1

Willkommen in der Welt der Programmierung!
Was Sie beschreiben, ist der typische Lebenszyklus einer Anwendung, beginnt als eine kleine einfache Anwendung, dann wird es mehr und mehr Funktionen, bis es nicht mehr wartbar ist. Du kannst dir nicht vorstellen, wie viele Projekte ich in dieser letzten Kollaps-Phase gesehen habe!
Müssen Sie umgestalten? Natürlich tust du! Die ganze Zeit! Müssen Sie alles neu schreiben? Nicht so sicher.
In der Tat ist die gute Lösung von Zyklen zu arbeiten: Sie entwerfen, was Sie Code benötigen, können Sie es codieren, Sie mehr Funktionalität benötigen, können Sie diese neue Funktionalität entwerfen, Umgestalten Sie den Code, so dass Sie den neuen Code integrieren, usw. Wenn du es nicht so machst, wirst du zu dem Punkt kommen, wo es weniger teuer ist, umzuschreiben als umzuformen. Holen Sie sich dieses Buch: Refactoring - Martin Fowler. Wenn Sie es mögen, dann erhalten Sie dieses: Refactoring zu Mustern.

0

Wie Pedro NF sagte, Martin Fowler "Refactoring" ist der schöne Ort, um sich damit vertraut zu machen.

0

empfehle ich eine Kopie von Robert Martins „Agile Prinzipien, Patterns and Practices in C#“ Kauf Er geht über einige sehr praktische Fallstudien, die zeigen, wie wie diese Wartungsprobleme zu überwinden.