Ingo schrieb:
> Welches Programm für eine Versionsverwaltung ist denn zu empfehlen?
Die beiden verbreiteten sind SVN und Git. Git bietet für
Fortgeschrittene mehr Möglichkeiten. Aber wenn ihr vorher noch nie mit
Versionsverwaltung gearbeitet habt, tut ihr euch mit SVN wahrscheinlich
leichter.
> Sind globale Variablen nötig bzw. lassen sie sich überhaupt
> vermeiden?
Sie lassen sich komplett vermeiden. Schau mal in diesen Beitrag:
Beitrag "Re: Sauber Programmieren mit Interrupts"
Kurzfassung: Nur modul-globale Variablen (static) und in Funktionen
lokale Stackvariablen verwenden.
> Ich würde halt alles Modular aufbauen wollen, das heißt jeder
> Hardwareabchnitt bekommt ein eigenes Modul (Source-File)
Guter Ansatz.
> und die Daten werden über Pointer geändert bzw. ausgetauscht.
Nicht so gut, zumindest nicht als grundsätzliche Vorgehensweise. Für
jede Variable bzw. jedes Datum sollte in jedem Programmzustand nur genau
ein Modul zugriffsberichtigt sein. Das stellt man am einfachsten sicher,
indem die Daten nach außen nur "by value" übergeben werden:
1 | static int counter;
|
2 |
|
3 | int get_counter(void) {
|
4 | return counter;
|
5 | }
|
6 |
|
7 | void set_counter(int value) {
|
8 | counter = value;
|
9 | }
|
Ein anderes Modul hat auf die Weise keine Chance, die Variable counter
unerwartet zu modifizieren. Man kann in die get- und set-Funktionen
gegebenfalls zusätzliche Überprüfungen einbauen oder den Aufruf an ein
anderes Modul bzw. Hardware delegieren.
Man sollte allerdings hellhörig werden, wenn man auf eine Modulvariable
sowohl per get- als auch per set-Funktion zugreifen kann. Oft wäre ein
anderes Interface passender, hier zum Beispiel:
1 | static int counter;
|
2 |
|
3 | int get_counter(void) {
|
4 | return counter;
|
5 | }
|
6 |
|
7 | void counter_increase(void) {
|
8 | if (counter < INT_MAX) {
|
9 | counter++;
|
10 | }
|
11 | }
|
12 |
|
13 | void counter_decrease(void) {
|
14 | if (counter > INT_MIN) {
|
15 | counter--;
|
16 | }
|
17 | }
|
Das geht natürlich nur mit kleinen Datentypen. Größeren Strukturen by
value übergeben wäre ineffizient, da kommt man um Zeiger nicht rum.
Trotzdem sollte man festlegen, wer die Zuständigkeit über die Daten an
dem Zeiger hat. Wenn beispielsweise Modul A einen Zeiger auf eine eigene
Modulvariable a_x an das Modul B übergibt, wechselt die Zuständigkeit
über a_x von Modul A nach B. Das heißt, Modul A darf nicht mehr auf a_x
zugreifen, solange B an den Daten arbeitet:
1 | // Modul A
|
2 |
|
3 | static int a_x;
|
4 |
|
5 | void eine_funktion(void) {
|
6 | mach_etwas(&a_x);
|
7 | }
|
1 | // Modul B
|
2 |
|
3 | void mach_etwas(int* p) {
|
4 | *p = 5;
|
5 | }
|
Man muss also festlegen, wie lange B die Zuständigkeit behält.
Sinnvollerweise beschränkt man sich auf die Dauer das Funktionsaufrufs.
B darf also nur innerhalb der Funktion mach_etwas() auf die Daten des
Zeigers zugreifen. B darf sich den Zeiger nicht irgendwo speichern und
später wieder von alleine auf die Daten zugreifen. Das sollte in der
Dokumentation zu mach_etwas() erwähnt werden oder als Standard im
Projekt festgelegt werden.
In anderen Fällen kann es sinnvoll sein, dass B die Zuständigkeit länger
behält. B speichert sich also den Zeicher, bis es ihn über eine
Callbackfunktion zurück gibt oder ihm der Zeiger über ein andere
Funktion entzogen wird. Währenddessen darf kein anderes Modul (auch
nicht A) etwas mit den Daten machen. Das gehört natürlich ebenfalls
dokumentiert und muss eingehalten werden.
> 3. Was muss vereinbart werden wenn zwei Leute an einem Programm
> arbeiten? Codestyle? Und was noch?
Das wichtigste ist, sich Gedanken um die Gesamtarchitektur und die
Schnittstellen zwischen den Modulen zu machen. Stichwort: Lose Kopplung,
starke Bindung. Was zusammen gehört, muss zusammengefasst und gekapselt
sein. Die Schnittstelle eines Moduls gibt nur die Informationen bekannt,
die der Programmierer braucht, um es zu benutzen. Die Schnittstelle ist
so zu gestalten, dass die Benutzung des Moduls so klar und einfach wie
möglich ist (und nicht etwa so, wie sich die Funktion innerhalb des
Moduls am leichtesten programmieren lässt!). Außerdem klare Bezeichner
wählen und sauber dokumentieren. Dem Benutzer des Moduls muss nach einem
Blick ins Header-File klar sein, wie er das Modul zu benutzen hat, ohne
den dazugehörigen Quellcode zu kennen.
Wenn es sich nicht von alleine ergibt wäre es auch sinnvoll,
Zuständigkeiten zu vereinbaren. Das heißt, für jedes Modul ist ein
Entwickler federführend. Änderungen an dem Modul, abgesehen von
offensichtlichen Bugfixes, müssen mit dem für das Modul Zuständigen
abgesprochen werden. Derjenige kann dadurch verhindern, dass an das
Modul mit der Zeit immer neue, kleine Hacks und Erweiterungen
drangebastelt werden, die da eigentlich nicht hingehören. Außerdem
müssen sich bei Schnittstellenänderungen immer alle Betroffenen
absprechen oder zumindest informiert werden. Deshalb lohnt es sich,
ordentlich Zeit in den Entwurf einer sauberen und stabilen Schnittstelle
zu stecken, die man im Idealfall nie mehr ändern muss.
Einen Codestyle zu vereinbaren ist auf jeden Fall sinnvoll, damit der
Code einheitlich aussieht. Das ist nicht nur eine ästhetische Frage,
sondern hilft auch Fehler leichter zu finden und zu vermeiden.