Hallo ich habe wieder einmal ein grundsätzliches "Problem". Ich bin gerade dabei ein Programm in C (ohne ++ und ohne #) zu schreiben und stolpere über den geeigneten Ort für die konfigurations Einstellungen. Bislang habe dich die Konfig aus einem File mit Fehlerbehandlung, etc. eingelesen und in einem Struct innerhalb der Main abgelegt. Jede Funktion der Main die etwas aus der Konfiguration brauchte, bekam diese als const Parameter übergeben und ich war eigentlich zufrieden. Jetzt habe ich aber das Problem das eine Funktion einen Wert aus der Konfiguration braucht, diese aber erst in der 5. verschachtelten Ebene unterhalb der Main aufgerufen wird, die Ebenen dazwischen brauchen die Informationen aus der Konfiguration nicht. Somit habe ich jetzt 3 Möglichkeiten: 1. Die Konfiguration durch alle Ebenen durchschleifen damit ich sie ganz unten in der Verschachtelung auch nutzen kann. 2. Die Konfiguration (das Struct) als globale Variable abzulegen, verliere damit aber die const Eigenschaft der Parameter. 3. Ich könnte auch die Konfiguration in einer globalen Variable innerhalb des Config Moduls schreiben, diese dort kapseln und nur über Funktionen von "außen" zugreifbar machen. Was würdet Ihr machen und warum?
Tim T. schrieb: > 3. Dann hast du auch alle Zugriffsmechanismen an einem Ort, und kannst diese einfacher anpassen.
Bei 3. kann es aber auch immer nur genau eine Config geben, und es ist schlecht für Unit-Tests. Version 1 ist definitiv die sauberste. So schlimm ist der Aufwand mit ein paar zusätzlichen Parametern nicht, aber du gewinnst maximale Flexibilität. Jeder, der an (größeren) Projekten mit globalen Variablen gearbeitet hat flucht darüber, weil man sich in einem Netz aus undurchsichtigen Abhängigkeiten verstrickt.
Tim T. schrieb: > Was würdet Ihr machen und warum? Du hast ja selbst festgestellt, dass #1 ab einer gewissen Programmgröße nicht mehr funktioniert. #3 ist am saubersten. Damit hast du auch eine Entkopplung, wenn sich die Konfiguration mal ändert und kannst über die Zugriffsfunktionen ggf. Parameter zwischen dem Ablageformat und dem Format welches die Clients benötigen konvertieren. Bei Objekten auf die auch schreibend zugegriffen wird, ist eine Kapselung noch wichtiger, da dann zusätzlich noch die Konsistenz zentral sichergestellt werden kann und ein Thread-Safe Zugriff über Locks synchronisiert werden kann. Es kann aber auch in deinem Fall schon wichtig sein, dass die Parameter die ausgelesen werden sollen zueinander konsistent sind, d.h. falls sich die Konfig im laufenden Betrieb ändert. Über Zugriffsfunktionen kannst du das sicherstellen. Die Zugriffsfunktionen müssen ja nicht einen einzelnen Parameter zurückgeben sondern können auch Sub-Strukturen zurückliefern (als Kopie), d.h. der Caller gibt einen Pointer auf eine Struktur rein und die Funktion füllt die Parameter aus. Michael
Das Problem ist nicht die globale Sichtbarkeit an sich, sondern der Schreibzugriff. Du erkärstallerdings das die Variable nur lesend gebraucht wird, ergo nicht schreibbar vom beliebigen Ort. Also, kein Problem. Und genau genommen handelt es sich hier nicht um eine variable Variable sondern eine Konstante oder einmal beschrieben (initialisiert) Variable.
Michael D. schrieb: > #3 ist am saubersten. Damit hast du auch eine Entkopplung, wenn sich die > Konfiguration mal ändert und kannst über die Zugriffsfunktionen ggf. > Parameter zwischen dem Ablageformat und dem Format welches die Clients > benötigen konvertieren. Das kann man bei 1. aber auch einbauen, indem man auf das struct nur über Zugrifssfunktionen zugreift. Dadurch hat man beide Vorteile. In C++ würde man die Strukturelemente "private" machen, in C kann man das struct "opaque" definieren, also z.B. "struct Config;", und nur in einer Config.c vollständig definieren. Dann kann man mit Funktion der Form "config_get_x (const Config* cfg);" arbeiten. Michael D. schrieb: > Du hast ja selbst festgestellt, dass #1 ab einer gewissen Programmgröße > nicht mehr funktioniert. Es funktioniert schon, ist nur minimal mehr Aufwand. Fpgakuechle K. schrieb: > Das Problem ist nicht die globale Sichtbarkeit an sich, Doch, früher oder später macht das Probleme.
Ok, ich seh schon 2 ist aus dem Rennen, geht jetzt noch um 1 vs 3. Wie schon richtig gesagt, es geht prinzipiell nur um lesenden Zugriff; die Config wird einmalig aus dem Configfile befüllt, was aber direkt am Anfang der Main passiert. Auch sehe ich bislang nicht die Notwendigkeit mehr als eine Config zu unterstützen. Entsprechende Unit-Tests werden jeweils als neue Instanz mit neuem Configfile durchgeführt. Wobei entsprechende Tests auch bereits innerhalb der Einleseroutine durchgeführt werden und bei Fehlern die weitere Ausführung bereits an dieser Stelle stoppen. Vom Gefühl her würde ich auch sagen das 1. der sauberste Weg ist aber eben mit dem stumpfsinnigen Durchschleifen was eventuell auch noch in anderen Funktionen vorkommen könnte. Das ist auch eine der Sachen die mir speziell bei VHDL immer am meisten auf den Zeiger ging. 3 war eher die Idee das Ganze in Richtung eines Config Objektes zu schieben, mit entsprechenden Vorteilen. Nur leidet dabei dann wieder die Transparenz des Gesamtkunstwerkes und das möchte ich natürlich auch vermeiden.
:
Bearbeitet durch User
Tja, manchmal merkt man dass die Möglichkeiten einer Sprache doch etwas begrenzt sind. Das hat auch Herr Stroustrup schon 1983 erkennt, und mit C++ die Möglichkeiten erweitert. Was hindert einen also 39 Jahre später daran, auch diese Möglichkeit zu nutzen? Zumal man oft statt gcc nur g++ eingeben muss. Das habe ich erst letzte Woche gemacht. Bei einem STM Beispielcode auf g++ gewechselt, weil ich noch ein Klasse anschliessen wollte. War eine Sache von 5 Minuten.
Tim T. schrieb: > Entsprechende Unit-Tests werden jeweils als neue Instanz mit neuem > Configfile durchgeführt Aber wo kommen die neuen Daten hin? Die globalen Config Variablen sind ja schon initialisiert. Tim T. schrieb: > Auch sehe ich bislang nicht die Notwendigkeit mehr als eine Config zu > unterstützen. Ja, bislang. Irgendwann kommen dann doch Ideen... Ein sehr wichtiger Aspekt der Software-Entwicklung ist es, den Code so zu strukturieren, dass man ihn später leicht ändern oder erweitern kann. Tim T. schrieb: > aber eben mit dem stumpfsinnigen Durchschleifen was eventuell auch noch > in anderen Funktionen vorkommen könnte. Wenn alle Funktionen zu Objekten (structs) gehören, kannst du den Config-Pointer auch im struct ablegen. Das vermeidet einige Übergaben. Aber wie gesagt, so schlimm ist diese Fleißarbeit nicht und sie ist auch nicht nachteilig für die Lesbarkeit und Wartbarkeit, im Gegenteil. Meistens gehe ich so vor: Die Datenstrukturen müssen so sein, dass ich das ganze Programm 2x nebeneinander laufen lassen kann, indem ich in der main () das Anlegen von ein paar Objekten verdopple. Diese 2 Programminstanzen wären dann komplett unabhängig. Natürlich kommt das so in der Praxis nicht vor, aber dieses Vorgehen stellt sicher dass man alles entkoppelt hat und das System modular ist. Bei einem Config-Modul mit integrierter globaler Variable geht das so nicht. PittyJ schrieb: > Tja, manchmal merkt man dass die Möglichkeiten einer Sprache doch etwas > begrenzt sind. In C geht das schon auch, man muss nur diszipliniert vorgehen und objektorientierte Ideen übernehmen. Das ist da halt mehr Tipparbeit und man hat weniger Hilfe vom Compiler. Leider sind viele C-Programme sehr traditionell-prozedural (mit vielen globalen Variablen) gebaut, obwohl man hier von neueren Ideen wie eben OOP profitieren kann. Das Gtk+ ist z.B. ein recht gutes Beispiel wie man es richtig machen kann.
Du musst Dich fragen, was an globalen Variablen schlecht ist, was Du davon vermeiden willst und was die Alternativen sind. Hier sehe ich a) Namensraum vermüllt. --> Abhilfe: Alles in einer Struktur. Ob eine globale Funktion oder eine globale Struktur ist gleichwertig. b) Schreibszugriffe von Überall --> Abhilfe: die Struktur const Machen bzw. nur als Pointer auf const veröffentlichen Ein Gott-Objekt durschleifen (Deine 1) macht es nicht besser. Ein Getter-Zugriff macht es nicht besser (Deine 3). Ja, sie machen in vielen Fällen Sinn, Du hast aber keinen solchen Fall erwähnt. Also Gott->Strom.Anker.Max statt GetGottStromAnkerMax(), GetGott(ANKER_STROM_MAX) oder GetGott(eAnkerStromMax).
A. S. schrieb: > Hier sehe ich Das wichtigste ist aber c) Alle Codeteile greifen auf die selbe Instanz zu, man kann nicht ohne weiteres an verschiedene Codeteile unterschiedliche Instanzen geben. Alles hängt dann voneinander ab. Und eben schlecht für Unittests. Erst recht wenn man die parallel (multithreaded) machen will und unterschiedliche Configs testen will. Ob der Zugriff jetzt über Getter-Funktionen oder direkt passiert ist gar nicht SO wichtig. Ob nur gelesen oder auch geschrieben wird ist dabei auch nicht so relevant.
Die "saubere" Variante ist es imho Funktionen immer in der Form output function(input) zu schreiben. Greift die Funktion auf andere (versteckte) Parameter zu... Programmierer schrieb: > Jeder, der an (größeren) Projekten mit globalen Variablen gearbeitet hat > flucht darüber, weil man sich in einem Netz aus undurchsichtigen > Abhängigkeiten verstrickt. Es sollte aber nicht die gesamte Konfiguration (mit möglicherweise zig Variablen) übergeben werden, nur das was gebraucht wird. Wird statt dessen ein (versteckter) globaler Zugriff verwendet, sollte das bei der Deklaration beschrieben werden. (Was aber nur bedingt hilft, wenn der Leser bspw. auf die übergeordnete Funktionen schaut. Gerade bei hohen Verschachtelungstiefen der Regelfall.) Bei großen Projekten (wo ich zZ mitarbeite) kann es Dutzende solcher globalen Zugriffe geben. Das ist u.a. historisch bedingt, weil man nicht immer das ganze Framework umschreiben kann (um solche Parameter durchzuschleifen). In der Praxis hat man nicht immer alle Möglichkeiten. ;-)
Tim T. schrieb: > 1. Die Konfiguration durch alle Ebenen durchschleifen damit ich sie ganz > unten in der Verschachtelung auch nutzen kann. > > 2. Die Konfiguration (das Struct) als globale Variable abzulegen, > verliere damit aber die const Eigenschaft der Parameter. > > 3. Ich könnte auch die Konfiguration in einer globalen Variable > innerhalb des Config Moduls schreiben, diese dort kapseln und nur über > Funktionen von "außen" zugreifbar machen. > > Was würdet Ihr machen und warum? Brauchst du denn auf unterster Ebene direkten Zugriff auf die komplette Konfiguration? Das würde ich eh vermeiden.
Ich würde 1 und 3 machen. Ich schleife die Konfiguration durch alle Ebenen durch. Allerdings nicht als Konfig-Struct, sondern in Form eines "Configuration-Providers". Das kann ein Objekt, eine einzelne Callback-Funktion (die einen Config-Key bekommt) oder ein Struct mit mehreren Callbacks sein (wenn ich callbacks für verschiedene Typen von Konfig-Werten habe). 3 ist dann die zentrale Implementierung des Konfig-Providers. Der kann vollständig im Konfig-Modul implementiert sein. Werte können ggf. auch static im Speicher liegen, zur Not auch als struct. Vorteile: * Du hast das an einer Stelle implementiert. * Es ist erweiterbar. Neue Konfig-Parameter brauchen keine Änderung an anderen Modulen. * Es ist gut testbar. Für Unit-Tests kann ein beliebiger anderer Konfig-Provider verwendet werden, so lange er nur das gleiche Interface hat.
Programmierer schrieb: > Tim T. schrieb: >> Entsprechende Unit-Tests werden jeweils als neue Instanz mit neuem >> Configfile durchgeführt > > Aber wo kommen die neuen Daten hin? Die globalen Config Variablen sind > ja schon initialisiert. Ja, meine sprachliche Ungenauigkeit: Jeder Test wird mit einer neuen Instanz des Programms/Moduls durchgeführt und hat entsprechend seine eigene globalen Config Variable. > Tim T. schrieb: >> Auch sehe ich bislang nicht die Notwendigkeit mehr als eine Config zu >> unterstützen. > > Ja, bislang. Irgendwann kommen dann doch Ideen... Ein sehr wichtiger > Aspekt der Software-Entwicklung ist es, den Code so zu strukturieren, > dass man ihn später leicht ändern oder erweitern kann. Dafür durfte es am einfachsten sein das Programm komplett zu beenden und mit neuem Configfile neu zu starten. > Meistens gehe ich so vor: Die Datenstrukturen müssen so sein, dass ich > das ganze Programm 2x nebeneinander laufen lassen kann, indem ich in der > main () das Anlegen von ein paar Objekten verdopple. Diese 2 > Programminstanzen wären dann komplett unabhängig. Natürlich kommt das so > in der Praxis nicht vor, aber dieses Vorgehen stellt sicher dass man > alles entkoppelt hat und das System modular ist. Bei einem Config-Modul > mit integrierter globaler Variable geht das so nicht. Das ist prinzipiell auch jetzt möglich, ich bräuchte nur eine zweite Instanz des Config structs und muss es entsprechend einlesen. Alles Weitere könnte ich entsprechend kapseln und müsste dieser Funktion nur das Config struct übergeben. Allerdings benutzt das Programm teilweise gewollt blockierende Netzwerk Funktionen, daher ist dieses Vorgehen nicht sinnvoll. Man könnte es jedoch auch eine Ebene höher sehen, wo die main() das zu verdoppelnde Objekt darstellt. Hier könnte problemlos das Programm mehrfach mit jeweils unterschiedlichen Configfiles starten. Die Datenbank- und Netzwerkzugriffe sind in sich konsistent und zustandsfrei, so dass es dabei keine Probleme auch bei gleichem Configfile geben wird.
:
Bearbeitet durch User
Tilo R. schrieb: > Das kann ein Objekt, eine einzelne Callback-Funktion (die einen > Config-Key bekommt) oder ein Struct mit mehreren Callbacks sein (wenn > ich callbacks für verschiedene Typen von Konfig-Werten habe). Das ist einer der Vorteile von C++: Man kann Funktionen nachträglich virtual machen ohne den ganzen Code, der sie nutzt, ändern zu müssen.
Tim T. schrieb: > Allerdings benutzt das Programm teilweise gewollt blockierende Netzwerk > Funktionen, daher ist dieses Vorgehen nicht sinnvoll. Was hat das mit der Config zu tun? Programmteile, die blockierende Funktionen nutzen, können problemlos ein Config-Objekt nutzen. Solange die Funktionen des Config-Objekts nicht selbst blockieren während sie einen Mutex halten o.ä., aber das ist ein Implementationsdetail. Tim T. schrieb: > Dafür durfte es am einfachsten sein das Programm komplett zu beenden und > mit neuem Configfile neu zu starten. Das würde ich halt grundsätzlich vermeiden. Wenn man das Programm neu starten muss, hat man die Datenstrukturen nicht im Griff. Vielleicht will man irgendwann Programmteile in einen Daemon übernehmen der kontinuierlich läuft?
Tim T. schrieb: > Was würdet Ihr machen und warum? Wenn sich die Konfiguration auf das ganze Programm bezieht, kann sie ruhig global definiert werden. Oder zumindest eine Zugriffsfunktion getConfig("name").asInteger etc. die auf einen gekapselt verborgenen Konfigurationsspeicher zugreift. In modernem C++ würde man sauber strukturieren, in dem man alles was zum Programm gehört und daher eventuell konfigurationsabhängig ist oder was konfigurationsabhängiges benutzt, als Methode/Funktion in eine Klasse aufnimmt, die als Member Variablen die Konfigurationswerte mit sich trägt. Man könnte dann 2 Programme als 2 Instanzen dieser Klasse laufen lassen mit 2 unterschiedlichen Konfigurationen. Wenn du diese Möglichkeit der mehreren Instanzen in einem Programm nicht brauchst, weil dein Programm nur 1 Konfiguration kennt, dann kannst du gut die Klasse auflösen und alles, Funktionen und Variablen, global definieren. Der resultierende Code ist dann schneller.
MaWin schrieb: > In modernem C++ würde man sauber strukturieren Das geht auch in C, auch da muss es nicht global sein. MaWin schrieb: > Der resultierende Code ist dann schneller. Da dürfte kein (messbarer) Unterschied sein. Globale Variablen werden oft auch indirekt adressiert (insbesondere bei Position-Independent-Code), da ist es egal ob der Basis-Pointer aus einem Parameter, Literal-Pool oder Global Offset Table (GOT) kommt.
Programmierer schrieb: > Tim T. schrieb: >> Allerdings benutzt das Programm teilweise gewollt blockierende Netzwerk >> Funktionen, daher ist dieses Vorgehen nicht sinnvoll. > > Was hat das mit der Config zu tun? Programmteile, die blockierende > Funktionen nutzen, können problemlos ein Config-Objekt nutzen. Solange > die Funktionen des Config-Objekts nicht selbst blockieren während sie > einen Mutex halten o.ä., aber das ist ein Implementationsdetail. Das hat dahingehend mit der Config zu tun, dass keine zwei Instanzen innerhalb eines Programms parallel laufen können, außer eben man würde alles in unabhängige Threads packen, was aber ansonsten für die Aufgabe nicht im geringsten notwendig ist. Aber ich denke ich habe ansonsten deine Idee verstanden und das Programm erfüllt diese Voraussetzung bislang, würde sie aber durch 3. brechen. > Tim T. schrieb: >> Dafür durfte es am einfachsten sein das Programm komplett zu beenden und >> mit neuem Configfile neu zu starten. > > Das würde ich halt grundsätzlich vermeiden. Wenn man das Programm neu > starten muss, hat man die Datenstrukturen nicht im Griff. Vielleicht > will man irgendwann Programmteile in einen Daemon übernehmen der > kontinuierlich läuft? Überraschung, es ist sogar ein Daemon. Nur wenn sich tatsächlich etwas an den Configparametern ändert muss eh eine Netzwerk oder Datenbankverbindung neu aufgebaut werden oder sonstwas Grundlegendes. Da aber alles auf diesen Verbindungen aufbaut, ist es wirklich am einfachsten an dieser Stelle einfach eine neue Instanz des Daemon mit den neuen Parametern zu starten.
:
Bearbeitet durch User
Tim T. schrieb: > Das hat dahingehend mit der Config zu tun, dass keine zwei Instanzen > innerhalb eines Programms parallel laufen können, Achso, das ist für die Betrachtung "2 Instanzen parallel laufen" egal. Es geht ja um die Datenstrukturen, und wenn ein Parallel-Lauf von den Datenstrukturen her möglich wäre, unabhängig von den Abläufen, ist das schon gut. Tim T. schrieb: > Da aber alles auf diesen Verbindungen aufbaut, ist es wirklich am > einfachsten an dieser Stelle einfach eine neue Instanz des Daemon mit > den neuen Parametern zu starten. Es sei denn man macht mehrere sequentielle Abläufe in die main() oder startet mehrere Threads. Wie gesagt geht es nicht darum, ob man wirklich mehrere Instanzen laufen lassen will - aber wenn man es aufgrund der Datenstrukturen nicht kann, sind diese schlecht strukturiert.
Programmierer schrieb: > Das wichtigste ist aber Dazu sollte sich der TO mal äußern. > c) Alle Codeteile greifen auf die selbe Instanz zu, man kann nicht ohne > weiteres an verschiedene Codeteile unterschiedliche Instanzen geben. Naja, differenzierte Configs brauchen differenzierte Lösungen, je nachdem wie differenziert es sein soll. Da helfen dann auch keine Getter-Funktionen oder Durchgeschleifte Konfigs. > Ob der Zugriff jetzt über Getter-Funktionen oder direkt passiert ist gar > nicht SO wichtig. > Ob nur gelesen oder auch geschrieben wird ist dabei auch nicht so > relevant. Ja. Darum ist es wichtig, dass der TO schreibt, was er erreichen will. Momentan schreibt er eher, wie er es tut.
Fpgakuechle K. schrieb: > Das Problem ist nicht die globale Sichtbarkeit an sich, sondern der > Schreibzugriff. Mitnichten. Mehrere Lesezugriffe von unterschiedlichsten Stellen auf einen globalen Bezeichner, egal ob das nun eine Konstante, eine Funktion, ein Singleton oder sonst was ist, führt zu einer völligen Chaos-Architektur. Ein Config-Objekt das irgendwie durchgereicht wird, macht es nicht besser. Der Gewinn ist einzig und allein, dass zur Laufzeit mehrere "Instanzen" des Programms im selben Prozess existieren können. Es geht einzig und allein darum, was von wem wie abhängig ist. Wenn eine tief verschachtelte Funktion/Methode einen globalen Parameter benötigt, ist etwas grundsätzlich falsch. Meiner Erfahrung nach passiert das fast garantiert, wenn nach dem (falschen) Prinzip "Höhere Abstraktion hängt von niedriger Abstraktion ab" vorgegangen wird. Also der Müll der in so vielen Büchern abgedruckt ist: Unten Low-Level-Layer, darüber Middle-Layer und dann der High-Level-Level. Und immer ruft die obere Schicht die untere Schicht auf. Und genau das ist halt der Fehler. Viel Besser wird es, wenn man genau umgekehrt vorgeht. Im Zentrum liegt die abstrakteste Schicht. Diesem Kern werden Funktionen/Methoden der Mittleren Schicht übergeben, und die mittlere Schicht wiederum wird mit der Low-Level-Layer parametriert. Dann verschwindet auch das Konfigurationsproblem. Denn je tiefer der Call-Stack, desto abstrakter die Informationen. Die Details bleiben außen vor. Nachlesen kann man das unter den Stichwörtern Hexagonal-Architecture, Ports and Adapters, Onion Architecture, Clean Architecture und wie alle die Spielweisen des immer gleichen Grundkonzepts lauten.
A. S. schrieb: > Programmierer schrieb: >> Das wichtigste ist aber > > Dazu sollte sich der TO mal äußern. Es geht mir einfach darum eine Best Practice für diese Art Probleme zu finden. Klar könnte ich es in diesem Fall mit jeder der drei von mir genannten Lösungen erreichen, aber langfristig möchte ich eben etwas finden wie man es grundsätzlich (in C) machen würde. >> c) Alle Codeteile greifen auf die selbe Instanz zu, man kann nicht ohne >> weiteres an verschiedene Codeteile unterschiedliche Instanzen geben. > > Naja, differenzierte Configs brauchen differenzierte Lösungen, je > nachdem wie differenziert es sein soll. Da helfen dann auch keine > Getter-Funktionen oder Durchgeschleifte Konfigs. Das ist regulär kein Problem bei mir, ich bin praktisch immer in der Lage eine neue Programm Instanz bei Änderungen zu starten. >> Ob der Zugriff jetzt über Getter-Funktionen oder direkt passiert ist gar >> nicht SO wichtig. >> Ob nur gelesen oder auch geschrieben wird ist dabei auch nicht so >> relevant. > Ja. Darum ist es wichtig, dass der TO schreibt, was er erreichen will. > Momentan schreibt er eher, wie er es tut. Ich dachte das wäre klar geworden. Der TO will eine allgemeine Lösung für das Problem in verschachtelten Konstrukten an quasi globale Infromationen zu gelangen, ohne das diese in den Funktionen verändert werden können. Eine Ausnahme davon soll die Initialisierungsfunktion/Ladefunktion bilden, welche natürlich die Informationen verändern können muss. Desweiteren geht es mir darum, wie von dir bereits geschrieben, mir den Namensraum nicht zu vermüllen.
:
Bearbeitet durch User
Einer schrieb: > Ein Config-Objekt das irgendwie durchgereicht wird, macht es nicht > besser. Der Gewinn ist einzig und allein, dass zur Laufzeit mehrere > "Instanzen" des Programms im selben Prozess existieren können. > Es geht einzig und allein darum, was von wem wie abhängig ist. Wenn eine > tief verschachtelte Funktion/Methode einen globalen Parameter benötigt, > ist etwas grundsätzlich falsch. So so, du möchtest die Länder- und Zeichensatzeinstellung lieber nur auf oberster Programmebrne haben und von da an durchreichen bis auch der letzte callback eines qsort im Stringpool der Bezechnetbibliothek richtig vergleicht. Schreib doch gleich, dass ihr im Kindergarten leider noch nie programmiert habt.
Tim T. schrieb: > Ich dachte das wäre klar geworden. Der TO will eine allgemeine Lösung > für das Problem in verschachtelten Konstrukten an quasi globale > Infromationen zu gelangen, ohne das diese in den Funktionen verändert > werden können. > Desweiteren geht es mir darum, wie von dir bereits geschrieben, mir den > Namensraum nicht zu vermüllen. Dann ist ein const-Pointer auf die Struktur ausreichend (und kaum einfacher realisierbar). Die Daten selber können beliebig tief versteckt sein, solange sie vor der ersten Verwendung zugewiesen werden.
1 | struct sGott {int a; int b; int c; ...}; |
2 | extern const struct sGott *Gott; |
3 | |
4 | int main(void) |
5 | {
|
6 | {
|
7 | static const struct sGott myGott = {3,x,y, ...}; |
8 | |
9 | Gott=&myGott; |
10 | }
|
11 | ...
|
12 | }
|
Multithreading, umschalten und vieles weitere ist möglich. Wenn Du weitere Anforderungen hast, nenne sie ruhig. Bis dahin ist jede komplexere Lösung erstmal nur ... komplexer. P.S.: Ja, man verwechselt "komplexer" gerne mit "zukunftssicher", "flexibler" oder "erweiterbar". Da ist der Mehraufwand dann öfter fürs Scheitern verantwortlich als dass er sich auszahlt. Es sei denn, ich weiß schon, wo ich hin will. Dann sollte man das aber auch artikulieren können.
A. S. schrieb: > Dann ist ein const-Pointer auf die Struktur ausreichend Das ist genau so schlimm wie die nackte globale Variable. A. S. schrieb: > Da ist der Mehraufwand dann öfter fürs Scheitern verantwortlich Der Mehraufwand für ein paar zusätzliche Parameter ist minimal. Das ist nur ein bisschen Tipparbeit, aber man muss kaum drüber nachdenken. Wenn das Projekt daran scheitert, habe ich Fragen.
Einer schrieb: > . Also der Müll der in so vielen Büchern abgedruckt > ist: Unten Low-Level-Layer, darüber Middle-Layer und dann der > High-Level-Level. Und immer ruft die obere Schicht die untere Schicht > auf. Und genau das ist halt der Fehler. Also du erwartest das die untere Schicht per Gedankenlesen im Bitrauschen oder dank prophetischer Vorwärtssimulation herausspekuliert was die obere Schicht will, bevor diese den Aufruf komplettiert hat? Und die mittlere Schicht ist ohnehin nur fuers logging ins protocol zustaendig ... Ja, es gibt sowas wie "branch prediction and speculative execution "aber so ein HokusPokus wider der hierarchischen Softwareorganisationist wohl eher nicht gemeint.
A. S. schrieb: > Tim T. schrieb: >> Ich dachte das wäre klar geworden. Der TO will eine allgemeine Lösung >> für das Problem in verschachtelten Konstrukten an quasi globale >> Infromationen zu gelangen, ohne das diese in den Funktionen verändert >> werden können. >> Desweiteren geht es mir darum, wie von dir bereits geschrieben, mir den >> Namensraum nicht zu vermüllen. > > Dann ist ein const-Pointer auf die Struktur ausreichend (und kaum > einfacher realisierbar). Die Daten selber können beliebig tief versteckt > sein, solange sie vor der ersten Verwendung zugewiesen werden. Zugegeben das würde alle von mir genannten Anforderungen erfüllen, fühlt sich aber trotzdem irgendwie falsch an. Dadurch das die Main komplett über die Laufzeit erhalten bleibt ist auch der Pointer auf ein nicht globales Objekt kein Problem und ein ungewolltes Ansprechen am const-Pointer vorbei auch nicht direkt möglich. Hmm, klingt wirklich erstmal verlockend, muss ich mir mal anschauen. Danke dafür. > Multithreading, umschalten und vieles weitere ist möglich. Wenn Du > weitere Anforderungen hast, nenne sie ruhig. Bis dahin ist jede > komplexere Lösung erstmal nur ... komplexer. Ja, die Einfachheit des const-Pointers ist schon bestechend... Also meine Lösung wäre dann in etwa:
1 | static const config_t *p_config; |
2 | // oder eben extern wenn direkt außerhalb des Moduls angesprochen werden soll
|
3 | |
4 | int main( void ) { |
5 | |
6 | config_t config; |
7 | p_config = &config; |
8 | |
9 | read_config( &config ); |
10 | |
11 | [...]
|
12 | }
|
:
Bearbeitet durch User
Tim T. schrieb: > Es geht mir einfach darum eine Best Practice für diese Art Probleme zu > finden. Das wäre OOP (was auch in C geht). Deine Funktionen (Methoden) werden an Daten (Objekten) gebunden die dann bspw. auch Konfigurationsdaten enthalten können. Möchte man dynamisch auf zukünftige Anforderungen reagieren definiert man Interfaces und erzeugt die Objekte über Factories. So die Theorie. Muss man natürlich am konkreten Beispiel gucken, ob/wie das zu den eigenen Anforderungen passt. OOP ist kein Allheilmittel. Aber es kann durchaus hilfreich sein. ;-)
Ganz klar GOBAL. Und wenn irgendwann einmal geplant ist, das mehre Konfigurationen möglich sein, braucht man nur EINE Integer-Variable mehr. ABER das muss man VORHER Planen. Dazu einfach die Structure an Array Anlegen. Da man das Array eh festlegen muss sollte, reicht ja am Anfang eine 2 . Die Extra-Variable steuert dann die Konfig. z.b. SO: Dim my_config(2) as ls_my_config ' schafft platz für ein unterschiedliche Config. dim config_nr as integer = 1 ' stetz erste config als gegeben Structure my_config dim name as string dim einstellung as integer end strukture im Programm dann einfach xx = my_config(config_nr).einstellungen Das mache ich dauernd mit Variablen die ich an mehr als 3 Stellen brauche, und das spart viel Zeit und Code. Der Code sollte so ähnlich auch in C funktionieren.
Mikro 7. schrieb: > Das wäre OOP (was auch in C geht). wobei OOP das genau Gegenteil einer zentralen Konfiguration ist. Entweder ich habe unabhängige Objekte, dann muss ich diese auch unabhängig konfigurieren/instanziiern können. Oder ich habe eine Gesamtkonfiguration (bzw. nur ein Objekt), dann gibt es in OOP dazu nur fancy Workarounds mit fancy Namen und fancy Kontrollflussverlust.
Schlaumaier schrieb Unsinn im Beitrag #7001838 Wenn es hier doch nur ein Killfile geben würde...
Schlaumaier schrieb: > Dazu einfach die Structure an Array Anlegen. Alter Schwede, immer wenn ich denke du hast den Vogel abgeschossen kommt sowas... Ist ja irgendwie auch witzig, so als Horrorgeschichte, aber es könnte jemand ernst nehmen und wirklich so umsetzen...
Ansonsten habe ich jetzt alle 3 Varianten implementiert und bin noch am überlegen. Aktuell gefällt mir die Variante mit const-Pointer im Config Modul am besten.
Programmierer schrieb: > Schlaumaier schrieb: >> Dazu einfach die Structure an Array Anlegen. > > Alter Schwede, immer wenn ich denke du hast den Vogel abgeschossen kommt > sowas... Ist ja irgendwie auch witzig, so als Horrorgeschichte, aber es > könnte jemand ernst nehmen und wirklich so umsetzen... Ja, manchmal frage ich mich ob er das absichtlich macht oder es einfach nur nicht besser weiß...
A. S. schrieb: > wobei OOP das genau Gegenteil einer zentralen Konfiguration ist. Im Gegenteil, ich habe es oben beschrieben: ein Objekt repräsentiert eine Instanz des Programms bzw. App und damit einen Satz Konfigurationswerte. Das ist sauberstes OOP. Und wenn man gar keine 2 Instanzen braucht, muss man die Dinge nicht in eine Klasse verpacken mit self Referenz, sondern kann sie global deklarieren.
MaWin schrieb: > Im Gegenteil, ich habe es oben beschrieben: ein Objekt repräsentiert > eine Instanz des Programms bzw. App und damit einen Satz > Konfigurationswerte. MaWin, wir kommen beide zu dem Ergebnis, dass eine zentrale Konfiguration auch global sein darf. Deine Ausführung dazu ist nicht konträr zu meiner. Ich bezog mich ausschließlich auf Mikro 7, der die Zentrale Konfiguration per OOP verbessern wollte. Wenn er es nicht näher ausführt, führt das entweder zu einem einzigen Objekt oder zu einem Konfig-Gottobjekt. Beides reduziert die Komplexität nicht.
Tim T. schrieb: > Ansonsten habe ich jetzt alle 3 Varianten implementiert und bin noch am > überlegen. Aktuell gefällt mir die Variante mit const-Pointer im Config > Modul am besten. Wenn du kein Problem mit den (versteckten) Abhängigkeiten hast, und welcher Konfigurationsparameter wo benutzt wird, warum nicht. Bei kleinen Programmen ist die einfache Lösung häufig die beste. Programmierer schrieb: > Jeder, der an (größeren) Projekten mit globalen Variablen gearbeitet hat > flucht darüber, weil man sich in einem Netz aus undurchsichtigen > Abhängigkeiten verstrickt. Ansonsten, wenn ich es richtig verstanden habe, wolltest du den neuen Parameter nicht durch viele Funktionen durchschleifen. Eine hohe Verschachtelungstiefe ist in meiner Erfahrung ein anfälliges Design. Bei Protokollen kann man die Implementierung bspw. in Module aufteilen: Low-Level Connection Management und High-Level Session Management; und so eine tief verschachtelte Implementierung entkoppeln. Die Module werden in main instantiert und kommunizieren über eine definierte Schnittstelle, Queues können zusätzlich für eine weitere Entkopplung sorgen. Falls man jetzt nachträglich einen Connection Timeout als Konfigurationsparameter benötigt, dann geht der direkt ins Low-Level Modul (bei der Instantiierung) ohne dass das High-Level Modul davon etwas wissen muss und irgendwas durchschleifen muss.
Mikro 7. schrieb: > Tim T. schrieb: >> Ansonsten habe ich jetzt alle 3 Varianten implementiert und bin noch am >> überlegen. Aktuell gefällt mir die Variante mit const-Pointer im Config >> Modul am besten. > > Wenn du kein Problem mit den (versteckten) Abhängigkeiten hast, und > welcher Konfigurationsparameter wo benutzt wird, warum nicht. Bei > kleinen Programmen ist die einfache Lösung häufig die beste. Naja, das ist in der Situation irgendwo auch ein Vorteil. Ich kann so die Konfigurationsparameter überall benutzen und muss mir keine Gedanken darüber machen sie dahin zu bringen wo ich sie gerade brauche. Worauf ich mich dabei aber immerhin (halbwegs) verlassen kann ist das nirgendwo an den Parametern rumgepfuscht wird. Und die einzige Abhängigkeit die ich direkt sehe ist dass die Konfiguration eben vor der ersten Benutzung eingelesen werden muss, aber das ist ja auch bereits sicher gestellt. Die komplette Validierung der Konfigurationsdatei erfolgt beim Laden, inklusive Abbruch bei Fehlern oder dem Fehlen einzelner Parameter, etc. Der const-Pointer im Config Modul gefiel mir dabei am besten weil praktisch alles außer dem Laden der Konfiguration und dem Ansprechen der Werte komplett im Modul liegt und dort zentral verwaltet wird. Das unterstützen mehrerer Konfigurationen gleichzeitig, ist ebenfalls weder erwünscht noch erforderlich. Weder für ein systemctl reload noch restart brauche ich diese Funktionalität. Wobei selbst ein reload problemlos mit nur einer Konfiguration funktioniert (aber aufwändiger ist als einfach den Daemon neu zu starten und die Konfig dabei neu einzulesen).
Es wurde ja schon ein paar Mal im Thread gesagt, dass die globale Config nicht in zu tiefe Ebenen mitgezogen werden darf. Möglichst viele Teile des Programms sollten wie eine Bibliothek funktionieren, also ohne Verflechtungen "in sich" funktionieren. Wie es auch der Netzwerk-Stack tut. Bei der Initialisierung übergibt man ihm die IP-Adresse usw., alles Daten und Einstellungegen, die der Netzwerk-Stack braucht. Ab da hält er diese Daten selber vor. Setzt man bspw. ein HTTP-Anfragen ab, dann wird der zugrundeliegende Code eine TCP-Verbindung aufbauen. Für die HTTP-Bibliothek stellt der TCP-Code gewissermaßen selber eine Bibliothek dar, die in sich abgeschlossen ist. Um die Verbindung aufzubauen übergibt die HTTP-Bibliothek die IP-Adresse und weiters an die TCP-Bibliothek, die wird diese Daten wieder selber vorhalten. Um eine Verbindung aufzubauen werden IP-Packete versandt, wieder ein in sich geschlossener Code, dem die IP-Adresse übergeben wird. Alle diese Programmteile haben keinen Zugriff auf eine globale Config-Datei, sondern halten nur das vor, was sie selber brauchen. Und in gleicher Weise sollte man in seinem Programm auch vorgehen, so dass am Schluss nur ein paar Programmteile auf oberster Ebene auf die globale Config zugreifen und jeweils nur die Werte und Einstellungen weitergeben, die die jeweiligen Programmteile auch benötigen. Die jeweiligen Programmteile haben dann jeweils ihre eigene lokale "Config". Das bedeutet natürlich dass Daten mehrfach im Speicher liegen.
MaWin schrieb: > So so, du möchtest die Länder- und Zeichensatzeinstellung lieber nur auf > oberster Programmebrne haben und von da an durchreichen [...] > Schreib doch gleich, dass ihr im Kindergarten leider noch nie > programmiert habt. Fpgakuechle K. schrieb: > Also du erwartest das die untere Schicht per Gedankenlesen im > Bitrauschen oder dank prophetischer Vorwärtssimulation herausspekuliert [...] > "aber so ein HokusPokus wider der hierarchischen Softwareorganisationist > wohl eher nicht gemeint. Gut. Neue Erkenntnisse setzen sich nicht überall so schnell durch. Das Konzept "Dependency Injection" ist ja auch erst knapp 20 Jahre alt. Und Menschen deren Weltbild erschüttert wird reagieren oft irrational. Ein kleiner Denkanstoß möge vielleicht helfen (oder auch nicht): Angenommen, Du hast ein Software-Modul welches mit einem Web-Server agiert. Die verwendete API sind also URLs. Diesen API-Calls oder URLs ist gemeinsam, dass sie auch nicht problemspezifische Daten enthalten, wie z.B. konkrete Serveradresse, Portnummer, Pfad der API und Authentifizierungsdaten, z.B. ein Token. Jetzt gibt es grob zwei Möglichkeiten: a.) Dem Software-Modul werden die ganzen Konfigurationen wie z.B. Serveradresse, Port, Auth-Token, TLS-Zertifikate etc. übergeben. Das Software-Modul muss diese Infos zur Low-Level HTTP-Schicht weiterreichen und sich mit dem Kram beschäftigen, obwohl es das nicht die Aufgabe dieses Moduls ist. b.) Dem Software-Modul wird nur ein Funktionspointer oder Interface etc. übergeben, das ausschließlich eine Funktion/Methode enthält. Diese Funktion/Methode hat als Parameter u.a. den API-Endpoint, also nur den Teil der URL der sich ändert. Servername, Portnummer, Auth-Token etc. kennt das verwendende Software-Modul nicht und es kümmert sich nicht darum. Nur die Callback-Funktion bzw. Interface-Methode macht aus dem API-Endpoint eine komplette URL, bastelt das Auth-Token hinzu, führt den API-Call mit oder ohne Verschlüsselung aus, etc. Der Unterschied ist u.a. nun, bei Lösung "a" schlägst Du hier auf und fragst, wie man am besten die Konfigurationsdaten zur HTTP-Layer durchreicht. Bei Lösung "b" stellt sich die Frage überhaupt nicht, da das Software-Modul niemals mit den Konfigurationsdaten der HTTP-Layer in Berührung kommt. Es weiß nichtmal, dass es per HTTP kommuniziert. Es kennt nur API-Endpoints und die Daten, mit dem es arbeitet.
Dependency Injection ist natürlich toll. Ich bin immer nur skeptisch, wenn das als Lösung präsentiert wird, mit der man globale Variablen/globalen State vermeiden kann. Tatsächlich ist das DI-Framework (bzw. die Konfiguration) dann der globale State...
Tim T. schrieb: > Hallo ich habe wieder einmal ein grundsätzliches "Problem". Ich bin > gerade dabei ein Programm in C (ohne ++ und ohne #) zu schreiben und > stolpere über den geeigneten Ort für die konfigurations Einstellungen. Was da bei dir konfguriert werden soll, kennst nur du. Ebenso ist es bei der zwischenzeitlichen Aufbewahrung in einer Konfigurationsdatei. Von dort und nach dort wird es aber irgendeine Konvertierung geben - vermute ich mal. Und da du ebenso vermutlich das Lesen und Schreiben der Konfiguration in irgend einen Modul verpackt hast und nicht direkt in main(), werden auch deine Konfigurationsdaten als solche zunächst in selbigem Modul angesiedelt sein. Ebenso vermutlich. Also, wonach fragst du? Natürlich kannst du dir die Sache kompliziert machen, indem du das Ganze in ein struct verpackst und dessen Adresse durch alle Ebenen mit durchschleifst, aber dennoch benötigen die Programmteile, die daraus ihre Konfiguration beziehen, die Struktur bzw. Definition eben dieses structs. Und wenn man das Ganze noch komplizierter machen will und den Zugriff nur über Getter und Setter organisieren will, dann braucht es eben die Definitionen besagter Getter/Setter. Mach es so kompliziert wie du es magst, aber glaube nicht, daß es dadurch besser wird. W.S.
Programmierer schrieb: > Bei 3. kann es aber auch immer nur genau eine Config geben, und > es ist schlecht für Unit-Tests. Wieso? Das Config-API kann für beliebig viele sorgen, einen Array oder eine List von Configs verwalten. Und ein Unit-Test kann eine andere, auf ihn zugeschnittene Implementierung davon nutzen. Programmierer schrieb: > Version 1 ist definitiv die sauberste. Finde ich nicht. Wozu sollen Funktionen einen Parameter erhalten, den sie gar nicht benötigen? Nur, damit tief unten drin eine darauf zugreifen kann? Damit macht man alle darüber liegenden Funktionen von dieser einen abhängig. Geht gar nicht.
:
Bearbeitet durch User
Tim T. schrieb: > 2. Die Konfiguration (das Struct) als globale Variable abzulegen, > verliere damit aber die const Eigenschaft der Parameter. Darf ich auch mitraten :-) Ich würde 2 wählen. Alles andere macht Schreibarbeit, und so hast du es ja schon fertig. Um const oder nicht const würde ich mir keine Gedanken machen, nutze die Zeit lieber um Funktionalität reinzubringen. Außerdem ist es eventuell mal sinnvoll, dass ein tiefer unten liegendes Modul die Konfigurationswerte ergänzen kann, die sind also gar nicht immer const.
Markus L. schrieb: > Wieso? Das Config-API kann für beliebig viele sorgen, einen Array oder > eine List von Configs verwalten. Und wie gibt man dann mit welche der Configs man will? Mit einem zusätzlichen Index? Also doch ein Parameter? Statt des Index kann man auch einfach einen Pointer auf das Config-Struct übergeben. Also Variante 1). Markus L. schrieb: > Finde ich nicht. Wozu sollen Funktionen einen Parameter erhalten, den > sie gar nicht benötigen? Nur, damit tief unten drin eine darauf > zugreifen kann? > Damit macht man alle darüber liegenden Funktionen von dieser einen > abhängig. Geht gar nicht. Doch, genau so macht das Sinn, weil man dann sieht wie der Datenstrom ist. Die oberste Funktion IST ja von den Config-Daten abhängig, denn das Verhalten der inneren Funktion, welche das Config-Objekt benutzt, beeinflusst ja das der äußeren. Wenn da noch "von der Seite" Configdaten reinkommen ist überhaupt nicht mehr ersichtlich wovon die Funktion abhängt. Wenn es nur um einen einzigen Wert oder eine kleine Menge an Werten aus der Config geht, kann man diesen natürlich auch "normal" übergeben und in einer der äußeren Funktionen aus der Config rausholen, oder nur ein Unter-Struct der Config übergeben. Auch dann ist klar, wo die Daten herkommen und welche Inputs eine Funktion verwendet.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.