Forum: Mikrocontroller und Digitale Elektronik "Clean Code" in C für Mikrocontroller


von Bernd (Gast)


Lesenswert?

Hallo Forum.
Ich habe gerade das Buch "Clean Code" gelesen. Darin geht es kurz gesagt 
darum Code schon durch seine Struktur verständlich und lesbar zu machen. 
Und vorhandenen Code durch Refactoring in dieser Hinsicht zu verbessern. 
Die Beispiele sind in Java und daher teils mit OO Sprachelementen 
versehen.

Mir hat das Buch sehr gut gefallen. Vieles klingt einfach und sinnvoll, 
manches erfodert aber viel Übung und Erfahrung bei der Umsetzung. Mein 
Arbeitsumfeld ist die Programmierung von Controllern (Atmel 8bit und ARM 
cores 32 bit meist STM32). D.h. die OO-Konzepte gibt es nicht. Keine 
Exceptions, keine Klassen, keine Vererbung, usw. Auch habe ich noch nie 
mit Unit Tests gearbeitet. In diesem Zusammenhang stelle ich mir ein 
Embedded System mit den ganzen I/Os und IRQs auch irgendwie schwierig 
vor.

Kennt jemand das Buch?
Meine Fragen gehen in folgende Richtung:
- wie macht man Errorhanding ohne Exceptions auf elegantem Weg ohne 
verstreuten Code?
- welchen Coding Style nutzt ihr? Quellen?
- sind Unit Tests unter C bei Embedded Software sinvoll umsetzbar? 
Womit? Das ist eigentlich die Kernvoraussetzung für die im Buch 
angewandten Heuristics. Nur durch die Tests fühlt man sich sicher, wenn 
man den Code immer wieder umbaut.
- wie weit treibt ihr es mit "Funktionen kleinhalten"? Meiner Meinung 
nach wird das im Buch teils zu weit getrieben und widerspricht der 
Forderung nach möglichst wenigen Funktionen
- sauber gekapselte Interfaces von Modulen und zu Fremdcode (libs) 
klingt mit den OO-Ansätzen gut. In plain C?
- lohnt doch der Umstieg auf C++ zumindest bei den STM32?

Mir fallen jetzt gerade nicht mehr alle Punkte ein, die ich beim lesen 
im Kopf hatte. Ich hoffe aber hier ein paar Anregungen und eine 
anregende Diskussion zu diesem Thema zu bekommen.

Gruß Bernd

von Scelumbro (Gast)


Lesenswert?

Zum Thema C++ auf uC empfehle ich das Buch:
"Real-Time C++ von C. M. Kormanyos"
Es geht eigentlich nur darum die Kosten ( zur Laufzeit) der C++ 
Sprachkonstrukte zu kennen und bewerten zu können. Und viele Elemente 
haben keine Laufzeitkosten: Templates, einfache Klassen.
Virtuelle Funktionen brauchen eine entsprechende Zeigertabelle, i.d.R. 
verkraftbar.
STL und Co. braucht oft dynamische Speicherverwaltung mit new. Auf uCs 
eher ungern gesehen. Aber  std:array z.B. ist eine C++ Abbildung von 
arrays ohne Laufzeitoverhead.
Mit etwas Template-Metaprogrammierung und sachen wie static_assert kann 
man elegant Code zur Compilezeit erzeugen und prüfen lassen.

Zum Thema Unittests: Bei ganz Hardwarenahen Sachen habe ich keine 
Erfahrung. Aber größere Programme haben sicher viele Einheiten die 
unabhängig sind von der Hardware oder die sich im Rahmen des OO Konzepts 
an Softwaredefinierte Pseudotesthardwareobjekte koppeln und dann testen 
lassen.

von Bernd (Gast)


Lesenswert?

Scelumbro schrieb:
> Zum Thema C++ auf uC empfehle ich das Buch:
> "Real-Time C++ von C. M. Kormanyos"

Hi und danke. Werde ich mir mal ansehen.
Ist halt etwas schwierig über Themen aus einem Buch zu sprechen, wenn 
man nicht das Buch zusammen fassen möchte.
Ich versuche herauszufinden,  was und wie ich aus dem Buch für mich in 
meiner Situation übernehmen und umsetzen kann.
Ich hatte mal nach Unit Test für C gesucht. Z.b. CuUnit. Aber ist sowas 
brauchbar im Vergleich zu Junit? Und dann noch auf einem embedded 
System.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

Bernd schrieb:
> D.h. die OO-Konzepte gibt es nicht. Keine
> Exceptions, keine Klassen, keine Vererbung, usw.

Wenn es rein um die Codequalität geht braucht man die nicht. Den Code 
schreibt ja kein Tool, sondern ein Mensch. Wenn der weiß was er tut und 
Ordnung halten kann (viele Helden-Hacker können es nicht, auch nicht mit 
OO ...), dann bekommt man guten Code.

Es scheint aber so zu sein, die Informatiker werden das besser wissen, 
dass Werkzeuge für Refactoring einfacher für OO-Sprachen zu 
implementieren sind. Was für C häufig bedeutet, dass man von Hand ohne 
Werkzeuge refactorn muss. Was nicht sehr schön ist.

> Auch habe ich noch nie
> mit Unit Tests gearbeitet. In diesem Zusammenhang stelle ich mir ein
> Embedded System mit den ganzen I/Os und IRQs auch irgendwie schwierig
> vor.

Ja, es kann schwierig sein. Man braucht Erfahrung und Tools.

Z.B: Tests in Simulations- oder Emulationsumgebungen. Die ganz einfache 
Variante ist zum Beispiel, dass man Algorithmen in Unterprogramme packt, 
und die Unterprogramme mit Unit Tests auf dem PC nativ compiliert und 
testet. Dann weiß man, dass der Algorithmus funktioniert, aber man weiß 
halt nicht ob er noch auf dem Mikrocontroller, wenn mit einem anderen 
Compiler compiliert, mit weniger Speicher zur Verfügung, usw. 
funktioniert. Das muss man dann wie bisher austesten.

Wichtig finde ich bei Unit Tests, dass man mal anfängt. Nicht, dass man 
sich Gedanken um 100% Testabdeckung macht. Ausreden nicht mit Unit Tests 
anzufangen gibt es genug. Die sollte man ignorieren und anfangen.

> Kennt jemand das Buch?

Dieses nicht, aber es gibt "hunderte" solcher Bücher.

> Meine Fragen gehen in folgende Richtung:
> - wie macht man Errorhanding ohne Exceptions auf elegantem Weg ohne
> verstreuten Code?

Auch Errorhandling mit Exceptions gibt verstreuten Code, also mal keine 
Panik. Bei Exceptions kannst du auf Zwischenebenen den Fehler ohne 
zusätzlich Code schreiben zu müssen, durchreichen. Das ist es aber 
schon.

Weiterhin, die 100%-Lösung gibt es nicht, also immer noch keine Panik 
und keine Zeit in 100% Lösungsversuche stecken.

Ordnung halten ist das Zauberwort. Also definiert man für seinen Code 
eine oder mehrere der folgenden Dinge: Das jede Funktion entweder gar 
nichts oder einen einheitlichen Fehlercode (bool, enum oder int) zurück 
gibt. Man definiert z.B. für seinen Code ob ein int der 0 ist, < 0, -1, 
> 0, 1 oder was auch immer OK oder Fehler bedeutet. Man definiert on 
NULL als Fehlercode durchgeht oder nicht. Man definiert sich 
Fehler-Report Strukturen die man entweder zurück gibt oder Pointer auf 
sie zurück gibt oder von der es global eine gibt, oder von der es einen 
Stack (LIFO), Map, etc. gibt. Oder man nimmt eine globale Fehlervariable 
(errno.h ...), oder Flag. Was auch immer.

Wichtig ist es für den eigenen Code einheitlich durchzuziehen. Natürlich 
ist es sinnvoll dabei auch darauf zu achten, was die externen 
Bibliotheken machen. Kann man da was wiederverwenden? Ist es clever im 
eigenen Code 0 als OK zu definieren, wenn verwendeten Bibliotheke eher 1 
als Ok definieren?

Aber Hauptsache:

1. Regel: Ordnung halten, einheitlich machen
2. Regel: Fehler (Rückgabewerte) auch abfragen

Es ist besonders beliebt letzteres nicht zu tun. 20 Funktionsaufrufe 
hintereinander, von denen jeweils kein Rückgabewert getestet wird und 
die Leute wundern sich das ihr Code nicht robust ist.

> - welchen Coding Style nutzt ihr?

Eigentlich egal, aber wo ich das Sagen habe gilt 1TBS, und nur 1TBS.

> Quellen?

https://en.wikipedia.org/wiki/Indent_style#Variant:_1TBS

> - sind Unit Tests unter C bei Embedded Software sinvoll umsetzbar?

Ja, wenn die Definition von "sinnvoll" ist, dass man einen Mehrwert 
erhält. Wenn die Definition ist, "100% Sicherheit", dann nein.

> Womit?

Mit Nachdenken.

1. Wie strukturiere ich meinen Code, dass ich separat testbare 
Einzelteile habe, zum Beispiel Funktionen die die Algorithmen enthalten 
aber nicht selber I/O machen?

2. Wie simuliere oder emuliere ich Teile des Prozessors, der Peripherie, 
des I/O, der Umgebung (//de.wikipedia.org/wiki/Hardware_in_the_Loop), 
damit ich einen Test machen kann? Kann ich I/O auch in separate 
Unterprogramme packen, die ich dann in meiner Simulationsumgebung (Unit 
Tests auf dem PC) durch billigen Mockup-Code ersetze, der Dummy-Werte 
liefert?

3. Ein billiger 50% Test (z.B. Unterprogramme auf dem PC compilieren und 
testen), ist besser als kein Test.

4. Automatisieren, automatisieren, automatisieren. Unit Tests müssen oft 
durchlaufen werden. Das sollte auf Knopfdruck passieren können, nicht 
nach einem halben Tag Testvorbereitung, sondern sofort. Was unter 
Umständen bedeutet, man hat, z.B. bei HIL ein paar permanente 
Testaufbauten auf die man aus der Ferne automatisch Code laden kann. Das 
kostet gerne etwas mehr.

Aber alles ist besser als für jeden Test elend lange fummeln zu müssen. 
Obwohl es gerade wieder die Helden-Hacker sind, die das geil finden. Da 
fühlen sie sich so richtig männlich und können zeigen was sie für 
Fummelhelden sind.

> - wie weit treibt ihr es mit "Funktionen kleinhalten"? Meiner Meinung
> nach wird das im Buch teils zu weit getrieben

Nur so groß wie nötig ist meine Devise. Da kommt auch die Erfahrung ins 
Spiel. Was nun mal 100 gut funktioniert hat wird wieder so gemacht.

> und widerspricht der
> Forderung nach möglichst wenigen Funktionen

Wo kommt die Forderung jetzt her? Welcher Guru hat die aufgestellt?

> - sauber gekapselte Interfaces von Modulen und zu Fremdcode (libs)
> klingt mit den OO-Ansätzen gut. In plain C?

Natürlich auch in Plain C. API Entwurf ist ein fast vergessenes 
Handwerk. Aber wer seine eigenen internen APIs vernünftig entwirft 
(siehe oben, z.B. einheitliche Fehlermeldung, gleiche / ähnliche 
Reihenfolge von Funktionsparametern, vernünftige, logische systematische 
Benennung von Funktionen, vernünftige Abstraktionsebenen, vernünftige 
Funktionsaufteilung, usw.), der macht sich das Leben leichter. Ist halt 
nicht für Hacker-Helden, der erst coden und nie nachdenken.

> - lohnt doch der Umstieg auf C++ zumindest bei den STM32?

Ansichtssache. Aus meiner Sicht nicht. C++ löst nicht die 
grundsätzlichen Probleme mit Programmstruktur, -architektur, 
-organisation oder mit Unit Tests.

C++ im Embedded-Bereich liefert mehr syntaktischen Zucker und mehr 
Fehlerquellen. Ja, toll, dass man sich dann bei C++ über den 
Resource-Verbrauch der C++-Features und Standardbibliotheken bis ins 
Detail bewusst sein muss. Toll, man hat ja sonst nichts zu tun und hat 
dann wieder 50 Fehlerquellen mehr bei denen man was übersehen kann. 
Helden-Hacker fühlen sich sicher toll, und behaupten das "echte 
Programmierer" damit kein Problem haben, sieht man ja an ihnen, sie 
manchen (angeblich) keine Fehler. Ausbaden darf es meist der 
Programmierer der so einen Scheiß warten muss. Der Held ist 
weitergezogen.

KISS: keep it simple stupid.

: Bearbeitet durch User
von Pirat (Gast)


Lesenswert?

Achtung, Wall of Text!

Bernd schrieb:
> Kennt jemand das Buch?
Ich kenne jetzt speziell das Buch nur vom hörensagen, versuche aber mal 
trotzdem ein paar Fragen zu beantworten.

> Meine Fragen gehen in folgende Richtung:
> - wie macht man Errorhanding ohne Exceptions auf elegantem Weg ohne
> verstreuten Code?
Ohne Exceptions wirst du nicht drum herum kommen immer wieder und 
überall auf Fehlerabfragen zu prüfen. Wenn ein Fehler auftritt sollten 
die meisten Funktionen nur ihr eigenes Zeug aufräumen (Speicher, locks, 
alles was sich nicht automatisch selbst aufraeumt). Und den Fehler dann 
an den Aufrufer weitermelden. Meist macht man das über den Rückgabewert. 
Ist dann grob die C-Variante von Exceptions, aber viel mehr wirst du 
nicht bekommen. Auch mit C++ hast du nicht immer Exceptions, daher mach 
dir da ruhig mal Gedanken wie man ohne auskommt und wie das 
sinnvollerweise aussehen mag.

> - welchen Coding Style nutzt ihr? Quellen?
Ich empfehle für die harten den GNU-Style. Ansonsten schau einfach mal 
in größere Projekte, die haben fast alle ihren eigenen oder einen von 
anderen. Sehr beliebt sind KNF und Linux kernel coding style. Ich 
arbeite an Einzelprojekten ohne festen Standard.
LKCS: https://www.kernel.org/doc/Documentation/CodingStyle
KNF: 
http://cvsweb.netbsd.org/bsdweb.cgi/src/share/misc/style?rev=HEAD&content-type=text/x-cvsweb-markup

> - sind Unit Tests unter C bei Embedded Software sinvoll umsetzbar?
> Womit? Das ist eigentlich die Kernvoraussetzung für die im Buch
> angewandten Heuristics. Nur durch die Tests fühlt man sich sicher, wenn
> man den Code immer wieder umbaut.
Ja und Nein. Testbarkeit ist keine Frage des Umfelds sondern der 
Strukturierung. Bestimmte Techniken funktionieren nicht oder nur 
eingeschränkt. Alles was nicht auf die konkrete Hardware angewiesen und 
einbisschen modular aufgebaut ist, kannst du auf deinem PC kompilieren 
und da in der Funktionalität testen oder in einem Simulator laufen 
lassen.
Bei der Aufteilung der Tests musst du dann schauen welche Größe ist 
sinnvoll als Test nutzbar. Faustregel hierbei: Getestet wird was in 
anderen Modulen sichtbar ist. Dass so sinnvoll zu strukturieren, dass 
man es auch testen kann, erfordert Übung.
Womit? Hab noch kein Testframework für C gefunden das Simulatoren direkt 
integriert. Das wär doch ein Projekt wert. :) (Oder mein google-fu ist 
nicht stark genug)

> - wie weit treibt ihr es mit "Funktionen kleinhalten"? Meiner Meinung
> nach wird das im Buch teils zu weit getrieben und widerspricht der
> Forderung nach möglichst wenigen Funktionen
Funktionen sollen am Ende lesbar sein wenn andere draufschauen die sich 
im Code kaum bis garnicht auskennen. Wie weit das heißt, dass Funktionen 
aufgesplittet werden, ist immer abwägung. Geht aber meistens dazu hin 
das auszulagern und sprechende Funktionsnamen zu wählen. Schau als 
Referenz andere Projekte an, schau wie sie das machen und überlege wie 
du das besser machen kannst.

> - sauber gekapselte Interfaces von Modulen und zu Fremdcode (libs)
> klingt mit den OO-Ansätzen gut. In plain C?
Sind Schnittstellen anders definiert. fopen(), fwrite(), fread(), 
fclose() sind z.B. eine Schnittstelle um auf Dateien zu arbeiten. Im OOP 
Ansatz hättest du dann eine Klasse FILE mit Konstruktor, read(), write() 
und close(). Gekapselt ist dass jetzt indem du nicht die Definition 
eines FILE Objektes hast. Zugriff hast du nur über fest definierte 
Methoden. Wie so oft: nicht immer sinnvoll, Verstand einschalten.

> - lohnt doch der Umstieg auf C++ zumindest bei den STM32?
Hab ne Münze geworfen. Sie zeigt Kopf. Was das für dich heißt weiß ich 
nicht.

von Rudolph R. (rudolph)


Lesenswert?

Bernd schrieb:
> - welchen Coding Style nutzt ihr? Quellen?

Mit den Links oben konnte ich dem erst einen Namen zuordnen. :-)
Ich bevorzuge die Version nach Allman.

Ich finde, das sieht aufgeräumter aus und mit sowas wie 1TBS Zeilen 
sparen zu wollen finde ich ziemlich albern heutzutage.
Aber jeder eben so wie er mag und im Team sollte es wer vorgeben.

Früher hat man auch mal Zeilen auf 80 Zeichen beschränkt,
weil man mehr Zeichen weder auf einmal anzeigen noch drucken konnte.

von Kaj (Gast)


Lesenswert?

Rudolph R. schrieb:
> Früher hat man auch mal Zeilen auf 80 Zeichen beschränkt,
> weil man mehr Zeichen weder auf einmal anzeigen noch drucken konnte.
Je nach Firma soltle man das auch heute noch machen, nämlich dann, wenn 
du Code(-teile) in ausgedruckter Form als Dokumentation vorlegen musst, 
was z.B. der Fall ist wenn die Firma Geldspielgeräte herstellt. 
Geldspielgeräte müssen nämlich von der PTB abgenommen werden, und die 
wollen die Dokumentation in Papierform haben, und zwar nur und 
ausschließlich einseitig bedruckt. :)

Das hat schon sinn, die Zeichen pro Zeile zu beschränken ;)

von berndl (Gast)


Lesenswert?

Pirat schrieb:
> Sehr beliebt sind KNF und Linux kernel coding style. Ich
> arbeite an Einzelprojekten ohne festen Standard.
> LKCS: https://www.kernel.org/doc/Documentation/CodingStyle

Genau das ist der Style, an den ich mich auch zu halten versuche. Und 
ich habe damit sehr gute Erfahrungen ueber die Jahre gemacht. Dann noch 
so Kleinigkeiten von MISRA wie z.B.
1
if (5==a) {
2
         ...
3
}
und der Code wird einfacher durchschaubar, fuer einen selber nach 
Monaten/Jahren und auch fuer andere...

von Clemens L. (c_l)


Lesenswert?

Bernd schrieb:
> - wie macht man Errorhanding ohne Exceptions auf elegantem Weg ohne
> verstreuten Code?

setjmp() ist nicht elegant; alles andere ist verstreuter Code.

> - welchen Coding Style nutzt ihr?

Egal, solange er konsistent benutzt wird.

> - sind Unit Tests unter C bei Embedded Software sinvoll umsetzbar?

Kommt drauf an. Du brauchst erst einmal irgendwelche Units, die du 
unabhängig voneinander testen kannst. Und wenn es bei deiner Software 
größtenteils nicht um das Transformieren von Daten, sondern um die 
Ansteuerung von Hardware geht, dann wird es mit dem automatischen Testen 
schwierig.

> - wie weit treibt ihr es mit "Funktionen kleinhalten"? Meiner Meinung
> nach wird das im Buch teils zu weit getrieben und widerspricht der
> Forderung nach möglichst wenigen Funktionen

Kleinere Funktionen lassen sich halt besser testen.

Sie lassen sich auch besser einzeln verstehen, aber das musst du abwägen 
gegen das Verständnis des Gesamtkunstwerks. Wo das Optimum liegt, musst 
du selbst entscheiden.

> - sauber gekapselte Interfaces von Modulen und zu Fremdcode (libs)
> klingt mit den OO-Ansätzen gut. In plain C?

Empfehlenswert: "Object-oriented design patterns in the kernel"
https://lwn.net/Articles/444910/
https://lwn.net/Articles/446317/

von Rudolph R. (rudolph)


Lesenswert?

Kaj schrieb:
> Geldspielgeräte müssen nämlich von der PTB abgenommen werden, und die
> wollen die Dokumentation in Papierform haben, und zwar nur und
> ausschließlich einseitig bedruckt. :)

Na und? Gibt doch A0 endlos-Plotter. :-)
Ausdrucken und 80 Zeichen am Bildschirm sind heute aber eher die 
Ausnahme.

Eine weitere "Regel" aus der Vergangenheit war die Einschränkung auf 
etwa 1000 Zeilen pro Modul aber früher war der gesammte Umhang mit 
Dateien auch noch viel umständlicher.

von Pedent (Gast)


Lesenswert?

berndl schrieb:
> Dann noch
> so Kleinigkeiten von MISRA wie z.B.
1
if (5==a) {
2
         ...
3
}

Das ist u.a. ein Grund, MISRA zu kritisieren. Das ist so eine Regel nach 
dem Motto "Gut gemeint, ist noch lange nicht gut gemacht". Sie soll die 
Verwechselung von '=' und '==' abfangen. Aber spätestens, wenn zwei 
Variablen miteinander auf Gleichheit getestet werden müssen, ist diese 
Regel eh hinfällig und nutzlos.

Das Problem an dieser Formulierung ist aber, dass sie "mental" falsch 
ist. Denn der Programmierer möchte eben nicht sicherstellen, dass die 
Fünf gleich 'a' ist. Das ist nämlich Unsinn, denn die Fünf ist immer die 
Fünf. Was hingegen der Programmierer will, ist zu vergleichen ob die 
Variable 'a' gleich der Fünf ist. Das ist mental ein großer Unterschied, 
obwohl es in der Aussagelogik selbstverständlich identisch ist.

Der springende Punkt ist aber, menschlich Absichten und Gedanken 
manifestieren sich in (mutter-)sprachlichen Sätzen. Und diese 
sprachliche Gedanken sind eben genau nicht in Aussagelogik formuliert, 
sondern in der Grammatik der (Mutter-)Sprache. Und eine 
Programmiersprache ist ein stark vereinfachte Form dieser Sprache, um 
die Gedanken und Intentionen des Programmierers einer Maschine bzw. 
einem Compiler verständlich zu machen.

Es ist eben ein Unterschied im mentalen Modell, ob "a > b" oder "b < a". 
Im ersten Fall liegt der Fokus auf der Festellung, dass die Variable 'a' 
größer als 'b' ist. Im zweiten Fall ist es anderes herum, der Fokus 
liegt darauf, dass 'b' kleiner als 'a' ist.
Mathematisch sind beide Formulieren absolut identisch. Aber für das 
menschliche Hirn, das mentale Modell des Programmierers, eben nicht.

Anderes Beispiel, der Ausdruck:
1
 0 <= x && x <= 10

ist "direkter" und einfacher zu lesen als:
1
 x >= 0 && 10 >= x

Es ist sehr wichtig, dass Programme das mentale Modell der zu lösenden 
Aufgabe möglichst geradlinig und direkt abbilden. Denn nur so lässt sich 
das Programm von anderen Menschen lesen und verstehen und das mentale 
Modell am bestens aufbauen.

Vertauschen der Operatoren in einem Vergleich ist daher nach meiner 
Ansicht kontraproduktiv. Moderne Compiler warnen vor einer Zuweisung in 
einem if-Ausdruck, und mit -Werror wird daraus auch ein Fehler. Daher 
wäre eine gute Lösung für dieses Problem gewesen, ein lint 
vorzuschreiben dass eine Zuweisung in einem if-Ausdruck als Fehler 
ablehnt. Damit wären dann auch Vertipper der Form "a = b" abgefangen. In 
der jetzigen Form verursacht diese MISRA-Regel (und weitere) mehr 
Probleme als Lösungen.

von Bernd (Gast)


Lesenswert?

Wow.
Da kam ja schon eine Menge an Infos.
Vielen Dank.
Werde ich mir in Ruhe angucke.
Ist auf dem Smartphone im Urlaub etwas schwierig auf einzelne Punkte 
einzugehen ;-)

von Bernd K. (prof7bit)


Lesenswert?

Pedent schrieb:

> if (5==a) {

> sondern in der Grammatik der (Mutter-)Sprache.

Master Yoda knows these things. His job it is ;-)
May the Source be with you!

von S. R. (svenska)


Lesenswert?

> - wie weit treibt ihr es mit "Funktionen kleinhalten"? Meiner Meinung
> nach wird das im Buch teils zu weit getrieben und widerspricht der
> Forderung nach möglichst wenigen Funktionen

Faustregel:
Eine Funktion sollte nicht länger sein als das, was du auf einen Blick 
darstellen kannst (d.h. eine Bildschirmseite). Ist eine Funktion länger, 
steigt die durchschnittliche Anzahl der Bugs pro Zeile, weil der 
Überblick fehlt. Ausnahmen bestätigen die Regel.

Wenn ich mich recht entsinne, waren das die FreeBSD-Leute, die darüber 
mal eine Statistik gemacht haben.

von cppler (Gast)


Lesenswert?

OO auf µC ist kein großes Problem.
Wenn man in C/PASCAL o.ä. strukturiert programmiert und auch immer die 
Variablennamen, Funktionsnamen usw. sinnvoll benennt braucht es auch 
keine extra Kommentare wozu die dienen.
Exception-Handling ohne throw und catch geht auch wenn man in der 
Funktion eine passende Variable definiert und dann wenn's inkonsequent 
wird via z.B. return aus der Funktion ausbricht.
Dann kann man dann in der aufrufenden Instanz den return auswerten und 
ein Fehlerprotokoll erstellen.

von Bernd (Gast)


Lesenswert?

In dem Buch Clean Code sind die Funktionen meist ein bis drei zeiler. Da 
wird alles streng nach dem SRP (single response p...) in Unterfunktionen 
ausgegliedert. Das gliedert die Zuständigkeit und Abstraktionslevel. Die 
Funktionsnamen beschreiben genau was die Funktion macht. Ist schon sehr 
übersichtlich, aber führt zu vielen kleinen Funktionen und man kann das 
was passiert an den Namen quasi ablesen. Also der Code erklärt sich 
selbst. Manches ist mir aber doch irgendwie zu zerteilt.

von Bernd (Gast)


Lesenswert?

cppler schrieb:
> Exception-Handling ohne throw und catch geht auch wenn man in der
> Funktion eine passende Variable definiert und dann wenn's inkonsequent
> wird via z.B. return aus der Funktion ausbricht.
> Dann kann man dann in der aufrufenden Instanz den return auswerten und
> ein Fehlerprotokoll erstellen.

Das wird in dem Buch als bad practice tituliert. Geht aber ohne 
exceptions eigentlich kaum anders. Es führt zu verstreutem code, weil 
immer der return Wert geprüft werden muss und Funktionalität mit 
Fehlerbehandlung vermischt wird.

von Jay (Gast)


Lesenswert?

Bernd schrieb:
> Das wird in dem Buch als bad practice tituliert. Geht aber ohne
> exceptions eigentlich kaum anders. Es führt zu verstreutem code, weil
> immer der return Wert geprüft werden muss und Funktionalität mit
> Fehlerbehandlung vermischt wird.

Man kann alles auf dem heiligen Altar der Objektorientierung opfern.

Exceptions sind allerdings gar kein OO-spezifisches Prinzip. Die gibt es 
nur häufig in OO-Sprachen, weil diese Sprachen neuer sind als die 
klassischen Programmiersprachen.

Selbstverständlich vermischt man auch bei Exceptions Funktion und 
Fehlerbehandlung im Code:
1
try
2
  {
3
     f();
4
     int ging_auch_nicht = system_call42();
5
     if(ging_auch_nicht) {
6
        throw ...;
7
     }
8
  }
9
  catch ...
10
  {
11
    // Fehlerbehandlung
12
  }
13
14
void f() {
15
    if(ging_schief) {
16
       throw ...
17
    }
18
}
Man muss sogar großartig mit try ankündigen, dass man eine 
Fehlerbehandlung machen möchte. Nicht dass das schlecht ist, aber diese 
Behauptung, dass Exceptions die Trennung von Funktion und 
Fehlerbehandlung bewirken hält sich zwar hartnäckig ist aber falsch, 
weil die Trennung nicht vollständig ist. Man muss nur in vielen Fällen 
weniger Code schreiben, der besser aussieht und übersichtlicher ist. 
Besonders, wenn man viele Statements in ein try/catch einklammern kann. 
Allerdings geht das nicht immer. Dann beginnen die viele try/catch die 
man statt dessen schreiben muss zu stören.

Wenn es in C wirklich störend wird, dass man viele if()s hat, kann man 
zur Not (aber wirklich zur Not), ein Macro nehmen:
1
// call-and-check
2
#define CAC(f)    do { int r = f; if(r != OK) return r; } while(0)
3
4
int f1() {
5
    CAC(f2(1, 2, 3, 4));
6
    CAC(f3("ABC"));
7
    CAC(f4());
8
    return OK;
9
}
Einen Schönheitspreis gibt es dafür nicht, aber wenn es bei einem 
konkreten Problem hilft, warum nicht? Natürlich muss der Compiler so ein 
Macro vernünftig optimieren können, sonst sollte man es lassen.

Man kann es mit den Macros noch weiter treiben, so in der Art
1
#define TRY_HANDLE(f, h) do { int r = f; if(r != OK) h; } while(0)
Allerdings verwende ich so etwas nicht (das Macro mag sogar falsch 
sein). Aber wer unbedingt will, der kann das so ähnlich machen.

von Walter T. (nicolas)


Lesenswert?

Jay schrieb:
> void f() {
>     if(ging_schief) {
>        throw ...
>     }
> }

Wobei Du den wichtigsten Zweck von try/catch allerdings übersiehst: Du 
kannst die Ebene, bei der aufgefangen wird, sehr komfortabel wählen. Bei 
C must Du Deinen Funktionsabbruch auf allen Ebenen einzeln 
implementieren, bist Du bei der Ebene angekommen bist, bei denen Deine 
Fehlerbehandlung greifen soll. Das sauber zu implementieren hat mich 
schon mehr als einmal viele Stunden gekostet.

von Axel S. (a-za-z0-9)


Lesenswert?

Bernd schrieb:
> cppler schrieb:
>> Exception-Handling ohne throw und catch geht auch wenn man in der
>> Funktion eine passende Variable definiert und dann wenn's inkonsequent
>> wird via z.B. return aus der Funktion ausbricht.
>> Dann kann man dann in der aufrufenden Instanz den return auswerten und
>> ein Fehlerprotokoll erstellen.
>
> Das wird in dem Buch als bad practice tituliert. Geht aber ohne
> exceptions eigentlich kaum anders. Es führt zu verstreutem code, weil
> immer der return Wert geprüft werden muss und Funktionalität mit
> Fehlerbehandlung vermischt wird.

Ganz recht. Die Fachbegriffe dazu sind in band signaling vs. out of 
band signaling. "In Band" bezieht sich dabei darauf daß es nur einen 
einzigen Datenfluß ("Band") gibt und daß in diesem "normale" Daten und 
Rückmeldungen über Fehlerzustände gemischt werden (müssen).
Die Folge ist dann wie du schon sagst, daß man Programmlogik und 
Fehlerbehandlungslogik ineinander verschachtelt programmieren muß. Was 
der Übersichtlichkeit massiv schadet.

Wenn man kein out of band signaling - z.B. in Form von Exceptions - 
hat, dann sind auch die kurzen Dreizeiler-Funktionen kontraproduktiv. 
Denn wenn man da noch Fehlerbehandlung einbaut, dann werden aus den 3 
Zeilen ganz schnell 6 Zeilen. Wobei dann immer noch nur 3 Zeilen 
Funktionalität drin stecken, aber zusätzlich 3 Zeilen Fehlerbehandlung.

Saubere Fehlerbehandlung in C ist möglich, sie ist aber alles andere als 
elegant. Ein Grund mehr, sich C++ auf dem µC wenigstens mal anzusehen. 
C++ Code mit Exceptions mag größer und teurer sein als der äquivalente C 
Code ohne Fehlerbehandlung. Aber der C Code mit äquivalenter 
Fehlerhandlung ist nicht nur noch größer und noch teurer, er ist vor 
allem auch viel schlechter lesbar.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Hallo Bernd,

> Kennt jemand das Buch?
ja, ich habe das Buch gelesen (und "The Clean Coder" auch).

> - wie macht man Errorhanding ohne Exceptions auf elegantem Weg ohne
> verstreuten Code?

ich denke, das Beste, was Du machen kannst, ist konsistent zu sein. 
Insbesondere das Handling von erkannten Software-Fehlern (aka 
INVALID_HANDLE, ecWrongParameter etc.) würde ich nicht über Error-Codes 
abhandeln. Manchmal gehört auch etwas Mut dazu, zu sagen: Diese Funktion 
kann auf keine Fehler laufen.

> - sind Unit Tests unter C bei Embedded Software sinvoll umsetzbar?

Ich benutze für meine Projekte bis jetzt Boost.Test und finde es recht 
brauchbar. Boost ist C++, aber ich warum sollte man damit keinen C-Code 
testen können? Wahrscheinlich würde man die Tests etwas anders aufbauen. 
Module (set von C-Funktionen) könnte man z.B. zur Link-Zeit ersetzen.

Schwierig, bis unmöglich wird es meiner Meinung nach, wenn es auf den 
untersten Level geht. Selbst wenn man alle Registerzugriffe mit Macros 
wrapen würde, um festzustellen, ob Register mit bestimmten Werten 
beschrieben werden und ob das in der richtigen Reihenfolge passiert, 
müsste man die (meist schlecht dokumentierte) Hardware schon sehr genau 
simulieren, um zu erkennen, ob der gewünschte Effekt korrekt eintritt.

> - wie weit treibt ihr es mit "Funktionen kleinhalten"? Meiner Meinung
> nach wird das im Buch teils zu weit getrieben und widerspricht der
> Forderung nach möglichst wenigen Funktionen

Wenn ich versucht bin, über eine Gruppe von Anweisungen einen Kommentar 
zu schreiben, dann nehme ich diese Gruppe, packe sie in eine Funktion 
und nehme den Kommentar-Text als Funktionsnamen.

Oder wenn die Abstraktionsebenen (einzelner Anweisungen) einfach sehr 
weit auseinander gehen, dann kommt auch schon mal eine einzelne 
Anweisung in eine Funktion. Häufig bleibt diese Anweisung dann auch 
nicht alleine (Beispiele wäre irgend eine Anweisung, die alle Interrupts 
abschaltet. Die eigentlich Aufgabe ist dann Synchronisation).

> - sauber gekapselte Interfaces von Modulen und zu Fremdcode (libs)
> klingt mit den OO-Ansätzen gut. In plain C?

Dass kann man auch in C machen. Wobei "Sauber" extrem schlecht definiert 
ist. "Sauber" wird meiner Erfahrung nach immer verwendet, wenn 
Berufsanfänger irgend wo mit der Komplexität überfordert sind. Dann 
rennen die zum Chef und behaupten, dass das alles nicht "Sauber" wäre 
und das man alles neu machen muss ;-)

> - lohnt doch der Umstieg auf C++ zumindest bei den STM32?

Meiner Meinung nach auf jeden Fall. Vorausgesetzt, Du kannst C++ oder 
bist bereit Dich damit zu beschäftigen.

mfg Torsten

von Der Andere (Gast)


Lesenswert?

Axel S. schrieb:
> Saubere Fehlerbehandlung in C ist möglich, sie ist aber alles andere als
> elegant. Ein Grund mehr, sich C++ auf dem µC wenigstens mal anzusehen.
> C++ Code mit Exceptions mag größer und teurer sein als der äquivalente C
> Code ohne Fehlerbehandlung. Aber der C Code mit äquivalenter
> Fehlerhandlung ist nicht nur noch größer und noch teurer, er ist vor
> allem auch viel schlechter lesbar.

Kann ich voll zustimmen. Ich habe (viel) früher mal EC Geldautomaten 
programmiert, da war wirklich 80-90% des Codes nur noch Fehlerhandling 
und sicheres Weiterarbeiten nach einem Fehler.

Die Ideen in Clean Code haben viel für sich, der/die Autor(en) schiessen 
aber über das Ziel hinaus, wenn man eine einfache Funktionalität von 25 
Zeilen Code noch in 5 Unterfunktionen zerteilt ist das weder 
übersichtlich noch performant.
Insofern sollte man es als Anregung nehmen und sein Hirn einschalten. 
Und man sollte selbstkritisch sein und Scheiße, den man programmiert hat 
auch als Scheiße erkennen und daraus lernen.

ich kenne noch ein altes Buch für C, das war richtig gut und hies 
"Writing solid Code".
Gibts aber wohl nur noch im Antiquaiat.

von benwilliam (Gast)


Lesenswert?

darüber hatte ich mir in letzter Zeit auch ein wenig Gedanken gemacht.

Letztendlich für unser Projekt hier war die Lösung, die gesamten 
Hardware Zugriffe in einer HAL(Hardware Abstraction Layer) zu bündeln 
und ein klares interface zu definieren. Die HAL haben wir dann auch auf 
einem Win32 Rechner implementiert/emuliert und konnten einige  Tests 
auch ganz einfach auf dem PC laufen lassen. Klar es ersetzt auf keinen 
Fall die tests auf dem echten Target.
Eine andere alternative für Cortex-M3 wäre den CPU emulatur QEMU 
einzusetzen.

Was ich mir aktuell noch überlege ist, in wiefern "Dependency Injection" 
(wie es gerne in der Java Welt gemacht wird) im embedded Bereich Sinn 
macht.
Aus Zeit gründen ist das bisher alles erstmal nur bei Gedanken 
Spielereien geblieben, daher hätte ich keine praktische 
Erfahrungsberichte aber an sich müsste es zu einem deutlich modulareren 
und besser testbaren Code führen.

von Henry P. (henrylexx)


Lesenswert?

Der Andere schrieb:
> ich kenne noch ein altes Buch für C, das war richtig gut und hies
> "Writing solid Code".
> Gibts aber wohl nur noch im Antiquaiat.

Meinst du das:
http://www.cesarkallas.net/arquivos/livros/informatica/Ms.Press.-.Writing.Solid.Code.-.Microsoft%27s.Techniques.for.Developing.Bug-Free.C-Programs.%28Maguire,.Scan.pdf

von Walter T. (nicolas)


Lesenswert?

Henry P. schrieb:
> Meinst du das:
> 
http://www.cesarkallas.net/arquivos/livros/informatica/Ms.Press.-.Writing.Solid.Code.-.Microsoft%27s.Techniques.for.Developing.Bug-Free.C-Programs.%28Maguire,.Scan.pdf

Hm... ein Buch von Microsoft Press, geschrieben in TEX, in dem 
Fallbeispiele über C auf dem M680x0 genutzt werden. Wie die Zeit 
vergeht.

Nichtsdestotrotz scheint alles Hand und Fuß zu haben und gut geschrieben 
ist es auch.

Danke für dieses Kleinod!

von Stefan F. (Gast)


Lesenswert?

> Kennt jemand das Buch?

ja. Es enthält hilfreiche Anregungen, ich jeodch nicht unbedingt der 
Weisheit letzter Schluss.

1) wie macht man Errorhanding ohne Exceptions auf elegantem Weg
ohne verstreuten Code?

Du meinste ohne Exceptions? Gar nicht. In C geben Funktionen 
traditionell entweder Fehlercodes zurück, die irgendwo angezeigt werden, 
oder sie schreiben die Fehlermeldung in ein Logfile.

2) welchen Coding Style nutzt ihr? Quellen?

Keinen, die einem bestimmten namen haben. Ich habe mich immer mit meinen 
Kollegen abgestimmt, was wir gar nicht sehen wollen. Alles andere bliebt 
jedem frei überlassen. Zu viele Regeln werden am Ende nicht ernst 
genommen. Unsere Regeln passen passen auf eine drittel Seite Din-A4.

So haben wir uns Beispielsweise darauf geeinigt, Klammern in eigene 
Zeilen zu schreiben und mit vier Leerzeichen einzurücken:
1
int bla()
2
{
3
    if (wasauchimmer)
4
    {
5
        tuwas();
6
    }
7
}
In der Firma, wo ich vorher arbeitete, galten in dieser Hinsicht ganz 
andere Regeln. Ich habe kein Problem damit, mich anzupassen. Man kann 
sich die IDE (z.B. Eclipse) ja entsprechend konfigurieren. Unter Java 
nutzen wir CheckStyle, um Regelbrüche gelb hervorzuheben.

3) sind Unit Tests unter C bei Embedded Software sinvoll umsetzbar?
Sicher. Unit unter Java ist ein ganz banales Rahmenwerk, welches 
Test-Funktionen nacheinander (in nicht definierter Reihenfolge!) aufruft 
und dann die Fehler irgendwie grafisch aufbereitet.

Ich habe allerdings noch kein reales Projekt gehabt, wo Unit das kann, 
was wir brauchen. Es fängt schon mit der Reihenfolge der Test an und ein 
großes Manko ist auch, das Unit Tests voneinander unabhängig sein 
müssen, während in realen Anwendungen die Test sehr häufig voneinander 
abhängen. Ganz besonders bei Interaktiven Vorgängen. Auch fehlt mir bei 
Unit die Messung der Zeit und sonstiger belegter Ressourcen.

Was Unit kann, kann sich ein erfahrener Programmierer an zwei tagen 
selbst neu schreiben. Am Ende geht es doch nur darum, funktionen 
nacheinander aufzurufen, das Ergebnis zu prüfen und die Fehler zu 
zählen.

> Nur durch die Tests fühlt man sich sicher, wenn
> man den Code immer wieder umbaut.

Ja, ein weitgehend automatischer Regressionstest ist bei allen 
Anwendungen sehr empfehlenswert, ganz egal mit welchem Tool man das 
macht, oder ob man es komplett "zu Fuß" programmiert hat.

> wie weit treibt ihr es mit "Funktionen kleinhalten"?

Sehr sinnvoll, da übersichtlicher. In Kombination mit 
Dokumentationszwang kommt man dadurch eher zu pflegbarem Code, als wenn 
man riesige Funktionen mit tausenden Zeilen zulässt.

Aber es gibt sicher immer ausnahmne. Wenn zum Beispiel ein Ereignis dazu 
führen soll, dass 2000 Aktionan nacheinander durchgeführt werdne sollen, 
dann ist das eine einfache Liste von Aufrufen, die man simpel von oben 
nach unter runter programmiert. So eine "Batch-Liste" künstlich 
aufzusplitten, würde ich von niemandem verlangen.

> widerspricht der Forderung nach möglichst wenigen Funktionen

Ich kann mich an die Forderung nicht erinnern. Generell würde ich 
niemals von einem Programmierer verlangen, dass er die Anzahl von 
Funktionen reduziert. Das kann nur in schlechter pflegbaren Code enden.

> sauber gekapselte Interfaces von Modulen und zu
> Fremdcode (libs) klingt mit den OO-Ansätzen gut. In plain C?

Man kann auch in C Objektorientiert programmieren. Zum Beispiel, indem 
man für die Daten der Objekte Strukturen benutzt. Jede Objekt-Funktion 
erhält eine Struktur als erstes Argument und die Namen der Funktionen 
haben alle einen Prefix. Zum Beispiel auto_losfahren(meinAuto), 
auto_anhalten(meinAuto), auto_preis_erhöhen(meinAuto,2), 
auto_verkaufen(meinAuto,kaeufer).

> lohnt doch der Umstieg auf C++ zumindest bei den STM32?

Betrachte C++ als eine Ergänzung zu C. Schau Dir an, welche Teile von 
C++ für dein Projekt am hilfreichsten sind, und setze nur diese ein.

Kapselung von Daten+Funktionen in Objekte wäre sicher ein guter Anfang.

Bedenke, dass "virtual" Methoden indirekt über Zeiger aufgerufen werden, 
das kostet etwas Zeit. Und las Dir nicht einreden, das man Objekte immer 
mit new erzeugen muss und sie somit Heap benötigen. Man kann Objekte 
auch statisch erzeugen, wenn die Anzahl der Instanzen beim Compilieren 
schon feststeht.

von Stefan F. (Gast)


Lesenswert?

> Was ich mir aktuell noch überlege ist, in wiefern
> "Dependency Injection" (wie es gerne in der Java Welt
> gemacht wird) im embedded Bereich Sinn macht.

Dabei wird zur Laufzeit Code generiert, das geht bei C nicht.

Und auch in Java hasse ich es wie die Pest. Macht nur Probleme.

von Der Andere (Gast)


Lesenswert?

Stefan U. schrieb:
> Und auch in Java hasse ich es wie die Pest.

100% Zustimmung

von Der Andere (Gast)


Lesenswert?

Walter T. schrieb:
> Danke für dieses Kleinod!

Gerne :-)
Wie gesagt für Informatik steinalt, aber für C waren viele Tipps und 
Vorschläge nach meiner Erinnerung ziemlich zeitlos und es gab zahllose 
Augenöffner bzgl. Fallen.

von Stefan F. (Gast)


Lesenswert?

Ich finde, dass die Genialität von Exceptions überschätzt wird. Ich 
finde sie nützlich, aber es ist keine Schande, in einem C Programm ohne 
sie auskommen zu müssen.

Immerhin habe ich einen ganzen Webserver in C++ ganz ohne Exceptions 
programmiert. Da hat sich noch keiner über unsauberen oder schlecht 
strukturierten Code beklagt.

von Mark B. (markbrandis)


Lesenswert?

Stefan U. schrieb:
> Ich finde, dass die Genialität von Exceptions überschätzt wird. Ich
> finde sie nützlich, aber es ist keine Schande, in einem C Programm ohne
> sie auskommen zu müssen.
>
> Immerhin habe ich einen ganzen Webserver in C++ ganz ohne Exceptions
> programmiert. Da hat sich noch keiner über unsauberen oder schlecht
> strukturierten Code beklagt.

Weil keiner den Code je gesehen hat? SCNR ;-)

duck und weg

von greg (Gast)


Lesenswert?

Das verhasste goto kann man in C recht sinnvoll nutzen, um 
Fehlerbehandlung (innerhalb einer Funktion) zu vereinfachen.

von Mark B. (markbrandis)


Lesenswert?

greg schrieb:
> Das verhasste goto kann man in C recht sinnvoll nutzen, um
> Fehlerbehandlung (innerhalb einer Funktion) zu vereinfachen.

Oder man entwirft die Software so, dass man empfangene Daten und 
Kommandos zuerst einmal auf Plausibilität prüft und danach erst anfängt 
herumzurechnen. Wenn die Rechnung stets nur mit gültigen Werten 
durchgeführt wird, braucht es schon gar nicht mehr so viel 
Fehlerbehandlung.

: Bearbeitet durch User
von Felix P. (zirias)


Lesenswert?

Mark B. schrieb:
> Oder man entwirft die Software so, dass man empfangene Daten und
> Kommandos zuerst einmal auf Plausibilität prüft und danach erst anfängt
> herumzurechnen. Wenn die Rechnung stets nur mit gültigen Werten
> durchgeführt wird, braucht es schon gar nicht mehr so viel
> Fehlerbehandlung.

Also in C# gibt es dafür eine Implementierung von "Code Contracts", mit 
der man dann sowas sogar noch RICHTIG sauber von Business Logic trennen 
kann -- schöne Sache, aber was gibt es in C? Nichts, natürlich ;)

Also bleibt: "fail early" ist ein gutes Prinzip, was immer man im 
"preface" einer Funktion testen kann, testet man auch da. Das ist dann 
quasi implizit der Contract. Für alle Fehlerfälle, die nicht vorab 
erkannt werden können, bieten moderne Sprachen Exceptions* (die man 
nicht mögen muss, aber das führt hier zu weit) ... in C ist da meiner 
Meinung nach "best bet" immer noch ein "goto" in einen nachgelagerten 
aufräum-Code. Wenn man es vernünftig baut, hat man damit sein Äquivalent 
zum "finally" Block.

*) C# Code Contracts werfen natürlich auch mit Exceptions um sich, nur 
tun sie das separiert und ohne den business logic code damit zu 
"verunstalten" -- und soweit möglich erlauben sie statische Analyse.

von cppler (Gast)


Lesenswert?

Neben der eigenen return Variante die meiner Meinung nach die 
flexibelste ist hättest Du auch mal selber in ein C Buch schauen können 
oder im WWW suchen.
In C/C++ gibt es noch asserts das ist ein Makro das eigentlich nichts 
anderes macht als test->Fehler->return bzw. ein exit samt Fehlerausgabe.
Wenn Du wirklich sicher gehen willst das Deine Funktion tut was Du 
willst und ordentlich terminiert brauchst Du immer eine Variante, 
Invariante und Vergleichswert der auf Plausibilität gestestet werden 
muß.
Das wird bei konsequenter Anwendung zu ca. 10 Zeilen Code pro Funktion 
mehr führen.
Da sind die Fehlercodes im return deutlich günstiger.
Und nebenbei die Wirth Methode immer kleinere Funktionen wegen Übersicht 
u.ä. zu bauen macht zwar dem Compiler keine großen Probleme weil der im 
Fall der Fälle die wieder zu einem Block zusammenfügt aber es macht mehr 
Sinn und ist einfacher in größeren Funktionen eineindeutige Variablen 
und Kommentare zu verwenden, dann ist der Code idR besser wartbar.
C++ macht aber auch Sinn auf kleinen Käfern, da Kapselung und Vererbung 
sowie die try/catch/throw Fehlerbehandlung das Leben leichter machen und 
der Code idR nicht wesentlich größer wird als bei reinem C.
Kommt auf das Projekt an und wieviel Ressourcen der µC an Speicher hat.
Achja und wenn man asserts nimmt dran denken das die abgeschaltet werden 
können ...

von hink (Gast)


Lesenswert?

Felix P. schrieb:
> Also in C# gibt es dafür eine Implementierung von "Code Contracts", mit
> der man dann sowas sogar noch RICHTIG sauber von Business Logic trennen
> kann -- schöne Sache, aber was gibt es in C? Nichts, natürlich ;)
C  -> 1972
C# -> 2001
Da kannst du ja gleich dem Steinzeitmenschen vorwerfen, dass er nicht 
integrieren kann. ;-)
Dafür kann er aber bauen und jagen und sich die Mittel dafür selbst 
herstellen. Also müsste man dem heutigen Ingenieur vorwerfen, warum er 
ausgesetzt in der Wildnis mit sehr hoher Wahrscheinlichkeit nicht 
überleben würde.

Ansonsten, man hat heute die Wahl zwischen diversen Werkzeugen. 
Hardwarenah bleibt es bei mir bei C, GUI  wird C++/Qt, manchmal probiere 
ich mit Python/Tkinter.
Ach ja, wenn da der Vorgesetzte nicht wäre. ;-)

Als Buch fand ich den Pragmatischen Programmieren ganz gut. Clean Code 
las sich sehr nach Datenbanken und Co. (also weiter weg von LowLevel) 
an.

von Daniel A. (daniel-a)


Angehängte Dateien:

Lesenswert?

Es gibt für C ein interessantes Projekt für Exceptions:
https://github.com/guillermocalvo/exceptions4c
Ich weiss aber nicht wie gut dass für UCs geeignet ist.


Soetwas zu machen ist jedoch nicht besonder schwer. Ich habe etwas 
änliches und kleineres ebenfalls schnell mal gemacht, (ist im anhang zu 
finden), wobei bei meiner Version das try und finally eine etwas andere 
funktionallität haben.

von greg (Gast)


Lesenswert?

Daniel A. schrieb:
> Es gibt für C ein interessantes Projekt für Exceptions:
> https://github.com/guillermocalvo/exceptions4c
> Ich weiss aber nicht wie gut dass für UCs geeignet ist.

Naja... das ist so ein typisches Rumgehacke mit setjmp/longjmp. Da gibt 
es zahlreiche andere Projekte, die ungefähr das Gleiche machen. Das ist 
nun wirklich nicht schön oder elegant. Ich würde sowas wahrscheinlich 
nicht in einem ernsthaften Projekt einsetzen wollen.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

greg schrieb:
> Daniel A. schrieb:
>> Es gibt für C ein interessantes Projekt für Exceptions:
>> https://github.com/guillermocalvo/exceptions4c
>> Ich weiss aber nicht wie gut dass für UCs geeignet ist.
>
> Naja... das ist so ein typisches Rumgehacke mit setjmp/longjmp. Da gibt
> es zahlreiche andere Projekte, die ungefähr das Gleiche machen. Das ist
> nun wirklich nicht schön oder elegant. Ich würde sowas wahrscheinlich
> nicht in einem ernsthaften Projekt einsetzen wollen.

Was C++ beim Stack Unwinding macht, beziehungsweise nicht macht ist auch 
nicht wirklich schön oder elegant. Mit der Einführung von noexcept und 
der Deprecation dynamischen Exception-Spezifikationen wurde es nicht 
wirklich besser. Du siehst es nur nicht, weil der Compiler es vor dir 
versteckt.

von Bernd (Gast)


Lesenswert?

Danke für die vielen interessanten Beiträgen.
Mit so einer fruchtbaren Diskussion habe ich ehrlich gesagt nicht 
gerechnet.
Werde mir zu hause einiges ansehen und ausprobieren.

von Sheeva P. (sheevaplug)


Lesenswert?

Walter T. schrieb:
> Jay schrieb:
>> void f() {
>>     if(ging_schief) {
>>        throw ...
>>     }
>> }
>
> Wobei Du den wichtigsten Zweck von try/catch allerdings übersiehst: Du
> kannst die Ebene, bei der aufgefangen wird, sehr komfortabel wählen. Bei
> C must Du Deinen Funktionsabbruch auf allen Ebenen einzeln
> implementieren, bist Du bei der Ebene angekommen bist, bei denen Deine
> Fehlerbehandlung greifen soll. Das sauber zu implementieren hat mich
> schon mehr als einmal viele Stunden gekostet.

In solchen Fehlersituationen kann es tatsächlich ausnahmsweise sinnvoll 
sein, "goto" zu benutzen.

von Der Andere (Gast)


Lesenswert?

Mark B. schrieb:
> Wenn die Rechnung stets nur mit gültigen Werten
> durchgeführt wird, braucht es schon gar nicht mehr so viel
> Fehlerbehandlung.

Kommt darauf an, ob du in deinem Programm einfach sagen kannst: " 
Fehler, ende aus", oder ob dein System tolerant auf die 
unterschiedlichsten Aktionen und/oder Zustände von "Aussen" reagieren 
muss ohne einfach eine message box mit Fehler präsentieren zu können.
Eine Steuerung sieht da ganz anders aus als ein Pipifax Rechenprogramm 
um ein paar Daten aufzuarbeiten.

von Mark B. (markbrandis)


Lesenswert?

Der Andere schrieb:
> Mark B. schrieb:
>> Wenn die Rechnung stets nur mit gültigen Werten
>> durchgeführt wird, braucht es schon gar nicht mehr so viel
>> Fehlerbehandlung.
>
> Kommt darauf an, ob du in deinem Programm einfach sagen kannst: "
> Fehler, ende aus", oder ob dein System tolerant auf die
> unterschiedlichsten Aktionen und/oder Zustände von "Aussen" reagieren
> muss ohne einfach eine message box mit Fehler präsentieren zu können.
> Eine Steuerung sieht da ganz anders aus als ein Pipifax Rechenprogramm
> um ein paar Daten aufzuarbeiten.

Sehe ich nicht so. Bei ungültigen Daten bzw. einem ungültigen Kommando 
geht das System dann eben in den sicheren Zustand. Davon hält einen doch 
nichts ab, das so zu machen.

von S. R. (svenska)


Lesenswert?

Mark B. schrieb:
> Bei ungültigen Daten bzw. einem ungültigen Kommando
> geht das System dann eben in den sicheren Zustand.
> Davon hält einen doch nichts ab, das so zu machen.

Das setzt voraus, dass es einen sicheren Zustand gibt, bzw. dass dieser 
sichere Zustand aus jedem Fehlerfall erreichbar ist. Das ist nicht immer 
gegeben.

von Mark B. (markbrandis)


Lesenswert?

S. R. schrieb:
> Mark B. schrieb:
>> Bei ungültigen Daten bzw. einem ungültigen Kommando
>> geht das System dann eben in den sicheren Zustand.
>> Davon hält einen doch nichts ab, das so zu machen.
>
> Das setzt voraus, dass es einen sicheren Zustand gibt, bzw. dass dieser
> sichere Zustand aus jedem Fehlerfall erreichbar ist. Das ist nicht immer
> gegeben.

In der Regel verhält sich ein (Sub-)System erst einmal passiv, wenn es 
keine gültigen Kommandos oder Daten mehr bekommt. Das heißt es ändert 
seinen Zustand zunächst einmal nicht. Wenn es über einen zu langen 
Zeitraum keine gültigen Daten mehr gibt, dann erfolgt die 
Ausfallreaktion. Das kann alles Mögliche sein, von einer simplen 
Diagnosemeldung (Eintrag ins Logfile) bis hin zu einem Ausschalten des 
Systems.

Das alles hat aber eher weniger mit der Programmiersprache C an sich zu 
tun, sondern mehr mit Systems Engineering bzw. Software Engineering.

Ausfallmechanismen kann man in jeder Sprache implementieren. Und die 
Anforderungen dazu sind gar komplett unabhängig von der Wahl der 
Programmiersprache. Wär ja noch schöner! ;-)

von Felix P. (zirias)


Lesenswert?

cppler schrieb:
> In C/C++ gibt es noch asserts das ist ein Makro das eigentlich nichts
> anderes macht als test->Fehler->return bzw. ein exit samt Fehlerausgabe.
[...]
> Achja und wenn man asserts nimmt dran denken das die abgeschaltet werden
> können ...

Das ist Sinn der Sache, da Asserts dafür gedacht sind, garantierte 
Vor- oder Nachbedingungen bzw Invarianten zu testen. Wenn ein Assert 
"anschlägt" entspricht das also einem Bug, nicht einer ungültigen 
Eingabe "von außen". Daher haben Asserts in produktivem Code auch nichts 
mehr zu suchen, der sollte ausreichend getestet sein -- anstatt sie 
auszubauen schaltet man sie ab. Richtig nützlich werden Asserts meiner 
Meinung nach, wenn sie statisch verifiziert werden können (also, ohne 
das Programm laufen zu lassen). Das geht in C leider nur mit Tricks und 
sehr eingeschränkt.

> C++ macht aber auch Sinn auf kleinen Käfern, da Kapselung und Vererbung
> sowie die try/catch/throw Fehlerbehandlung das Leben leichter machen und
> der Code idR nicht wesentlich größer wird als bei reinem C.
> Kommt auf das Projekt an und wieviel Ressourcen der µC an Speicher hat.

Nunja, darüber kann man eben geteilter Meinung sein. Kapselung geht auch 
in C (teilweise sogar einfacher/besser durch opaque pointer). 
Vererbung hat ihre Anwendungen, aber die Erfahrung hat gezeigt, dass man 
sie vorsichtig einsetzen sollte, siehe "composition over inheritance" 
Prinzip. Und Exceptions machen das Leben manchmal nur auf einen ersten 
oberflächlichen Blick einfacher -- insbesondere in einer Sprache mit 
explizitem Resourcenmanagement.

von cppler (Gast)


Lesenswert?

Felix P. schrieb:
> cppler schrieb:
>> In C/C++ gibt es noch asserts das ist ein Makro das eigentlich nichts
>> anderes macht als test->Fehler->return bzw. ein exit samt Fehlerausgabe.
> [...]
>> Achja und wenn man asserts nimmt dran denken das die abgeschaltet werden
>> können ...
>
> Das ist Sinn der Sache, da Asserts dafür gedacht sind, *garantierte*
> Vor- oder Nachbedingungen bzw Invarianten zu testen. Wenn ein Assert
> "anschlägt" entspricht das also einem Bug, nicht einer ungültigen
> Eingabe "von außen". Daher haben Asserts in produktivem Code auch nichts
> mehr zu suchen, der sollte ausreichend getestet sein -- anstatt sie
> auszubauen schaltet man sie ab. Richtig nützlich werden Asserts meiner
> Meinung nach, wenn sie statisch verifiziert werden können (also, ohne
> das Programm laufen zu lassen). Das geht in C leider nur mit Tricks und
> sehr eingeschränkt.

Ja sicher, deswegen habe ich ja auch das mit den Varianten, Invarianten 
und Vergleichwerten geschrieben.
Asserts machen das gleiche und wenn es keine Fehler mehr gibt kann man 
sie abschalten und spart Code.
Nur hilft das nicht bei Raceconditions o.ä.

>
>> C++ macht aber auch Sinn auf kleinen Käfern, da Kapselung und Vererbung
>> sowie die try/catch/throw Fehlerbehandlung das Leben leichter machen und
>> der Code idR nicht wesentlich größer wird als bei reinem C.
>> Kommt auf das Projekt an und wieviel Ressourcen der µC an Speicher hat.
>
> Nunja, darüber kann man eben geteilter Meinung sein. Kapselung geht auch
> in C (teilweise sogar einfacher/besser durch opaque pointer).
> Vererbung hat ihre Anwendungen, aber die Erfahrung hat gezeigt, dass man
> sie vorsichtig einsetzen sollte, siehe "composition over inheritance"
> Prinzip. Und Exceptions machen das Leben manchmal nur auf einen ersten
> oberflächlichen Blick einfacher -- insbesondere in einer Sprache mit
> explizitem Resourcenmanagement.

Ich denke nicht das es "einfacher" geht wenn man class, private und 
public einfach ausschreibt ohne Pointer&Co.
Was die Exceptions angeht gibt's ja einen weg wie schon beschrieben.
Aber alle Token von C++ machen den Code lesbarer und wartbarer.
Wenn man es übertreiben will kann man auch selbstmodifizierenden Code 
bauen, dann braucht man weniger Ressourcen hat aber das Problem das der 
Code ohne Fünfmilliarden Zeilen Kommentare nicht wartbar ist und sobald 
ein Register in der Hardware umgestellt wird oder der Opcode geändert 
steht man im Regen.
Sollte ein Tiny25 nicht mehr ausreichen für C++ nimmt man einen Tiny85, 
kostet auch nicht wesentlich mehr aber man muß nicht unnötig 
herumfummeln um ein paar Byte zu sparen.
Meine Meinung.

von S. R. (svenska)


Lesenswert?

cppler schrieb:
> Wenn man es übertreiben will kann man auch selbstmodifizierenden Code
> bauen,

Auf einem AVR ist der Code zur Laufzeit schreibgeschützt. Nix mit 
selbstmodifizierendem Code.

von xXx (Gast)


Lesenswert?

Stefan U. schrieb:
>> Was ich mir aktuell noch überlege ist, in wiefern
>> "Dependency Injection" (wie es gerne in der Java Welt
>> gemacht wird) im embedded Bereich Sinn macht.
> Dabei wird zur Laufzeit Code generiert, das geht bei C nicht.
>
> Und auch in Java hasse ich es wie die Pest. Macht nur Probleme.

Ich denke, das verwechselst du mit Aspect Oriented Programming. Bei 
Dependency Injection geht es ja nur darum, dass benoetigte Resourcen 
uebergeben werden statt von dem Objekt/der Methode selbst beschafft 
wird. Das senkt die direkten Abhaengigkeiten und erleichtert die 
Testbarkeit. Ist aber ein alter Hut mit neuem Namen, das kann man 
natuerlich auch in C machen. Dann muss man aber auf die heiss geliebten 
globalen Variablen verzichten. :-)

von Walter T. (nicolas)


Lesenswert?

Walter T. schrieb:
> Hm... ein Buch von Microsoft Press, geschrieben in TEX, in dem
> Fallbeispiele über C auf dem M680x0 genutzt werden. Wie die Zeit
> vergeht.
>

OK, das Buch war doch in Word 2.0 geschrieben - TEX stand da nur im 
Copyright, weil es als gutes Beispiel für Fehlerkultur erwähnt wird.

Walter T. schrieb:
> Nichtsdestotrotz scheint alles Hand und Fuß zu haben und gut geschrieben
> ist es auch.

Diesen Punkt muß ich allerdings (nachdem ich das Buch jetzt durch habe) 
nicht revidieren.

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.