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 | static void function_2( void ); |
2 | static void function_3( void ); |
3 | |
4 | |
5 | void function_1( void ) |
6 | {
|
7 | function_2(); |
8 | }
|
9 | |
10 | static void function_2( void ) |
11 | {
|
12 | function_3(); |
13 | }
|
14 | |
15 | static void function_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 | static void lcd_nibble( uint8_d d ) |
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 | static void lcd_byte( uint8_d d ) |
14 | {
|
15 | lcd_nibble( d ); |
16 | lcd_nibble( d<<4 ); |
17 | _delay_us( 45 ); |
18 | }
|
19 | |
20 | void lcd_command( uint8_d d ) |
21 | {
|
22 | LCD_RS = 0; |
23 | lcd_byte( d ); |
24 | if( d <= 3 ) |
25 | _delay_ms( 2 ); |
26 | }
|
27 | |
28 | void lcd_data( uint8_d d ) |
29 | {
|
30 | LCD_RS = 1; |
31 | lcd_byte( d ); |
32 | }
|
33 | |
34 | void lcd_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 | static void lcd_byte( uint8_d d ); |
2 | static void lcd_nibble( uint8_d d ); |
3 | |
4 | void lcd_init( void ) |
5 | {
|
6 | // ...
|
7 | lcd_nibble( 0x30 ); |
8 | // ..
|
9 | lcd_command( 0x28 ); |
10 | // ..
|
11 | }
|
12 | |
13 | void lcd_command( uint8_d d ) |
14 | {
|
15 | LCD_RS = 0; |
16 | lcd_byte( d ); |
17 | if( d <= 3 ) |
18 | _delay_ms( 2 ); |
19 | }
|
20 | |
21 | void lcd_data( uint8_d d ) |
22 | {
|
23 | LCD_RS = 1; |
24 | lcd_byte( d ); |
25 | }
|
26 | |
27 | static void lcd_byte( uint8_d d ) |
28 | {
|
29 | lcd_nibble( d ); |
30 | lcd_nibble( d<<4 ); |
31 | _delay_us( 45 ); |
32 | }
|
33 | |
34 | static void lcd_nibble( uint8_d d ) |
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?
1 | LCD_D4 = ( d & 1<<4 ) ? 1 : 0; |
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.
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.