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
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.
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.
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.
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.
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 ;-) )
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.
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
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
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.
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
> 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.
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
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.
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.
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.
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:>
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
intmain(void){
8
uint8_tbuffer[]="0123456789";
9
clear_buff(buffer,MAXLEN);
10
for(uint8_ti=0;i<MAXLEN;++i){
11
if(buffer[i]!=0){
12
return-1;
13
}
14
}
15
return0;
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.
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.
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
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.
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. :-(
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
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.
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.
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.
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.
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.
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.
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"?
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.
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.
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".
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.