Wie am besten Code aufbauen der sich debugen lässt?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Hab ein Problem mit nen Sourcecode von mir.

Vorweg: Hab ein AST-Engine programmiert, die mir Python Code in nem AST darstellt (aber nicht nuanciert ;) Das kommt erst wenn ich mir einen genauen Plan gemacht habe, wie man Terme und sonstiges am besten verarbeitet, so das alles korrekt in nem AST übertragen wird.).

Das nächste ist dann eine Metrik Einheit die aus den Informationen im AST die Metrik erzeugt (Anzahl Code-/Blank-/Commentlines).

Zum Problem:
- Die AST-Engine (besser gesagt der Generator) ist seit Heute Bug-Frei bzw. alle lokalisierten Bugs konnte ich entfernen.


- Das problem ist nun die Metrik-Engine :roll:

Hab da bugs drine und es ist verdammt schwer nun die Abhängigkeiten zu lokalisieren die dazu geführt haben. Eins weis ich auf jeden Fall, das es an der AST-Engine zu 98% nicht liegen kann, weil ich mir dafür eine Funktion geschrieben habe die mir die Verzweigung anzeigt, womit ich überprüfen kann ob die Elemente auch an ihren richtigen Platz kommen (Adressierung, etc wird unter anderem dafür benutzt).

Nach dem ich das ausschließen kann, muss irgendwo in der Metrik Einheit der Käfer drin sein.

print:
Mit printen geht das bei der Größe des codes schlecht. Da sind zuviel stellen die ich mit print untersuchen müsste und dann muss man das ja auch noch logisch nachvollziehen können was da gerade passiert. Arbeitszeit = ca. 1-2 Tage mit diesen verfahren. Erfolg, dabei nicht garantiert.

debuger:
Hmm, ist auch nicht gerade besser weils sehr Zeit intensiv ist und mir bisher auch nicht geholfen hat (vielleicht benutze ich den debuger in PyDev auch falsch).

Nun meine Frage:
Vorweg: Ich möchte nun den Metrik teil nun neu schreiben.
Wie baue ich den code am besten auf das ich den gut Debugen kann? Ich ahb mir da vorgestellte das ich mit print in wichtigen abschnitten eine Status der verwendeten Elemente zurückgebe, anhand dessen ich nachvollziehen kann was gerade passiert. Wie gehe ich da am besten vor ohne den Code mit tausenden "if _debug_:" zu bombardieren. Und wie baut man sowas am besten auf mit dem Debugen? In C/C++ hatte man dafür halt Konstanten verwendet und solche sachen mit den man Festlegen kann das der Debug code nachher in der Finale-Version nicht mit Kompiliert wird.

Wie geht ihr am besten vor wenn ihr code schreibt? Benutzt ihr irgendwelche bestimmten Sachen oder benötigt ihr das vielleicht gar nicht weil ihr den Code gleich nachvollziehen könnt ohne stunden darüber nachzudenken was der Code von euch nun an der stellen macht? :D

Würde mich über Ratschläge freuen, den so wie ich momentan arbeite² kann es nicht mehr weitergehen (da steigt ja kein mensch nach langer Zeit durch).

² = Tja ich schreiben den Code, kommentiere wichtige stellen und benutze Docstrings. Mehr mache ich nicht. Debugen tue ich mit "print" und wehrend den Programmieren kommt auch sehr oft "print" zum Einsatz um zu überprüfen ob der code das tut was ich mir gedacht habe.

lg

P.S.: BTW: Ja ich weiß, Python hat selber was um nen AST zu erzeugen. Auch Tools die Metrik von nem Code anzeigen gibt es auch tausende. Aber, die Aussage "Ich will wissen wie das geht und will dabei was Lernen" rechtfertigt so ein unterfangen jederzeit zu 100%!! ;)
Benutzeravatar
keppla
User
Beiträge: 483
Registriert: Montag 31. Oktober 2005, 00:12

Wie baue ich den code am besten auf das ich den gut Debugen kann? Ich ahb mir da vorgestellte das ich mit print in wichtigen abschnitten eine Status der verwendeten Elemente zurückgebe, anhand dessen ich nachvollziehen kann was gerade passiert. Wie gehe ich da am besten vor ohne den Code mit tausenden "if _debug_:" zu bombardieren
Ich persönlich würde das ganze geprinte und die if-debug-sachen lassen, und stattdessen mehr auf saubere Kapselung achten*. Schreibe Code, den du gut Unitttesten kannst (wenn du es noch nicht tust: tue es, mir hilft es sehr) und achte darauf, dass du immer in wenigen Sätzen sagen kannst, was genau eine Funktion/eine Klasse tut. Wenn die Erklärung kompliziert ist, ist das meist ein Hinweis darauf, dass da etwas auseinander genommen werden sollte.
Wenn du nun Bugs findest, solltest du sie eigentlich recht schnell isolieren können d.h. du kannst bestimmen, in welchem Teil deines Codes was schiefläuft. Diesen Teil kann man dann, wenn man nicht anders drauf kommt, Schritt für Schritt mit einem Debugger durchgehen.

*) damit kann man sich imho zu gut fallen bauen: man schreibt aus versehen schreibt Code, der nur beim Debuggen das gewünschte Verhalten zeigt.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

>>> und achte darauf, dass du immer in wenigen Sätzen sagen kannst, was genau eine Funktion/eine Klasse tut.

Gibt es einen Anhaltspunkt an den man sich halten, kann wie wie weit man Kappseln darf?
Zuletzt geändert von sape am Dienstag 12. Dezember 2006, 22:18, insgesamt 2-mal geändert.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Hab ein unittest versucht dafür zu schrieben und gemerkt das der Code nicht richtig testbar ist. Daher werde ich das nochmal überdenken müsse.
Zuletzt geändert von sape am Dienstag 12. Dezember 2006, 22:20, insgesamt 1-mal geändert.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Keppla, ich danke dir soweit. Hab darüber nachgedacht und bin zu dem Entschluss gekommen das ich die Parser-Einheit vom AST und die Metrik-Engine komplett neu schreibe. Ich werde versuchen das ganze so weit zu Kapseln, dass unter den einzelnen Teilabschnitten keine Abhängigkeiten zu einander existieren, um das Debugen zu erleichtern. Auch auf unittest Verwendbarkeit werde ich versuchen zu achten.

Vielleicht hat auch jemand Lust dann im Laufe dieser oder nächsten Woche, einzelne Code abschnitte zu begutachten und mir sagt was man daran verbessern könnte. Ich werde selbstverständlich auch UMLs erstellen (so weit es mir mit meine wenigen wissen über UML möglich ist) und die Bilder dann hier posten.

lg
sape
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

sape hat geschrieben:Ich werde versuchen das ganze so weit zu Kapseln, dass unter den einzelnen Teilabschnitten keine Abhängigkeiten zu einander existieren, um das Debugen zu erleichtern. Auch auf unittest Verwendbarkeit werde ich versuchen zu achten.
Du kannst ja Test-First Programmierung machen, damit sind deine Programme automatisch testbar.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

>> Test-First Programmierung
Sorry das Sagt mir nichts. Wie genau geht das?

EDIT: Ok, hab mit google schon was gefunden. http://www.phpunit.de/pocket_guide/2.3/ ... mming.html
Werde mir das mal gleich durchlesen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Dive Into Python hat auch ein Kapitel dazu: Chapter 14. Test-First Programming.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Interessanter Ansatz Leo und auch gewöhnungsbedürftig für mich. Ich werde dies Methode mal eine zeit lang ausprobieren.
Nirven
User
Beiträge: 130
Registriert: Mittwoch 10. Mai 2006, 08:18
Wohnort: Bremerhaven

Da ihr mich jetzt darauf gebracht habt mal etwas mehr auf potentielle fehler und Tests zu achten stelle ich die Frage auch hier.

Klingt irgendwie doof, aber das hat mich jetzt die Busfahrt nach Hause beschäftigt:

Ich habe mehrere Funktionen, A bekommt einen Pfad, gibt den an B, und anschließend an C.

Bisher habe ich nur in A geprüft, ob der Pfad gültig ist. Sollte ich das auch in B und C tun? So reicht es ja, der geprüfte Pfad wird weiterverwendet. Wenn ich jemals die Funktionen B und C von woanders aufrufe wäre ein eigener test sinnvoll. Aber ist das nicht leichter Overkill, dreimal das gleiche zu testen, wenn zumindestens momentan eh nichts passieren kann? Oder seh ich das doch zu blauäugig?

Danke schonmal für Antworten (und Sorry falls es jetzt Off-Topic ist in diesem Thread),

Sören
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Nirven hat geschrieben:Ich habe mehrere Funktionen, A bekommt einen Pfad, gibt den an B, und anschließend an C.

Bisher habe ich nur in A geprüft, ob der Pfad gültig ist. Sollte ich das auch in B und C tun? So reicht es ja, der geprüfte Pfad wird weiterverwendet. Wenn ich jemals die Funktionen B und C von woanders aufrufe wäre ein eigener test sinnvoll. Aber ist das nicht leichter Overkill, dreimal das gleiche zu testen, wenn zumindestens momentan eh nichts passieren kann? Oder seh ich das doch zu blauäugig?
Du packst das einfach in keine Klasse, prüfst den Pfad in Konstruktor (oder eben wo du den Pfad übergibst) und hinterlegst den Pfad dann in einer Variblen der Klasse. Nun ist es sicher, dass der Pfad i.O. ist (provided dass dein Check in Ordnung ist) und du kannst gefahrlos aus B und C drauf zugreifen (sofern es Funktionen der Klasse sind, aber davon gehe ich aus).
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

Es sei denn der Pfad kann während der Abarbeitung ungültig werden.

Wenn man mit einem ungültigen Pfad arbeitet, dann wird es doch irgendein Problem geben. Also kann man sich eventuell auch eine vorherige Prüfung sparen und auf das Problem reagieren.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Nirven, wenn A, B, C... Teile einer Klasse sind, dann erledige ich das auch im Konstruktor (so weit es geht) wie Leonidas. Aber wenn das unabhängige Funktionen sind, die nicht in einer Klasse stecken, dann wird die gleiche Überprüfung in jeder Funktion eingebaut um zu garantieren das alle 3 Funktionen auf einen Gültigen "Pfad" zugreifen. Dabei würde ich aber nicht einfach Copy&Paste verwenden, sondern eine spezielle Überprüfungsfunktion im Modul definieren, die alle 3 Funktionen Benutzen.

Beispiel:

Code: Alles auswählen

def is_exist_path(string_): 
    pass

def A(string_):
    if not is_exist_path(string_):
        raise irgend_eine_exception
    # Alles i.O. Normal weitermachen. 

def B(string_):
    if not is_exist_path(string_):
        raise irgend_eine_exception
    # Alles i.O. Normal weitermachen.

[...]
Damit ist sichergestellt das alle 3 Funktionen immer mit gültigen, vorher überprüften, Daten Arbeiten.

Das hat mir schon etlichen Stress erspart.

...

Hab nun Test-First-Programming getestet und finde es sehr gut. Bei Sachen wo ich in vornherein alle Bedingungen kenne schreibe ich erste den Unittest, wie in dem Artikel den du mir gegeben hast Leo, und beginne erst danach die eigentliche Funktion zu schriben. Bei Sachen wo ich noch nicht alle Eckdaten kenne und die sich erste Beim Programmieren entwickeln, schrieb ich danach den passenden Unittest bzw. parallel. Ist am Anfang etwas gewöhnungsbedürftig und auch etwas zeitintensiver.

Wie geht ihr eigentlich genau bei der Planung eines Programms vor? Programmiert ihr einfach darauf los oder macht ihr euch erst Skizzen? Was für Möglichkeiten stehen eine zur Verfügung um erst alles genau zu Planen und dann umzusetzen?

Da ich nun den AST, etc ja nochmal neu schreibe, weiß ich ja nun grob was ich alles Brauche und hab dementsprechend einen "Plan". Aber das nächste mal würde ich gerne gleich mit nen Richtigen Plan an die Sache gehen. Blos ich weiß nicht wie ich am besten alles bedenken soll, um ja nichts zu vergessen oder Falsch anzusetzen. Irgendwas wird es doch dafür geben oder?

lg
sape

EDIT: Fehler berichtigt.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

sape hat geschrieben:

Code: Alles auswählen

def is_exist_path(string_): 
    pass

def A(string_):
    if not is_exist_path(string_):
        raise irgend_eine_exception
    # Alles i.O. Normal weitermachen. 

def B(string_):
    if not is_exist_path(string_):
        raise irgend_eine_exception
    # Alles i.O. Normal weitermachen.

[...]
Warum mift die Check-Funktion nicht selbst die Exception? So hast du entweder den gleichen Exception-Werf-Code in jeder aufrufenden Funktion oder wirst beim gleichen Fehler drei verschiedene Exceptions, was IMO keinen Sinn macht, da der Fahler ja gleich bleibt.
sape hat geschrieben:Wie geht ihr eigentlich genau bei der Planung eines Programms vor? Programmiert ihr einfach darauf los oder macht ihr euch erst Skizzen? Was für Möglichkeiten stehen eine zur Verfügung um erst alles genau zu Planen und dann umzusetzen?
Hängt ab.. die meisten meiner Programme sind ausreichend klein, dass ich sie einfach so zusammenhacke bzw. nur sehr umständlich mit Unittests zu testen, etwa Websachen (die teilweise mehrere Verbindungen zu verschiedenen Servern haben und bei denen die Unittests etwas arg abenteuerlich wären). Sie entwickeln sich dann meist auf Proofs-of-Concept, d.h. ich schaue (mitunter mit mehreren verschiedenen kleineren Programmen) erstmal ob und wie etwas zu realisieren ist und dann klebe ich die Programme entweder zusammen oder baue etwas neues draus. Webprogramme sind auch noch so eine Sache, die vergleichsweise kompliziert zu testen ist. Dafür kann ich mir dort recht hübsch die Models für die Datenbank skizzieren. Trotzdem ist der Plan nie sonderlich detailiert, weil es meiner Erfahrung nach keinen Sinn macht einen Plan 1:1 implementieren zu wollen - die teschnischen Möglichkeiten sind anders als der Plan das vorraussah oder eine andere Lösung ist viel unkomplizierter.
sape hat geschrieben:Da ich nun den AST, etc ja nochmal neu schreibe, weiß ich ja nun grob was ich alles Brauche und hab dementsprechend einen "Plan". Aber das nächste mal würde ich gerne gleich mit nen Richtigen Plan an die Sache gehen. Blos ich weiß nicht wie ich am besten alles bedenken soll, um ja nichts zu vergessen oder Falsch anzusetzen. Irgendwas wird es doch dafür geben oder?
Sowas kommt mit der Erfahrung - man schriebt ja nicht einmal einen AST (oder etwas anderes) und ist dann fertig, oft baut man es um, oder gar neu, weil die eigenen Fähigkeiten sich verbessert haben. Es passiert mir auch, dass ich je mehr in EInblick ins Thema habe, mehr Code wegschmeiße als neuschreibe.
Die Einbindung von dynamisch übersetzten Templates in Django habe ich selbst drei mal überarbeitet (wobei der dritte Ansatz dem ersten sogar ähnlicher war als dem zweiten) bis es eine Form hatte, die mir zugesagt hat.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

Tja das mit dem Planen ist so eine Sache. Ich seh's ähnlich wie Leonidas. Ein grober Plan was man erreichen möchte, eventuell Milestones festlegen und sich vorher etwas mit der jeweiligen Materie beschäftigen und dann erstmal experimentieren. Am Anfang kann man einen "schlechten" Weg noch recht einfach verwerfen, wenn man erst einmal eine Codebasis mit einer schlechten Architektur hat, ist es schwieriger eine 180°-Wende zu machen.

Es gibt da so einen Militärspruch (sinngemäss): "Man kann eine Schlacht präzise planen -- bis zu dem Zeitpunkt wo man auf den Gegner trifft". Man sollte nicht völlig unvorbereitet anfangen, aber immer darauf achten, dass die Pläne kontinuierlich überprüft und gegebenenfalls angepasst werden. Man will ja schliesslich nicht eine Planung/Spezifikation auf biegen und brechen so umsetzen, wie man sie am Anfang mal festgelegt hat, sondern ein Ziel erreichen.

Zu Planungstools zählen bei mir auf jeden Fall Papier und Bleistift. :-)

Von UML halte ich bei Python-Programmen nicht allzuviel, weil die Klassenhierarchien und Objektbeziehungen meistens sehr einfach bleiben. Datenbankentwurf erfordert IMHO ein wenig mehr Planung, da man den, wenn erst einmal Daten in der DB sind, nicht mehr so einfach ändern kann wie man Programme umschreiben kann.

Beim Einsatz von formalen Mitteln wie Anforderungsanalyse, Spezifikation, UML etc. kommt es natürlich letztendlich auch auf die Projektgrösse an und ob/wieviele Leute die Architektur verstehen wollen/müssen.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Danke euch für die ausführliche Antwort. Das hilft mir weiter. Ich werde dann mal probieren einen Groben Ablauf zu skizzieren und je nachdem das Ganze immer wider anpassen/aktuell halte.

lg
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Leonidas hat geschrieben:
sape hat geschrieben:

Code: Alles auswählen

def is_exist_path(string_): 
    pass

def A(string_):
    if not is_exist_path(string_):
        raise irgend_eine_exception
    # Alles i.O. Normal weitermachen. 

def B(string_):
    if not is_exist_path(string_):
        raise irgend_eine_exception
    # Alles i.O. Normal weitermachen.

[...]
Warum mift die Check-Funktion nicht selbst die Exception? So hast du entweder den gleichen Exception-Werf-Code in jeder aufrufenden Funktion oder wirst beim gleichen Fehler drei verschiedene Exceptions, was IMO keinen Sinn macht, da der Fahler ja gleich bleibt.
[...]
Ne hast natürlich recht, aber da bin ich einfach nur von einer Unabhängigen Funktion ausgegangen die man durchaus auch zum Test gebrauchen kann. `is_exist_path` soll True zurückgeben wenn der Pfade existiert und False wenn nicht. Die soll also auch für andere Bereiche gebraucht werden und nicht nur für `A()` und `B()` und soll keinsefalls eine Exception auslösen wenn False ist.

Aber du hast mich gerade auf ein Idee gebracht.

Code: Alles auswählen

def is_exist_path(path): # Soll für jede Funktion benutzbar sein
    if os.path.exists(path):
        return True
    else:
        return False

# Testfunktion für `A()` und `B()`. Ist private!
def _check_is_data_valid(string_)
    if not is_exist_path(string_):
        raise irgend_eine_exception

def A(string_):
    _check_is_data_valid(string_)
    # Alles i.O. Normal weitermachen. 

def B(string_):
    _check_is_data_valid(string_)
    # Alles i.O. Normal weitermachen. 

[...]
So kann ich mir die Überprüfung ersparen und rufe nur die Funktion auf. Wenn False, wird die Exception selbständig ausgelöst. Gleichzeitig ist die Funktion `is_exist_path` weiterhin Unabhängig und nicht an `A()` und `B()` als exception schleuder gebunden. Ich glaube so in der Art hast du dir das auch gedacht oder?

BTW: Dient nur als Beispiel. Um einen Pfad zu testen würde ich gleich `os.path.exists` nutzen.
BlackJack

Gerade bei diesem speziellen Beispiel kannst Du Dir alle Tests sparen. Nachdem getestet wurde und bevor mit dem Pfad etwas getan wird, kann der von einem anderen Programm gelöscht werden. Also musst Du sowieso mit Ausnahmen rechnen und die entsprechend behandeln.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Stimmt schon, obwohl die Wahrscheinlichkeit wirklich gering ist, das gerade in der selben Sekunde der Pfad bzw. Ordner von einem anderen Programm gelöscht wurde. Ich meine das ist Millisekunden bereich zwischen der Überprüfung und dann den Benutzen des Pfades. Ich denke die Wahrscheinlichkeit liegt im gleichen bereich wie das mit denn PIDs bei Linux oder?
BlackJack

sape hat geschrieben:Stimmt schon, obwohl die Wahrscheinlichkeit wirklich gering ist, das gerade in der selben Sekunde der Pfad bzw. Ordner von einem anderen Programm gelöscht wurde. Ich meine das ist Millisekunden bereich zwischen der Überprüfung und dann den Benutzen des Pfades. Ich denke die Wahrscheinlichkeit liegt im gleichen bereich wie das mit denn PIDs bei Linux oder?
Das kommt ganz darauf an um was für Pfade es sich handelt. Im `/tmp/`-Verzeichnis ist die Wahrscheinlichkeit sicher höher.

Aber das ändert nichts daran, dass so ein Test implizit immer gemacht wird, wenn man mit einem Pfad eine Datei öffnen will. Wenn man ihn vorher explizit macht, dann wird er letztendlich zweimal ausgeführt.
Antworten