Forum: PC-Programmierung Programmier-Stil: Globale Variablen vs. Parameterübergabe


von Christian J. (Gast)


Lesenswert?

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?

von Michael B. (laberkopp)


Lesenswert?

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.).

von Jan H. (j_hansen)


Lesenswert?

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.

von Jan H. (j_hansen)


Lesenswert?

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 :)

von Christian J. (Gast)


Lesenswert?

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();

von Horst (Gast)


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

Jan H. schrieb:

> Warum verändert "DrawFeuchte" dein Struct? Und warum heißt die Routine
> nicht "ZeichneFeuchte" oder "DrawHumidity"?

Häh? kopfkratz

von Hans (Gast)


Lesenswert?

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?

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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

von MaWin (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Sheeva P. (sheevaplug)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von wurst (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.