Dessiner un graphique d'appel

11

Je maintiens une ancienne base de code écrite en python. En particulier, il existe un morceau de code complexe qui à partir d'un module appelle d'autres fonctions à partir d'autres modules qui appellent d'autres fonctions et ainsi de suite. Ce n'est pas de la POO, juste des fonctions et des modules.
J'ai essayé de garder une trace du début et de la fin du flux chaque fois que j'appelle la fonction principale, mais je sens que je dois dessiner cela parce que je me perds dans les sous-appels.

Ce qui m'inquiète, c'est que chaque fonction appelle plusieurs fonctions externes au sein de son corps pour terminer sa tâche et renvoyer la valeur à l'appelant.

Comment puis-je dessiner cela? Signification quel type de graphique / graphique serait approprié pour documenter ce type de comportement / code?

Donc, je ne pense pas qu'il serait utile de dessiner un diagramme UML, ni un organigramme. Un graphique d'appel, peut-être?

Leonardo
la source
doxygen - générera des graphiques d'appel / appelant, je ne sais pas combien de support il a pour python. Je sais que vous pouvez documenter le code python pour cela.
gbjbaanb
J'ai essayé pycallgraph mais c'est juste trop compliqué / trop profond pour l'utiliser. Cela est dû à la complexité de mon code car il mélange du python ordinaire avec django et un appel externe à l'URL de l'API. C'est pourquoi j'ai voulu le dessiner à la main en ne tenant compte que de la partie pertinente dont j'ai besoin. Le problème est que je ne sais pas quel type de graphique utiliser pour avoir une compréhension complète du système
Leonardo
5
Si c'est juste pour vous aider à le comprendre, dessinez simplement ce qui vient naturellement. Vous pouvez toujours le ranger plus tard s'il entre dans une documentation formelle.
jonrsharpe

Réponses:

9

Je pense que ce que vous cherchez ici est un diagramme de séquence . Ceux-ci vous permettent de visualiser l'ordre dans lequel les différents modules s'appellent via l'utilisation de flèches.

En construire un est simple:

  1. Dessinez votre classe de départ avec une ligne pointillée en dessous.
  2. Dessinez la classe / méthode suivante dans la trace d'appel avec une ligne pointillée en dessous
  3. Reliez les lignes avec une flèche, positionnée verticalement sous la dernière flèche que vous avez dessinée
  4. Répétez les étapes 2-3 pour tous les appels de votre trace

Exemple

Supposons que nous ayons le code suivant pour lequel nous voulons créer un diagramme de séquence:

def long_division(quotient, divisor):
    solution = ""
    remainder = quotient
    working = ""
    while len(remainder) > 0:
        working += remainder[0]
        remainder = remainder[1:]
        multiplier = find_largest_fit(working, divisor)
        solution += multiplier
        working = calculate_remainder(working, multiplier, divisor)
    print solution


def calculate_remainder(working, multiplier, divisor):
    cur_len = len(working)
    int_rem = int(working) - (int(multiplier) * int (divisor))
    return "%*d" % (cur_len, int_rem)


def find_largest_fit(quotient, divisor):
    if int(divisor) == 0:
        return "0"
    i = 0
    while i <= 10:
        if (int(divisor) * i) > int(quotient):
            return str(i - 1)
        else:
            i += 1


if __name__ == "__main__":
    long_division("645", "5")

La première chose que nous allons dessiner est le point d'entrée ( main) se connectant à la méthode long_division. Notez que cela crée une boîte dans long_division, signifiant la portée de l'appel de méthode. Pour cet exemple simple, la boîte sera la hauteur totale de notre diagramme de séquence car c'est la seule chose exécutée.

entrez la description de l'image ici

Maintenant, nous appelons find_largest_fitpour trouver le plus grand multiple qui correspond à notre numéro de travail et nous le renvoie. Nous dessinons une ligne de long_divisionà find_largest_fitavec une autre case pour indiquer la portée de l'appel de fonction. Notez comment la boîte se termine lorsque le multiplicateur est renvoyé; c'est la fin de cette portée de fonctions!

entrez la description de l'image ici

Répétez plusieurs fois pour un plus grand nombre et votre graphique devrait ressembler à ceci:

entrez la description de l'image ici

Remarques

Vous pouvez choisir si vous souhaitez étiqueter les appels avec les noms de variable transmis ou leurs valeurs si vous ne souhaitez documenter qu'un cas spécifique. Vous pouvez également afficher la récursivité avec une fonction qui s'appelle elle-même.

De plus, vous pouvez montrer aux utilisateurs ici et les inviter et afficher leur entrée dans le système assez facilement. C'est un système assez flexible que je pense que vous trouverez plutôt utile!

Ampt
la source
Merci, je connais le diagramme de séquence, mais il me semble qu'il est plus approprié pour oop. Dans mon cas, les choses sont un peu plus compliquées, ce qui signifie que, par exemple, j'ai environ 20 fonctions / assistants répartis sur plusieurs modules. Comment devrais-je spécifier le module auquel appartient la fonction? Étant donné que certaines fonctions sont également renommées lors des importations.
Leonardo
1
Je dirais que peu importe le nombre de modules que vous avez - l'exemple ci-dessus n'est pas du tout non plus. Nommez-les simplement pour les retrouver plus tard, ModuleA / fonction1, ModuleB / Fonction2, etc. Pour 20 fonctions, ça va être plus grand, mais certainement pas impossible à comprendre. Vous pouvez également penser à mettre fin à la ligne d'une fonction après sa dernière utilisation et à placer une autre ligne de fonctions en dessous pour économiser de l'espace horizontal dans votre diagramme.
Ampt
5

Je pense qu'un graphique d'appel serait la visualisation la plus appropriée. Si vous décidez de ne pas le faire à la main, il existe un joli petit outil appelé pyanqui effectue une analyse statique sur un fichier python et peut générer un graphique d'appel visualisé au moyen d'un fichier de points graphviz (qui peut être rendu sur une image). Il y a eu quelques fourches, mais la plus complète semble être https://github.com/davidfraser/pyan .

Il vous suffit de spécifier tous les fichiers que vous souhaitez traiter lorsque vous exécutez la commande:

python ~/bin/pyan.py --dot a.py b.py c.py -n > pyan.dot; dot -Tpng -opyan.png pyan.dot

ou

python ~/bin/pyan.py --dot $(find . -name '*.py') -n > pyan.dot; dot -Tpng -opyan.png pyan.dot

Vous pouvez rendre le graphique plus propre avec le '-n' qui supprime les lignes indiquant où une fonction a été définie.

seren
la source