Was ist ein guter Weg, um die Gesamtstruktur einer Codebasis zu verstehen?

8

Manchmal ist es in meiner Arbeit nützlich, den Open-Source-Code eines anderen zu ändern oder herauszufinden, wie Sie bestimmte Dinge für Ihre eigene Anwendung entwickeln können. Allerdings verfügen nicht alle Softwareprogramme über eine gute Dokumentation.

Was ist ein guter Weg, um die Gesamtstruktur einer Codebasis zu verstehen?

Welche Routinen rufen beispielsweise welche Routinen usw. auf? Ich könnte zu diesem Zweck selbst ein Dokumentationstool wie Doxygen verwenden. Ich habe mich jedoch gefragt, ob es eine bessere Strategie gibt.

Allan P. Engsig-Karup
quelle
5
Ich denke, dies ist eine vernünftige Frage, aber ich denke auch, dass sie wahrscheinlich stattdessen von Programmierern gestellt werden sollte. Es ist keine reine Computerwissenschaft, sondern eher konzeptionelle Programmierung.
Geoff Oxberry
2
Gibt es dafür Techniken, die spezifisch für wissenschaftliche Codes sind?
David Ketcheson
Ich benutze Doxygen, weil es einfach ist und schöne Bilder mit Grafiken liefern kann, die zeigen, wie Routinen verbunden sind. Beachten Sie, dass es möglicherweise erforderlich ist, eine Hauptroutine zu erstellen, die alles abdeckt, um ein so vollständiges Bild zu erhalten.
Allan P. Engsig-Karup
@ DavidKetcheson: Ich glaube nicht, dass es Methoden gibt, um dies spezifisch für wissenschaftliche Codes zu tun. Angesichts der starken Tradition undokumentierter Forschungscodes sollte dies vielleicht der Fall sein.
Geoff Oxberry

Antworten:

9

Die folgenden Themen sind tangential miteinander verbunden:

Im ersten Teil meiner Arbeit habe ich 18 Monate damit verbracht, undokumentierten Fortran-Code zu ändern. Eine der ersten Aufgaben bestand darin, die Gesamtstruktur einer Codebasis zu verstehen. Das Wichtigste, was Sie vorschlagen, ist, sich jedes Mal Notizen in einer Textdatei zu machen, wenn Sie etwas herausfinden. Sie möchten während dieses zeitaufwändigen und frustrierenden Prozesses keine Dinge neu lernen oder neu entdecken müssen.

In meinem Fall gab es keine nennenswerte "API", da die Argumente der Funktionen nicht selbstdokumentierend waren, da der vorherige Programmierer den Fortran 77-ähnlichen Stil und damit kurze Bezeichner mit wenig bis gar keinem Hinweis darauf verwendete Sie meinten. Es gab keine Tests und da es Fortran ist, keine Überschriften. Um dem Mix noch mehr Spaß zu machen, wurden hier und da einige Funktionen in C oder C ++ geschrieben.

Dinge, die für mich funktioniert haben (vorausgesetzt, Sie arbeiten unter Linux):

  • grep. Lerne zu lieben grep; Sie werden es in der Shell verwenden, um herauszufinden, wo Funktionen deklariert und aufgerufen werden, und die Ausgabe gibt an, in welchen Dateien gesucht werden soll.
  • Der Befehl "find" in Ihrem bevorzugten Code-Editor oder Ihrer bevorzugten IDE. Sobald Sie wissen, in welcher Datei Sie suchen müssen, sparen Sie mit dem Befehl "find" Zeit beim Suchen nach Funktionsaufrufen.
  • Aggressives Refactoring, wenn Sie die Codebasis ändern können. Ich habe die Variablennamen selbstdokumentierend gemacht, damit ich mich nicht geistig anstrengen musste, um Dinge neu zu lernen, die ich bereits herausgefunden hatte. Ich habe auch das Design des Codes optimiert, als ich es herausgefunden habe, damit es weniger verwirrend war. Kein 1000 Zeilen langes Hauptprogramm mehr!
  • Aggressives Kommentieren. Wenn Sie die Codebasis ändern können, kommentieren Sie die Dinge, damit Sie wissen, was Sie herausgefunden haben. Ich habe Doxygen nicht verwendet, aber Doxygen ist gut dafür.
  • nm. Dies kann für Bibliotheken hilfreich sein, wenn Sie keinen Quellcode für sie haben, aber wissen möchten, ob sich eine Funktion in dieser Bibliothek befindet, auf die Sie gestoßen sind. Dies funktioniert jedoch nur, wenn die Symbole der Bibliothek nicht entfernt wurden.
  • Gehen Sie den Code mit einem Debugger durch. Es ist viel effizienter als die Verwendung von printAnweisungen. dddund gdbsind großartig und auf praktisch jedem Linux-System da draußen. Fühlen Sie sich frei, Ihren Lieblings-Debugger zu verwenden.
  • Bug die Entwickler. Diese Option eignet sich am besten für sehr gezielte Fragen. Wenn Sie zu ihnen gehen (wie ich) und sagen: "Ich verstehe nicht, was hier vor sich geht", haben sie möglicherweise Mitleid mit Ihnen und versuchen, die Dinge im Allgemeinen detailliert zu erklären, aber das wird in nur begrenzt von Nutzen sein auf lange Sicht. Sie müssen die Beinarbeit erledigen, da die Entwickler dies nicht für Sie getan haben, indem sie Dokumentation geschrieben und die Struktur ihrer Codebasis schriftlich erklärt haben. Die Entwickler sind wirklich gut, wenn Sie wirklich an kleinen Dingen festhalten (wenn sie sich daran erinnern, was sie getan haben).

Dinge, an die ich gerne früher gedacht hätte oder die für mich einfach keine Optionen waren:

  • Sauerstoff. Wenn Sie die Doxyfile-Optionen optimieren, generiert Doxygen automatisch eine Menge Dokumentation für Sie, auch ohne die spezielle Doxygen-Kommentarsyntax, die ein guter Ausgangspunkt sein könnte. Ich habe dies für spätere Projekte verwendet und es war unglaublich hilfreich.
  • Unit Testing. Wenn Sie die Codebasis ändern können und eine Vorstellung davon haben, was sie tun soll, schreiben Sie Komponententests für verschiedene Funktionen. (Es ist eine nützliche Fähigkeit, unabhängig davon zu lernen.)
  • Wenn Sie mit C / C ++ arbeiten, sehen Sie sich die Header an.
  • Schreiben Sie Beispielprogramme. Keine Option für mich in diesem Fortran-Projekt, aber es war nützlich für mich, APIs von Drittanbietern zu finden. Schauen Sie sich auch ihre Beispielprogramme an, falls vorhanden.
  • Verwenden Sie gcovund lcov, um eine Abdeckungsanalyse für typische Codeläufe durchzuführen, wenn Sie Beispiele oder ausführbare Dateien haben, mit denen Sie arbeiten können. Wenn es Beispiele gibt, die große Teile der Codebasis ausführen sollen, zeigen diese beiden Tools zusammen an, wie oft jede Codezeile besucht wird. Dies ist am nützlichsten, wenn Debugging-Flags aktiviert sind. Wenn ein Teil des Codes überhaupt nicht besucht wird, ist es wahrscheinlich weniger wichtig, ihn sofort zu verstehen. Wenn ein Teil des Codes häufig besucht wird, lohnt es sich wahrscheinlich zu verstehen, was es ist. Möglicherweise wird der Code häufig ausgeführt, weil es sich um eine unwichtige Schleife handelt, oder es könnte sich um eine Schlüsselfunktion handeln, auf die sich viele andere Funktionen stützen. Sie können nicht nur anhand der Abdeckungsanalyse feststellen, aber die Abdeckungsanalyse gibt Ihnen eine Vorstellung davon, wo Sie Ihre Zeit konzentrieren müssen.
  • Statische Code-Analyse-Tools wie splintkönnen Ihnen sagen, ob im Code etwas faul ist, da einige Variablen niemals verwendet werden.
  • Profilerstellung. Auch hier erhalten Sie Daten, die Ihnen nicht sofort sagen, was wichtig ist oder nicht, sondern vorschlagen, was wichtig sein könnte. Wenn viel CPU-Zeit für den Aufruf einer Funktion aufgewendet wird, sollten Sie sich diese ansehen und sehen, was sie bewirkt. Sie können die Profilerstellungsausgabe auch mit dotund verwenden graphviz, um Anrufdiagramme zu erstellen und zu sehen, wie oft Funktionen aufgerufen werden, z. B. die Abdeckungsanalyse. Bei komplexen Codes kann eine grafische Analyse viel hilfreicher sein.
  • Wenn Sie in C arbeiten, sollte Frama-C bei der Analyse von Code hilfreich sein, aber ich habe es nie verwendet, weil es zu kompliziert schien, um die Mühe wert zu sein. Ich arbeite in reinem C, aber es ist hauptsächlich Code, den ich schreibe. Ich habe noch nie mit undokumentiertem C-Code gearbeitet.
Geoff Oxberry
quelle
2
Was ist die häufigste Methode, um eine sehr große C ++ - Anwendung zu verstehen? on Stack Overflow ist C ++ -spezifisch, aber eine andere bestehende Frage der Antike.
dmckee --- Ex-Moderator Kätzchen
Schöne Liste. Ich setze alles in git und benutze "git grep". Und auch den Git-Verlauf für einzelne Dateien, falls verfügbar.
Ondřej Čertík
3

Ich fordere meine Schüler immer auf, einen Code von unten nach oben zu lesen: Sie beginnen in main () und sehen, wie er aufgerufen wird. In der Regel ist dies nur eine kleine Anzahl von Funktionen. Anschließend sehen Sie sich die von main () aufgerufenen Funktionen an, die normalerweise den Gesamtfluss des Algorithmus definieren (Zeitschrittschleife, Assembly, Solver, Ausgabe usw.). Gehen Sie zwei oder mehr Ebenen tief, um einen Überblick über den Algorithmus aus einer Entfernung von 30.000 Fuß zu erhalten. Der Rest kann häufig aus der Sauerstoffdokumentation usw. entnommen werden.

Aber wie gesagt, die Nachricht lautet: Lesen Sie den Code von unten nach oben.

Wolfgang Bangerth
quelle
3
Ihr Rat ist gut aufgenommen, hört sich aber überhaupt nicht so an, als würde man Code von unten nach oben lesen. Es klingt eher so, als würde man von oben nach unten lesen. Main () (oder Program in Fortran) ist die oberste Programmeinheit in einem ausführbaren Code. Darüber hinaus muss Ihr Rat ein wenig angepasst werden, wenn es sich bei dem fraglichen Code um eine Bibliothek handelt, die eine API definiert. Es gibt keine main () -Funktion. In diesem Fall müssen Sie Ihre Strategie, zu sehen, welche Funktionen aufgerufen werden, mit der Entscheidung kombinieren, welche Funktionen für Sie wichtig sind, indem Sie normalerweise Beispielprogramme betrachten (oder codieren).
Geoff Oxberry
Es kann einige Verwirrung über "von unten" geben, weil Programmierer, die Pascal früh gelernt haben, häufig den Einstiegspunkt am Ende (oder am Ende ) der Datei setzen - weil Pascal nur einen Durchgang gemacht hat und alles vorher eingeführt werden musste hieß. Wie Geoff bevorzuge ich jedoch die Logik, in der Main () an der fiktiven "Spitze" eines Baumes steht, der nach unten wächst.
dmckee --- Ex-Moderator Kätzchen
Ja, ich meinte dmckees Art, main () am Ende der Datei zu haben, weil man sonst alles vorwärts deklarieren müsste.
Wolfgang Bangerth