Forum: PC-Programmierung Unittest - Wie macht man es richtig?


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

'nabend.

Meine Frage nach dem "Wie macht man es richtig" bezieht sich vorallem 
auf Funktionen die z.B. keinen Rückgabewert haben.

Als Beispiel:
1
// C-Code
2
void clear_buff(uint8_t *buff_to_clear, uint8_t buff_len)
3
{
4
    memset(buff_to_clear, 0, buff_len);
5
}
Oder Funktionen die z.B. was in eine Datei schreiben?
Ebenso Klassen-Funktionen, die offtmals keine Parameter brauchen, weil 
sie ja so auf die Daten der Klasse zugreifen können.

Wie testet man sowas jetzt mit Unittests? Muss man das testen?
Baut man da jetzt nur zum testen Rückgabewerte/Exceptions ein, die man 
aber für den Produktiveinsatz gar nicht braucht?

Grüße

von Sebastian V. (sebi_s)


Lesenswert?

Was haben Unit-Tests mit Rückgabewerten zu tun? Wenn wie in deinem 
Beispiel die Funktion irgendwelche Parameter verändern soll kann man das 
natürlich genauso testen. Dafür braucht es keinen Rückgabewert.

von KI Entwurf (Gast)


Lesenswert?

die Test-Theoretiker verabscheuen Funktionen mit Seiteneffekte weil 
sie eben den (Be|Nach)weis der Korrektheit erschweren.
Davon leitet sich die Präferenz für strikt funktionales Programmieren 
ab...

Aber zurück zur Praxis.

Umgebung vorher erfassen, zu testendes Programmschnipsel laufen lassen, 
Umgebung nachher erfassen, Differenz vorher zu nachher 
untersuchen/bewerten.

1.
- Buffer mit "bekanntem Müll" füllen
- clear_buf(...) aufrufen
- Buffer auf korrektes clear untersuchen
- Buffer entsorgen

2.
- neues Arbeitsverzeichnis anlegen (ist dann leer, ggfs. 0 Einträge 
verifizieren)
- datei_anlegen(...) aufrufen
- Arb.verzeichnis untersuchen:
  - nur 1 Datei?
  - Dateinamen korrekt?
  - Dateinhalt korrekt?
  - Metadaten (ownership, permissions, timestamp, etc.) korrekt?
- Arbeitsverzeichnis entsorgen

Im Testjargen werden die Vorbereitenden Massnahmen auch test fixture 
und die Aufräumarbeiten teardown genannt.

von Gerd E. (robberknight)


Lesenswert?

KI Entwurf schrieb:
> - Buffer mit "bekanntem Müll" füllen
> - clear_buf(...) aufrufen
> - Buffer auf korrektes clear untersuchen
> - Buffer entsorgen

Hier würde ich den Buffer deutlich größer machen und den Bereich, den 
ich dem clear_buf vorwerfe, in die Mitte legen. Dann testen, ob wirklich 
genau die richtige Menge an Bytes an der richtigen Position gelöscht 
wurde. Gerne passieren bei soetwas off-by-one Fehler, bei denen z.B. ein 
Byte zu wenig gelöscht wird.

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


Lesenswert?

Kaj G. schrieb:

> Wie testet man sowas jetzt mit Unittests? Muss man das testen?
> Baut man da jetzt nur zum testen Rückgabewerte/Exceptions ein, die man
> aber für den Produktiveinsatz gar nicht braucht?

Unittests sind immer Whitebox Tests. Du nimmst also Dein Wissen über die 
Implementierung mit in die Tests und guckst, ob der gewünschte Effekt 
eintritt (hier das Initialisieren eines Speicherbereichs). Zusätzlich 
kannst Du dann auch noch testen, ob ein evtl. befürchteter Effekt 
ausbleibt (das wäre hier das Schreiben über die Grenzen hinaus).

Frag Dich am Ende einfach, ob Du das so einem Kollegen geben würde und 
Stein und Bein schwören würdest, dass da keine Fehler mehr sind.

von Jonas B. (jibi)


Lesenswert?

>off-by-one Fehler, bei denen z.B. ein
>Byte zu wenig gelöscht wird.

Oder fast noch schlimmer ein byte zu viel (-1 vergessen) :D...

GRuß J

von Eric B. (beric)


Lesenswert?

Geschickter ist es zu der zu testenden Unit erstmal eine Spezifikation 
zu schreiben. Dieses Dokument spezifiziert dann pro Funktion:
- Input- und Outputparameter  mit Wertebereichen
- Rückgabewerte
- Seiteneffekte

Gegen dieser Spezifikation schreibst du dann die Unittests, und nicht 
gegen die Implementierung. Erst dann kannst du feststellen ob die 
Implementierung mit der Spezifikation übereinstimmt.

Alternativ wäre Test-Driven Development, wobei zuerst eine Unittest 
geschrieben wird und erst danach die minimale Implementierung die der 
Unittest erfüllt. (Und das dann wiederholen ad infinitum oder des Geldes 
Ende ;-) )

von MarcelF (Gast)


Lesenswert?

Kann mir eine Literatur zum Thema Unit Test empfehlen? Die Bücher, die 
ich bisher gefunden haben, wurden eher schlecht bis sehr schlecht 
bewertet. Aktuell lese ich Test-driven development for embedded c. hier 
fehlt mir allerdings eine struktur, wie man einen Unit Test aufbaut. 
Nimmt man die Testmodule mit in das reguläre Softwareprojekt auf oder 
macht man sich ein TestProjekt, dass das gesamte Testen abdeckt.

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


Lesenswert?

MarcelF schrieb:
> Kann mir eine Literatur zum Thema Unit Test empfehlen?

Ich würde mir mal die Dokumentation und Beispiele zu gängigen 
Test-Frameworks angucken (Boost, Google etc.). Für ein ganzes Buch, 
geben Unit-Tests wahrscheinlich nicht genug her ("Clean Code" schon 
gelesen?).

> Nimmt man die Testmodule mit in das reguläre Softwareprojekt auf oder
> macht man sich ein TestProjekt, dass das gesamte Testen abdeckt.

Ich weis nicht, wie Du ein "Softwareprojekt" definierst, bei mir sind 
die Tests mehrere Build-Targets (Cmake/make), die ich einfach bauen und 
laufen lassen kann. Zusätzlich habe ich dann noch ein target mit dem 
Namen "all_tests.run", dass alle Tests baut und laufen lässt.

mfg Torsten

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?


von Seho85 (Gast)


Lesenswert?

Hey,

mir hat The Art of Unitesting von Roy Oshrove gut geholfen einen 
Einstieg in Gutes Unitesting zu bekommen.

Ist zwar für C# geschrieben gilt aber genauso für viele andere Sprachen.

Gruß Sebastian

von A. S. (Gast)


Lesenswert?

Kaj G. schrieb:
> void clear_buff(uint8_t *buff_to_clear, uint8_t buff_len)
> {
>     memset(buff_to_clear, 0, buff_len);
> }

Dein Beispiel zeigt gut, dass es bei Unittest vor allem darum geht, sich 
intensiv mit dem Code zu beschäftigen und Redundanz zu erzeugen.

clear_buff ist ja durchaus kurz und sinnig. Ein sinnvoller Test hingegen 
würde mehrer hundert Zeilen füllen ohne annähernd umfassend zu sein. 
Zudem würde er umfangreiche Festlegungen erfordern, die z.B. 
Speichersegmente und Wertebereiche der Datentypen umfassen.

Ein Unit-Test kann hier also praktisch nur zeigen, ob memset korrekt 
aufgerufen wurde und memset muss für sich getestet, bzw. als korrekt 
angenommen werden. Wirklich sinnvoll ist das nicht. Ein implementierter, 
funktionierender Test aber
- zeigt, dass jemand ein Vielfaches an Zeit und Code in diese Funktion 
gesteckt hat
- verhindert durch Redundanz (Code + Test), dass versehentliche 
Änderungen (z.B. beim Refaktorieren) unentdeckt bleiben.

von seho85 (Gast)


Lesenswert?

Kaj G. schrieb:
> Oder Funktionen die z.B. was in eine Datei schreiben?
> Ebenso Klassen-Funktionen, die offtmals keine Parameter brauchen, weil
> sie ja so auf die Daten der Klasse zugreifen können.
>
> Wie testet man sowas jetzt mit Unittests? Muss man das testen?
> Baut man da jetzt nur zum testen Rückgabewerte/Exceptions ein, die man
> aber für den Produktiveinsatz gar nicht braucht?

Normalerweise verwendet man dafür Abstraktionen in irgendeiner Art.

Beispiel: Deine Applikation erzeugt eine XML Datei.

Du willst nun per Unit Test testen ob deine Klasse wirklich eine Datei 
erzeugt:
Das ist unter verschiedenen Gesichtspunkte kein guter Unit Test:
-das erzeugen der Datei dauert verhältnismäßig lange, und damit der auch 
Unit Test. Hat man nun viele solcher Tests dauert das durchlaufen der 
Tests länger und man fängt (unter Umständen) an diese aus Zeitgründen 
nicht mehr laufen zu lassen.
-der Test kann fehlschlagen wenn es Probleme beim erzeugen der Datei 
gibt, die mit deinem eigentlichen Code nichts zu tun haben.

Das überwachen der eigentlichen Erzeugung der XML Datei überschreitet 
den "Kompetenzrahmen" des Unittests.

Bessere wäre der Test formuliert:
Wird die Funktion zum erzeugen der XML-Datei aufgerufen?

Daher ist es besser z.B. ein Interface zu definieren.
1
interface IReadParameter {
2
   +void ReadParameter()
3
}

Die Klasse welche nun die Daten aus einer XML Datei verarbeiten soll, 
verwendet nun eine konkrete Implementierung von IReadParameter.

Zum Testen der Funktion erstellt man dann z.B. ein ITestReadParameter 
der speichert ob ReadParameter() aufgerufen wurde.

Im eigentlichen Test kann man diesen Parameter abfragen.

Ich habe so etwas in ähnlicher Form genutzt um z.B. Library zum 
ansprechen eines MCP3424 (I2C-ADC) zu testen, ohne diesen als Hardware 
angeschlossen haben zu müssen.

Zum eigentlichen lesen und schreiben kommen Funktions Pointer zum 
Einsatz, diese können dann beim Test (ohne Hardware) von einem 
"virtuellen" MCP3424 bereitgestellt werden.

Der virtuelle MCP3424 stellt nun definierte Zustände bereit.

Damit ist auch die für Unit Tests geforderte "Wiederholbarkeit" 
erreicht. Der zu testende "MCP3424" verhält sich immer gleich.


In "The Art of Unit Testing" von Roey Osherove ist gut beschrieben was 
ein Unit Test ist, wie er sich z.B. von Integration Tests unterscheidet.

Auch wie man Software unter dem Gesichtspunkt der automatischen 
Testbarkeit erstellt wird hier beschrieben.

Das Buch ist absolut empfehlenswert.

Gruß,
Sebastian

von QA is for QualityART (Gast)


Lesenswert?

> Baut man da jetzt nur zum testen Rückgabewerte/Exceptions ein, die man
> aber für den Produktiveinsatz gar nicht braucht?

Was bisher unerwähnt blieb:
solche (zu unit-testende) Funktionen gehören in *separat kompilierbare 
Einheiten* aka mindestens in separate .c/.h Dateien, wenn nicht gar in 
eigenständig zu handhabende Libs.
(ist überspitzt scharf formuliert, die Erläuterung/Begründung/Auflösung 
folgt sogleich)

Erst diese Massnahme erlaubt UnitTests "ohne Störung des 
Produktiveinsatzes" zu realisieren: UnitTest sind nämlich ein/mehrere 
vom eigentlichen Produkt-Binary unabhängig/e Binaries.

D.h. man kann zu einem ausführbaren UnitTest auch schlicht 
Demoprogramm sagen, ein Programm welches auf möglichst transparente, 
nachvollziehbare Weise den Gebrauch und das Verhalten der zu testenden 
Codeschnipsel vorführt, Idealerweise mit Zeitstempel fürs Archiv 
protokolliert.

Nun dürften ein paar Sachen klarer rauskommen: (Unit)TestCode ist 
Idealerweise nicht (nicht zwingend) "an der selben Stelle" wie der 
"Testopfer"-Code, zumeist landet er nicht im auszuliefernden 
Produkt-Binary.
(Unit)TestCode organisiert man gerne in Regelbasiert benannten 
Zusatzdateien: bsp. buffer.[ch] + buffer_Tests001.[ch] + 
buffer_Tests002.[ch] wobei die "${NAME}_TestXXX.*" Dateien, "${NAME}.h" 
inkludieren.

Der Buildablauf für das Produkt-Binary wird gemäss den Zeilen "#include 
".../buffer.h" im ProdukteCode NUR die interessante Funktionalität 
mitnehmen.

Der Buildablauf für UnitTests hingegen vereint, automatisiert oder 
händisch, die "*_Test*.*" Dateien also test fixture, test code, 
teardown und macht einen Gesamtablauf daraus.
---

Aus der Sicht von uC-Programmierung (im sub-XXkB-ROM-Bereich) wo oft der 
gesamte Anwendungscode in einer einzigen, wenn auch mit etlichen kLOCs 
lange, Quelldatei gehandhabt wird und kaum je was ausser den mit der 
Target-Toolchain mitgelieferten "includes" inkludiert werden, da sieht 
das oben beschriebene natuerlich aufwändig aus.

Aber SW-Engineering muss halt besonders auch bei Projekte standhalten, 
welche mit deutlich mehr Dateien als wir Finger haben gesegnet sind.

---
Eine interessante Implementationsvariante von (Unit)Tests, bringt Python 
ab Werk integriert mit: DocTest .
Man schreibt dabei im zur Funktion/Modul zugehörigen 
Dokumentationskommentar einfach Aufrufsbeispiele hin, wie wenn man 
interaktiv die Funktion an der Konsole aufrufen würde UND der 
zugehörigen Output.
Wird dann das Programm *DocTest.py* passend auf die eigene 
Quellcodedatei losgelassen, führt *DocTest.py* automagisch die selben 
im Kommentar festgehaltenen Aufrufe durch UND vergleicht den neu 
erzielten mit den im Kommentar festgehaltenen Output.
Übereinstimmung wird "stillschweigend" gebuchhaltet, Abweichungen jedoch 
angemeckert!

Die Trennung vom Produktiven Code ist hier eben dass der Testcode im 
Kommentar da steht.
Das Gratisschmankerl dabei: er landet gleichzeitig AUCH in der 
automagisch generierbaren (HTML)Dokumentation - siehe PyDoc .

Es lohnt sich zwecks Horizonterweiterung Pythons DocTest mal 
anzuschauen und einige Zeit damit rumzuspielen! Man kann da was in 
andere Bereiche mitnehmen...

Es ist danach einfacher andere (Unit)Test-Frameworks zu verstehen.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Danke fuer Eure antworten und erfahrungen. :)

Ich habe gerade diesen Artikel gefunden und finde der passt hier sehr 
gut rein:

Mit Metriken managen: Mist!
https://www.heise.de/developer/artikel/Mit-Metriken-managen-Mist-3568010.html

von seho85 (Gast)


Lesenswert?

Hey,

interessanter Artikel meine Konsequenz daraus wäre aber eher zu sagen:

Ein Projekt mit falschen Metriken zu managen ist Mist.

Das resultiert aber wohl eher aus den falschen Vorstellungen der 
Projektmanager, als aus der Erzeugung und Verwendungen von Code Metriken 
selbst.

Aus meiner Erfahrung bieten Code Metriken eine gute Möglichkeit, eine 
erste Idee über, die Qualität seiner eigenen Änderungen zu erhalten.

Und Metriken sind meiner Meinung nach schon wichtig - Es ist nur wie bei 
allem, man muss es (sie) richtig Einsetzen.

Und sie werden bei Projekten mit mehreren Entwicklern irgendwann auch 
notwendig z.B.
-um sicherzustellen das nicht jemand das System "herhunzt".
-um sicherzustellen das sich bei einem OO Projekt an die "Grundlagen der 
Objektorientierten Entwicklung" gehalten wird.
-u.v.m.

Zum Thema Testabdeckung:
Ich teste in meinen Applikationen alles was Logik enthält und public 
oder protected ist (zumindest versuche ich es :D).

Meine Tests sollen die Funktion der Komponente nach außen hin 
sicherstellen.
Wie diese das intern erreicht ist mir aus dem Gesichtpunkt des Unit 
Tests erstmal egal. Wichtig ist nur das sich nach außen so verhält wie 
erwartet. (Die Unittests überprüfen genau dieses Verhalten).

(Bei dem Thema "wie diese das Erreichen" kommen wieder die Codemetriken 
mit ins Spiel.)

Als Ausnahme gelten z.B. Member Variablen die für die generelle Funktion 
gebraucht werden.

Die %-Zahl an Testabdeckung sagt nichts über die Software Qualität, der 
Software selbst, sowie der Tests aus.


Gruß,
Sebastian

von Nop (Gast)


Lesenswert?

Zu den positiven Aspekten ist ja schon genug gesagt worden hier, von 
daher sage ich dazu jetzt mal nichts und konzentriere mich stattdessen 
eher auf die kritischen Dinge.

Man sollte vor allem nicht zuviel Zeit mit Unittesting verschwenden. Auf 
Mikrocontrollern wird Unittesting an sich schon nervig, weil keine 
Infrastruktur besteht.

Bei größeren Projekten sind die Hauptfehler nicht etwa kleine 
Codeschnipsel, die man mit Unittests überhaupt sinnvoll abtesten kann, 
sondern Integrationsfehler. Jeder Bestandteil tut, was er soll, aber das 
Interfacing ist verkehrt. Unittests eliminieren die Abhängigkeiten und 
können das deswegen nicht entdecken.

In der Praxis hat man aber nur ein begrenztes Testbudget zur Verfügung, 
so daß jeder Unittest de facto Zeit vom Integrationstest und Systemtest 
stiehlt. Das muß man abwägen.

Zweitens sind im Grunde nur relativ triviale Codeschnipsel wirklich mit 
Unittests zu erschlagen. Im genannten Beispiel mit dem Memset wäre ein 
Unittest für die Memset-Funktion selber sinnvoll - testen, daß exakt 
soviele Bytes genullt werden wie angegeben (weder mehr noch weniger), 
mit jeder Kombination von Pointer-Alignment und Bufferlängen-Alignment. 
Das macht man aber nach deren Implementation ja sowieso, zumindest 
manuell.

Kandidaten sind immer solche, die ihrer Natur nach schon isolierte 
Funktionalität bieten, also Dinge, die man in Libraries packen könnte. 
Beispielsweise auch Parser, welche Daten entgegennehmen und in andere 
Formate übersetzen oder Information extrahieren sollen. Oder Sortier- 
und Suchalgorithmen auf gegebene Eingabedaten.

Der Wert von Unittests besteht hier darin, daß man sich allerhand 
"normale" und auch "pathologische" Eingabedaten überlegen kann. Geht der 
Sortieralgorithmus auch, wenn doppelte Elemente da sind? Oder mit 
Listenlängen von 1, oder auch 0? Was passiert, wenn man sein Memset mit 
Bufferlänge null aufruft? Was macht der Speicherallokator, wenn man 
einen Buffer mit 0 Byte Länge anfordert? Ist das überhaupt spezifiziert 
worden?

Aber spätestens, wenn die Coder anfangen, Funktionen aufzuspalten, damit 
sie die Bestandteile noch unittesten können, nähert man sich einer 
letztlich unwartbaren Systemarchitektur, weil zusammengehörige 
Codepassagen auseinandergerissen werden.

Das Pattern, was hier alarmierend sein sollte, sind größere Funktionen, 
die als Klebstoff dienen und Sachen wie "do_stuff_1(), do_stuff_2() usw 
enthalten - jedenfalls dann, wenn do_stuff_x() nicht deswegen eigene 
Funktionen sind, weil sie mehrfach an verschiedenen Stellen notwendig 
sind.

Oder weil sie logisch gesehen eigene Einheiten bilden, etwas wie 
"motor_control()" und "heater_control()", die nicht in eine gemeinsame 
Funktion gehören. Sondern deswegen, weil man die Funktionen 
auseinanderreißt, um Unittests machen zu können.

Drittens haben Unittests ein grundsäzliches Problem mit der Wartbarkeit. 
Jedesmal, wenn für das System letztlich irrelevante 
Implementationsdetails geändert werden, muß man die Unittests anpassen, 
wenn man die nicht nur auf der Ebene von Memset() angelegt hat. Wenn der 
Punkt erreicht ist, an dem der Testcode aufwendiger wird als der 
Nutzcode, ist es Zeit, "STOP!" zu sagen.

Eine der Folgen ist in der Praxis ansonsten, daß dann notwendiges 
Refactoring unterbleibt, weil man nicht das Budget hat, außer dem Code 
auch noch die zehnfache Menge an Testcode anzupassen. Das führt dann zu 
Architekturfäulnis.

Automatisierte Systemtests haben dieses Problem nicht, denn solange die 
Spec sich nicht ändert, darf ein Refactoring nicht das Verhalten an den 
relevanten Schnittstellen verändern.

Außerdem ist die Annahme, daß der Testcode weniger Fehler enthalte als 
der Produktivcode, reines Wunschdenken. Wenn man zehnmal soviel Testcode 
wie eigentlichen Code hat, dann wird jeder entdeckte Fehler mit 90% 
Wahrscheinlichkeit reine Zeitverschwendung und Rumgesuche im Testcode 
sein.

Ganz besonders sinnlos ist es, wenn Testcode und Produktivcode von 
derselben Person geschrieben werden, weil man dann die Denkfehler nur 
wiederholt. Wer beim Produktivcode nicht an irgendeinen fiesen Fall 
gedacht hat, wird auch beim Testcode nicht daran denken.

Zudem sind Unittests sicherlich schön, wenn das Verhalten eines 
Subsystems nur von den Eingaben abhängt. Bei Microcontrollern hat man 
aber sehr häufig auch zeitabhängiges Verhalten, beispielsweise bei 
Regelschleifen, Signalfilterung, Timeouts und ähnliche Sachen. Bei sowas 
sollte man das Budget lieber nicht mit zuviel Unittests belasten, damit 
man auch noch was für hardware-in-the-loop-Tests übrig hat.

Dahingehend ist übrigens auch funktionale Programmierung letztlich eine 
nette Theorie, praktisch aber weitgehend wertlos. Es sei denn, man würde 
wirklich alles als Input in die Funktion werfen bis hin zur Systemzeit 
und allen relevanten Werten der Vergangenheit mit Zeitstempeln, und dann 
wäre das zwar formal gesehen immer noch funktionale Programmierung, aber 
so komplex, daß es auch nicht mehr mit Unittests abzudecken wäre. Das 
wäre wohl das äquivalente Pattern zum Godobject bei OOP.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Ich finde es schoen, wie sich dieser Thread entwickelt. :)

Prinzipiell erkenne ich die Vorteile von Unittests an, aber natuerlich 
bleibt immer etwas kritisches zurueck, da kann ich Nop nur zustimmen.

Die besten Unittests bringen nichts, wenn es an der Integration oder 
Akzeptanz scheitert.

Nop schrieb:
> Aber spätestens, wenn die Coder anfangen, Funktionen aufzuspalten, damit
> sie die Bestandteile noch unittesten können, nähert man sich einer
> letztlich unwartbaren Systemarchitektur, weil zusammengehörige
> Codepassagen auseinandergerissen werden.
Ich glaube, dass dieses Problem deutlich groesser ist, wenn man 
nachtraeglich Unittests fuer bestehende Software schreiben soll.
Wenn man sowas von anfang an einplant (z.B. durch TDD) glaube ich, dass 
das Problem nicht mehr sooo gross ist.

Bei meinem alten Arbeitgeber bestehen die Hauptkomponenten aus teilweise 
20 Jahre altem Code, der aber wunderbar funktioniert und gewartet wird.
Dann kam ein neuer Geschaeftsfuehrer und man wollte unbedingt Scrum, TDD 
usw. einfuehren. Gut, gabs halt ne In-House-Schulung zu TDD (fand ich 
gar nicht schlecht, weil es fuer mich neu war).
Tja, ende vom Lied war, dass man eben diesen alten Code auf biegen und 
brechen Testbar machen wollte, man Dinge auseinander gerissen hat und 
sich nachher erstmal 3 Tage nichts mehr compilieren lies...

Natuerlich zeigt sowas, dass es da Code gibt, der mal Aufgeraeumt werden 
muesste. Es hinterlaesst trotzdem einen schlechten Nachgeschmack, denn 
der Code hat ja mal funktioniert, und jetzt muss man viel Arbeit 
reinstecken, nur um der veraenderung willen.

Deswegen denke ich, dass es im nachhinein immer deutlich schwieriger 
ist, als wenn man sowas von anfang an mit einbezieht.

Wie Unittests im Embeddedbereich aussehen kann ich mir gar nicht 
vorstellen. Fuer Architekturen wie z.B. AVR koennte ich mir einen 
Emulator vorstellen, wo man auch externe erignisse emulieren kann (z.B. 
Pin-Change Interrupt). Aber das ist ja noch immer nicht die echte 
Umgebung auf der der Code spaeter mal laeuft.

Generell laesst sich, glaube ich, sagen, dass Testing (Unittest, 
Integrationtest, usw.) sinnvoll ist. Es ist aber kein Allheilmittel und 
ersetzt auch nicht brain_2_0.exe

Um die Codequalitaet zu steigern ist es vielleicht auch sinnvoll die 
Mitarbeiter regelmaessig weiterzubilden.

Bei einem Projekt mit einem Kollegen (der da schon 20 Jahre in der Firma 
war), hat der Kollege Arrays gerne von Hand kopiert oder genullt. Auf 
meine Frage, warum er denn nicht memcpy/memset nimmt, bekam ich die 
Antwort, dass er ja nicht weiss was da genau passiert und sich nicht 
sicher ist wie Dinger genau funktionieren.
Naja, was der Bauer nicht kennt...

Ich glaube das der erste Schritt zu besserer Software gut geschulte 
Mitarbeiter sind. Nur weil man etwas seit 20 Jahren macht, heisst das ja 
nicht, dass man es auch richtig macht. Und erst dann kommen m.M.n. 
Unittests.

von Nop (Gast)


Lesenswert?

Kaj G. schrieb:

> Prinzipiell erkenne ich die Vorteile von Unittests an, aber natuerlich
> bleibt immer etwas kritisches zurueck, da kann ich Nop nur zustimmen.

Ich denke mal, man sollte bei einem Werkzeug nicht nur erkennen, wo es 
nützlich ist, sondern auch, wo seine Grenzen sind. Letztlich macht ja 
jeder irgendwo Unittests, weil man zwar top-down designed, aber 
bottom-up testet. Nicht unbedingt automatisiert, aber zumindest manuell.

> Wenn man sowas von anfang an einplant (z.B. durch TDD) glaube ich, dass
> das Problem nicht mehr sooo gross ist.

Mit TDD fängt man sich noch ganz andere Probleme ein. Das führt nämlich 
dazu, daß die Coder den Anreiz kriegen, die Testcases zu erfüllen, 
anstatt sich robuste Systeme zu überlegen. Ein ähnliches Problem wie mit 
SCRUM, was auch zu verkorksten Designs führt, weil der Anreiz ist, 
schnell eben was zurechtzuhacken.

> Tja, ende vom Lied war, dass man eben diesen alten Code auf biegen und
> brechen Testbar machen wollte, man Dinge auseinander gerissen hat und
> sich nachher erstmal 3 Tage nichts mehr compilieren lies...

Weil niemand die kritische Frage gestellt hat, welchen Gegenwert man 
bekommt, wenn man die Arbeit bezahlt, das alles zu tun.

> Es hinterlaesst trotzdem einen schlechten Nachgeschmack, denn
> der Code hat ja mal funktioniert, und jetzt muss man viel Arbeit
> reinstecken, nur um der veraenderung willen.

Eben, und die Grundfrage auf Ebene des Entiwcklungs-Chefs muß sein, 
welchen Gegenwert die Kunden bekommen, wenn man da Geld reinsteckt. Denn 
sonst werden die Kunden das letztlich nicht bezahlen wollen.

> Deswegen denke ich, dass es im nachhinein immer deutlich schwieriger
> ist, als wenn man sowas von anfang an mit einbezieht.

Selbst von Anfang an muß man schon aufpassen, daß die Coder nicht 
anfangen, anal bezüglich Unittests zu werden - und dann das Design 
vorrangig auf Unittests auszulegen. Da würde dann ja der Schwanz mit dem 
Hund wedeln.

> Wie Unittests im Embeddedbereich aussehen kann ich mir gar nicht
> vorstellen. Fuer Architekturen wie z.B. AVR koennte ich mir einen
> Emulator vorstellen, wo man auch externe erignisse emulieren kann (z.B.
> Pin-Change Interrupt). Aber das ist ja noch immer nicht die echte
> Umgebung auf der der Code spaeter mal laeuft.

Und vor allem - welcher Aufwand soll denn das am Ende werden, und was 
kostet das? Bezahlt der Kunde das dann auch? Klar, wenn man Produkte 
hat, die sehr strenge Normen erfüllen müssen, um überhaupt am Markt 
zugelassen zu werden, hat man da mehr Spielraum. Die Konkurrenz muß ja 
ebenfalls einigen Aufwand treiben und bezahlen. Aber alles kann man sich 
auch da nicht erlauben.

Was man machen kann, wenn man das von vornherein so aufplant (ich habe 
schonmal so ein Projekt gemacht), das ist ein Testbett. In dem Fall war 
der Hintergrund, daß die Hardware noch nicht da war und ich deswegen die 
komplexe Anwendungslogik erstmal auf dem PC implementiert habe.

Das hat dann ein Layerdesign erzwungen, wo ich beim Übergang auf 
embedded nur noch die unterste Schicht auswechseln mußte gegen neu 
implementierte embedded-Routinen. Die komplette API war aber identisch 
aus Sicht der Anwendungslogik.

Dadurch war allerdings auch das Profiling und Debugging der Anwendung 
ein Traum, weil ich die kompletten Linuxtools drauf loslassen konnte. 
Auch bei späteren Änderungen konnte ich das alles erstmal auf dem PC 
austesten, mit haufenweise Debug-Ausgaben.

> Generell laesst sich, glaube ich, sagen, dass Testing (Unittest,
> Integrationtest, usw.) sinnvoll ist.

Auf jeden Fall. Sieht man sich mal die bekannte Exponentialfunktion an 
(Kosten der Fehlerbehebung als Funktion der Projektphase), dann sollte 
man Fehler möglichst früh entdecken.

Wobei das ja nicht erst beim Test losgehen sollte, sondern schon das 
Design sollte getestet werden, also VOR der Implementation. Das sind 
z.T. dann tagelange Diskussionen im Team, was wie wo wann passieren kann 
und was man tun sollte, aber das sind welche der wenigen langen 
Meetings, wo jede Minute bares Gold ist.

> Es ist aber kein Allheilmittel und ersetzt auch nicht brain_2_0.exe

Und genau DAS ist aber oftmals die Zielsetzung bei exzessiven Unittests.

> Bei einem Projekt mit einem Kollegen (der da schon 20 Jahre in der Firma
> war), hat der Kollege Arrays gerne von Hand kopiert oder genullt.

Kenne ich bei manchen Produkten auch, und das hat den Hintergrund, daß 
vorgefertigte Libraries einen riesigen Aufwand in der Zertifizierung 
darstellen würden. Insbesondere, wenn man deren Source nicht hat und 
deswegen z.B. MISRA-Checks nicht nachgewiesen werden können.

> Ich glaube das der erste Schritt zu besserer Software gut geschulte
> Mitarbeiter sind.

Word. Was man gerade embedded z.T. an Code sieht, das ist von vornherein 
erschreckend und sollte bei einem Code Review schlichtweg durchfallen. 
Dazu braucht man aber Leute, die merken, daß das so nicht OK ist, und 
die bekommt man nicht für Dumpinglöhne.

von Sheeva P. (sheevaplug)


Lesenswert?

Kaj G. schrieb:
> Meine Frage nach dem "Wie macht man es richtig" bezieht sich vorallem
> auf Funktionen die z.B. keinen Rückgabewert haben.
>
> Als Beispiel:
>
1
> // C-Code
2
> void clear_buff(uint8_t *buff_to_clear, uint8_t buff_len)
3
> {
4
>     memset(buff_to_clear, 0, buff_len);
5
> }
6
>

Da macht man sich eine Attrappe (mockup) mit definierten Eingabewerten, 
läßt die betreffende Funktion darüber laufen, und testet dann, ob die 
Attrappe so verändert wurde wie erwartet. Für Deine zitierte Funktion 
könnte ein Unittest -- je nach Framework -- also etwa so aussehen:
1
#include <string.h>
2
#include <inttypes.h>
3
#define MAXLEN 10
4
5
#include "funktionen.c"
6
7
int main(void) {
8
    uint8_t buffer[] = "0123456789";
9
    clear_buff(buffer, MAXLEN);
10
    for(uint8_t i = 0; i < MAXLEN; ++i) {
11
  if(buffer[i] != 0) {
12
      return -1;
13
  }
14
    }
15
    return 0;
16
}

> Oder Funktionen die z.B. was in eine Datei schreiben?

Da schreibt man in die Datei und vergleicht die Datei hinterher mit 
einem vorher als erwünscht definierten Inhalt.

> Ebenso Klassen-Funktionen, die offtmals keine Parameter brauchen, weil
> sie ja so auf die Daten der Klasse zugreifen können.

Dazu erstellt man eine Attrappe der Klasse mit Testdaten, läßt die 
Methode darauf arbeiten, und testet hinterher, ob die Testdaten von der 
Methode so verändert worden sind, wie zuvor erwartet.

> Wie testet man sowas jetzt mit Unittests?

Wie gesagt: man stellt dem zu testenden Code eine Umgebung zur 
Verfügung, wie er sie zur Laufzeit vorfindet, läßt dann den zu testenden 
Code laufen und vergleicht, ob der Test die gewünschten Änderungen 
vorgenommen hat.

> Muss man das testen?

Wenn Du es richtig machen willst: ja.

> Baut man da jetzt nur zum testen Rückgabewerte/Exceptions ein, die man
> aber für den Produktiveinsatz gar nicht braucht?

Nein.

von Sheeva P. (sheevaplug)


Lesenswert?

seho85 schrieb:
> Ein Projekt mit falschen Metriken zu managen ist Mist.

Metriken stellen immer nur eine Informationsreduktion dar, daher ist ein 
Management by Metriken mittelfristig immer Mist. Vor langer Zeit sind 
mir mal Manager begegnet, die tatsächlich geglaubt haben, durch die 
Metrik "source lines of code" könnten sie die Produktivität einzelner 
Entwickler erkennen. Und dann wurden auch nur die Nettozahlen verwendet: 
wenn ein Entwickler zwanzig Zeilen üblen durch fünf Zeilen besseren Code 
ersetzen konnte, wurde das als -15 SLOC gezählt. Es hat mich einige 
Wochen Arbeit gekostet, diesen Irrsinn zu beenden.

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


Lesenswert?

Nop schrieb:
> Mit TDD fängt man sich noch ganz andere Probleme ein. Das führt nämlich
> dazu, daß die Coder den Anreiz kriegen, die Testcases zu erfüllen,
> anstatt sich robuste Systeme zu überlegen.

Das hast Du Dir gerade ausgedacht, oder gibt es Untersuchungen dazu? Ich 
entwickle so weit es geht durchgängig mit TDD und mein Anreiz sind 
robuste, fehlerfreie Systeme, die meinen Kunden erfreuen.

Tests sind die "Dinger", die ich brauche um mir sicher zu sein, dass die 
Software so funktioniert, wie sie funktionieren soll.

mfg Torsten

von Nop (Gast)


Lesenswert?

Torsten R. schrieb:

> Das hast Du Dir gerade ausgedacht, oder gibt es Untersuchungen dazu?

Es gibt schon kritische Stimmen, ich hab mir jetzt aber nicht alles als 
Bookmark gesetzt. Halt mal spontan Google angeworfen:

http://beust.com/weblog/2014/05/11/the-pitfalls-of-test-driven-development/

> Tests sind die "Dinger", die ich brauche um mir sicher zu sein, dass die
> Software so funktioniert, wie sie funktionieren soll.

Natürlich braucht man Tests, das bestreite ich auch nicht. Aber TDD ist 
etwas mehr als nur zu sagen, daß man die Software gründlich testen soll. 
TDD spannt den Karren vor das Pferd.

Ich sehe nur einen einzigen guten Aspekt bei TDD, und zwar daß man sich 
als Coder nämlich frühzeitig Gedanken macht, was schieflaufen kann und 
wie die Software darauf reagieren soll. Das ist besonders embedded 
wichtig, weil es da meist keinen Nutzer gibt, der dem System interaktiv 
Händchen hält.

Nur, diese Überlegungen gehören in die Designphase, und die sollte auch 
eine Art challenging enthalten, wo also Leute aus einem anderen Team 
(ideal: erfahrene Tester) versuchen sollen, das Design kaputtzumachen.

Das wirkt deswegen besser als TDD, weil TDD genau wie agile letztlich 
zum Pfusch in der Designphase führt, indem genau diese Phase gar nicht 
mehr da ist, sondern schnell irgendwas zurechtgehackt wird, was gerade 
mal die vorhandenen Testfälle irgendwie erfüllt bzw. was eine user card 
abhakt.

Das kann man allenfalls dann machen, wenn das System bereits existiert 
und der Job nur noch in kleinen Anpassungen besteht.

von Mark B. (markbrandis)


Lesenswert?

Kaj G. schrieb:
> Um die Codequalitaet zu steigern ist es vielleicht auch sinnvoll die
> Mitarbeiter regelmaessig weiterzubilden.

Nicht nur vielleicht, sondern ganz bestimmt!

Leider kapieren viele Manager das nicht. Oder sie sehen nur die Kosten, 
die eine Schulung verursacht - nicht aber die Kosten, die durch das 
Fehlen von Schulungen entstehen. :-(

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Mark B. schrieb:
> Oder sie sehen nur die Kosten,
> die eine Schulung verursacht - nicht aber die Kosten, die durch das
> Fehlen von Schulungen entstehen. :-(
Da hast du leider recht :-C

von A. S. (Gast)


Lesenswert?

Nop schrieb:
> Das wirkt deswegen besser als TDD, weil TDD genau wie agile letztlich
> zum Pfusch in der Designphase führt,

Der (beliebige Sprache einsetzen)-Code ist das Design.

Agile Methoden und TDD sind nur ein Workaround, dieses Missverständnis 
aufzulösen und mit dem Design (dem Code) früher anzufangen anstatt eine 
Phase mit Pseudo-Design (UML, Word, Statecharts) vorzulagern.

Bitte nicht falsch vestehen: UML, Word oder Charts sind gut, wenn sie 
helfen, das Design zu verstehen, zu untersuchen, etc. Aber sie sind 
(solange kein Code automatisch aus ihnen generiert wird) kein Design, 
sondern ein Pendant zu Vorüberlegungen und Visualisierungen.

http://www.bleading-edge.com/Publications/C++Journal/Cpjour2.htm

Source-Code ist das Pendant zu einem Schaltplan/Layout in HW, oder einer 
technischen Zeichnung im Maschinenbau.

TDD formalisiert quasi nur den (sonst als unnütz oft entfallenden) 
Peer-Review-Prozess. Und bei Ein-Mann-Designs stellt es sicher, dass 
wenigsten der Programmierer selber sich nochmals Gedanken macht. Die 
große (fatale) Gefahr, dass er "TDD-optimiert" programmiert und 
zusammenhängende Funktionen auseinander reisst wurde schon hinreichend 
erwähnt.

von Nop (Gast)


Lesenswert?

Achim S. schrieb:
> Der (beliebige Sprache einsetzen)-Code ist das Design.

Nein, das ist die Implementation des Designs. So wie ein PCB auch nicht 
der Schatplan ist. Einfach mal draufloshacken ist keine gute Idee, wenn 
die Sache komplexer wird.

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


Lesenswert?

Nop schrieb:

> Nein, das ist die Implementation des Designs. So wie ein PCB auch nicht
> der Schatplan ist. Einfach mal draufloshacken ist keine gute Idee, wenn
> die Sache komplexer wird.

Mit Dir ist es schwer zu diskutieren, wenn Du Dich immer auf Vergleiche 
zurück ziehst. Überlasse das doch den Politikern und den Theologen!

Das Problem mit Vergleichen ist, dass man erst einmal nachweisen müsste, 
dass die in einem Bereich gemachten Beobachtungen so häufig auch in 
einem anderen Bereich gemacht werden, dass es vernünftig erscheint, eine 
nur in einem Bereich gemachte Beobachtung auch in dem anderen Bereich zu 
vermuten. Ansonsten kann ich Dir mit Vergleichen alles belegen oder 
wiederlegen.

Ich würde nicht vermuten, dass Software-Entwicklung genügend 
Eigenschaften mit Hardware-Entwicklung teilt, als das es Sinnvoll wäre 
hier wirklich sinnvoll Parallelen zu ziehen. Auch das mittelalterliche 
Transportwesen oder der Hausbau haben nur wenig Parallelen zu 
SW-Engeniering.

So, genügend Meta ;-)

Was für Werkzeuge haben wir den wirklich, um SW Design zu betreiben? 
UML? Flussdiagramme? etc. Alles nicht so prall um aus den Anforderungen 
ein Design zu erstellen. Und wo gucke ich den später, wenn ich wirklich 
was über das SW-System wissen möchte?

Ich nehme in der Design-Phase meinen Editor und suche nach eine guten 
Aufteilung der Gesamtfunktionalität in Klassen, Funktionen etc. 
Vielleicht entstehen dabei sogar schon die ersten Test-Beschreibungen. 
Schnittstellenbeschreibungen, UML-Snipets am Whiteboard etc.

Am Ende stehen eine Menge C++ header mit Doxygen Dokumentation, die das 
Design deutlich besser beschreibt, als das mit UML möglich wäre.

Und dann geht es mit den Tests weiter. TDD bezieht sich übrigens nicht 
zwingend (oder zumindest würde ich es so nicht verstehen) nur auf 
Unit-Tests! Bei größeren Systemen kann man natürlich auch bei 
Component-/Integration-Tests anfangen.

Letztendlich sind automatische Tests ein Abbild der Anforderungen und 
solange sich nichts an den Anforderungen ändert, bleiben diese Tests 
korrekt und wertvoll.

von Nop (Gast)


Lesenswert?

Torsten R. schrieb:

> Mit Dir ist es schwer zu diskutieren, wenn Du Dich immer auf Vergleiche
> zurück ziehst. Überlasse das doch den Politikern und den Theologen!

Lies zurück. Achim hat den Vergleich mit der HW gebracht.

> Was für Werkzeuge haben wir den wirklich, um SW Design zu betreiben?

Papier und Bleistift, beispielsweise. Welche Module gibt es, was tun die 
(grob gesagt), und wie interagieren sie. In den Modulen können es dann 
Flußdiagramme, endliche Automaten und dergleichen sein.

Einen endlichen Automaten mit komplexerer Funktion kann man übrigens 
auch nicht vernünftig reviewen, wenn man den irgendwie runterhackt, 
sondern dazu braucht es erstmal einen Ablaufplan, DEN kann man nämlich 
im peer review diskutieren.

Das ist deswegen sinnvoll, weil die Kosten für Fehlerbehebung in 
Abhängigkeit der Projektphase exponentiell ansteigen. Designfehler erst 
beim Test (oder gar beim Kunden) rauszufinden, ist enorme 
Geldverschwendung.

Außerdem ist das Runterhacken eines endlichen Automaten, auch wenn 
komplexer, eine Arbeit, die im Prinzip auch ein dressierter Affe machen 
kann, WENN man erstmal das Zustandsdiagramm hat. Das zu erstellen und im 
Vorfeld schon mit verschiedensten Umgebungsbedingungen und Fehlerfällen 
abzuchecken ist die eigentliche Arbeit.

Es gibt allerdings eine Ausnahme, wo ich kein Design machen würde, und 
das ist rapid prototyping, besonders bei allem was user interface ist. 
Das kann man nicht objektivieren, weil "softe" Faktoren ins Spiel kommen 
wie Usability, und die hängt wiederum von den bestehenden 
Usergewohnheiten ab. Aber den entstehenden Code wirft man danach ja 
ohnehin weg, weil der nur dazu dient, rauszufinden, was den Kunde denn 
genehm findet.

Ist immer noch billiger als erst alles fertigzumachen, rauszufinden, daß 
der Kunde das nicht will, dann rauszufinden, daß die Systemstruktur die 
tatsächlichen Kundenwünsche nicht so ermöglicht und mit 
von-hinten-durch-die-Brust-ins-Auge-Hacks irgendwie rechtzeitig vor der 
Deadline alles noch irgendwie chaotisch hinzupfuschen.

> Und wo gucke ich den später, wenn ich wirklich
> was über das SW-System wissen möchte?

Im Code bestimmt nicht, wenn es mehr als Trivialschnipsel sind. Erzähl 
mir nicht, daß man mal eben aus 50.000 Codezeilen die Architektur der SW 
entnimmt. Da stehen die Implementationsdetails, aber das Konzept hat man 
hoffentlich vorher erstellt.

Im Prinzip sitzt die Firma mit sowas auf einem riesigen Berg an 
technischen Schulden, und sobald personelle Fluktuation geschieht, 
verwandeln sich diese technischen Schulden in konkrete Mehrkosten.

Das geht soweit, daß bestehende Software nicht mehr wartbar ist, weil es 
mehr Zeit kostet, durch den Wust erstmal durchzusteigen, als ihn neu zu 
programmieren. Alles schon gesehen.

> Ich nehme in der Design-Phase meinen Editor und suche nach eine guten
> Aufteilung der Gesamtfunktionalität in Klassen, Funktionen etc.

Das ist kein Design, das ist draufloshacken. Kann man machen, wenn man 
entweder ein recht simples Tool schreibt, oder wenn man etwas macht, was 
es grundlegend schon gibt.

> Am Ende stehen eine Menge C++ header mit Doxygen Dokumentation

Der Horror für die Wartungsprogrammierer, die 5 Jahre nachdem Du nicht 
mehr in der Firma bist, da ein neues Modul machen sollen. 900 Seiten 
automatisch erstellte Doxygen-Dokumentation, die im größeren 
Zusammenhang genauso nichtssagend ist wie der Code selber.

Und besonders bei C++, wo man nichtmal einfach den Code lesen kann wie 
bei prozeduraler Programmierung, weil es später sehr vom Kontext 
abhängt, was wie wo ausgeführt wird. Außer man beschränkt sich auf C+, 
dann geht's einigermaßen.

> Und dann geht es mit den Tests weiter.

Das ist dann auch nicht mehr TDD, weil "test driven" soviel wie "tests 
first" heißt und nicht nach dem Codieren.

> Letztendlich sind automatische Tests ein Abbild der Anforderungen und
> solange sich nichts an den Anforderungen ändert, bleiben diese Tests
> korrekt und wertvoll.

Das stimmt nur für automatisierte Systemtests, nicht aber z.B. für 
Unittests. Weil das Systemverhalten sich nach Refactoring nicht ändern 
soll, das Modulverhalten kann es aber sehr wohl - u.a. weil komplett 
neue Module entstehen und alte verschwinden können.

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


Lesenswert?

Nop schrieb:

> Einen endlichen Automaten mit komplexerer Funktion kann man übrigens
> auch nicht vernünftig reviewen, wenn man den irgendwie runterhackt,
> sondern dazu braucht es erstmal einen Ablaufplan, DEN kann man nämlich
> im peer review diskutieren.

Nur, weil Du nicht in der Lage bist, einen FSM so zu kodieren, dass er 
lesbar ist, bedeutet das ja nicht, dass andere nicht dazu in der Lage 
sind. Was glaubst Du den, was als erstes über Board geht, wenn es mal 
hektisch im Projekt wird?

> Das ist deswegen sinnvoll, weil die Kosten für Fehlerbehebung in
> Abhängigkeit der Projektphase exponentiell ansteigen. Designfehler erst
> beim Test (oder gar beim Kunden) rauszufinden, ist enorme
> Geldverschwendung.

Ja, das ist eine Binsenweisheit, die hier niemand in Frage stellt. 
Letztendlich ist es total egal, welches SW-Design gewählt wurde, solange 
die Software macht, was sie machen soll. Und genau das stellt man mit 
Tests sicher. Design-Fehler, die dazu führen, dass trotzdem am Ende 
funktionierende SW herauskommt, gibt es nicht!

> Außerdem ist das Runterhacken eines endlichen Automaten, auch wenn
> komplexer, eine Arbeit, die im Prinzip auch ein dressierter Affe machen
> kann, WENN man erstmal das Zustandsdiagramm hat. Das zu erstellen und im
> Vorfeld schon mit verschiedensten Umgebungsbedingungen und Fehlerfällen
> abzuchecken ist die eigentliche Arbeit.

Wenn die Abbildung von Diagram zum Code so trivial ist, dann könnte es 
doch auch sein, dass es Menschen gibt, die so vertraut mit dem Lesen von 
Code sind (z.B. weil es ihr Beruf ist), dass Ihnen die andere Richtung 
auch nicht schwer fällt.

> Im Code bestimmt nicht, wenn es mehr als Trivialschnipsel sind. Erzähl
> mir nicht, daß man mal eben aus 50.000 Codezeilen die Architektur der SW
> entnimmt. Da stehen die Implementationsdetails, aber das Konzept hat man
> hoffentlich vorher erstellt.

Die Architektur eines Systems mit 50 KLOC ist in der Regel trivial. 
Das Design verschiedener Komponenten kann man bei guten Code aus dem 
Code und deren Dokumentation heraus lesen. Insbesondere wenn die 
"üblichen" Namen verwendet werden (Pattern).

> Das ist kein Design, das ist draufloshacken.

Kannst Du das auch irgend wie Begründen? Wenn ich Deine Pferd<->Wagen 
Analogie bis jetzt richtig verstanden habe, dann war Deine Kritik an 
TDD, dass kein Design statt findet. Dafür fehlen mir noch Argumente.

Nur, weil ich keinen Bleistift mehr im Büro habe, einen Editor verwende 
und eine von UML abweichende Notation verwende, bedeutet das doch nicht, 
dass ich (und andere) keine Designs machen. TDD setzt auch nicht 
zwangsläufig bei unit tests an.

>> Am Ende stehen eine Menge C++ header mit Doxygen Dokumentation
>
> Der Horror für die Wartungsprogrammierer, die 5 Jahre nachdem Du nicht
> mehr in der Firma bist, da ein neues Modul machen sollen. 900 Seiten
> automatisch erstellte Doxygen-Dokumentation, die im größeren
> Zusammenhang genauso nichtssagend ist wie der Code selber.

Wenn ich schreibe "Doxygen Dokumentation", dann meine ich sicher nicht 
automatisch erstellte Dokumentation. Das zu unterstellen ist albern!

Was glaubst Du, wird eine Wartungsphase besser überstehen? a) Irgend wo 
abgeheftet Bleistift Dokumentation, vor deren Existenz man erst einmal 
erfahren muss, oder b) Dokumentation, die mit im Code eingebettet ist?

> Und besonders bei C++, wo man nichtmal einfach den Code lesen kann wie
> bei prozeduraler Programmierung, weil es später sehr vom Kontext
> abhängt, was wie wo ausgeführt wird. Außer man beschränkt sich auf C+,
> dann geht's einigermaßen.

Ja, wenn man C++ nicht kann, dann ist C++ schwer zu lesen. Das ist mit 
jeder Sprache so. Ist mit UML übrigens auch nicht anders (die Spec hat 
800 Seiten). Und wenn Du eine eigene Notation verwendest, oder Dich mit 
anderen Kollegen auf eine eigene Notation geeinigt hast, dann muss auch 
diese Notation von Kollegen gelernt werden.

> Das ist dann auch nicht mehr TDD, weil "test driven" soviel wie "tests
> first" heißt und nicht nach dem Codieren.

Ich würde "eine Menge C++ header mit Doxygen Dokumentation" jetzt nicht 
Codieren nennen.

von A. S. (Gast)


Lesenswert?

Nop schrieb:
> Lies zurück. Achim hat den Vergleich mit der HW gebracht.

Und Du hast den Artikel nicht gelesen und meine Worte nicht 
Verstanden.

Es geht darum, dass der Quelltext das Design ist und das der Compiler 
die Arbeit übernimmt, die sonst ein 
Elektriker/Bestücker/Bastler/Dreher/Modellbauer übernimmt.

Natürlich ist Layout nicht Schaltplan. Beide zusammen sind das Design. 
Wenn Du das Layout als reine Handarbeit ansiehst (manchmal zurecht), 
dann entspricht der Quelltext dem Schaltplan.

Das man Schaltpläne (oder Software) nicht draufloszeichnen sollte ist 
klar. Es fänden aber viele Leute absurd, Schaltpläne generell in Word 
zu designen und zu prüfen, bevor sie dann in Protel oder Eagel 
gezeichnet werden dürfen. Genau das passiert bei Software!

Deine Metapher vom Draufloshacken ist halt sinnlos. Wenn ein Buchautor 
einfach *so* drauflosschreibt, ein Maler einfach so Farbe verteilt 
oder ein Bildhauer einfach so draufloshämmert, dann wird das auch 
nichts. Trotzdem ist das runtergeschriebene Buch das Werk des Autors, 
das gemalte Bild das des Malers und die Statue das des Bildhauers. Wenn 
sie vorher mit Papier und Bleistift Brain2.0 füttern, OK. Oder ein paar 
Proben anfertigen. Aber niemand würde eine Skizze zu Mona Lisa als 
Design ansehen und das Kunstwerk nur als dessen Umsetzung.

von A. S. (Gast)


Lesenswert?

Nop schrieb:
> Da stehen die Implementationsdetails, aber das Konzept hat man
> hoffentlich vorher erstellt.

Ich glaube, im Implementierungsdetail liegt der eigentliche 
Knackpunkt.

Viele glauben, bei 100.000 Zeilen wäre das Konzept das, was auf 20 
Seiten Word + 20 UML-Diagramme passt. Und der Rest wäre 
"Implementierungsdetail".

Das ist Falsch. Eine hinreichende Beschreibung ist meist größer als der 
Sourcecode, da dieser ja in der optimalen Notation erstellt wird. Das 
ist unabhängig davon, dass verschiedene Sichten des Designs für 
verschiedene Zielgruppen erstellt werden, z.B. auch Über-Sichten für 
benachbarte Prozesse.

Natürlich gibt es auch Teilbereiche, die einfach vollständig zu 
beschreiben und aufwendig zu implementieren sind. Hier kann man zurecht 
von "Implementierungsdetail" reden, meist ausgelagert in Libraries. 
sin() oder sqrt() wären so Beispiele, Sortieralgorithmen. Standardteile, 
die von verschiedensten Projekten benutzt werden.

Und es mag auch Teilbereiche geben, wo irgendein Framework oder Ansatz 
hunderte gleichartiger Codezeilen (z.B. Attribute) hundertfach erwartet. 
Aber dann ist das Framework oder der Programmierer einfach nicht das/der 
Richtige.

Oder habe ich den David erschaffen, wenn ich sage: "Michelangelo, mach 
mir mal einen nackten Mann, mit Schleuder und Stein, so etwa 5m hoch"?

von Nop (Gast)


Lesenswert?

Achim S. schrieb:
> Es geht darum, dass der Quelltext das Design ist

Das ist immer noch falsch. Der Quelltext setzt das Design um. Ich habe 
den Eindruck, Du weißt gar nicht, was Design überhaupt bedeutet.

> Eine hinreichende Beschreibung ist meist größer als der
> Sourcecode

Korrekt, weswegen Architektur auch keine vollständige Systembeschreibung 
ist, sondern nur die Struktur beschreibt.

Wenn man natürlich ohne Struktur einfach mal draufloshackt und sich am 
Ende irgendwie irgendeine Struktur ergibt, dann hat man zwar ein Design 
gemacht - aber ein adhoc-Design. Das ergibt am Ende gewachsene Systeme, 
welche ein Wartungsalptraum werden. Alternativ stellt man fest, daß man 
sehr oft Refactoring machen muß, was auch ein project smell ist.

Weil der Grundirrtum vieler Programmierer darin besteht, daß sie umso 
eher fertig werden, je schneller sie loshacken. Eigentlich sollte man je 
nach Größe der Aufgabe dem Programmierer z.B. in der ersten Woche 
einfach mal den Rechner wegnehmen.

Übrigens ist diese chaotische Arbeitsweise bei Physikern extrem 
verbreitet, bei Ingenieuren immer noch merklich, und bei Informatikern 
am wenigsten. Weswegen es eine gute Idee ist, zu Anfang weder einen 
Physiker noch einen Ingenieur dranzusetzen, sondern einen Informatiker.

von Mark B. (markbrandis)


Lesenswert?

Achim S. schrieb:
> Nop schrieb:
>> Lies zurück. Achim hat den Vergleich mit der HW gebracht.
>
> Und Du hast den Artikel nicht gelesen und meine Worte nicht
> Verstanden.
>
> Es geht darum, dass der Quelltext das Design ist

Nein. Jedenfalls dann nicht, wenn man nach gewissen Normen entwickeln 
muss um eine Zulassung für die Software zu erhalten. Dann gilt:

Anforderungen --> Architektur und Design (mit jeweils eigenen 
Dokumenten, die vorzuweisen sind) --> Quelltext

Somit kann in dem Fall der Quelltext nicht das Design sein.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Mark B. schrieb:
> Nein. Jedenfalls dann nicht, wenn man nach gewissen Normen entwickeln
> muss um eine Zulassung für die Software zu erhalten. Dann gilt:
>
> Anforderungen --> Architektur und Design (mit jeweils eigenen
> Dokumenten, die vorzuweisen sind) --> Quelltext
>
> Somit kann in dem Fall der Quelltext nicht das Design sein.

Da hast Du recht. Ich kenne zwar nur die IEC61508 näher (als 
zertifizierter "Tüv Functional Safety Engeneer (Tüv Rheinland)"). Ist 
zwar schon lange her, habe (zuhause) auch keine Normen zum Nachschlagen, 
aber dort wird genau das V-Modell gefordert, von den Anforderungen bis 
runter zum Codieren und mit Tests wieder rauf. Mit Unit-Tests und 
Testabdeckung, mit "handlichen" McCabe-Zahlen, halt nach Lehrbuch.

Das steht zwar dem was ich oben sage diametral entgegen, dennoch ist 
beides richtig.

Wenn ich von Software-Entwicklung rede, dann meine ich, neue Funktionen 
für neue Geräte und neue Aufgaben zu entwickeln. Wo (auch da besteht 
heute eigentlich Konsens) noch keiner Wissen kann, was das Gerät tun 
soll. Der Appetit kommt beim Essen. Wenn ich ein neues Gerät (oder 
Programm) entwickle, dann muss ich es in der Hand halten und damit 
spielen, um es zu begreifen.

Sicherheitsfunktionen sind dagegen zwingend (so steht es fast wörtlich 
in der Norm) einfach, im Sinne von bekannt, beherrscht, schon x-mal 
gemacht, trivial, wenig komplex. Und fix.  Da gibt es kein "schön 
wäre", "besser wäre" oder "versuch mal". Da gibt es kein "und wenn wir A 
nicht erreichen, nehmen wir B". Da gibt es nur: Im Fall X muss Y 
innherhalb von Z stromlos sein. Unverhandelbar.

In Sicherheitsfunktionen nach IEC61508 ist davon auszugehen, dass die 
komplette Funktionalität vom "Designer" endgültig festgelegt wird, und 
der Rest nur Handwerk (Implementierung) ist. Das ist ähnlich wie einen 
Bekannten Schaltplan wiederzuverwenden oder die Monalisa nochmal im 
Maßstab 1:5 zu malen.

Für solche Anwendungen habt Ihr natürlich recht, dass dort das 
Programmieren nur Handwerk ist. Vermutlich verdränge ich solch 
geknechtete "Programmiertätigkeiten".

von Nop (Gast)


Lesenswert?

Achim S. schrieb:
> Wo (auch da besteht
> heute eigentlich Konsens) noch keiner Wissen kann, was das Gerät tun
> soll. Der Appetit kommt beim Essen.

OK, das kenne ich völlig anders, weil eine sehr klare Vorstellung davon 
herrscht, was das Gerät können soll. Das war nämlich Teil der 
Marktanalyse bzw. Verkaufsverhandlungen.

Der Punkt, wo man Ideen einstreuen konnte, was man sinnvollerweise 
vielleicht noch alles an Features haben könnte, war bei der 
Kickoff-Diskussion. Danach noch einen Featurecreep hinlegen ist nicht, 
weil sonst Zeit oder Budget überschritten werden. Auslieferung ist 
schließlich ein Produktfeature, und zwar das wichtigste überhaupt.

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.