Hallo,
ich habe ein Projekt für RC Modelle Realisiert in C, es funktioniert
auch alles soweit, nur finde ich den Code nicht sonderlich "schön". Wie
optimiert man am besten einen Code....
als Beispiel
DDRB = 0b11111111; -> ist es eleganter 0xFF zu schreiben oder alle
einzeln mit (1<<PBX)
in der FH merkt man im Praktia das man in Java als Student relativ viel
Programmiert und der Prof. dann alles in einer Zeile umsetzt ;). Das ist
in C sicherlich ähnlich und mit der zeit kommen die kniffe...
Gibt es aber irgend wo beispiele, oder eine art Festlegung wie man den
Code gestaltet. Hardware nahe C Programmierung fängt jetzt erst an aber
das Projekt wollen einige in ihren Modellen einsetzen und da will ich es
sauber umgesetzt haben. Ich setze mich immer mal wieder dran und
überarbeite einige stellen, aber viele Wege führen nach Rom ;).
Es geht mir um grundlegende Regeln, inoffizielle Festlegungen die
wichtig werden wenn die Programme größer werden...
lg
Viel wichtiger als schöner code... sind schöne Kommentare...
also z.B. kann man ruhig
DDRB = 0xff;
schreiben, sollte dann aber darüber "Alle Pins an Port B als Ausang"
schreiben...
Wenn die Pins eine spezielle Bedeutung haben, wuerde ich auf jeden Fall
defines dafuer definieren und auch verwenden. Kuerzer ist meist nicht
besser, sondern nur schlechter lesbar.
Alte Regel:
"Code is written once, but read many times."
>schreiben, sollte dann aber darüber "Alle Pins an Port B als Ausang">schreiben...
Viel wichtiger ist aber eine Erklärung, warum alle Pins als Ausgang
gesetzt werden.
Nur ein paar Tipps, die sich für mich bewährt haben:
1. Bestimmte Werte wie z.B. die Anzahl von Schleifendurchläufen bei der
Bearbeitung von Tabellen oder bei Timeouts, etc sollten im Header der
betreffenden C-Datei per #define hinterlegt werden.
#define C_MAX_PACKET_LENGTH 12
#define C_MAX_BUS_TIMEOUT 100
2. Prototypen, neu angelegte Datentypen/Strukturen, Defines usw gehören
in
die Headerdatei (sofern sie Modulübergreifend verwendet werden
sollen).
Variablen werden hingegen in der .C Datei angelegt. Genauso wie die
Funktionen. Kurz, alles was Code/Daten erzeugt kommt in die .C-Datei,
alles was diese nur beschreibt in den Header.
3. Versuch aussagekräftige Variablennamen zu verwenden, die auch schon
einen Hinweis auf den Datentypen geben. Dann muss man später nicht
rätseln, ob die Variable nun ein Pointer, ein Integer, ein... ist.
unsigned char ucMyChar;
signed int uiMyInt;
char szMyString[] = "Hallo Welt";
4. Versuch so wenig globale Variablen wie möglich zu verwenden. Und wenn
es
UNBEDINGT sein muss, versuch den Gültigkeitsbereich einzuschränken.
Also
zunächst über 'static'-Variablen in der Funktion (sofern diese nicht
rekursiv aufgerufen wird). Falls das nicht ausreicht, beschränke die
Gültigkeit wenigstens auf das Modul, in dem sie benötigt wird. Wenn
du
z.B. für einen UART-Treiber einen Ringpuffer brauchst, muss dieser
ausserhalb uart.c nicht sichtbar sein.
5. Kennzeichne Funktionen, die nur im aktuellen Modul verwendet werden
mit
einem Unterstrich am Anfang:
unsigned int _calcCrc(unsigned char pucBuffer);
6. Versuch Funktionsnamen mit dem Modul in Verbindung zu bringen:
void uart_initDriver(void);
void spi_sendCharacter(unsigned char ucData);
7. Trenne innerhalb eines Moduls getrennte Bereiche ruhig durch aus-
kommentierte Trennlinien:
/* Variablen *********************************************/
unsigned char _aucRingBuffer[C_MAX_RINGBUFFER_LENGTH];
unsigned char _ucWritePtr;
unsigned char _ucReadPtr;
/* Funktionen ********************************************/
void uart_initDriver(
unsigned char uiBaudrate, /* Baudrate */
unsigned char ucParityFlag /* Paritäts-Einstellung */
)
{
...
}
8. Rücke alle Blöcke vernünftig ein. Leerzeichen sind Tabs immer vorzu-
ziehen. Am besten im Editor gleich alle Tabs durch 2 oder 4
Leerzeichen
ersetzen lassen.
9. Gute Kommentare sind wichtig. Sie sollten nicht den Code wiederholen,
sondern den Sinn der Zeile wiedergeben:
Schlecht:
ucReadPtr++; /* Variable ucReadPtr inkrementieren */
Besser:
ucReadPtr++; /* Lesezeiger auf nächstes Element setzen */
10. Scheue dich nicht davor, auch mal 2,3 Zeilen Kommentar über
den Sinn bzw. die Funktion eines Algorithmus zu schreiben. Gerade
bei komplizierterern Funktionen muss man dann einige Wochen später
nicht erst den Code reverse-engeneeren um dahinter zu kommen, was
genau er macht.
11. Versuch den Quellcode stets bündig zu kommentieren. Es liest sich
angenehmer, wenn ein Block eingerückt an einer festen Stelle beginnt
und eine Zeile einheitlich z.B. max. 100 Zeichen lang ist. An dieser
Länge kann man dann auch festmachen, wo man Kommentare schliesst.
/* Variablen für Ringpuffer-Handling
**********************************/
unsigned char _ucReadPtr; /* Lesezeiger im Ringpuffer
*/
unsigned char _ucWritePtr; /* Schreibzeiger im Ringpuffer
*/
unsigned char _pRingBuffer; /* Zeiger auf Ringpuffer
*/
/* Prototypen für lokale Funktionen
***********************************/
void _clearRingBuffer(void); /* Löscht den Ringpuffer
*/
void _sendCrc(void); /* Sendet CRC für akt. Packet
*/
Seh gerade das die Zeile im Antwort-Feld hier etwas länger ist als die
tatsächlich dargestellte. arrg
Alle Kommentare in den Beispiel-Codes enden in der selben Zeile in
der sie eröffnet wurden. Kommentare in aufeinander folgenden Zeilen
enden ebenfalls in der selben Spalte.
Peter Stegemann wrote:
> Ganz sicher keine Typpraefixes, die sind grauenhaft zu lesen und bringen> wenig.
Ungarische Notation halt. Alle Microsoft-Jünger lieben sie, alle
anderen hassen sie wie die Pest... Ich habe mich früher immer
gefragt, warum die Programme alle mit dem Drucker reden wollen
(Variablennamen, die mit "lp" anfangen :-).
Hmm... wrote:
> Seh gerade das die Zeile im Antwort-Feld hier etwas länger ist als die> tatsächlich dargestellte. *arrg*
Siehste, wenn du dich anmeldest, darfst du deine Postings nachher
sogar noch (ein Weilchen) editieren. Aber auch sonst gibt's dafür
die Vorschau.
> Alle Kommentare in den Beispiel-Codes enden in der selben Zeile in> der sie eröffnet wurden. Kommentare in aufeinander folgenden Zeilen> enden ebenfalls in der selben Spalte.
Dafür wiederum gibt's die [c]-Markierungen.
@Peter:
Es gibt verschiedene Stile und Notationen. Die vorgeschlagene ist EINE
von denen, die in der Praxis gut funktionieren. K&R-Notation (siehe
Linux-Kernel) ist eine andere, aber die finde ich persönlich ein wenig
umständlich zu lesen.
Typ-Prefixe können aber bei der späteren Pflege des Codes durch einen
anderen Entwickler eine große Hilfe darstellen, wenn sie konsequent
eingesetzt werden. Oder weißt du sofort, welchen Datentyp ein Variable
wie
"act_pump_pressure" hat? Ist das ein Integer oder ein Float?
Vorzeichenbehaftet, weil kein negativer Druck möglich ist, oder
vielleicht mit Vorzeichen weil später damit gerechnet wird?
danke für die Tips :) genau das meinte ich. Mir fehlen nur die Ansätze,
kommentiert habe ich eigentlich alles aber mit der Define Funktion kaum
gearbeitet :(. auch arbeite ich zuviel mit Globalen variablen wie
int count;
int result;
es fehlt noch die Code sicherheit aber ich bin froh das das 1 Programm
schon mal Funktioniert... na ja aber mit den Tips komme ich schon mal ne
ecke weiter :)
Hmm... wrote:
> Oder weißt du sofort, welchen Datentyp ein Variable> wie> "act_pump_pressure" hat?
Der Datentyp der einzelnen Variablen ist als allererstes für das
Verständnis unwichtig. Wichtiger ist, dass der gesamte Algorithmus
in einem Kommentar kurz beschrieben wird, und dort wäre auch Platz,
sich über die Typenwahl auszulassen, warum man vielleicht den
eingelesenen uint16_t aus dem ADC anschließend lieber als float
weiter verarbeiten möchte.
> Wichtiger ist, dass der gesamte Algorithmus> in einem Kommentar kurz beschrieben wird, und dort wäre auch Platz,> sich über die Typenwahl auszulassen
Und mit entsprechenden Doku-Tools (Doxygen, etc) hat man dann auch eine
schöne Code-Doku zum nachschlagen. ;)
> Ungarische Notation halt. Alle Microsoft-Jünger lieben sie, alle> anderen hassen sie wie die Pest...
Meine Windows-Codingzeit ist schon eine Weile her, aber manches wird man
wohl nie los...
Aber nochmal kurz zum Thema zurück, die Sache mit den Datentypen ist mir
letztens erst in der Atmel-Appnote zu den AT90USBxxx aufgefallen. Dort
wird im Beispiel USB-Stack eine 8-Bit Variable "data_to_transfer"
verwendet, welche an einigen Stellen überlaufen kann. Wenn man dann mal
einen größeren Descriptor (Report-Deskriptor,etc) schicken möchte, geht
dieser dann auf einmal nicht mehr korrekt zum Host, obwohl der Stack ja
eigentlich den Deskriptor korrekt in kleine Packete zerlegt, die in den
Endpoint passen.
Hier werden an einigen Stellen munter Werte in die Variable geschrieben,
die schnell größer als 255 werden können. Ein entsprechender Hinweis
fehlt, allerdings denke ich das es sich hier eher um einen Bug den als
um ein Feature handelt. ;)
Den coding style der USB-Appnotes würde ich auch alles andere denn
als vorbildlich bezeichnen. Die Teile erinnern eher an einen riesigen
Teller voller Spaghetti...
Besonders witzig wird ungarische Notation, wenn der Autor dann lustig
zwischen a fuer Array und p fuer Pointer wechselt. Am besten ist dann
noch ein pa fuer Strings...
Hmm... wrote:
> 1. Bestimmte Werte wie z.B. die Anzahl von Schleifendurchläufen bei der> Bearbeitung von Tabellen oder bei Timeouts, etc sollten im Header der> betreffenden C-Datei per #define hinterlegt werden.
In kleiner Umgebung ja, ansonsten definitiv die falsche Methode. Der
C-Präprozessor ist doof wie Stroh, deshalb, wenn möglich, immer mit dem
Compiler arbeiten:
1
constintmeine_konstante=5;
2
/* anstelle von */
3
#define meine_konstante 5
> 3. Versuch aussagekräftige Variablennamen zu verwenden, die auch schon> einen Hinweis auf den Datentypen geben. Dann muss man später nicht> rätseln, ob die Variable nun ein Pointer, ein Integer, ein... ist.
Glaubenssache. Der Typenpräfix ist meiner Ansicht nach blödsinnig und
macht die Sache weder übersichtlicher noch lesbarer.
Kurze Variablennamen sind in Standardkonstrukten legitim:
1
for(inti=0;i<5;i++){}
2
/* und ja, mit C99 geht das */
Überhaupt: nicht das Rad neu erfinden, sondern Mut zu eingängigen
Konstruktionen. Das For oben liest sich zehnmal eingängiger (weil schon
hundertmal gelesen), als sowas:
> 4. Versuch so wenig globale Variablen wie möglich zu verwenden. Und wenn> es UNBEDINGT sein muss, versuch den Gültigkeitsbereich einzuschränken.
Gleiches gilt für Funktionen, die können und sollten auch, wenn möglich,
statisch sein. Unter Umständen kanns trotzdem sinnvoll sein, komplexere
Hilfsvariablen mit Static außerhalb von Funktionen zu definieren, je
nach Compiler. Nicht immer ist der beschränkteste auch der effizienteste
Gültigkeitsbereich, das ist aber dann Compiler-Magie...
> Falls das nicht ausreicht, beschränke die Gültigkeit wenigstens auf> das Modul, in dem sie benötigt wird.
Und zwar auch mit Static.
> 5. Kennzeichne Funktionen, die nur im aktuellen Modul verwendet werden> mit einem Unterstrich am Anfang:
Dabei allerdings vorsichtig sein, denn Unterstricherei kann mit den
Namensräumen Standardbibliothek kollidieren. Und modulinternes Zeugs
statisch machen:
1
staticunsignedint_calcCrc(unsignedcharpucBuffer);
Und nach Möglichkeit "unsigned char" vermeiden. Für Zeichen ok, zum
Rechnen sind die Typen aus <stdint.h> zu verwenden (uint8_t ...).
> 6. Versuch Funktionsnamen mit dem Modul in Verbindung zu bringen:
Und versuch, Matschenamen zu vermeiden. EntwederDurchgehendCamelCase
oder durchgehend_mit_unterstrichen. Aber auch wieder Glaubenssache...
Hauptsache systematisch.
> 7. Trenne innerhalb eines Moduls getrennte Bereiche ruhig durch aus-> kommentierte Trennlinien:
Auch wieder Glaubenssache. Nach Variablen und Funktionen zu trennen, ist
m.E.n. blödsinn, viel wichtiger wärs zu wissen, wo Funktionseinheiten
sind.
Dass da Variablen kommen, seh ich auch selber.
Und bitte durchgängig:
> unsigned char uiBaudrate, /* Baudrate */> unsigned char ucParityFlag /* Paritäts-Einstellung */
Warum zeigt der eine Präfix einen "ui", der andre aber einen "uc"?
> 8. Rücke alle Blöcke vernünftig ein. Leerzeichen sind Tabs immer vorzu-> ziehen. Am besten im Editor gleich alle Tabs durch 2 oder 4> Leerzeichen ersetzen lassen.
Bloß nicht, was Schlimmeres gibts ja kaum noch... doch, und zwar Tabs
und Leerzeichen gemischt.
Meine Meinung: IMMER TABS benutzen. Dann kannst DU dir einstellen, wie
breit ein Tab sein soll. Passts mir dann nicht, stell ich die Breite
halt größer. Einmal mit Leerzeichen gearbeitet macht es anderen
unmöglich, die Darstellung anzupassen.
Immer schön dran denken: du bist nicht der einzige, der den Code liest,
und du bist vorallem nicht derjenige, nach dessen Vorliebe beim
Bearbeiten sich alle richten müssen.
Nen guter Einrückungsstil zeichnet sich dadurch aus, dass er bei 2
Spalten breiten Tabs genausogut lesbar ist, wie bei 4 Spalten breiten
Tabs. Ergo: ASCII-Diagramme und Ähnliches sauber mit Leerzeichen, den
Rest mit Tabs.
> 10. Scheue dich nicht davor, auch mal 2,3 Zeilen Kommentar über> den Sinn bzw. die Funktion eines Algorithmus zu schreiben. Gerade> bei komplizierterern Funktionen muss man dann einige Wochen später> nicht erst den Code reverse-engeneeren um dahinter zu kommen, was> genau er macht.
Aber Vorsicht dabei: Wenn zu fünf Zeilen Algo fünfzehn Zeilen Kommentar
nötig sind, die erklären, warum wo eine Schleife und wo kein Break
steht, ist das in vielen Fällen ein Anzeichen davon, dass der Code
unnötig kompliziert ist. Lieber nochmal überdenken.
> 11. Versuch den Quellcode stets bündig zu kommentieren. Es liest sich> angenehmer, wenn ein Block eingerückt an einer festen Stelle beginnt> und eine Zeile einheitlich z.B. max. 100 Zeichen lang ist. An dieser> Länge kann man dann auch festmachen, wo man Kommentare schliesst.
Codezeilen werden so lang, wie es nötig ist, um sie SAUBER zu
formulieren. Erzwungene Umbrüche wegen der 100 Zeichen sind ebenso
blöde. Kommentare zu wichtigen Dingen schreiben sich übrigens noch
besser in der Zeile davor. Dann kann man auch gleich Doxygen-Kommentare
benutzen und sich nachher ne schöne Dokumentation erstellen lassen.
Bei Kommentaren in derselben Zeile merk ich selbt immer, wie ich zu
Stummeleien neige, um noch unter die 100 Zeichen zu kommen und so. Mag
aber wieder mal subjektiv sein.
Soviel zu meinen persönlichen Eindrücken aus der Praxis :-}
Im ersten Fall ist es in C aber leider auch noch eine Variable, bei
denen der Compiler lediglich Änderungsversuche monieren wird. (Er
kann sie aber auch inline expandieren, muss aber nicht.)
In C funktioniert aber stattdessen auch:
> > 8. Rücke alle Blöcke vernünftig ein. Leerzeichen sind Tabs immer vorzu-> > ziehen. Am besten im Editor gleich alle Tabs durch 2 oder 4> > Leerzeichen ersetzen lassen.> Bloß nicht, was Schlimmeres gibts ja kaum noch... doch, und zwar Tabs> und Leerzeichen gemischt.> Meine Meinung: IMMER TABS benutzen.
Wenn wir schon beim Thema sind... ;)
Mit welcher Zeichenkodierung erstellt ihr eure Quellen? Hatte schon
etliche Fälle wo jeder Kollege einen anderen Editor mit anderer
Kodierung nutzte. Erstelt wurde da in Unicode, dann mit UTF8 nochmal
nachgebessert und das Bugfix wurde dann mit einem Linux-Editor
eingepflegt. Als Resultat waren dann sämtliche Umlaute/Sonderzeichen aus
den Kommentaren getilgt und einige Tabs automatisch in 8 Leerzeichen,
andere hingegen zu 4 oder 2 Leerzeichen mutiert, je nach dem verwendeten
Editor...
Die Module mit Leerzeichen statt Tabs haben wenigstens ihre Formatierung
behalten.
Kleinster gemeinsamer Nenner: ASCII. Schreibst du alles in Englisch,
dann hast du sowieso keinen Stress mit den Umlauten. ;-) Aus dem
Jörg einen Joerg zu machen, stört mich in dem Zusammenhang auch nicht
nennenswert.
1. #define möglichst nicht benutzen. Wenn du Konstanten brauchst dann
halt mit const definieren. #define ist eigentlich nicht für Konstanten
gedacht.
2. Für Variablen- und Funktionsnamen bitte keine Unterstrichei. Es macht
die Namen unnötig lang.
NixChecker wrote:
> 1. #define möglichst nicht benutzen. Wenn du Konstanten brauchst dann> halt mit const definieren.
Siehe oben. In dieser Allgemeinheit mag die Aussage für C++ OK
sein, für C aber nicht: dort belegen mit const deklarierte Objekte
in erster Linie erst einmal Speicherplatz (falls der Optimierer
sie nicht doch noch inlinen kann/möchte).
> #define ist eigentlich nicht für Konstanten> gedacht.
<Lorio>Ach!</Loriot>
Wofür ist es deiner Meinung nach denn gedacht?
Ja, ich weiß, bei C++ sind sie dank "echten" const und Templates
mittlerweile ziemlich entbehrlich geworden, bei C sieht die Sache
aber immer noch anders aus.
> 2. Für Variablen- und Funktionsnamen bitte keine Unterstrichei. Es macht> die Namen unnötig lang.
Naja. Bei 20 Zeichen langen Namen machen 2 oder 3 Unterstriche das
Kraut fett? Persönlich_finde_ich_sie_besser_lesbar (da es mehr dem
normalen Satzfluss entspricht) alsWennManZwischendrinGrossbuchstaben
einfügt. Das ist aber sicher Geschmackssache und daher, wiederum
siehe oben, ist es das Wichtigste, dass man sich auf einen Stil
einigt und den dann durchzieht.
>> #define ist eigentlich nicht für Konstanten>> gedacht.><Lorio>Ach!</Loriot>>Wofür ist es deiner Meinung nach denn gedacht?
Na ja "#define c a+b" geht aber "#define a 7" ist doch bescheuert.
Und was ist mit Dingen wie
#define F_CPU 8000000L
#define BAUDRATE 19200
#define HYSTERESE_MAX 1200
#define HYSTERESE_MIN 1000
Wenn ich auf meinem AVR nicht für jede Konstante eine Variable im Flash
oder gar RAM haben möchte, sind solche Sachen doch als #define ganz gut
aufgehoben...
NixChecker wrote:
> Na ja "#define c a+b" geht aber "#define a 7" ist doch bescheuert.
Gerade das erste fällt eher unter die Kategorie "bescheuert". Wenn
du es nämlich in einem Ausdruck benutzt wie:
1
result=5*c;
...dann wunderst du dich hinterher ziemlich lange, warum da nicht
das Gewünschte rauskommt.
Was wären denn für reine Konstanten deine Alternativvorschläge?
"const" ist in C nicht wirklich der Bringer, da es trotzdem ein
Objekt im RAM bleibt. enum hilft auch nur begrenzt, da es per
definitionem auf den Datentyp int beschränkt ist. Die Konstante
pi kannst du damit also schon einmal nicht definieren.
Der C-Standard selbst schreibt übrigens auch eine Reihe von Makros
vor, die auf Kontanten definiert sind, ERANGE beispielsweise.
Defines sind voellig in Ordnung. Typsicherheit wird oft gar nicht
gebraucht, wozu dann mit Variablen um sich schmeissen? Man muss eben,
wie immer, auch wissen wo der Sinn einer Regel ist - dann weiss man
auch, wann sie anzuwenden ist.