Forum: Compiler & IDEs Nomenklatur für Unterfunktionen und gibt es eine Art Kapselung?


von Third E. (third-eye)


Lesenswert?

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

von Peter II (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von PittyJ (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Udo S. (urschmitt)


Lesenswert?

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.

von Karl (Gast)


Lesenswert?

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.

von Mark B. (markbrandis)


Lesenswert?

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.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

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

von Udo S. (urschmitt)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

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

von Fabian O. (xfr)


Lesenswert?

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;

von Daniel -. (root)


Lesenswert?

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.

von Third E. (third-eye)


Lesenswert?

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