Code verstehen - Welche Hilfsmittel?

ZuseZ3

Lt. Commander
Registriert
Jan. 2014
Beiträge
1.659
Ich arbeite gerade daran, zwei verschiedene C Algorithmen zusammenzuführen.
Die Idee der beiden ist mir jeweils bekannt und theoretisch spricht alles für eine gute Zusammenführbarkeit.
Beide Implementierungen arbeiten auf Bit-Ebene und insbesondere einer der beiden (ca. 1000 LoC) ist recht stark auf performance optimiert (Zahlreiche, verschachtelte und z.T. 30 Zeilige Defines, bzw. undefines, magic Numbers,..). Dafür ist er aber schon über 10 Jahre alt.

Ich hatte schonmal angefangen den Code zu refaktorisieren, da der Code sehr alt ist hatte ich, vmtl. da die compiler in den letzten 10, 15 Jahren deutlich besser geworden sind, keine nennenswerten Performance Verluste, soweit ich mich erinnere.

Davon abgesehen, habt ihr tipps um derartigen code zu verstehen?
Ich hatte z.B. auf Tools gehofft, die mir aus dem Programm direkt passende UML Diagramme erstellen, so als Beispiel. Natürlich geht das auch per Hand, aber man will ja neue Sachen lernen.
Es geht mir dabei vor allem darum eine ausreichend gute Übersicht über beide Programme zu erhalten um sie zusammen zu führen, wenn dabei jeder Algorithmus für sich etwas ineffizienter wird ist das ok, sie sollten gemeinsam genug Performance wieder rausholen.
Den originalen Author kann ich, (natürlich ;) ) nicht mehr fragen, wäre ja auch langweilig..
 
Als Zusatzhinweis: Schreib Tests für den Code (wahrscheinlich sind keine da), sodass du sofort weißt, sobald du etwas falsch gemacht hast beim Refactoren (was schnell passieren kann).

(Hinweis zum Zusatzhinweis: Durchlaufen der Tests garantiert i.d.R. jedoch keine 100%ige Korrektheit)
 
  • Gefällt mir
Reaktionen: ZuseZ3 und Hayda Ministral
Enterprise Architekt erstellt direkt UML Diagramme - ob und wie hiflreich so autogeneriertes Zeug ist, schwankt aber stark.

Ansonsten einfach Breakpoint setzen und mit durchlaufen? So komme ich meistens ganz gut klar, wenn ich in unbekannte und nicht dokumentierte Software rein muss.

und was @new Account() sagt. Ansonsten debuggst du dich später zu Tode. Jetzt erstmal möglichst viele Testfälle aus der realen Anwendung zusammen suchen.

Letztlich musst du ja auch alle Bugs in deinem neuen Code wieder so einbauen, wenn es wichtig ist, dass das Ergebniss vom neuem Code exakt dem alten entspricht. Dann müssen ja auch alle Fehler reproduziert werden.
 
  • Gefällt mir
Reaktionen: ZuseZ3 und tony_mont4n4
Das mit dem Fehler einbauen hat mich zum lachen gebracht, danke @Autokiller677
Der Code wurde damals von mehreren Leuten genutzt und Fehler wären in dem Fall sehr offensichtlich gewesen.
Da die Ergebnisse praktischerweise mathematisch sehr einfach zu verifizieren sind werde ich auf jeden Fall Tests schreiben, danke für die Erinnerung.

Ich denke ich werde SourceTrail testen, das arbeitet auch mit Vim zusammen, was ich bisher gern genutzt habe.
 
  • Gefällt mir
Reaktionen: new Account()
Ich wollte nur drauf Hinweisen, weil es durchaus häufig vorkommt, dass nach einer großen Übererarbeitung Code zu gut funktioniert und dann etwas nicht mehr geht, weil man sich auf die Fehler des alten Codes verlassen hat.
 
  • Gefällt mir
Reaktionen: ZuseZ3
IMHO ist es kontraproduktiv, alten Code irgendwie zu migrieren zu versuchen. Einfach deswegen, weil eine programmierte Applikation eben NICHT das tun muß, was man intuitiv erwarten würde. Das funktioniert evtl bei objektorientiertem Code, wenn der ordentlich semantisch gehalten ist; dann kann man nach Objektstruktur und dem Verlauf rekonstruieren, was der Autor damit sagen wollte.

Aber insbesondere C ist 150% Interpretation. Es gibt in C Hunderte Wege, simple Dinge komplex zu lösen und komplexe Dinge auf einen eleganten Einzeiler herunterzubrechen - aber die Nachvollziehbarkeit ist praktisch Null und schlimmer noch, mit Refactoring schadet man mehr als daß man nutzt, weil das die Strukturen nicht (notwendigerweise) hergeben.

Sinngemäß ist meine Anwendung eigentlich nur so eine Art Hashfunktion: was hinten rauskommt, läßt keinerlei Rückschlüsse mehr zu auf das, was man vorne reingesteckt hat, buchstäblich (cf Eingaben) wie auch übertragen (cf Semantik).


Daher halte ich es für zielführender, die Applikation neu zu entwerfen gemäß Anforderungen an die alte (plus was auch immer noch dazukommen soll) entlang "moderner" Programmierrichtlinien. Dann kann man ggfs sogar die Plattform wechseln, wenn einem C nicht so liegt und lieber C# oder sonstwas anderes gerne hätte.

Wenn das fertig ist, kann man nachoptimieren.

Ja, die Chancen stehen gut, daß die ursprüngliche C-Anwendung gemäß Performance unschlagbar sein wird. Es ist eine C-Anwendung und Leute mit Ahnung kitzeln aus C sehr sehr viel raus, ebenso wie Leute mit noch mehr Ahnung noch sehr viel mehr aus Assembler herauskitzeln können. Für solche Kitzelarbeiten eignen sich modernere Sprachen nur sehr bedingt (wenn überhaupt).

Aber im Sinne der erwarteten Laufzeit sollte die Performance der C-Applikation zumindest erreichbar sein, und -- imo viel wichtiger -- man weiß hinterher, was sie tut und kann dokumentieren.

(Außen vor lassen wir mal die angezogenen Standards für C - keine Ahnung ob der ursprüngliche Quellcode nach aktuellen C-Standards überhaupt kompilierbar ist. Oder was dabei rumkommt, wenn.)
 
@RalphS
Danke für deine Meinung und bei Software im Software Sinn mag das auch richtig sein für manche Fälle.
Gerade mit Blick auf Abhängigkeiten denke ich aberdass es selbst dort oft nicht so einfach ist wie du es darstellst.

Hier geht es aber um einen optimierten mathematische Algorithmus für ein mathematisches Probleme, wo z.T. nicht mal mehr 64 Bit Integer Zahlen reichen und die Sachen auf die Art geschrieben werden, damit man sicher sein kann, dass diese Bits genau so hintereinander im L1 Cache landen.
Objektorientierung ist da ein komplett sinnloser und sogar kontraproduktiver Ansatz, finde nicht, dass man das, sowie "modernere" Sprachen überall auf biegen und brechen reinquetschen muss.
Man vergleiche den LinuxKernel.

Oh und nur damit es klar ist, falls das bisher nicht hervor ging.
Es handelt sich um reine Implementierungen von Algorithmen mit Berechnungen auf Bitebene, ohne jegliche Gui und abseits von Bibliotheken a la stdio.h ist nichts eingebunden.
Dementsprechend ists auch "gut gealtert".
Ne weitere Implementierung mit Assembler bestandteilen existiert auch, die beachte ich aber erstmal auch nicht, auch da ich dort nicht mehr sicher bin wie gut 10 Jahre alter asm code heute noch läuft.
 
Zuletzt bearbeitet:
DoubleJ2k schrieb:
Hast du selbst erfahrung damit?
Ich finde den Ansatz gut, bei der Umsetzung scheitert er aber bei meinem Code an grundlegendem.
Try ist z.B. in c kein keywoard und zugegeben kein sinnvoller Funktionsnahme.
Allerdings sollte er keine Probleme machen, ich musste die Funktion trotzdem umbenennen, weil er zahlreiche error angab.
Dazu war er, im Gegensatz zu gcc, nicht in der lage folgendes als char * zu erkennen:
(argc = 3) ? "Foo" : argv[3]
Da dies ein Funktionsparameter war hat er die zugehörige Funktion als error markiert, da sie ihm mit den Parametern nicht bekannt war.

Naja, vorerst habe ich die Probleme gelöst, dann schau ich mal, was ich aus den 25 Macros, 20 globalen Variablen und 10 typedefs machen kann.
 
Zuletzt bearbeitet:
Zurück
Oben