N'Abend :-) Mein Projekt wächst und wächst derzeit auf dem STM32 und ich komme etwas ins Schwimmen, was die Übersichtlichkeit angeht. Als Datenbasis habe ich einen recht großen Struct, wo alle möglichen Daten wie temperatur, Feuchte, Druck, Minima und Maxima sowie die letzten 48h Historie all dieser Werte reinwandern. Der Struct ist global in main.h und in jedem Modul verfügbar durch extern Anweisungen. Nahezu sämtliche Routinen verändern Daten in diesem Struct oder holen sich welche da raus, zb fuer den Bildschirm. UpdateData() DrawTemperatur() DrawFeuchte() DrawHistory() usw. Keine dieser Funktionen hat mehr Parameter. Sie werden zyklisch aufgerufen in der Hautpschleife und triggern durch Zeitflags der RTC. Nun tragen Parameter aber zur Kapselung von Daten bei und machen klar, was eine Funktion verändert. Bei mir sieht man das nicht mehr, man muss schon genau hinschauen. Bloss macht es wohl keinen Sinn eine globale Variable als Parameter zu übergeben, weil die eh bekannt ist. Und noch weniger den struct sogar durch mehrere Instanzen durch zu reichen, wenn Subroutinen ihn auch brauchen. C++ ist nicht so meins, da ist das besser organsiert. Wie handhabt ihr das?
Christian J. schrieb: > Nun tragen Parameter aber zur Kapselung von Daten bei und machen klar, > was eine Funktion verändert. Wenn eine Funktion immer auf denselben Wert zugreift, braucht man diesen nicht als Parameter spezifizieren, sondern kann ihn fest in die Funktion reinschreiben. (Und wenn jetzt jemand schwätzt, man wäre aber doch viel flexibler für die Zukunft, wenn man ihn trotzdem als immer gleichen Parameter übergibt: Dan kann man ja dann in dieser Zukunft in der man die Funktion wirklich mal mit 2 unterschiedlichen Werten arbeiten lassen will immer noch machen.) Ein Programm, das ein paar globale Variablen hat und eine Reihe von Funktionen, die auf diese zugreifen, ist nichts anderes, als EINE Instanz einer Klasse mit eben diesen Variablen als Klassenvariablen und eben diesen Funktionen als Memberfunktionen. Andere "Features" von C++ hat man damit aber nicht (wie Vererbung etc.).
Christian J. schrieb: > Nahezu sämtliche Routinen verändern Daten in diesem Struct Warum verändert "DrawFeuchte" dein Struct? Und warum heißt die Routine nicht "ZeichneFeuchte" oder "DrawHumidity"? Ich bin ja OO-Fan was das betrifft, aber das willst du ja nicht. Ich würde dieses Struct aufteilen. Wie genau ist die Kunst, aber das Ding scheint ja ein "Gottobjekt" zu sein.
Michael B. schrieb: > Wenn eine Funktion immer auf denselben Wert zugreift, braucht man diesen > nicht als Parameter spezifizieren, sondern kann ihn fest in die Funktion > reinschreiben. So richtig schön kann man das, nur am Rande erwähnt, mit IoC lösen. Ist aber im uC-Feld wohl schon zu viel des guten. Michael B. schrieb: > Ein Programm, das ein paar globale Variablen hat und eine Reihe von > Funktionen, die auf diese zugreifen, ist nichts anderes, als EINE > Instanz einer Klasse mit eben diesen Variablen als Klassenvariablen und > eben diesen Funktionen als Memberfunktionen. Eine sehr weite Auslegung von OO :)
Nur zur Verdeutlichung:
1 | // Arbeitswerte die im Batterie NV Ram liegen |
2 | struct gfx_Work_t { |
3 | int32_t id; // = 0x1234 nach Initialisierung |
4 | int16_t Pressure_Now, // Aktueller Druckwert |
5 | Pressure_Max, // Aktuell oberer Druckwert |
6 | Pressure_Min; |
7 | |
8 | double Temp_Now; // Aktueller Temperaturwert |
9 | |
10 | // Datenstruktur der Historie, stuendliche Eintragungen |
11 | uint16_t P_History[HISTORY_MAX_IDX+1]; // Druck Historie |
12 | double T_History[HISTORY_MAX_IDX+1]; // Temperaturhistorie |
13 | double T_History_Max, // Maximaler gemessener Temperaturwert |
14 | T_History_Min, // Minimaler gemessener Temperaturwert |
15 | T_History_Avg; |
16 | uint16_t P_History_Max, // Maximaler gemessener Druckwert |
17 | P_History_Min, // Minimaler gemessener Druckwert |
18 | P_History_Avg; |
19 | uint16_t pointer; |
20 | }; |
21 | |
22 | // Prototypen |
23 | void gfx_UpdateTemperature(); |
24 | void gfx_UpdateHistory(); |
25 | void gfx_UpdatePressure(); |
26 | void gfx_InitDisplay(); |
27 | void gfx_DrawHistory(); |
28 | void gfx_DrawDateTime(); |
Jan H. schrieb: > So richtig schön kann man das, nur am Rande erwähnt, mit IoC lösen. Ist > aber im uC-Feld wohl schon zu viel des guten. Service, um sich das Googlen der Nichtwissenden zu ersparen: https://en.m.wikipedia.org/wiki/Inversion_of_control
Jan H. schrieb: > Warum verändert "DrawFeuchte" dein Struct? Und warum heißt die Routine > nicht "ZeichneFeuchte" oder "DrawHumidity"? Häh? kopfkratz
Christian J. schrieb: > Jan H. schrieb: > > Warum verändert "DrawFeuchte" dein Struct? Und warum heißt die Routine > nicht "ZeichneFeuchte" oder "DrawHumidity"? > > Häh? kopfkratz Denglisch?
Christian J. schrieb: > Mein Projekt wächst und wächst derzeit auf dem STM32 und ich komme etwas > ins Schwimmen, was die Übersichtlichkeit angeht. > > Als Datenbasis habe ich einen recht großen Struct, wo alle möglichen > Daten wie temperatur, Feuchte, Druck, Minima und Maxima sowie die > letzten 48h Historie all dieser Werte reinwandern. Der Struct ist global > in main.h und in jedem Modul verfügbar durch extern Anweisungen. > [...] > Wie handhabt ihr das? Du hast Dich leider in diese blöde Situation gebracht, weil Du schon viel zu früh optimieren wolltest. "Premature optimization is the root of all evil" schrieb Donald Knuth schon 1973, und "The Art Of UNIX Programming" empfiehlt die Reihenfolge: "make it work, make it right, make it fast". Die Optimierung ist immer der letzte Schritt bei der Entwicklung eines Programms, nicht der erste. Nach Deiner Beschreibung habe ich den Eindruck, daß Deine Struktur zu groß und Deine Funktionen zu umfangreich geworden sind. Daß zudem so viele der Funktionen auf ein und dieselbe Struktur zugreifen, macht die Sache sicher nicht einfacher, aber wenn Du Dir nicht die Zeit dazu nimmst, wird es in Zukunft immer schwieriger werden, damit umzugehen. Zuletzt dürfte es Dein Programm nicht allzu sehr aufblähen, den Funktionen Zeiger auf die Strukturen zu übergeben. Vermutlich wird das Dein Problem mit der abhandengekommenen Übersichtlichkeit aber nicht lösen, daher würde ich über eine Restrukturierung nachdenken -- und zwar genau jetzt, denn je länger Du damit wartest, umso aufwändiger und fehleranfälliger wird das.
Michael B. schrieb: > Christian J. schrieb: >> Nun tragen Parameter aber zur Kapselung von Daten bei und machen klar, >> was eine Funktion verändert. > > Wenn eine Funktion immer auf denselben Wert zugreift, braucht man diesen > nicht als Parameter spezifizieren, sondern kann ihn fest in die Funktion > reinschreiben. > > (Und wenn jetzt jemand schwätzt, man wäre aber doch viel flexibler für > die Zukunft, wenn man ihn trotzdem als immer gleichen Parameter > übergibt: Dan kann man ja dann in dieser Zukunft in der man die Funktion > wirklich mal mit 2 unterschiedlichen Werten arbeiten lassen will immer > noch machen.) Letztlich haben Funktionen zwei Funktionen: einerseits sollen sie mehrmals benötigten Code einkapseln, andererseits aber auch die Übersichtlichkeit des Programms erhöhen. Zu letzterem Aspekt gehört auch die Typsicherheit: wenn ich einer Funktion, die einen Zeiger auf "struct foo" erwartet, einen Zeiger auf "struct bar" übergebe, dann meckert mein Freund, der Compiler. Insofern dient es der Übersichtlichkeit und Fehlerfreiheit, keine globalen Variablen und für die Übergabe Zeiger auf lokale Strukturen zu benutzen. Dabei geht es dann nicht um Flexibilität, sondern um Übersichtlichkeit und Klarheit.
Sheeva P. schrieb: > Die Optimierung ist immer der letzte Schritt bei der > Entwicklung eines Programms, nicht der erste. "Ich habe ja nichts gegen Flüchtlinge, aber ...." So würde ich da argumentieren, denn man muss sich vorher über den Datenfluss in einem Programm klarwerden, bevor man loslegt. Nachher lässt sich das nur schwer wieder ändern und man haut sich jede Menge Fehler rein, muss alte Backups zurückspielen, weil plötzlich nichts geht geht und mehr gehbar gemacht werden kann. (Inzwischen bin ich soweit meinen Backup Server auf 15 Minuten eingestellt zu haben.) Es gibt zwei Arten von Routinen: 1. die, die rechnen 2. die die zeichnen Beide sind scharf gegeneinander abgegrenzt. Was ist also an dem Konzept falsch? Ich pappe mal einen Source dran, nur zur Demo.
Christian J. schrieb: > Nur zur Verdeutlichung: Also ich sehe da drei "Arten" von Daten: 1. aktuelle Meßwerte 2. historische Meßwerte 3. akkumulierte Meßwerte Das könnte man doch prima in Einzelteile strukturieren, etwa:
1 | struct data { |
2 | double temperatur; |
3 | uint16_t druck; |
4 | }; |
5 | |
6 | struct data aktuell; |
7 | struct data history[HISTORY_MAX + 1]; |
8 | struct akkumuliert { |
9 | struct data min; |
10 | struct data max; |
11 | struct data avg; |
12 | }; |
Sheeva P. schrieb: > Insofern dient es der Übersichtlichkeit und Fehlerfreiheit, keine > globalen Variablen und für die Übergabe Zeiger auf lokale Strukturen zu > benutzen. Dabei geht es dann nicht um Flexibilität, sondern um > Übersichtlichkeit und Klarheit. Die Übersicht hast du offenbar verloren, klar ist dir nichts. Wenn (von mehreren Funktionen) auf eine (oder mehrere) globale Variablen zugegriffen wird, ohne Parameter, ohne struct Pointer, sondern so wie Chrustian es hat, dann ist das sehr klar und vor allem wird es vom Compiler sehr gut typgeprüft. Aber die Klarheit kann im Wahn der Überstrukturierung dir schon mal abhanden kommen.
Sheeva P. schrieb: > Also ich sehe da drei "Arten" von Daten: > > 1. aktuelle Meßwerte > 2. historische Meßwerte > 3. akkumulierte Meßwerte > > Das könnte man doch prima in Einzelteile strukturieren, etwa: Welchen Vorteil bringt außer dass die Zugriffszeilen immer länger werden und viele Punkte haben? Der Compiler macht daraus sowieso eine einzige Tabelle.
Christian J. schrieb: > Sheeva P. schrieb: >> Die Optimierung ist immer der letzte Schritt bei der >> Entwicklung eines Programms, nicht der erste. > > "Ich habe ja nichts gegen Flüchtlinge, aber ...." Der Zusammenhang erschließt sich mir leider nicht, was möchtest Du mir damit mitteilen, bitte? > So würde ich da argumentieren, denn man muss sich vorher über den > Datenfluss in einem Programm klarwerden, bevor man loslegt. Das ist natürlich immer sinnvoll, aber nicht zwingend notwendig. Es reicht schon, das Programm sofort zu refaktorieren, wenn man bemerkt, daß es sich in die falsche Richtung entwickelt. Und natürlich ist es auch immer eine gute Idee, von einander unabhängige Programmteile von einander unabhängig zu schreiben und mit klaren Schnittstellen zu versehen -- um den Überblick zu behalten, und damit einem der Compiler besser helfen kann. > Nachher > lässt sich das nur schwer wieder ändern und man haut sich jede Menge > Fehler rein, muss alte Backups zurückspielen, weil plötzlich nichts geht > geht und mehr gehbar gemacht werden kann. (Inzwischen bin ich soweit > meinen Backup Server auf 15 Minuten eingestellt zu haben.) Ein Versionskontrollsystem wie Git oder Subversion ist bei so etwas extrem hilfreich. > Es gibt zwei Arten von Routinen: > > 1. die, die rechnen > 2. die die zeichnen Das sind die Teile "V" (Verarbeitung) und "A" (Ausgabe) des EVA-Prinzips. Es fehlt "E", die Eingabe -- also Funktionen, die die Daten von Deinen Sensoren (oder sonstwoher) einlesen. Und wenn ich so auf Deine Datenstruktur schaue, müßten da noch viel mehr Arten von Funktionen sein: 1. Funktionen, die die Daten einlesen 2. Funktionen, die sie in die Historie eintragen 3. Funktionen, die die Historie akkumulieren 4. Funktionen, die auf das Display zeichnen Und ich persönlich würde die Funktionen zu 4. sogar noch einteilen in 4a. Funktionen, die die Überschrift und die Skalen etc., sowie 4b. Funktionen, die die eigentlichen Datenplots auf das Display malen. > Beide sind scharf gegeneinander abgegrenzt. ...und durch die gemeinsame fette Datenstruktur wieder eng und untrennbar mit einander verheiratet. > Was ist also an dem Konzept falsch? Ebenjene enge Verheiratung; mit kleineren, spezialisierteren Strukturen wäre die Angelegenheit deutlich übersichtlicher. > Ich pappe mal einen Source dran, nur zur Demo. Ohne da jetzt komplett durchgestiegen zu sein, sieht man den Funktionen an, daß die Datenstruktur suboptimal ist. Und, sei mir bitte nicht böse, das Verschieben Deiner History ab Zeile 44 ist wirklich grausam; das kann man viel schöner mit einer lokalen static-Variable lösen.
Christian J. schrieb: > Sheeva P. schrieb: >> Also ich sehe da drei "Arten" von Daten: >> >> 1. aktuelle Meßwerte >> 2. historische Meßwerte >> 3. akkumulierte Meßwerte >> >> Das könnte man doch prima in Einzelteile strukturieren, etwa: > > Welchen Vorteil bringt außer dass die Zugriffszeilen immer länger werden > und viele Punkte haben? Der Compiler macht daraus sowieso eine einzige > Tabelle. In Deinem Eröffnungsbeitrag hast Du Dich doch darüber beklagt, daß Dir der Überblick verloren gegangen sei, und gefragt, wie andere das machen. Oder habe ich das falsch verstanden? Ich verstehe auch nicht, was Du gegen längere "Zugriffszei_l_en" oder gegen Punkte hast, wenn sie Dir helfen, die Übersicht zu behalten. Und warum man viele Punkte braucht, wenn man eine große Datenstruktur auf mehrere kleinere verteilt, ist mir ebenfalls schleierhaft. Kannst Du mir also bitte kurz einmal erklären, was Du mit Deinem Thread eigentlich bezweckst? Vielen Dank.
MaWin schrieb: > Die Übersicht hast du offenbar verloren, klar ist dir nichts. > > Wenn (von mehreren Funktionen) auf eine (oder mehrere) globale Variablen > zugegriffen wird, ohne Parameter, ohne struct Pointer, sondern so wie > Chrustian es hat, > > dann ist das sehr klar und vor allem wird es vom Compiler sehr gut > typgeprüft. > > Aber die Klarheit kann im Wahn der Überstrukturierung dir schon mal > abhanden kommen. Es ist schade, daß Du wieder nicht mehr zum Thema beitragen kannst als eine dumme Pöbelei und einen erneuten Beweis, daß Du ein lausiger Programmierer bist. Es gibt ja gute Gründe, warum globale Variablen von professionellen Entwicklern als schlechter Stil betrachtet werden, warum alle mir bekannten Standards von ihrem Gebrauch dringend abraten und warum auch Kerninghan und Ritchie dabei ausdrücklich zur Vorsicht mahnen.
Sheeva P. schrieb: > Kannst Du mir also bitte kurz einmal erklären, was Du mit Deinem Thread > eigentlich bezweckst? Vielen Dank. Du hast mir schon viele Ansätze geliefert, keine Sorge. Und dass man das "Rollen" auch mit einem Ringpuffer machen kann ohne darin wirklich etwas zu rollen ist mir klar. Aber das Funktionieren war mir erst wichtiger. Bei der Aktualisierung jede Minute mit 59s "CPU Stop" dazwischen spielt sowas aber auch keine Rolle mehr, Zeit habe ich ohne Ende. Eine Separierung vom Zeichnen statischer Elemente (Beschriftungen, Rahmen) und dynamischen (Balken) kommt noch, morgen vielleicht. Ich habe aber erst gerade die Balken wieder vereinfacht, vorher habe ich nur das Delta vorne abgezogen oder drangesetzt was aber wegen der dyn. Skalierung manchmal schiefging, nur bei schnellen Wechseln Sinn macht, die flackerfei sein sollen und ich einen Knoten im Hirn hatte.
Also bei so nem kleinen Furzprogramm mit IoC und anderem Patterngewichse anzukommen ist völliger overkill. Das lässt sich komplett klassisch strukturiert erschlagen. Der Moster-struct ist hässlich ja, aber selbst wenn dieser oder Teile davon global bleiben ist das oft übersichtlicher und einfacher für Anfänger zu handeln als ständig Referenzen oder kopierterte Werte wie wild herumzureichen. Gut, Performance spielt hier keine Rolle aber der JEWEILIGE Autor soll seinen Code lesen und handeln können und da ist nur ER der Massstab, wenn er mit anderen Paradigmen nix anfangen kann wird dann auch nicht besser wenn er die krampfhaft anwendet. Hier und da ein paar kleine Ändeurngen und das Ding läuft und bleibt dennoch übersichtlich. Und git solltest du dir mal anschauen, Versionskontrolle ist eine enorme Hilfe beim Ausprobieren und "Zurückrudern" zu früheren stabilen Versionen.
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.