Meinungen zu typeid?
-
rtti2011 schrieb:
Im Projektzusammenhang wurde so ein sehr kompliziertes Problem gelöst, das man imho hätte ohne diese Typprüfung nur viel komplexer hätte anders lösen können.
Ich wage zu bezweifeln dass es für dieses komplizierte Problem in diesem Projekt keine elegante Lösung gegeben hätte, aber um darüber diskutieren zu können, müsstest du uns das Problem erstmal genau schildern.
Im Allgemeinen würde ichtypeidwie auchdynamic_castals Symptom eines Designfehlers behandeln. Ausnahmen mag es geben, mir ist aber noch keine untergekommen
-
ich brauche es nur an einer Stelle, ist das jetzt auch schlechtes Design?

template<typename T> T* CreateObj () { CString typeName = CStringA2CString (typeid(T).name()); MCTraceLib (NULL, eTraceFull, TFCTX, _T("try to create object of '%s'"), typeName); ... return obj; }Herzliche Grüsse
Walter
-
weicher schrieb:
ich brauche es nur an einer Stelle, ist das jetzt auch schlechtes Design?

template<typename T> T* CreateObj () { CString typeName = CStringA2CString (typeid(T).name()); MCTraceLib (NULL, eTraceFull, TFCTX, _T("try to create object of '%s'"), typeName); ... return obj; }Herzliche Grüsse
WalterIch Trace sowas eigentlich nicht. Oder nur recht kurz. Kurz mal was machen kann man darf man alles.
typeid wäre nicht dringend notwendig.#define CREATE_OBJ(TYPENAME) \ { \ CString typeName = CStringA2CString (#TYPENAME); \ MCTraceLib (NULL, eTraceFull, TFCTX, _T("try to create object of '%s'"), typeName); \ return CreateObj<TYPENAME>(); \ }
-
weicher schrieb:
ist das jetzt auch schlechtes Design?

Ja:
- Du gibst einen rohen Pointer zurück. Das ist fehleranfällig.
- Du fummelst deine Tracing/Debugging-Ausgaben direkt in der CreateObject-Funktion zusammen. Das gehört ausgelagert.
zum typeid in der Situation: Für Debugzwecke kannst du natürlich mal ein typeid benutzen. Wenn du also das tracing irgedwo ein Stück weiter weg ausgelagert hast, darf dort im Debugbuild auch mal ein typeid stehen.
-
weicher schrieb:
ich brauche es nur an einer Stelle, ist das jetzt auch schlechtes Design?

Naja,
typeidfür Debugausgaben ist prinzipiell sicher Ok. Allerdings würd ich den Namen wohl einfach hardcoden, weil ich da wohl plattformübergreifend konsistenten Output haben will und wastypeid(T).name()zurückliefert, ist zur Gänze dem Compiler überlassen.
-
@pumuckl:
Wie willst du das Tracing auslagern?
-
hustbaer schrieb:
@pumuckl:
Wie willst du das Tracing auslagern?Indem ich für das Tracing der gezeigten Funktion eine eigene Funktion schreibe, die je nach Debug- oder Releasebuild die gezeigten zwei Zeilen oder garnichts ausführt. Der typeName-String und die ganzen Parameter, die da an die TarceLib übergeben werden, haben mit der eigentlichen Aufgabe der Funktion nichts zu tun und gehören deshalb nicht dort rein.
-
Hallo Pumuckl,
ich kann natürlich nur aus meiner Aufgabenstellung und Erfahrung erzählen.
Die Tracing Funktionen müssen bei uns im Debug und Release funktionieren.
Wir haben drei TraceFunktionen MCTraceLib für das Tracing aus der Library, MCTraceDLL für das Tracen aus einer DLL und MCTraceApp für das Tracen aus der Applikation.
Alle 3 Funktionen werden vom Tracer zur Verfügung gestellt und können einzeln ein- und ausgeschaltet werden. Zusätzlich kann das Tracing für jede Klasse welche von unserer Root Klasse abgeleitet ist ein- und ausgeschaltet werden. Und das immer während der Laufzeit der Applikation.
Der Grund für dieses Verfahren, wir haben neben dem Werk in der Schweiz auch Werke in Thailand und China wenn sie da ein Problem haben, dann schalten sie das Tracing ein und schicken uns dann die Datei (kommt zum Glück sehr selten vor
).Ich möchte mal sehen, wie Du die schon ausgelagerte Tracingfunktion noch weiter auslagerst. Abgesehen davon dass ich nichts davon halte das zu machen, es erschwert nur die Fehlersuche.
Ich habe früher ganz nach der reinen Lehre zB. auch alle Konstanten am Anfang der Sourcefiles deklariert. Auch die Command Sequenzen für Messgeräte. Das hat sich aber über die Jahre als grössten Schwachsinn herausgestellt.
Heute stehen die genau da wo sie auch an das Messgerät gesendet werden. Jetzt kann ich mit unserem Messguru den Source ansehen und er sieht auf den ersten Blick, ohne verwirrendes hin- und herblättern, ob ich da die richtigen Kommandos sende.
Herzliche Grüsse
WalterPS. Immer daran denken, 2 Programmierer => 4 Meinungen

-
Ok, dann fällt die Unterscheidung Release/Debug weg - ändert aber nicht am Rest.
Ich weiß nicht, was eTraceFull und TFCTX sind, d.h. ob sie nur direkt in der Funktion zugänglich sind oder anderweitig übergeben werden können. Ich halte mein Beispiel mal so, dass eTRaceFull auch außerhalb von CreateObj erreichbar ist, TFCTX aus irgendeinem Grund aber nicht - z.B. weil es ein Macro ist, das die Zeilennummer oder sonstwas enthält.template <typename T> void TraceCreateObj(TFCTX_type t) //z.B. private in der gleichen Klasse wie CreateObj() { CString typeName = CStringA2CString (typeid(T).name()); MCTraceLib (NULL, eTraceFull, t, _T("try to create object of '%s'"), typeName); } template<typename T> T* CreateObj () { TraceCreateObj<T>(TFCTX); ... return obj; }Was hab ich dadurch gewonnen? Wenn ich als Maintainer oder beim Debuggen durch den CreateObj-Code lese, sehe ich in einer Zeile, was hier passiert, nämlich das Tracing. Mehr brauche ich normalerweise nicht zu wissen. Ich werde nicht mit meist unnötigen Details belastet wie dem genauen Text des Tracings, der Methode, nach der der auszugebende Typname ermittelt wird und was genau an Parametern an die Trace-Bibliothek geliefert wird. z.B. brauch ich mir keinen Kopf zu machen, welche Bedeutung das NULL als erster Parameter hat. Das ist ein Detail des Traicings, nicht der Objekterzeugung selbst.
Wenn nur das Tracing von Interesse ist, habe ich auch etwas erreicht: Ich weiß beim Debuggen/Lesen genau, dass nur die zwei Zeilen zum Tracing gehören und muss mich nicht durch den Rest der CreateObj-Funktion mühen, um festzustellen, ob nicht doch eine der Zeilen für mich von Interesse ist.Siehe auch http://en.wikipedia.org/wiki/Separation_of_concerns und http://en.wikipedia.org/wiki/Single_responsibility_principle
-
Hallo Pumuckl,
Was hab ich dadurch gewonnen?
Von mir aus gesehen rein gar nichts sondern nur verloren und das nur weil irgendjemand eine Regel aufgestellt hat...
TFCTX ist ein Makro und steht für "Trace Function ConTeXt", wir habe auch ein Makro für Fehlermeldungen das heisst FCTX für "Function ConTeXt", bei FCTX wird zusätzlich der Name der Sourcedatei und die Zeilenummer ausgegeben.
In der Tracedatei steht zB.
[Timestamp]CreateObj;try to create object of 'XY_Table'
[Timestamp]CreateObj;creation of 'XY_Table' failed!Mit Deiner Version des Trace Aufrufs:
TraceCreateObj<T>(TFCTX);geht jetzt die sucherei los wo die Texte 'try to create object of 'XY_Table'' und 'creation of 'XY_Table' failed! herkommen.
Und so wie ich das sehe, muss man, als weiteren Nachteil bei Deiner Version, für jeden Tracing Text eine eigene Funktion schreiben.
Herzliche Grüsse
Walter
-
weicher schrieb:
nur weil irgendjemand eine Regel aufgestellt hat...
Ich mach sowas nicht wegen irgendwelcher Regeln, sondern um den Code übersichtlich zu halten. Es lohnt sich.
TFCTX ist ein Makro und steht für "Trace Function ConTeXt", wir habe auch ein Makro für Fehlermeldungen das heisst FCTX für "Function ConTeXt", bei FCTX wird zusätzlich der Name der Sourcedatei und die Zeilenummer ausgegeben.
Also genau die Situation, die ich für mein Beispiel oben angenommen habe.
In der Tracedatei steht zB.
[Timestamp]CreateObj;try to create object of 'XY_Table'
[Timestamp]CreateObj;creation of 'XY_Table' failed!Mit Deiner Version des Trace Aufrufs:
TraceCreateObj<T>(TFCTX);geht jetzt die sucherei los wo die Texte 'try to create object of 'XY_Table'' und 'creation of 'XY_Table' failed! herkommen.
Nicht mehr als vorher auch. Wenn du nur den Text hast, weißt du im aktuellen Zustand auch nicht, wo die herkommt. Du schaust dir aber den Funktionsnamen an und weißt wo du hinschauen musst. Wunderbarerweise wird der durch das Makro TFCTX generiert, das ich deshalb auch absichtlich in der Funktion CreateObj aufrufe und das Ergebnis an die Tracefunktion übergebe. Die Ausgabe ist also in beiden Fällen die selbe, man weiß wo der Trace-Aufruf steht, und wenn ihr nicht grade 10 Trace-Aufrufe pro Funktion habt, findet man auch sofort, welcher Trace-Aufruf das wohl gewesen sein wird.
Und so wie ich das sehe, muss man, als weiteren Nachteil bei Deiner Version, für jeden Tracing Text eine eigene Funktion schreiben.
Jup. Der Nachteil besteht aus einem Funktionskopf, zwei geschweiften Klammern und dem Funktionsaufruf. Die zwei Zeilen Code in der Funktoin schreibt man ja so oder so. Wenn euer Code nie gelesen wird, ist das natürlich merklicher Mehraufwand. In dem konkreten Fall ca. 90 Anschläge. Wenn der Code häufig gelesen wird, geht der relative Aufwand gegen null, zumal man jedesmal eine Zeile Leserei mit unnötigen Infos einspart.
Das Ganze amortisiert sich natürlich sofort, wenn man die selbe Tracingmeldung/Fehlermeldung etc an zwei oder mehr Stellen hat. Aber ich geh mal davon aus, dass ihr dann sowieso eine Funktion auslagert und nicht einen Copy&Paste-Wartungsalptraum pflegt?
Davon abgesehen ist natürlich auch die Frage, wie redundant die Tracingmeldungen aussehen sollen. Deine Ausgabe "CreateObject; try to create object" zeigt das schon (was soll in einer Funktion die so heißt anderes passieren....) - Ich würde in meiner Version den Text abändern in "entered function for type %s" - und schon könnte ichs für viele Teplates benutzen. Oder ich verallgemeinere die Funktion als "TraceFunctionTemplate" und gebe ihr den Format-text mit dem Platzhalter für den Typnamen mit. Dann habe ich immernoch die ewig gleiche Methode der typname-Ermittlung, die NULL und das eTraceFull ausgelagert und weggekapselt und Übersichtlichkeit gewonnen.
-
@pumuckl:
Dein Tausch sieht folgendermassen:
-1 Zeile beim Aufruf
+6 Zeilen & +1 Funktion an anderer StelleFür mich geht diese Rechnung nicht auf.
Tracing/Logging Code ist nach meiner Erfahrung meist so eng mit dem tracenden/loggenden Code verwoben, dass man durch Auslagerung in hochspezialisierte Hilfsfunktionen kaum was verbessern kann.
Falls die zwei Zeilen in weiteren Funktionen wiederholt werden, OK, dann zahlt es sich vielleicht aus. Falls es wirklich nur diese eine Stelle ist, dann eher nicht.
p.S.: ich definiere solche Funktionen meist in nameless Namespaces. D.h. es kommen nochmal 2-3 Zeilen für den Namespace dazu. Bzw. wenn man eine Memberfunktion nimmt, dann kommt nochmal eine Zeile im Header-File dazu.
-
Hallo zusammen,
ich habe diesen Thread mal meinem Mitarbeiter gezeigt.
Sein Kommentar:
"Mann, wenn man keine Probleme hat, kann man sich auch welche suchen! Aber wenn wir gerade dabei sind, in der Core.Lib und der Common.Lib haben wir seit über einem Jahr keine Fehler mehr gefunden, da könnte man die Traces jetzt löschen".Das habe ich dann auch gemacht.
Für mich hat sich damit das Thema erledigt, es gibt sinnvolleres zu tun als über eine Funktionalität zu streiten welche sowieso nur für den Programmierer nützlich ist.
Herzliche Grüsse
Walter
-
weicher schrieb:
geht jetzt die sucherei los wo die Texte 'try to create object of 'XY_Table'' und 'creation of 'XY_Table' failed! herkommen.
Ach, deswegen ist TRACE ein Makro.
Bei Dir eben TRACE_LIB, TRACE_DLL und TRACE_APP.
-
Nennt mich meinetwegen übertrieben idealistisch - ich fahr damit ganz gut

-
weicher schrieb:
Hallo zusammen,
ich habe diesen Thread mal meinem Mitarbeiter gezeigt.
Sein Kommentar:
"Mann, wenn man keine Probleme hat, kann man sich auch welche suchen! Aber wenn wir gerade dabei sind, in der Core.Lib und der Common.Lib haben wir seit über einem Jahr keine Fehler mehr gefunden, da könnte man die Traces jetzt löschen".Das habe ich dann auch gemacht.
Ich bin ratlos, worin die Ironie sein soll
