Hallo Leute,
es geht um AVR-Programmierung in GCC. Ich würde mich als
fortgeschrittenen Einsteiger einschätzen.
Ich versuche immer möglichst, "Don't repeat yourself" zu befolgen.
Daraus resultiert aber dann auch, dass eine Funktion eine weitere
aufruft und die dann vielleicht noch eine weitere.
So zum Beispiel:
function_1()
|
+---> function_2()
|
+---> function3_()
function_2() wird nur von function1_() aufgerufen,
function_3() nur von function_2().
Mir geht es dann oft so, dass ich durch den Code scrolle, ich eigentlich
die Hauptfunktion function_1() suche, aber ich z.B. erst mal bei
function_2() oder function3_() lande.
Dann lese ich die Kommentare und stelle fest, dass das lediglich
Unterfunktionen von function_1() sind und mich deshalb erst mal gar
nicht interessieren.
In diesem fiktiven Beispiel ist es ja naheliegend, dass function_1() die
Hauptfunktion ist, aber in Wirklichkeit haben die Funkionen ja Namen,
normalerweise ohne Durchnummerierung.
Wie kann ich die Funktionen sinnvoll benennen, sodass man weiß, dass
diese zusammengehören? Gibt es dafür eine Konvention?
Sodass man am besten auch gleich erkennt, dass die Funktionen nicht
dafür vorgesehen sind, von woanders aus aufgerufen zu werden.
Kann ich die Unterfunktionen irgendwie "unsichtbar" machen? Sodass sie
z.B. in meiner Main-Schleife gar nicht sichtbar sind?
Oder dass eine Warnung kommt, wenn ich es dennoch versuche?
Ich möchte aber auch nicht für jede Funktion ein eigenes C- oder H-File
erzeugen müssen.
Danke!
Third-Eye
Third Eye schrieb:> Wie kann ich die Funktionen sinnvoll benennen, sodass man weiß, dass> diese zusammengehören? Gibt es dafür eine Konvention?
eigentlich braucht man das nicht. Jede Funktion sollte so benannt sein,
das man weis was sie tut. Ob sie nur von einer anderen Funktion
verwendet wird spielt dabei gar keine rolle. Sinn und zweck der
Funktionen ist doch, das man sie überall verwenden kann.
> Mir geht es dann oft so, dass ich durch den Code scrolle, ich eigentlich> die Hauptfunktion function_1() suche, aber ich z.B. erst mal bei> function_2() oder function3_() lande.
warum scrollt du überhaupt durch den code. Wenn ich ein Funktion suche,
dann kenne ich den Name und lasse einfach suchen.
Wenn ich function_1 suche, muss ich mir doch nicht function_2 anschauen.
Schieb die function1() an den Anfang der C-Datei.
Die lokalen Funktionen schiebst du ans Ende der Datei, auch wenn das
bedeutet, dass du einen Protoypen brauchst.
Den lokalen Funktionen verpasst du ein 'static'.
Manche machen es auch so, dass sie an der 'Trennlinie' einen Kommentar
mit einem dicken fetten Balken einziehen. Da bin ich nicht so der Freund
davon.
Die von aussen sichtbaren Funktionen an den Anfang der C-Datei schieben
reicht im Normalfall aus, um diese Unterscheidung treffen zu können.
Wobei: So oft ist diese Unterscheidung ja gar nicht wichtig. Denn ist
das Modul erst mal geschrieben, dann interessiert ja eigentlich mehr das
Header-File, als die tatsächliche Implementierung. Dann das ist es ja,
was ich zur Benutzung der Funktionen benötige.
Also, um bei deinem Beispiel zu bleiben
1
staticvoidfunction_2(void);
2
staticvoidfunction_3(void);
3
4
5
voidfunction_1(void)
6
{
7
function_2();
8
}
9
10
staticvoidfunction_2(void)
11
{
12
function_3();
13
}
14
15
staticvoidfunction_3(void)
16
{
17
}
Die wichtige, die Hauptfunktion, steht ganz oben. Die benutzten
Hilfsfunktionen stehen darunter. Und da diese Hilfsfunktionen ausserhalb
des C-Files niemanden zu interessieren haben, sind sie auch noch static
markiert. Selbst wenn jemand wollte, er könnte diese Funktionen gar
nicht ausserhalb dieser C-Datei aufrufen.
>>Schieb die function1() an den Anfang der C-Datei.>>Die lokalen Funktionen schiebst du ans Ende der Datei, auch wenn das>>bedeutet, dass du einen Protoypen brauchst.
Meist ist es aber genau anders herum, Da man sich die static
Definitionen sparen will, kommt also zuerst function3, dann function2
und zuletzt function1.
Schaue ich mir also Code an, dann gehe ich rückwärts. Unten stehen die
wichtigen Funktionen, und je weiter man im Code nach oben kommt, desto
mehr kommt man in den Bereich der Hilfsfunktionen.
Das ist auch bei vielen anderen Sprachen (Pascal und Nachfolger) so.
Ursprünglich lag das in den Compilern begründet, die nur 1 Pass im Code
gemacht haben. Alles was benutzt werden soll, muss vorher definiert
werden. Dadurch liegen die Haupt-Dinge wie unten, und die
Unterfunktionen davor.
Das wurde aber nicht gemacht weil es so logisch oder übersichtlich ist,
sondern weil sich das aus dem Sprachkonzept zu ergeben hat und die
Compiler vereinfacht hat.
<nicht ernst gemeint>
Deshalb sind ja auch in einem Buch, die ganzen Anhänge und
Literaturverweise immer vorne, weil das so logisch ist, dass man erst
mit den Hilfskapitel anfängt.
Nicht falsch verstehen. Ich hab auch kein Problem damit, wenn die
Hauptfunktion unten ist und die Hilfsfunktionen darüber. Wenn der TO
aber ein Problem damit hat und eine Systematik benutzen will, dann
kriegt er was zu dem Thema. Wobei ich denke, dass alleine das 'static'
schon viele Probleme rausnehmen würde.
Eine gute IDE z.B. Eclipse bietet dir zusätzliche Hilfe beim schnellen
Navigieren im Code.
z.B. ein Fenster das alle Funktionen des Moduls anzeigt, ein Hotkey, der
alle Verwendungen der Funktion anzeigt, oder einer der zu der Definition
der Funktion springt, ...
Auch kann manje nach Ide ganze Funktionen "einklappen" wenn man das mag.
Ansonsten gilt was Karl Heinz (und andere) schon gesagt haben.
Das "static" sparen?
Was kostet denn dieses luxuriöse "static", dass man damit so haushalten
muss? Außer 1.5s wertvolle Tippzeit?
Ganz im Gegenteil, "static" sollte so oft wie möglich verwendet werden.
Dadurch kann der Compiler auch besser optimieren. Und dann "sparst" du
richtig.
Keine Prototypen, und alle Hilfsfunktionen an den Anfang? Noch dazu ohne
"static", so dass sie erst auf den zweiten Blick als Hilfsfunktion
identifizierbar sind? Sorry, das ist unorganisierter Bastelcode.
@ TE: wenn deine Hilfsfunktionen in der Main nicht bekannt (=
"unsichtbar") sein sollen dann müssen diese als "static" und in einer
separaten C-Datei deklariert werden. Aber dagegen sträubst du dich ja.
Macht nix, das kommt mir der Zeit. Spätestens wenn du UART, LCD, ADC und
sonst-noch-was ansteuerst dann willst du nicht mehr alles in einer Datei
haben.
Third Eye schrieb:> Ich möchte aber auch nicht für jede Funktion ein eigenes C- oder H-File> erzeugen müssen.
Die Funktionen, die logisch zusammengehören (z.B. weil sie das gleiche
Subsystem ansprechen), kann man in die gleiche Datei stecken. Zumindest
solange diese nicht zu groß wird. Niemand will durch zehntausend Zeilen
Code am Stück durchscrollen müssen.
Es sprich auch nichts dagegen, für Funktionen die eng miteinander
zusammenhängen jeweils das gleiche Präfix im Namen zu verwenden. Man hat
31 Zeichen zur Verfügung, da muss man nicht nur zehn verwenden.
Third Eye schrieb:> Ich versuche immer möglichst, "Don't repeat yourself" zu befolgen.
Meiner Meinun nach nicht gerade das beste Pradigma...
Es macht einfach keinen Sinn auf Krampf Unterfunktionen zu bauen, für
konkrete Tipps/Hinweise solltest du am besten mal ein Beispiel posten wo
es dir "schwer fällt".
Läubi .. schrieb:> Meiner Meinun nach nicht gerade das beste Pradigma...>> Es macht einfach keinen Sinn auf Krampf Unterfunktionen zu bauen
Es ist durchaus oft richtig, gerade wenn man Code hat, der von
verschiedenen Personen über Jahre weiterentwickelt wird. Oft genug
laufen dann gleiche Dinge auseinander.
Aber nicht um jeden Preis, da stimme ich dir vollauf zu.
Wir haben hier auch so eine Gruppe die "Clean Code" für das zentrale
Mantra des Softwareuniversums halten, und 3 Hierarchien abgleitete
Klassen einführen nur um 3-5 Zeilen Code zusammenzuführen.
Manchmal zum Verzweifeln....
Udo Schmitt schrieb:> Manchmal zum Verzweifeln....
2 Dinge
* in der Programmierung gibt es kein 'one size fits all'. Jeder Fall ist
anders, jede Entscheidung muss auf den vorliegenden Einzelfall angepasst
werden. Sicher, es gibt Dinge, die können in 95% aller Fälle stur
einfach angewendet werden und man fährt damit nicht schlecht. Aber
sobald etwas zum unanfechtbaren Dogma wurde, das im Einzelfall mit einem
guten Grund nicht übergangen werden kann, .. sobald so etwas vorliegt,
hat man etwas ganz grundsätzliches nicht verstanden.
* Es gibt immer einen Punkt, an dem sich auch der beste Vorsatz ins
Gegenteil verkehrt. Es gibt immer einen Punkt, an dem die Dinge nur noch
schlechter werden und nicht besser.
In diesem Sinne:
> Manchmal zum Verzweifeln....
Ja, leider.
Edit:
Was natürlich nicht heißen soll, dass Narrenfreiheit für Neulinge ok
ist. Neulinge fahren am besten, wenn sie sich erst mal strenger an die
Regeln halten, bis sie gelernt haben, wo es Sinn macht, davon
abzuweichen.
Läubi .. schrieb:> Meiner Meinun nach nicht gerade das beste Pradigma...
Ich benutze es auch und finde es sehr sinnvoll. Es erhöht die Übersicht
und senkt drastisch die Fehlerquote.
Sobald ich nämlich Copy&Paste bemühe, passiert folgendes:
Ich kopiere einen Fehler und merke, es funktioniert nicht. Ich suche den
Fehler in dem einen Teil, vermute ihn an einer Stelle, korrigiere sie
und es läuft immer noch nicht. Also mache ich die Änderung rückgängig
und suche weiter. Und schon nach eine Woche merke ich endlich, ich hätte
ihn an 2 Stellen korrigieren müssen.
Hier mal ein schönes Beispiel für perfekte Aufteilung:
1
staticvoidlcd_nibble(uint8_dd)
2
{
3
LCD_D4=0;if(d&1<<4)LCD_D4=1;
4
LCD_D5=0;if(d&1<<5)LCD_D5=1;
5
LCD_D6=0;if(d&1<<6)LCD_D6=1;
6
LCD_D7=0;if(d&1<<7)LCD_D7=1;
7
8
LCD_E0=1;
9
_delay_us(1);
10
LCD_E0=0;
11
}
12
13
staticvoidlcd_byte(uint8_dd)
14
{
15
lcd_nibble(d);
16
lcd_nibble(d<<4);
17
_delay_us(45);
18
}
19
20
voidlcd_command(uint8_dd)
21
{
22
LCD_RS=0;
23
lcd_byte(d);
24
if(d<=3)
25
_delay_ms(2);
26
}
27
28
voidlcd_data(uint8_dd)
29
{
30
LCD_RS=1;
31
lcd_byte(d);
32
}
33
34
voidlcd_init(void)
35
{
36
// ...
37
lcd_nibble(0x30);
38
// ..
39
lcd_command(0x28);
40
// ..
41
}
Viele andere LCD-Routinen kranken dagegen an Unmengen Copy&Paste. Und
die Folge davon ist, die Leute sitzen ewig am Fehler suchen.
Peter Dannegger schrieb:> Sobald ich nämlich Copy&Paste bemühe, passiert folgendes:
Ich habe nicht Copy&Paste als Alternative propagiert!
Eine sinnvolle Strukturierung ist immer Gold wert, wenn man aber
folgendes Problem hat:
Third Eye schrieb:> Mir geht es dann oft so, dass ich durch den Code scrolle, ich eigentlich> die Hauptfunktion function_1() suche, aber ich z.B. erst mal bei> function_2() oder function3_() lande.> Dann lese ich die Kommentare und stelle fest, dass das lediglich> Unterfunktionen von function_1() sind und mich deshalb erst mal gar> nicht interessieren.
dann bezweifle ich jetzt einfach mal ganz frech das der Code sinnvoll
und semantisch strukturiert ist ;-)
Udo Schmitt schrieb:> Oft genug laufen dann gleiche Dinge auseinander
Manchmal sind Dinge die auf den ersten Blick gleich erscheinen halt
nicht so gleich wie man zuerst dachte. Aber wie gesagt ich will hier
nicht C&P propagieren, aber die "Problemstellung" des TEs hört sich
etwas bizar an.
Udo Schmitt schrieb:> 3 Hierarchien abgleitete Klassen einführen
Ach nur 3? ;-P
Peter Dannegger schrieb:> Hier mal ein schönes Beispiel für perfekte Aufteilung:
In umgekehrter Reihenfolge ist es imo leichter zu erfassen: Zuerst die
High-Level-Funktionalität und die Details am Schluss.
1
staticvoidlcd_byte(uint8_dd);
2
staticvoidlcd_nibble(uint8_dd);
3
4
voidlcd_init(void)
5
{
6
// ...
7
lcd_nibble(0x30);
8
// ..
9
lcd_command(0x28);
10
// ..
11
}
12
13
voidlcd_command(uint8_dd)
14
{
15
LCD_RS=0;
16
lcd_byte(d);
17
if(d<=3)
18
_delay_ms(2);
19
}
20
21
voidlcd_data(uint8_dd)
22
{
23
LCD_RS=1;
24
lcd_byte(d);
25
}
26
27
staticvoidlcd_byte(uint8_dd)
28
{
29
lcd_nibble(d);
30
lcd_nibble(d<<4);
31
_delay_us(45);
32
}
33
34
staticvoidlcd_nibble(uint8_dd)
35
{
36
LCD_D4=0;if(d&1<<4)LCD_D4=1;
37
LCD_D5=0;if(d&1<<5)LCD_D5=1;
38
LCD_D6=0;if(d&1<<6)LCD_D6=1;
39
LCD_D7=0;if(d&1<<7)LCD_D7=1;
40
41
LCD_E0=1;
42
_delay_us(1);
43
LCD_E0=0;
44
}
Offtopic: Gehen die Anweisungen in der letzten Funktion nicht einfacher?
Third Eye schrieb:> Mir geht es dann oft so, dass ich durch den Code scrolle, ich eigentlich> die Hauptfunktion function_1() suche, aber ich z.B. erst mal bei> function_2() oder function3_() lande.> Dann lese ich die Kommentare und stelle fest, dass das lediglich> Unterfunktionen von function_1() sind und mich deshalb erst mal gar> nicht interessieren.> In diesem fiktiven Beispiel ist es ja naheliegend, dass function_1() die> Hauptfunktion ist, aber in Wirklichkeit haben die Funkionen ja Namen,> normalerweise ohne Durchnummerierung.https://www.gnu.org/software/cflow/
kann ich dir ans Herz legen.
Bekommst du einen schönen statischen Baum.
Nicht zu verwechseln mit dynamischen Baum, der durch den Profiler
wie zB gprof erstellt wird. So ein statischer Baum ist sehr oft
vollkommen ausreichend.
Und wenn man durch fremden Code durchgeht, vielleicht vorher mit
http://astyle.sourceforge.net/
bearbeiten.
Karl Heinz schrieb:> Neulinge fahren am besten, wenn sie sich erst mal strenger an die> Regeln halten, bis sie gelernt haben, wo es Sinn macht, davon> abzuweichen.
Genau das ist der Grund, warum mich immer brennend interessiert, wie man
denn "richtig" programmiert oder besser gesagt, wie man es machen KANN.
Ich denke mir dann manchmal "Das ist ja praktisch! Ich habe das immer
umstänglich so und so gemacht".
Manche Sachen empfinde ich aber auch eher als für mich nicht sinnvoll.
Danke für die Anregungen! Das mit den static-Funktionen scheint eine
praktikable Lösung zu sein.