Hi, ich bin noch relativ unerfahren im Bereich Mikrocontroller-Programmierung und C im allgemeinen, habe allerdings einige berufliche Erfahrung in der Programmierung von Unternehmensanwendungen – primär in Java. Ich versuche bei der Programmierung ein paar Prinzipien und Regeln zu beherzigen, die von manchen unter dem Begriff 'Clean Code' zusammengefasst werden. Was mir gerade schwerfällt, ist diese Prinzipien mit 'idiomatischem' C für Mikrocontroller zu verheiraten: Funktionen: - keine kryptischen Abkürzungen als Namen, gilt auch für Argumente - tut genau EINE Sache (Single Responsibility Priciple) - kurz, möglichst nicht mehr als 3 bis 5 Zeilen - möglichst wenige Funktionsargumente - alle Logik befindet sich auf dem selben Abstraktionslevel, keine Vermischung von high- und lowlevel-Logik. - keine tiefen Verschachtelungen von if- und Schleifenkonstrukten. - wiederkehrende Muster in Funktionen auslagern - Duplikation vermeiden Boolsche Ausdrücke: - Zuweisung an eine Variable mit aussagekräftigem Namen statt direkter Verwendung in einem if-Ausdruck - Komplexen Ausdruck in mehrere kleine zerlegen, auch wieder jeden mit aussagekräftiger Bezeichnung versehen Variablen: - keine mehrfachen Zuweisungen - keine kryptischen Abkürzungen, schon gar keine einzelnen Buchstaben So eine Praxis führt ggf. zu vielen kleinen Funktionen, mehr Variablen, längeren Bezeichnern und generell mehr Text. Dafür kann man sich oftmals Kommentare sparen, da der Code für sich spricht. In Java muss man sich um die Effizienz solchen Codes keine Gedanken machen. Der Compiler bzw. die virtuelle Maschine optimieren das alles weg. Hier könnte ich mir allerdings vorstellen, dass einige bei einer solchen Programmierpraxis die Hände über dem Kopf zusammenschlagen. Mich würde eure Meinung interessieren. Wie sollte lesbarer, wartbarer Qualitätscode aussehen, der gleichzeitig die Performancebedingungen von Mikrocontrollern berücksichtigt und als idiomatisches C angesehen werden kann? Wie lässt sich der Begriff 'Clean Code' in die Embedded-Welt einordnen?
nik janusch schrieb: > Was kostet eine Funktion? Nun ich sage einmal das kommt auf die Menge an. Für größere Mengen gibt es sicher Rabatt.
nik janusch schrieb: > In Java muss man sich um die Effizienz solchen Codes keine Gedanken > machen. In C eigentlich auch nicht. Kleine Funktionen in der gleichen Übersetzungseinheit (C-Datei) inlint der Compiler von selber, wenn es sich lohnt. Wenn nicht, kostet der Funktionsaufruf halt ein paar Takte (hängt von der Anzahl und Größe der Parameter und des Rückgabewerts ab), dafür wird der Code kleiner. Zusätzliche lokale Variablen für Zwischenergebnisse kosten sowieso nichts. Im Gegenteil, in manchen Fällen kann der Code dadurch sogar besser optimiert werden, z.B. wenn man eine volatile-Variable zuerst in einer lokalen Variable zwischenspeichert und dann mehrmals verwendet. Ist also imo schon genau richtig, wie Du es machst. Schreib den Code so, dass er lesbar, verständlich und wartbar ist und lass den Compiler den Rest machen.
nik janusch schrieb: > - kurz, möglichst nicht mehr als 3 bis 5 Zeilen Das ist aber eine ziemlich starke Einschränkung, ne Schleife mit if-Abfrage passt da nicht mehr rein. 10 Zeilen sollte man schon mindestens erlauben.
Fabian O. schrieb: > In C eigentlich auch nicht. Kleine Funktionen in der gleichen > Übersetzungseinheit (C-Datei) inlint der Compiler von selber, wenn es > sich lohnt. Das würde ich so nicht unterschreiben wollen, weil eine Funktion Inline zu machen das Verhalten des Programmes stark verändert. Schau lieber in der Dokumentation des Compilers nach, da steht drin wann und ob er das macht. Ganz grob kann man aber sagen, dass Prozessoren gerne auf Funktionsaufrufe optimiert werden. Sprich je optimierter der Prozessor auf Geschwindigkeit ist, um so weniger kostet ein Funktionsaufruf. Auf modernen PCs sind Funktionsaufrufe sogar schneller als Inlinefunktionen, weil durch das letztere mehr Programmcode aus dem Speicher geladen werden muss, was dort meistens das Nadelör ist. Fefe hat dazu mal einen Vortrag gemacht http://www.fefe.de/know-your-compiler.pdf Oder als Video hier: ftp://ftp.ccc.de/events/camp2007/video/m4v/cccamp07-en-1952-Know_your_co mpiler.m4v
erdnuss schrieb: > nik janusch schrieb: >> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen > > Das ist aber eine ziemlich starke Einschränkung, ne Schleife mit > if-Abfrage passt da nicht mehr rein. 10 Zeilen sollte man schon > mindestens erlauben. Eben nicht. Jede Funktion soll nur eine Sache machen. Du hättest hier schon zwei Dinge: das Iterieren und dann die if-Logik. Laut Clean-Code sollte in deiner Schleife also nur ein Funktionsaufruf stehen. Deine if-Abfrage kommt dann in diese Funktion.
Was sauberer Code ist musst du selber für Dich herausfinden. Les Dir mal "The Art of Unix Programming" durch. Das ist eine fundierte Meinung, auf der Du Deine eigene Meinung aufbauen kannst, oder von der Du Dich distanzieren kannst.
nik janusch schrieb: > Funktionen: > - kurz, möglichst nicht mehr als 3 bis 5 Zeilen Ich versuche gerade mir vorzustellen, wie die libc oder die libmath im inneren aussieht, zerlegt in Scheibchen mit 3 bis 5 Zeilen MfG Klaus
Christian Berger schrieb: > Was sauberer Code ist musst du selber für Dich herausfinden. Les Dir mal > "The Art of Unix Programming" durch. Unbedingt. Christian Berger schrieb: > Fabian O. schrieb: >> In C eigentlich auch nicht. Kleine Funktionen in der gleichen >> Übersetzungseinheit (C-Datei) inlint der Compiler von selber, wenn es >> sich lohnt. > > Das würde ich so nicht unterschreiben wollen, weil eine Funktion Inline > zu machen das Verhalten des Programmes stark verändert. Ne, eben gerade nicht. Sämtliche Optimierungen, die der Compiler vornehmen darf, dürfen das Verhalten des Programmes nicht verändern. Diese Voraussetzung funktioniert allerdings nur, wenn man auch verstanden hat, was das Verhalten eines Programmes in C überhaupt ist. Christian Berger schrieb: > Auf modernen PCs sind Funktionsaufrufe sogar schneller als > Inlinefunktionen, Das hingegen würde ich nicht unterschreiben. Auf der einen Seite forderst du, kurze Funktionen zu schreiben. Ich erlaube mir mal, das so zu erweitern, dass man auch lieber kurze Funktionen statt Makros verwenden soll. Aber eine Funktion, die gerade ein Bit im Register setzt, ist bei Umsetzung als inline mit Sicherheit schneller und kompakter, als ein Funktionsaufruf... Zur Thematik insgesamt ist m.M.n. das gleiche zu sagen, wie zur Kommentierung (siehe Thread nebenan(1)): Verallgemeinerungne sind grundsätzlich falsch... Es spricht überhaupt nichts dagegen, einen Schleifenzähler 'i' zu nennen. In einer maximal fünf Zeilen langen Funktion schon garnicht schmunzel Aber ob das Zerbröseln von Quelltext in unzählige Unterfunktionen immer sinnvoll ist, bezweifle ich stark.
nik janusch schrieb: > Was kostet eine Funktion? Aktuell 7,32€ + MwSt., aber das ändert sich alle paar Stunden je nach Nachfrage.
Klaus schrieb: > nik janusch schrieb: >> Funktionen: >> - kurz, möglichst nicht mehr als 3 bis 5 Zeilen > > Ich versuche gerade mir vorzustellen, wie die libc oder die libmath im > inneren aussieht, zerlegt in Scheibchen mit 3 bis 5 Zeilen :-) ...und ich versuche gerade mit vorzustellen, wie GCC aussehen würde. Bei 4000000 Zeilen Code wäre man bei 100000 Funkionen oder mehr. Dabei ist schon ein Faktor von 1/4 eingerechnet (Quelle besteht zu 75% aus Kommentaren, also 1000000 LOC netto) und eine Funktion darf 10 Zeilen groß sein. Bibliotheken wie libgcc, libada, libfortran, libstdc++, libsupc++, libiberty, libcpp, ...) sind da noch nicht eingerechnet. Eine Coding-Rule starr nach einer Code-Metrik auszurichten scheint mir wenig zielführend zu sein, zumindest bei komplexen Aufgabenstellungen.
Sven P. schrieb: > Das hingegen würde ich nicht unterschreiben. Auf der einen Seite > forderst du, kurze Funktionen zu schreiben. ... > Aber eine Funktion, die gerade ein Bit im Register > setzt, ist bei Umsetzung als inline mit Sicherheit schneller und > kompakter, als ein Funktionsaufruf... Nein, Funktionen müssen nicht "kurz" sondern "in der richtigen Länge" sein. Da musst Du mich wohl mit jemanden verwechselt haben.
Christian Berger schrieb: > Sven P. schrieb: >> Das hingegen würde ich nicht unterschreiben. Auf der einen Seite >> forderst du, kurze Funktionen zu schreiben. ... >> Aber eine Funktion, die gerade ein Bit im Register >> setzt, ist bei Umsetzung als inline mit Sicherheit schneller und >> kompakter, als ein Funktionsaufruf... > > Nein, Funktionen müssen nicht "kurz" sondern "in der richtigen Länge" > sein. Da musst Du mich wohl mit jemanden verwechselt haben. Mea Culpa. Ja, ich war wieder beim OT.
Um zur Frage zurück zu kommen: Die Kosten einer Funktion sind zunächst mal abhängig von: • Dem verwendeten Compiler (Sprache, Zielarchitektur, Hersteller) • Dessen Version und Verschalterung, insbesondere Inline-Heuristiken, Optimierung auf Codegröße, Geschwindigkeit, ... • Der Kontext, in dem diese Funktion aufgerufen wird • Was ist über die Funktion prinzipiell bekannt? - Wie oft wird sie (statisch) aufgerufen? - Ist der Aufruf direkt oder indirekt? - Ist dem Compiler der Code bekannt? - Wie groß ist die Funktion? - Welche Registerlast herrscht zum Zeitpunkt des Aufrufs? - Welche interprozeduralen Optimierungen werden gemacht? partial inlinig, interprecedural constant propagation, LTO, ... • Wie genau sind die wirklichen Kosten im Compiler modelliert bzw. überhaupt modellierbar? Und wo und wie finden diese Verwendung? Bottom Line: Nur Erfahrung wird dir helfen, zu beurteilen, ob der erzeugte Code deinen Ansprüchen an Geschwindigkeit, Codeverbrauch und Stack- bzw. Kontextverbrauch genügt. Erschwerend kommt kinzu, daß die Anforderungen an den Code stark vom Einsatzfeld abhägen und i.d.R. selst innerhalb eine und derselben Anwendung stark variieren: Eine GUI-Anwendung auf einem 64-Bit Rechner hat andere Anforderungen als die Firmware eines 8-Bit Mikrocontrollers, die harten Echtzeit-Anforderungen gerecht werden muß. Als Beispiel sei die Darstellung einer Software-UART oder -PWM genannt.
Sven P. schrieb: >> Das würde ich so nicht unterschreiben wollen, weil eine Funktion Inline >> zu machen das Verhalten des Programmes stark verändert. > Ne, eben gerade nicht. Sämtliche Optimierungen, die der Compiler > vornehmen darf, dürfen das Verhalten des Programmes nicht verändern. Eine Ausnahme: Die Laufzeit darf sich ändern. Fefes Papier führt mit Recht auf, dass die Effizienz von Caching in erheblichem Umfang die Laufzeit bestimmt. Allerdings lässt sich daraus keine Regel bauen. Denn die die Laufzeit dominierenden Schleifen von Programmen können sehr gross sein (typisch für Datenbanksysteme), oder sehr klein (technisch/wissenschaftliche Aufgaben, Grafik). Abhängig davon kann aggressives Inlining schlecht oder gut sein. Das Papier setzt aber voraus, dass man überhaupt Caches hat, tiefe Pipelines mit dynamischer Sprungvorhersage hat etc. Diese Gedanken spielen in der Welt von PC-Prozessoren. Mikrocontroller der hier im Forum üblichen Kategorie haben beides nicht oder nur rudimentär (z.B. für nichtsequentielle Flash-Zugriffe). Da muss der Code nur ins ROM passen und RAM-Daten verhalten sich gewissermassen stets wie permanent zu 100% gecached In sehr einfach aufgebauten 10-100MHz Mikrocontrollern können die Regeln für laufzeiteffiziente Programmierung daher deutlich anders lauten als in hochkomplexen 3GHz PC-Systemen.
nik janusch schrieb: > Funktionen: > ... > - kurz, möglichst nicht mehr als 3 bis 5 Zeilen Das ist zwar theoretisch machbar, trägt aber – konsequent umgesetzt – garantiert nicht zur besseren Lesbarkeit des Codes bei. Ich würde sagen: Eine Funktion sollte möglichst nicht länger sein als die Größe eines Editorfensters auf einem nicht allzugroßen Bildschirm, also etwa 50 Zeilen. Kürzer ist gut, wenn eine weitere Unterteilung der Funktion logisch sinnvoll ist, aber nur dann. MS schrieb: > Jede Funktion soll nur eine Sache machen. Du hättest hier schon zwei > Dinge: das Iterieren und dann die if-Logik. Laut Clean-Code sollte in > deiner Schleife also nur ein Funktionsaufruf stehen. Deine if-Abfrage > kommt dann in diese Funktion. Im folgenden Beispiel wird in einem Graustufenbild, das durch ein zwei- dimensionales Array gegeben ist, das Pixel mit der maximalen Helligkeit ermittelt werden. Ich würde das normalerweise etwa so schreiben:
1 | unsigned char maxintensity(int height, int width, unsigned char image[height][width]) { |
2 | unsigned char intens, maxintens; |
3 | int y, x; |
4 | |
5 | maxintens = 0; |
6 | for(y=0; y<height; y++) { |
7 | for(x=0; x<width; x++) { |
8 | intens = image[y][x]; |
9 | if(intens > maxintens) |
10 | maxintens = intens; |
11 | }
|
12 | }
|
13 | return maxintens; |
14 | }
|
Ich finde, man erkennt schon beim kurzen Überfliegen, was die Funktion tut. Man würde die Funktion sogar dann verstehen, wenn sie einen weniger passenden Namen (bspw. einfach nur f) hätte. Allerdings hat die Funktion 7 Zeilen (ohne Deklarationen und Zeilen ohne aktiven Inhalt), was nach obiger Regel schlecht ist. Außerdem enthält sie zwei For-Schleifen und eine If-Abfrage, die auch noch ineinander verschachtelt sind, was nach deiner Interpretation der Regeln ebenfalls schlecht ist. Teilt man die gleiche Funktionalität in mehrere Funktionen auf, von denen jede maximal eine For- oder eine If-Anweisung enthält, sieht das Ganze so aus:
1 | unsigned char updatemaxlineintensity(unsigned char intens, unsigned char oldmaxintens) { |
2 | unsigned char maxintens; |
3 | |
4 | maxintens = oldmaxintens; |
5 | if(intens > maxintens) |
6 | maxintens = intens; |
7 | |
8 | return maxintens; |
9 | }
|
10 | |
11 | unsigned char lineintensity(int width, unsigned char line[width]) { |
12 | unsigned char maxintens; |
13 | int x; |
14 | |
15 | maxintens = 0; |
16 | for(x=0; x<width; x++) |
17 | maxintens = updatemaxlineintensity(line[x], maxintens); |
18 | |
19 | return maxintens; |
20 | }
|
21 | |
22 | unsigned char updatemaxintens(int width, unsigned char line[width], unsigned char oldmaxintens) { |
23 | unsigned char intens, maxintens; |
24 | |
25 | maxintens = oldmaxintens; |
26 | intens = lineintensity(width, line); |
27 | if(intens > maxintens) |
28 | maxintens = intens; |
29 | |
30 | return maxintens; |
31 | }
|
32 | |
33 | unsigned char maxintensity(int height, int width, unsigned char image[height][width]) { |
34 | unsigned char maxintens; |
35 | int y; |
36 | |
37 | maxintens = 0; |
38 | for(y=0; y<height; y++) |
39 | maxintens = updatemaxintens(width, image[y], maxintens); |
40 | |
41 | return maxintens; |
42 | }
|
Der Code ist jetzt dreimal so lang, und ich für meinen Geschmack hat die Lesbarkeit ganz klar gelitten. Diese 3-bis-5-Zeilenregel mag für VHL-Sprachen wie Haskell gelten (und ist dort auch relativ leicht einzuhalten), wo eine einzelne Codezeile i.Allg. sehr viel mehr tut als in C, aber für C ist die Regel unsinnig.
Sven P. schrieb: >> Auf modernen PCs sind Funktionsaufrufe sogar schneller als >> Inlinefunktionen, > Das hingegen würde ich nicht unterschreiben. Das bezieht sich vermutlich eher auf den Rahmen von Fefes Papier: Wenn Inlining den Code so sehr aufbläht, dass die Effizienz des L1 Cache und des Branch Predictors darunter leidet. Andernfalls kann ich mir auch kaum vorstellen, dass Inlining schädlich ist. Im Gegenteil: Nicht selten werden einige Optimierungen innerhalb der aufgerufenen Funktion überhaupt erst mit Inlining möglich. Beispielsweise wenn die Argumente des Aufrufs Konstanten sind und der Code der Funktion dadurch auf einen Bruchteil des Aufwands eindampft.
A. K. schrieb: > Eine Ausnahme: Die Laufzeit darf sich ändern. Das ist das Problem. Die Laufzeit gehört nämlich eigentlich nicht zum Verhalten des Programms. Zumindest nicht im Sinne der Sprachbeschreibung im C99-Standard.
Gleich zu Anfang möchte ich mich entschuldigen, weil ich den ganzen Thread nicht gelesen habe. Da ein ähnliches Thema hier vor kurzem eröffnet wurde, ich selbst erst seit kurzem mit dem Programmieren von µC angefangen habe, fiel mir gerade eine Frage im Allgemeinen dazu ein. Gibt es Lektüre dazu? Wie, nicht in welcher Sprache, programmiert werden sollte? Oder ist es grundsätzlich Sprachen abhängig? Gibt es eigentlich einen Industriestandard oder so was wie eine DIN (gibt es sogar fürs Klo)? Das waren dann doch mehr als eine Frage.:-)
Frank O. schrieb: > Oder ist es grundsätzlich Sprachen abhängig? Definitiv ist es das. Einen 5-Zeiler in der extrem kompakten Sprache APL kann man schlecht mit einem 5-Zeiler in der extrem geschwätzigen Sprache COBOL vergleichen. ;-)
A. K. schrieb: > Frank O. schrieb: >> Oder ist es grundsätzlich Sprachen abhängig? > > Definitiv ist es das. Einen 5-Zeiler in der extrem kompakten Sprache APL > kann man schlecht mit einem 5-Zeiler in der extrem geschwätzigen Sprache > COBOL vergleichen. ;-) Irgendwie dachte ich mir das schon. Deshalb ist wahrscheinlich so wenig zu Grundsätzlichem zu lesen. Gerade am Anfang ist es schwer die richtige Strategie zu entwickeln, wie ich finde. So werden ganze Programme am Ende nur schwer lesbar, wartbar und Teile werden nur schwer portierbar, weil Bücher eigentlich nicht darauf eingehen. Aber ich will diesen Thread nicht weiter kapern. Also, schönen Sonntag!
> - keine kryptischen Abkürzungen als Namen, gilt auch für Argumente Kürzt der Compiler weg, bei C sind keine Namen mehr im Kompilat. Übrigens macht er das auch mit Kommentaren, also kommentiere ruhig viel und hfetig. > - tut genau EINE Sache (Single Responsibility Priciple) Schlecht. Vor allem weil zwischendrin Speicher benutzt wird und Speicher auf Microcontrollern oft rar ist. Dynamischen Speicher (in C malloc) sollte man gar ganz meiden. Man ist also ganz ganz weit weg von der garbage collection von Java. BeispieL: Einen String von der seriellen Schnittstell einlesen, im Zeichensatz wandeln und Escape Sequenzen entfernen und ein Kommando erkennen. Wenn man das in 4 Funktionen aufspaltet, braucht man doppelten Speicher für den String. Man liest ihn erst in den Puffer, dann wandelt eine Funktion aus diesem Puffer in den nächsten, dann rödelt man zur Entfernung von Escapes durch, dann analysiert eine Funktion was übrig blieb. Im uC reduziert man am besten, in dem die Funktion, die die Zeichen aus der seriellen abholt schon den Zeichensatz wandelt, bei erkannten Escapes einen Zustand speichert, und nur noch die echten Zeichen abliefert, die gleich von einem state machine Parser analysiert werden, dann muß nämlich der String gar nicht gepeichert werden, nur 1 Byte für Escape und 1 Wort für den Zustand der state machine des Parsers. Man kann auch solchen Code so schreiben, daß man ihn noch durchschaut und nachträglich ändern kann. Aber Anfänger können das nicht. > - kurz, möglichst nicht mehr als 3 bis 5 Zeilen Schlecht weil Funktionsaufrufe/Verlassen Platz und Zeit kostet. Manche Funktioen können vom Compiler inline aufgelöst werden, aber besser man macht das per Hand. Bei Microcontroller sind Fuktionen von tausenden von Zeilen keine Seltenheit, weil man oft state machines mit switch/case abbildet. Davon wird natürlich bei jedem Durchlauf nur ein kurzes Stück ausgeführt, aber den Funktionsaufruf, den spart man möglichst schon bei schreiben des Programms ein. Auch eine Funktion die z.B. eine LED-Matrix mit einem Video versoprgt und in Helligkeitn multiplext, die also strengen zeitlichen Kriterien unterliegt, schreibt man am Stück. Allerdings nicht in C sondern gleich in Assembler. Jeder Funktonsaufruf bei einem Pixel wäre schon zu viel, man veruscht im Gegenteil 8 Pixel zusammen abzuhandeln. Beliebt ist auch solcher Code:
1 | uint8_t tasten,gedrueckt; // globale Variablen |
2 | while(1)// die Programm-Hauptschleife |
3 | {
|
4 | tasten=PIND; // 8 Tasten auf ein mal |
5 | gedrueckt=tasten&~gedrueckt; // 8 Tasten auf ein mal |
6 | if(gedrueckt&1) |
7 | {
|
8 | // Taster 1 wurde gerade runtergedrückt, mach was
|
9 | }
|
10 | if(gedrueckt&2) |
11 | {
|
12 | // Taster 2 wurde gerade runtergedrückt, mach was
|
13 | }
|
14 | // mach was sonst in der Programm-Hauptschleife passieren muß
|
15 | gedrueckt=tasten; // 8 Tasten auf ein mal |
16 | _delay_ms(10); // Prellzeit abwarten |
17 | }
|
Der entprellt gleich alle Tasten in einem Rutsch ohne daß überhaupt eine Funktion dafür nötig ist. > - möglichst wenige Funktionsargumente Bei Microcontroller C darf man sogar massenhaft globale Variablen verwenden um die Parameterlisten kurz zu halten, denn es gibt keinen zweite Instanz (und wenn, dann per Interrupt um den man sich sowieso kümmern muß, z.B: mit volatile). > - alle Logik befindet sich auf dem selben Abstraktionslevel, keine > Vermischung von high- und lowlevel-Logik. Nicht klug. Siehe das genannte Beispiel. > - keine tiefen Verschachtelungen von if- und Schleifenkonstrukten. Gilt auch, weil so was ja quadratische Rechenzeit kostet. > - wiederkehrende Muster in Funktionen auslagern - Duplikation vermeiden Wenn man keine Platz hat sucht man möglichst alles was doppelt ist zusam,men. Braucht man Geschindigkeit, macht man loop unrollment und dupliziert eben auch. Meistens braucht man Geschwindigkeit und dupliziert schon deswegen den Code, weil man ihn an siener Verwendungsstelle optimal an das anpasst, was man dort wirklich braucht, also Funktionen auf das beim jeweiligen Aufruf wirklich benötigte abspeckt - und schon sind es gar keine Duplikate mehr. > - Zuweisung an eine Variable mit aussagekräftigem Namen statt direkter > Verwendung in einem if-Ausdruck Nur für die dümmsten der dummen Programmierer notwendig. > - Komplexen Ausdruck in mehrere kleine zerlegen, auch wieder jeden mit > aussagekräftiger Bezeichnung versehen Kann man machen, der (GCC) Compiler kürzt diese temporären Variablen normalerweise weg, aber wenn man einfachere Kompiler verwendet, ist das nicht immer der Fall (BASCOM ?). > - keine mehrfachen Zuweisungen > - keine kryptischen Abkürzungen, schon gar keine einzelnen Buchstaben i und j sind super Variablennamen, nur sollte der Scope sich höchstens auf eine Funktion beschränken, sondern verliert man damit den Überblick. > In Java muss man sich um die Effizienz solchen Codes keine Gedanken Doch, auch, man macht sich nur nicht und deswegen sind viele Java-Programme grottenlangsam und riesigfett. > Der Compiler bzw. die virtuelle Maschine optimieren das alles weg. Normalerweise optimiert C besser. > Hier könnte ich mir allerdings vorstellen, dass einige bei einer > solchen Programmierpraxis die Hände über dem Kopf zusammenschlagen. Natürlich, weil manches doch sehr auf Anfänger zugeschnitten ist. Der normale uC Programmierer läuft sehr schnell an Speichergrenzen (Programmspeicher und RAM-Speicher) und dann an Geschwindigkeitslimits. Jeder trick der es trotzdem ermöglicht den kleineren billigeren Controller zu verwenden ist sinnvoll, denn er spart in der Produktion Millionen ein. Bei Java "nur-Programmieren" werden diese Kosten ja auf den Anwender abgewälzt. Was schlechter Java Code schon beweirkt weil Hundertmillionen Benutzer z.B. ihr altes Smaprtphone weil zu klein und zu langsam für das neue schlecht geshriebene Programm sind in neue Hardware investieren, istb dem Programmierer solcher Apps ja egal. Bei Micircontroller haut ihm sein Chef schon auf's Haupt, denn der muß die leistungsfähigeren Recher den Kunden auf edn Kaufpreis draufshclagen und verliert dabei gegen die KOnkurrenz. Die Allianz Microsoft (schreibt fette Programme die nur auf dem neuesten Prozessor laufen) und Intel (verkauft gerne diese Prozessoren) ist in der Microcontrollerwelt nicht möglich. Allerdings: Auch Microcontroller werden immer gröser, Megabytes an Speicher sind keine Seltenheit mehr, man programmiert nicht mehr in Assembler, nicht unbedingt in C, sondern auch in Java, tweilweise laufen WebServer darauf und eben ganze Smartphones.
Aber wo wir schon grad so schön am Optimieren sind, eine kleine Schote aus der Abteilung "premature optimization...": Android verwendet eine andere virtuelle Maschinen für seinen Zwischencode als Java, nämlich die Dalvik-VM. Hintergrund war, dass sich eine registeroptimierte VM effizienter in echten Code umsetzen lässt, als die Stackmaschine der Java-VM. Soweit die Theorie. In der Realität ist es freilich andersrum. Der mittlerweile ziemlich weit entwickelte Hotspot-Compiler der Java-VM führt zu schnellerem Code als der weniger weit entwickelte Compiler der Dalvik-VM.
@MaWin: Melde dich mal richtig an! Danke für den Beitrag! Du hilfst mir mit deinem Wissen in vielen Überlegungen weiter.
MaWin schrieb: > tweilweise laufen WebServer darauf und eben ganze Smartphones. Würde ich freilich nicht mehr als Microcontroller bezeichnen. Ein PC wird ja nicht dadurch zum Microcontroller, dass man ihn auf die Dimension einer Zigarettenschachtel eindampft.
MaWin schrieb: > Beliebt ist auch solcher Code:
1 | uint8_t tasten,gedrueckt; // globale Variablen |
2 | while(1)// die Programm-Hauptschleife |
3 | {
|
4 | tasten=PIND; // 8 Tasten auf ein mal |
5 | gedrueckt=tasten&~gedrueckt; // 8 Tasten auf ein mal |
6 | if(gedrueckt&1) |
7 | {
|
8 | // Taster 1 wurde gerade runtergedrückt, mach was
|
9 | }
|
10 | if(gedrueckt&2) |
11 | {
|
12 | // Taster 2 wurde gerade runtergedrückt, mach was
|
13 | }
|
14 | // mach was sonst in der Programm-Hauptschleife passieren muß
|
15 | gedrueckt=tasten; // 8 Tasten auf ein mal |
16 | _delay_ms(10); // Prellzeit abwarten |
17 | }
|
> Der entprellt gleich alle Tasten in einem Rutsch ohne daß überhaupt eine > Funktion dafür nötig ist. Der Code ist eher ein Anti-Beispiel statt eins für guten Code: - Schlecht lesbar, da keine Leerzeichen - Überflüssige Kommentare - Verschwendet ein Byte Speicher für globale Variable tasten - Variable gedrueckt programmweit sichtbar, obwohl nicht außerhalb gebraucht. - Vermischt Logik verschiedener Schichten Nach Optimierung dürfte hier ziemlich das gleiche rauskommen:
1 | static uint8_t get_keys(void) |
2 | {
|
3 | return PIND; |
4 | }
|
5 | |
6 | static uint8_t get_pressed_keys(void) |
7 | {
|
8 | static uint8_t keys_prev = 0; |
9 | uint8_t keys = get_keys(); |
10 | uint8_t pressed = keys & ~keys_prev; |
11 | keys_prev = keys; |
12 | return pressed; |
13 | }
|
14 | |
15 | void main(void) |
16 | {
|
17 | while (1) { |
18 | uint8_t gedrueckt = get_pressed_keys(); |
19 | if (gedrueckt & 1) { |
20 | // Taster 1 wurde gerade runtergedrückt, mach was
|
21 | }
|
22 | if (gedrueckt & 2) { |
23 | // Taster 2 wurde gerade runtergedrückt, mach was
|
24 | }
|
25 | // Prellzeit abwarten
|
26 | _delay_ms(10); |
27 | }
|
28 | }
|
A. K. schrieb: > Android verwendet eine andere virtuelle Maschinen für seinen > Zwischencode als Java, nämlich die Dalvik-VM. Hintergrund war, dass sich > eine registeroptimierte VM effizienter in echten Code umsetzen lässt, > als die Stackmaschine der Java-VM. Das mag ein Grund gewesen sein. Es gibt noch etliche mehr. > Soweit die Theorie. In der Realität ist es freilich andersrum. Der > mittlerweile ziemlich weit entwickelte Hotspot-Compiler der Java-VM > führt zu schnellerem Code als der weniger weit entwickelte Compiler der > Dalvik-VM. Magst du das auch irgendwie belegen? Mein Eindruck ist ja, daß Java (und Derivate) überall da wo Geschwindigkeit essentiell ist (z.B. Video-Decoding) mittlerweile an nativen Code delegieren und "echter" Java-Code nur noch als Glue dient. Diese Entwicklung war abzusehen, weil Java ja von Anfang an performancepessimiert designt war. Ein Schelm wer Arges dabei denkt, wenn eine Programmiersprache ausgerechnet von einer Hardware-Bude gehyped wird. Ich würde die Aussage des TE nik janusch schrieb: > In Java muss man sich um die Effizienz solchen Codes keine Gedanken > machen. Der Compiler bzw. die virtuelle Maschine optimieren das alles > weg. eher so formulieren: In Java muß man sich um die Effizienz des selber geschriebenen Codes keine Gedanken machen, weil es von Hause aus schon so langsam ist, daß man es gar nicht mehr verschlechtern kann. XL
Axel Schwenke schrieb: > Magst du das auch irgendwie belegen Behauptet zumindest Oracle, wobei die in dieser Frage vielleicht nicht gänzlich neutral sind: https://blogs.oracle.com/javaseembedded/entry/how_does_android_22s_performance_stack_up_against_java_se_embedded > eher so formulieren: In Java muß man sich um die Effizienz des selber > geschriebenen Codes keine Gedanken machen, weil es von Hause aus schon > so langsam ist, daß man es gar nicht mehr verschlechtern kann. ;-) Jedenfalls neigen Java-Webserver erfahrungsgemäss dazu, weit mehr CPU und Speicher zu erfordern als PHP-Webserver. Wobei das nicht direkt vergleichbar ist, weil stets unterschiedliche Funktion, aber der Trend ist erkennbar.
Es freut mich, ein Thema angestoßen zu haben, das einiges an Kontroverse zulässt. Ich gebe zu, unkommentiert ein paar Regeln formuliert zu haben, die ich in dieser Radikalität auch nicht stehen lassen würde. Bob Martin hat genau solche Regeln in seinem Buch 'Clean Code' aufgestellt und damit einiges an Bewegung und Gegenbewegung in der Entwicklerwelt augelöst – hin zum Positiven, wie ich finde. Natürlich würde ich z.B. nicht stumpfsinnig darauf beharren, dass eine Funktion nicht mehr als 5 Zeilen Code enthalten darf, sondern eher nach der 'richtigen' Länge suchen, wie @casandro empfiehlt. Vielen Dank an alle, die sich hier konstruktiv mit dem Thema auseinandersetzen! Den Vorschlag von @mahwe, das Wiki um die Thematik zu bereichern, finde ich sehr interessant. Allerdings fürchte ich, dass hier auch leider zuviel Potential für Flamewars besteht. Nik
Axel Schwenke schrieb: >> Soweit die Theorie. In der Realität ist es freilich andersrum. Der >> mittlerweile ziemlich weit entwickelte Hotspot-Compiler der Java-VM >> führt zu schnellerem Code als der weniger weit entwickelte Compiler der >> Dalvik-VM. > > Magst du das auch irgendwie belegen? "In the JITC mode, however, Dakvik is slower than HotSpot by more than 2.9 times": http://dl.acm.org/citation.cfm?id=2388956
Das Primäre ist die gute Lesbarkeit der Quellen. Dieses realisiert man durch Aufteilung in Dateien und Funktionen. Eine Funktion ist dann gut lesbar, wenn sie eine Bildschirmseite zu 50-150 % ausfüllt. Mann sollte ein Problem also nicht zu sehr sezieren denn eine Horde von Dreizeilen-Funktionen ist eben so ungünstig wie seitenlanger Spagettikode.
A. K. schrieb: > Jedenfalls neigen Java-Webserver erfahrungsgemäss dazu, > weit mehr CPU und Speicher zu erfordern als PHP-Webserver. Wo gibt es den PHP-Webserver? ;-P Erfahrungsgemäß hängt vieles auch einfach von der Umsetzung ab.
Falk Brunner schrieb: > Strukturierte Programmierung auf Mikrocontrollern Sehr schön. Der Artikel enthält sehr viele gute Hinweise. Nur statt SVN würde ich heutzutage eher raten, git zu verwenden – besonders wenn man im Team arbeitet oder Open Source macht.
nik janusch schrieb: > Nur statt SVN > würde ich heutzutage eher raten, git zu verwenden – besonders wenn man > im Team arbeitet oder Open Source macht. Naja, Geschmackssache. Um nicht zu sagen: hat einen hohen Religiösitätsfaktor. Letztlich ist selbst CVS vor allem dafür geschaffen worden, damit man (im Gegensatz zu seinem Vorgänger RCS) im Team an einem Projekt arbeiten kann, ohne sich gegenseitig zu blockieren. Und Opensource gab's schon lange vor git, sogar mit CVS. ;-)
Wie schon erwähnt hängt das von der Architektur und Programmiersprache ab. Bei jedem Funktionsaufruf muß der Rechner den aktuellen Kontekt sichern und nach verlassen der Funktion wieder herstellen. Oder meinst Du nicht die Takte die benötigt werden um eine Funktion zu bedienen ?
nik janusch schrieb: > - kurz, möglichst nicht mehr als 3 bis 5 Zeilen Mein Senf dazu: Ich hab mal eine Weile hauptberuflich C-Code mit einem Struktogramm-Editor entwickelt. Bei diesem konnten Blöcke (Schleifen, If, Case usw.) ausgeblendet und durch Kommentare ersetzt werden. Am Ende hatte man einen sehr kompaktes Struktogramm, welches sich Block für Block "aufklappen ließ". Die Funktionen hatten mitunter mehrere 100 Zeilen Code, aber dank des Editors war es trotzdem sehr übersichtlich. Ist Geschmackssache, aber mir hat das richtig gut gefallen, da die SW-Doku direkt im Struktogramm bzw. C-Code verhanden war. Man durfte nur nie auf die verwegene Idee kommen, und so eine Datei in einem normalen Texteditor öffnen...
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.