Softwarearchitektur: Abhängigkeiten in normalen Funktionen



  • Hallo Leute,

    ich möchte meine Softwarearchitektur verbessern und bräuchte ggf. ein paar Tipps 😕 von euch.
    Folgendes... Neben meinen Klassen habe ich auch eine Vielzahl von Funktionen, die nur über namespaces gruppiert sind.

    etwa so:

    namespace meineFunktionen
    {
    	void fkt1()
    	{
    		[...]
    		if (something == wrong)
    		{
    			throw meineException(...);  // 1. Problem: irgendwelche Abhängigkeiten
    		}
    		[...]
    	}
    
    	void fkt2()
    	{
    		[...]
    		if (something == wrong)
    		{
    			throw meineException(...);
    		}
    		[...]
    	}
    
    	int fkt3()
    	{
    		[...]
    		fkt1(); // 2. Problem: Aufruf von anderen Funktionen
    		[...]
    
    		if (something == wrong)
    		{
    			throw meineException(...);
    		}
    		[...]
    	}
    
    	[...]
    }
    
    void main()
    {
    	[...]
    	int irgendwas = meineFunktionen::fkt3();
    	[...]
    }
    

    Nun habe ich in den Funktionen dummerweise 2 Probleme die ich gerne bereinigen möchte:

    1. Ich habe in den Funktionen bestimmte Abhängigkeiten drin. Im Codebeispiel ist es irgendeine benutzerdefinierte Exception. Es könnte aber auch ein Logger oder etwas anderes sein. Kann ich da irgendetwas machen? Beim Aufruf jedesmal die Abhängigkeit als Parameter zu Übertragen scheint mir nicht so praktikabel zu sein. Ich könnte auch alle Funktion in einer Klasse kapseln und dann via Dependency Injection diese Anhängigkeiten übergeben. Ist das sinnvoll? In meinem Falle sind das so ca. 50 Funktionen pro Gruppierung, die alle keine Membervariablen bräuchten.

    2. Einige wenige Funktionen greifen auf andere Funktionen der gleichen oder einer anderen Gruppe (namespace) zu. Das habe ich irgendwie mal gemacht, um den Code nicht unnötig doppelt zu haben. Aber das kommt mir auch nicht so gut vor. Eine größere Änderung einer Funktion könnte somit andere Funktionen beeinflussen und zu Fehlern führen. Wie könnte ich denn da vorgehen?

    viele Grüße,
    SBond



  • SBond schrieb:

    1. Ich habe in den Funktionen bestimmte Abhängigkeiten drin. Im Codebeispiel ist es irgendeine benutzerdefinierte Exception. Es könnte aber auch ein Logger oder etwas anderes sein. Kann ich da irgendetwas machen? Beim Aufruf jedesmal die Abhängigkeit als Parameter zu Übertragen scheint mir nicht so praktikabel zu sein.

    Es ist erstmal überhaupt nicht ersichtlich, was diese Abhängigkeiten sind und ob man da überhaupt was machen muss. Abhängigkeiten sind an sich erstmal völlig in Ordnung und es gibt überhaupt keinen Grund, auf Teufel komm raus zu versuchen, alles noch komplizierter zu machen, indem man jede Kleinigkeit lose koppelt.
    Das macht erst in einem größeren Zusammenhang Sinn. Wenn du ein größeres "Modul" oder Bibliothek hast, die auch in einem evtl. anderen Kontext verwendbar sein könnte, wäre es vorteilhaft, wenn man nicht irgendwelche Abhängigkeiten aufgezwungen bekommt. Das seh ich bei paar einzelnen Funktionen in einem Namespace aber noch nicht. Und auch nicht, wenn sich das alles innerhalb von einem Programm abspielt. Write programs, not frameworks.



  • Gerade beim Refactoring können voreilig eingegangene enge Kopplungen empfindlich lästig fallen. Da kann es sich je nach Fall lohnen, erst einmal Kopplungen zu lösen, Methoden in freie funktionen zu wandeln und Members in Argumente.



  • Das war auch nur ganz allgemein. Könnte ja sein, dass es für so etwas ein bestimmtes vorgehen gibt. Es ist ja auch eine Sache der Testbarkeit einzelner Funktionen.
    Die Abhängigkeiten sind bei in der Regel Logger, Debugausgaben oder eigene Exceptions.

    ...möglicherweise denke ich wirklich zu kompliziert.



  • SBond schrieb:

    ...möglicherweise denke ich wirklich zu kompliziert.

    Muss nicht sein... Aber man muss da schon unterscheiden, ob sich das lohnt oder nicht.
    Bei Logging versteh ich das schon... Irgendwie zumindest. Es wäre vielleicht nicht schlecht, eine eigene Logger Schnittstelle reinzugeben, die man unterschiedlich implementieren kann. Vor allem, wenn die Funktionen wirklich an unterschiedlichen Stellen gebraucht werden, vielleicht sogar in unterschiedlichen Projekten. Bei einem Projekt kann man aber denke ich damit leben, einen Logging Mechanismus festzulegen. Wir verwenden log4cxx. Wenn man irgendwo was loggen will, dann nimmt man das halt. Kommt notfalls auch bei Unit Tests was ins Log. Da schauen wir teilweise sogar drauf, ob die Unit Tests nicht irgendwelche Fehler im Log produziert haben. Muss man jetzt auch nicht über tausende Klassen und Komponenten irgendwelche Logging Schnittstellen durchreichen. Und notfalls könnte man log4cxx auch "umkonfigurieren" und eigene Appender reinhängen, wenn man unbedingt will.
    Bei eigenen Exceptions seh ich das aber grad gar nicht. Wenn deine "Funktion" bestimmte Exceptions definiert, die sie schmeißt, ist das eben so. Warum sollte der Benutzer eigene Exceptions haben wollen?



  • Das ganze ist ein schwieriges Thema. Zumindest wenn man kein "evangelist" ist 😃

    Logging per explizit mitgegebenem Logger zu machen hat definitiv Vorteile. z.B. tut man sich leichter wenn man Unit-Tests schreiben will - bzw. allgemein automatisierte Tests. Einerseits kann man die Tests beschleunigen wo Log-Ausgaben nicht erwünscht sind - indem man eben keine schreibt (Logger mitgeben der einfach nix tut). Und andrerseits kann man einen Logger mitgeben der z.B. mitzählt wie viele Ausgaben eines bestimmten Typs ausgegeben wurden (z.B. Fehler), oder wie viele Ausgaben die bestimmte Teilstrings enthalten, einer bestimmten Regex entsprechen oder was auch immer. Bzw. einen Logger der die Daten einfach nur in einer Liste im RAM puffert damit man dann danach bestimmte Dinge im Test asserten kann.
    Das ganze geht natürlich auch mehr oder weniger gut mit einem zentralen Logger, aber eben auch nur mehr oder weniger gut. Sobald man dann mehrere Tests multi-threaded laufen lassen möchte wird es schon schwierig, und wenn man erstmal bei den Tests Worker-Thread-Pools verwendet aus denen auch geloggt wird, wird es ... naja zumindest sehr lästig. Wenn nicht gar gänzlich unpraktikabel.

    Dem gegenüber steht der Mehraufwand die quasi omnipresente Dependency "Logger" überall mitzugeben -- bald benötigt jede noch so kleine freie Funktion nen Logger-Parameter. Das nervt natürlich.

    Als "Zwischenlösung" bietet sich vielleicht Thread-Local-Storage an. D.h. man setzt den Logger in den Thread-Local-Storage, und wer ihn braucht, der holt ihn sich von dort. Hat aber natürlich auch Nachteile. Wenn man beim Erzeugen von Threads nicht immer den Logger neu setzen möchte, muss man sich 'was einfallen lassen mit dem man den Logger des Parent-Thread automatisch an den Child-Thread "vererben" kann. Ebenso muss man sich u.U. 'was basteln um in einem bestimmten Abschnitt, wo man z.B. in einem "geborgten" Thread läuft den Logger auf den TLS drauf und möglichst automatisch bei Verlassen des Scopes wieder runter nimmt. (Gut, das ist bloss ne kleine RAII Helper-Klasse, keine Tragik.)
    Dann geht es weiter mit Fällen wo z.B. Work-Items aus mehreren Quellen vom selben Worker-Thread-Pool bearbeitet werden müssen. Auch hier muss man dann irgendwie den Logger des "Erzeugers" des Work-Items mit kommunizieren und für die Dauer des Work-Items auf den TLS setzen (und danach wieder entfernen).

    Das .NET Framework bietet für solche Fälle z.B. die Klasse CallContext an, siehe z.B. http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html
    In C++ ist mir kein Mechanismus bekannt der dafür geeignet wäre. Allerdings kenne ich die neueren Standards auch nicht ausreichend, mag sein dass es etwas gibt.

    ---

    Was Exceptions angeht... ich bin nicht sicher ob ich dich richtig verstehe. Ich sehe zwei Möglichkeiten.

    1. Die "Dependency" die du da erwähnst betrifft bzw. ändert irgendwie den Exception-Typ. Das halt ich für fragwürdig - siehe Antwort von Mechanics.

    2. Die "Dependency" die du da erwähnst betrifft irgendwelche Daten die mit in die Exception reingesteckt werden sollen. Das könnte mMn. schon eher Sinn machen. Also z.B. wenn man die Exception nicht an 100 Stellen fangen und dann eine mit Zusatzinformationen versehene Exception weiterwerfen will. Wobei man auch das als fragwürdig ansehen kann. Zumindest in dem Sinn dass man nochmal drüber nachdenken sollte und Alternativen in Betracht ziehen. Könnte es z.B. reichen statt dessen einen Callstack zu ziehen? (Geht nicht portabel, aber es gibt Libraries mittels derer man es auf verschiedenen Plattformen ohne all zu viel Aufwand hinbekommt.) Usw.

    ---

    tl;dr: Kommt drauf an. Und zwar auf die Art der Dependency. Und was für Code in welchem Umfeld man entwickelt.



  • Mechanics schrieb:

    Es ist erstmal überhaupt nicht ersichtlich, was diese Abhängigkeiten sind und ob man da überhaupt was machen muss. Abhängigkeiten sind an sich erstmal völlig in Ordnung und es gibt überhaupt keinen Grund, auf Teufel komm raus zu versuchen, alles noch komplizierter zu machen, indem man jede Kleinigkeit lose koppelt.

    Ich kann zumindest aus meiner Erfahrung sagen das jedes gescheiterte Projekt oder jedes Projekt in dem die Entwicklung immer langsamer wurde, keine oder kaum lose Kopplung und immer "Pragmatismus" als Totschlagargument verwendet hat.

    Ich würde die Entscheidung zwar auch nicht explizit ohne weitere Angaben fällen, aber allgemein sagen: Je kleiner das Projekt umso unproblematischer sind Abhängigkeiten, je größer umso gefährlicher werden sie (bis hin das ein Neuschreiben einer Anwendung billiger ist, als eine Fehlerbehebung).

    Und gerade bei großen Projekten (oder solchen, die vermutlich groß werden) würde ich heutzutage TDD und Unittests einsetzen, und Paradigmen wie SOLID und Co einhalten wenn möglich. Und gerade Unittests und SOLID führen zwangsläufig zu einer lose Kopplung.



  • Wow. Vielen Dank für die umfangreichen Antworten. Es ist wirklich gar nicht so einfach.

    Ich zeige mal was ich so habe...
    ...verzeiht den schlechten Aufbau 😞

    int getExtendedKeyUsageFlagCount(X509 *x509)
    	{
    		// function tracker for debugging
    		FUNCTION_TRACE;
    
    		int extIndex = 0;
    		X509_EXTENSION *ext = nullptr;
    		EXTENDED_KEY_USAGE_Guard eku(nullptr);
    		int ekuFlagsNum = 0;
    
    		if (x509 == nullptr)
    		{
    			NTS_EXCEPTION("A passed parameter is invalid.", "parameter 'x509' is a nullptr");
    		}
    
    		extIndex = X509_get_ext_by_NID(x509, NID_ext_key_usage, -1);
    		if (extIndex == -1)
    		{
    			NTS_EXCEPTION("The certificate extension 'Extended Key Usage' couldn't be found.", "external function failed");
    		}
    
    		ext = X509_get_ext(x509, extIndex);
    		if (ext == nullptr)
    		{
    			NTS_EXCEPTION("Failed to get the X.509 extension from the certificate.", "variable 'ext' is a nullptr");
    		}
    
    		eku.reset(static_cast<EXTENDED_KEY_USAGE*>(X509V3_EXT_d2i(ext)));
    		if (eku == nullptr)
    		{
    			NTS_EXCEPTION("Failed to decode the X.509 extension.", "variable 'eku' is a nullptr");
    		}
    
    		ekuFlagsNum = sk_ASN1_OBJECT_num(eku.get());
    		if (ekuFlagsNum < 0)
    		{
    			NTS_EXCEPTION("The number of flags in the stack couldn't be determined.", "external function failed");
    		}
    
    		return ekuFlagsNum;
    	}
    

    ...das Makro...

    #ifndef THIS_LINE
    #define THIS_LINE (std::string(__FILE__) + ";" + __FUNCTION__ + ";" + std::to_string(__LINE__)) ///< the error source in string form
    #endif
    
    #define NTS_EXCEPTION(description, cause)\
    {\
    	std::stringstream s;\
    	s << description << "   [" << cause << "]";\
    	NtsLogger().log("FATAL", s.str(), THIS_LINE);\
    	throw s.str();\
    }
    

    Der Code der oben zu sehen ist, entstand ziemlich am Anfang meines aktuellen Projekts. Zu diesem Zeitpunkt hatte ich erst 2-3 Monate Erfahrung mit C++ und dementsprechend ist auch der Aufbau nicht besonders. ...Zumindest hatte ich zu diesem Zeitpunkt das RAII-Idiom eingepflegt. Es ist jetzt eine von ca. 150 Funktionen, die alle die selbe Struktur verfolgen und zumindest ...naja... optisch konsistent sind. Dieses Gefrickel mit meiner NTS_EXCEPTION war eine Notlösung, da ich es zu diesem Zeitpunkt einfach nicht besser wusste. Die einzelnen Funktionen sind über namespaces gruppiert und werden von Klassen einer höheren Abstraktionsebene verwendet.

    Da ich an der Fachhochschule bin, habe ich die Möglichkeit ohne Termin- oder Kundendruck meine Software zu verbessern. Habe einige Bücher gelesen, aber oft sind es dann eben doch besondere Situationen, in denen mir auch Bücher nicht helfen und mir noch die Erfahrung fehlt. Im Code oben ist noch so einiges was ich ändern würde, aber nur die Exceptions bereiten mir erneut Probleme. Aus aktueller Sicht möchte ich feste Verbindungen im Code lösen und durch lose Kopplungen ersetzen. Das Makro würde ich an dieser Stelle entfernen und es durch eine Exception-Klasse ersetzen. Bin mir ehrlich gesagt noch nicht sicher ob es mir was bringt, aber ggf. kann ich implizit besser loggen oder die Ausgaben steuern. ...ich weiß es nicht 😕

    ...Anstatt der Exception hier, könnte es genau so gut ein Loggger sein, der eingebaut wird. Da hat man dann solche Überlegungen was man mit diesen Abhängigkeiten macht.

    hustbaer schrieb:

    Dem gegenüber steht der Mehraufwand die quasi omnipresente Dependency "Logger" überall mitzugeben -- bald benötigt jede noch so kleine freie Funktion nen Logger-Parameter. Das nervt natürlich.

    ...ja genau das ist der Punkt 😉
    Von Thread-Local-Storage habe ich bis jetzt noch nie gehört, aber ich werde mal stöbern.

    ...möglicherweise mache ich mich wieder verrückt und in simplen Funktionen ist es total unkritisch. Allerdings versuche ich auch mögliche Tests zu berücksichtigen. ...auch da bin ich zugegebenermaßen Amateur und habe noch nie eine Testumgebung von innen gesehen. Bis jetzt habe ich nur einfache quick-and-dirty unit-tests gemacht und einige memcheck-Überprüfungen mit Vallgrind.

    Auf jeden Fall nochmals Danke dass ihr euch Zeit für mich genommen habt 🙂
    Ich weiß jeden Rat zu schätzen und manchmal ist es ja nur ein Schlüsselwort, das die Entscheidung bringt 😉

    viele Grüße,
    SBond



  • SBond schrieb:

    hustbaer schrieb:

    Dem gegenüber steht der Mehraufwand die quasi omnipresente Dependency "Logger" überall mitzugeben -- bald benötigt jede noch so kleine freie Funktion nen Logger-Parameter. Das nervt natürlich.

    ...ja genau das ist der Punkt 😉
    Von Thread-Local-Storage habe ich bis jetzt noch nie gehört, aber ich werde mal stöbern.

    Das ordentlich zu machen ist halt auch *einiges* an Aufwand. Und man kann auch viele Fehler damit machen. Vermutlich viel mehr als wenn du der einfach nen Logger* mitgibst.



  • (ich kenne im Java/C#-Bereich keine einzige Software die den Logger nicht statisch bezieht, alle Log-Frameworks sind auch darauf ausgelegt, um das DI-Problem "switch the implementation" anzugehen wird meistens eine Fassade ala slf4j verwendet ... ich bin persönlich großer Fan von DI und IoC, aber auch ich beziehe alle Logger statisch via LogManager in ein statisches Klassenfeld)

    MfG SideWinder



  • hustbaer schrieb:

    Als "Zwischenlösung" bietet sich vielleicht Thread-Local-Storage an. D.h. man setzt den Logger in den Thread-Local-Storage, und wer ihn braucht, der holt ihn sich von dort. Hat aber natürlich auch Nachteile. Wenn man beim Erzeugen von Threads nicht immer den Logger neu setzen möchte, muss man sich 'was einfallen lassen mit dem man den Logger des Parent-Thread automatisch an den Child-Thread "vererben" kann. Ebenso muss man sich u.U. 'was basteln um in einem bestimmten Abschnitt, wo man z.B. in einem "geborgten" Thread läuft den Logger auf den TLS drauf und möglichst automatisch bei Verlassen des Scopes wieder runter nimmt. (Gut, das ist bloss ne kleine RAII Helper-Klasse, keine Tragik.)

    Ja, ich bin neulich auch über ein ähnliches Problem gestolpert. Nicht mit Loggern, aber andere Objekte, die so ähnlich überall benutzt werden. Hab mir zuerst gedacht, ganz einfach, steck die Teile ins TLS. Und dann nach paar Minuten Nachdenken gemerkt, dass es in dem Fall viel komplizierter war. Grad sowas wie Parent Thread muss es an die erzeugten Threads weitergeben. Und dann gibts auch noch Cross-Thread Calls, was will ich in dem Fall überhaupt haben? Und dann könnte es noch passieren, dass der ursürüngliche Thread früher beendet wird, als irgendwelche Threads, die noch diese Objekte benutzen, was mache ich damit? Hab im Endeffekt keine brauchbare Lösung gefunden.



  • Ich halte es etwas fraglich einen stringstream mittels throw zu werfen. Nach diversen Coding-Guidelines sollte nur std::exception oder abgeleitete Typen geworfen werden. Ausserdem bietet std einige vorgefertigte an, z.B. runtime_error oder logic_error. Und wenn es sich hier um die Authentifizierung handlet, dann kann diese schonmal fehlschlagen, und es ist zu ueberlegen, ob eine Exception gerechtfertigt ist.

    Ansonsten bin ich dazu uebergegangen nicht nur zu loggen, sondern meine Applikationen zu ueberwachen. D.h. Schluesselindikatoren fuer Performance sind in shared memory abgelegt und werden durch einen anderen Prozess zu ausgewerten. Dazu zaehlt auch eine In-Shared-Memory-Console mit 128 MByte. Ein kleiner Serviceprozess haelt diese dauerhaft offen. Es wird Monostate-Pattern benutzt. Selbst halte ich nicht viel von LogManager, TextureManager: link



  • @SideWinder
    Ja kommt drauf an was man unter "dem Logger" versteht. Das Ding wo im Prinzip nur drinnen steht wie der Logger heisst und vielleicht ab welchem Log-Level er denn loggt, das kann ruhig statisch sein.

    Im Prinzip geht es um die "Sink" von der dann letzten Endes die Messages rausgeschrieben (bzw. halt allgemein: konsumiert) werden. Diese, sowie diverse Settings (aktiver Log-Level, vielleicht noch Formatierung, diverse Zusatzinfos die mit jeder Zeile rausgeloggt werden sollen, ...) sollte IMO zumindest thread-local sein.



  • Mechanics schrieb:

    Ja, ich bin neulich auch über ein ähnliches Problem gestolpert. Nicht mit Loggern, aber andere Objekte, die so ähnlich überall benutzt werden. Hab mir zuerst gedacht, ganz einfach, steck die Teile ins TLS. Und dann nach paar Minuten Nachdenken gemerkt, dass es in dem Fall viel komplizierter war.

    Japp. Alles nicht so einfach 🙂

    Mechanics schrieb:

    Grad sowas wie Parent Thread muss es an die erzeugten Threads weitergeben. Und dann gibts auch noch Cross-Thread Calls, was will ich in dem Fall überhaupt haben?

    Naja, bei Cross-Thread Calls will man üblicherweise dass das Ding vom Caller temporär das Ding vom Callee "überlagert".

    Mechanics schrieb:

    Und dann könnte es noch passieren, dass der ursürüngliche Thread früher beendet wird, als irgendwelche Threads, die noch diese Objekte benutzen, was mache ich damit?

    Mögliche Ansätze:

    1. Ref-Counting (ob über shared_ptr oder wie auch immer), idealerweise dann in Verbindung mit "immutable"
    2. Cloneable

    Mechanics schrieb:

    Hab im Endeffekt keine brauchbare Lösung gefunden.

    Ja. Eines der Probleme is auch dass es keinen Standard gibt, und es daher nicht von diversen Libs unterstützt wird. Weil es sollte ja natürlich im Idealfall alles automatisch gehen, also wenn man nen Work-Item über Library X in Pool Y queued, dann sollte das Ding da mitwandern.

    Und wenn die Library X dann keine passenden "Erweiterungspunkte" hat, wo man sich einklinken könnte, dann muss man wieder wrappen oder im schlimmsten Fall an den Stellen wo 'was wandern sollte wieder alles mit Hand machen.



  • knivil schrieb:

    Ich halte es etwas fraglich einen stringstream mittels throw zu werfen. Nach diversen Coding-Guidelines sollte nur std::exception oder abgeleitete Typen geworfen werden. Ausserdem bietet std einige vorgefertigte an, z.B. runtime_error oder logic_error.

    Ja das werfen von stringstream ist in der tat keine gute Idee und muss noch geändert werden.

    knivil schrieb:

    Und wenn es sich hier um die Authentifizierung handlet, dann kann diese schonmal fehlschlagen, und es ist zu ueberlegen, ob eine Exception gerechtfertigt ist.

    Das ist immernoch ein kompliziertes Thema für mich, obwohl ich mich intensiv damit beschäftigt habe ....Rückgabewerte vs Exceptions. Da gibt es scheinbar viele Meinungen und ob meine Umsetzung ok ist, ist fraglich. Wenn man sich fremde Codeprojekte anschaut findet man leider keine Lösung die von vielen Entwicklern bevorzugt werden. ....oder meine Sicht der Dinge ist verzerrt. ...wer weiß.

    In 10 Jahren kann ich über meine Foreneinträge bestimmt nur noch müde lächeln (hoffe ich).



  • Nö, ist nicht schwierig ob Exception oder Rückgabewert.

    checkPassword(string pwd);
    

    Exception oder Rückgabewert? Rückgabewert, z.B. Boolean. Denn ein falsches Passwort ist nicht unerwartet und kein Ausnahmefall. Wird ja explizit danach geprüft.

    tryLogin(string user, string pwd);
    

    Rückgabewert, weil der Versuch des Logins erfolgreich oder fehlschlagen könnte, das sagt die Funktion aus. Ob da ein gültiges Login-Objekt, Session-Objekt o.ä. zurück gegeben wird, oder ein Boolean, ist ein anderes Thema.

    login(string user, string pwd);
    

    Exception, weil der Funktionsname nichts von einem möglichen Fehlschlag erzählt.

    Eine Exception ist für unerwartete Ausnahmesituationen. D.h. auch das vorletzte Beispiel (tryLogin) könnte eine Exception werfen. Sie könnte z.B. eine LostConnection-Exception werfen, etwas was unerwartet ist und mit dem Login selbst nichts zu tun hat.
    Diese Ausnahme würde aber für alle anderen genannten Funktionen auch gelten!

    Wenn du einen gültigen Printer-Handler hast, und du in einem zweiten Schritt ein Dokument mit diesem Printer-Handler drucken willst, kann der Benutzer z.B. den Drucker vor oder in diesem zweiten Schritt ausschalten. Bamm! Runtime-Exception! Drucker nicht erreichbar!
    http://www.cplusplus.com/reference/stdexcept/runtime_error/

    Bei tryLogin und login könnte man auch eine invalid_argument-Exception werfen, wenn der Username ungültige Zeichen hat.
    http://www.cplusplus.com/reference/stdexcept/invalid_argument/

    Runtime-Exceptions (out of memory u.ä.) sind Fehler, für die der Aufrufer nichts kann.
    Logic-Exceptions (invalid arguments u.ä.) sind Fehler, für die der Aufrufer etwas kann.

    Ich würde aber immer versuchen zu spezialisieren, also z.B. invalid_username_chars. Dann kann man gezielter mit einem Catch darauf reagieren. Oder wenn es nicht abgefangen wird, kann man zumindest den Exception-Typ später heraus finden.

    http://www.cplusplus.com/reference/exception/exception/



  • hustbaer schrieb:

    Mechanics schrieb:

    Grad sowas wie Parent Thread muss es an die erzeugten Threads weitergeben. Und dann gibts auch noch Cross-Thread Calls, was will ich in dem Fall überhaupt haben?

    Naja, bei Cross-Thread Calls will man üblicherweise dass das Ding vom Caller temporär das Ding vom Callee "überlagert".

    Oder auch nicht 😉 Ich weiß es wirklich nicht. Kann sein, dass Thread A etwas macht und ein Objekt hat, das in Thread B "lebt" und an diesen Thread gebunden ist (muss aber nicht sein, da haben wir verschiedene Möglichkeiten, so ähnlich wie Apartment Models). Dann geht der Aufruf in den anderen Thread rüber und wird dort ausgeführt. Und das Objekt macht vielleicht irgendwas, was ich nicht wirklich im Griff habe (wir haben zig Millionen Zeilen Code, "keine Ahnung", wer was warum macht) und will dann einfach "X" benutzen. Vielleicht wurde es im Caller Thread mit Absicht überschrieben und der Callee sollte tatsächlich das nutzen. Kann aber auch sein, dass es einen Grund gibt, warum das Objekt an seinen Thread gebunden ist und vielleicht wurde "X" auch in diesem Thread aus gutem Grund überschrieben und dem Caller ist es hingegen relativ egal. Das kann ich tatsächlich nicht wirklich entscheiden.
    Technische Probleme, wie man sowas rüberreicht und irgendwelche fremden Libs miteinbezieht natürlich inbegriffen


Anmelden zum Antworten