Statische Analyse großer Mengen von Python-Code: Instagram-Erfahrung. Teil 2

Heute veröffentlichen wir den zweiten Teil der Übersetzung von Material zur statischen Analyse großer Mengen serverseitigen Python-Codes in Instagram.



Der erste Teil

Programmierer, die es satt haben zu fusseln


In Anbetracht der Tatsache, dass wir ungefähr hundert unserer eigenen Flusenregeln haben, kann die pedantische Bilanzierung von Empfehlungen, die durch diese Regeln herausgegeben werden, schnell zu einer Zeitverschwendung für Entwickler führen. Es wäre besser, die Zeit damit zu verbringen, den Codestil zu korrigieren oder veraltete Muster zu entfernen, um etwas Neues zu erstellen und das Projekt zu entwickeln.

Wir haben festgestellt, dass Programmierer, die zu viele Benachrichtigungen vom Linter sehen, alle diese Meldungen ignorieren. Dies gilt auch für wichtige Meldungen.
Angenommen, wir erklären die Funktion fn veraltet und verwenden die Funktion mit einem besseren Namen. add stattdessen hinzu. Wenn Sie die Entwickler nicht darüber informieren, wissen sie nicht, dass sie die Funktion fn nicht mehr verwenden müssen. Schlimmer noch, sie wissen nicht, was sie anstelle dieser Funktion verwenden sollen. In dieser Situation können Sie eine Linter-Regel erstellen. Aber jede große Codebasis enthält bereits viele Regeln. Infolgedessen ist es wahrscheinlich, dass eine wichtige Linter-Benachrichtigung im Haufen von Benachrichtigungen über kleinere Fehler verloren geht.


Linter ist zu pingelig und das „nützliche Signal“ kann leicht im „Rauschen“ verloren gehen.

Was sollen wir damit machen?

Sie können viele vom Linter erkannte Probleme automatisch beheben. Wenn der Linter selbst mit der Dokumentation verglichen werden kann, die dort angezeigt wird, wo er benötigt wird, sind solche automatischen Korrekturen eine Art Refactoring von Code, der dort ausgeführt wird, wo er benötigt wird. Angesichts der großen Anzahl von Entwicklern, die an Instagram arbeiten, ist es fast unmöglich, jeden von ihnen in unseren besten Techniken zum Schreiben von Code zu schulen. Durch Hinzufügen automatischer Codekorrekturfunktionen zum System können wir Entwickler über neue Techniken informieren, wenn sie diese Techniken nicht kennen. Dies hilft uns, Entwickler schnell auf den neuesten Stand zu bringen. Durch automatische Korrekturen konnten wir Programmierer außerdem dazu bringen, sich auf wichtige Dinge zu konzentrieren, anstatt sich auf monotone kleinere Codeänderungen zu konzentrieren. Im Allgemeinen kann festgestellt werden, dass automatische Codekorrekturen für die Schulung von Entwicklern effektiver und nützlicher sind als einfache Linter-Benachrichtigungen.

Wie erstelle ich ein System zur automatischen Codekorrektur? Ein auf Syntaxbäumen basierender Flusen gibt uns Informationen über einen funktionsgestörten Knoten. Daher müssen wir keine Logik erstellen, um Probleme zu erkennen, da wir bereits die entsprechenden Regeln für den Linter haben! Da wir wissen, welcher bestimmte Knoten nicht zu uns passt und wo sich sein Quellcode befindet, können wir, ohne das Risiko einzugehen, etwas zu verderben, beispielsweise den Namen der Funktion fn durch add ersetzen. Dies ist gut geeignet, um einzelne Verstöße gegen die Regeln zu korrigieren, die ausgeführt werden, wenn solche Verstöße erkannt werden. Was aber, wenn wir eine neue Regel für den Linter einführen, was bedeutet, dass die Codebasis Hunderte von Codefragmenten enthalten kann, die dieser Regel nicht entsprechen? Können all diese Inkonsistenzen im Voraus korrigiert werden?

Code Mods


Ein Codemod ist nur eine Möglichkeit, Probleme zu finden und Änderungen am Quellcode vorzunehmen. Codemods basieren auf Skripten. Codemod kann als "Steroid Refactoring" angesehen werden. Das Spektrum der Aufgaben, die durch Codemodi gelöst werden, ist äußerst breit: von einfachen Aufgaben wie dem Umbenennen einer Variablen in einer Funktion bis zu komplexen Aufgaben wie dem Umschreiben einer Funktion, sodass ein neues Argument erforderlich ist. Bei der Arbeit mit dem Codemod werden die gleichen Konzepte verwendet wie beim Betrieb des Linter. Anstatt den Programmierer wie der Linter über das Problem zu informieren, löst der Codemodus dieses Problem automatisch.

Wie schreibe ich ein Codemod? Betrachten Sie ein Beispiel. Hier wollen wir aufhören, get_global . In dieser Situation können Sie den Linter verwenden, es ist jedoch nicht bekannt, wie lange es dauern wird, den gesamten Code zu reparieren. Außerdem wird diese Aufgabe auf viele Entwickler verteilt. Selbst wenn das Projekt ein System zur automatischen Codekorrektur verwendet, kann es gleichzeitig einige Zeit dauern, bis der gesamte Code verarbeitet ist.


Wir möchten nicht mehr get_global verwenden und stattdessen Instanzvariablen verwenden

Um dieses Problem zu lösen, können wir zusammen mit der Linter-Regel, die es erkennt, ein Codemod schreiben. Wir glauben, dass das Zulassen, dass veraltete Muster und APIs den Code allmählich verlassen, Entwickler ablenken und die Lesbarkeit des Codes beeinträchtigen wird. Wir ziehen es vor, den veralteten Code sofort zu entfernen und nicht zu beobachten, wie er allmählich aus dem Projekt verschwindet.

Angesichts des Volumens unseres Codes und der Anzahl der aktiven Entwickler bedeutet dies häufig, dass veraltete Designs automatisch entfernt werden. Wenn wir in der Lage sind, Code schnell aus veralteten Mustern zu entfernen, bedeutet dies, dass wir die Produktivität aller Instagram-Entwickler aufrechterhalten können.

Also, wie macht man ein Codemod? Wie kann man nur das Codefragment ersetzen, das uns interessiert, während die Kommentare, Einrückungen und alles andere erhalten bleiben? Es gibt Tools, die auf einem bestimmten Syntaxbaum basieren (z. B. das, was LibCST erstellt), mit denen Sie den Code mit chirurgischer Präzision ändern und alle Hilfskonstruktionen darin speichern können. Wenn wir also den Namen der Funktion von fn ändern müssen, add in den folgenden Baum einzufügen, können wir den Namen add anstelle von fn in den Knoten Name schreiben und dann den Baum auf die Festplatte schreiben!


Sie können den Codemodus ausführen, indem Sie anstelle von fn add in den Knoten Name schreiben. Dann kann der geänderte Baum auf die Festplatte geschrieben werden. Weitere Informationen hierzu finden Sie in der LibCST- Dokumentation .

Nachdem wir uns ein wenig mit den Code-Mods vertraut gemacht haben, schauen wir uns ein praktisches Beispiel an. Die Mitarbeiter von Instagram arbeiten hart daran, die Codebasis des Projekts vollständig zu tippen. Kodmody hilft ihnen ernsthaft in dieser Angelegenheit.

Wenn wir einen bestimmten Satz untypisierter Funktionen haben, die eingegeben werden müssen, können wir versuchen, die von ihnen zurückgegebenen Typen durch die übliche Typinferenz zu generieren! Wenn eine Funktion beispielsweise nur Werte eines primitiven Typs zurückgibt, weisen wir der Funktion einfach diesen Typ von Rückgabewert zu. Wenn die Funktion beispielsweise Werte eines logischen Typs zurückgibt, wenn sie etwas mit etwas vergleicht oder etwas überprüft, können wir ihr den Rückgabewerttyp bool zuweisen. Wir haben festgestellt, dass dies im Verlauf der praktischen Arbeit mit der Instagram-Codebasis eine ziemlich sichere Operation ist.


Ermitteln der von Funktionen zurückgegebenen Wertetypen

Was aber, wenn die Funktion keinen Wert explizit oder implizit None zurückgibt? Wenn die Funktion nichts explizit zurückgibt, kann ihr der Typ None zugewiesen werden.

Dies kann im Gegensatz zum vorherigen Beispiel gefährlicher sein, da von Entwicklern häufig verwendete Muster vorhanden sind. In einer Basisklassenmethode können Sie beispielsweise eine NotImplemented Ausnahme NotImplemented , und in Methoden von Unterklassen, die diese Methode überschreiben, können Sie eine Zeichenfolge zurückgeben. Es ist wichtig zu beachten, dass alle diese Techniken heuristisch sind, aber die Ergebnisse ihrer Anwendung erweisen sich häufig als korrekt. Infolgedessen können sie als nützlich angesehen werden.


Funktionen, die nichts zurückgeben

Code-Module mit Pyre erweitern


Gehen wir noch einen Schritt weiter. Instagram verwendet Pyre, ein ausgewachsenes statisches Typprüfungssystem, das mypy ähnelt. Mit Pyre können wir Typen in einer Codebasis überprüfen. Was wäre, wenn wir die von Pyre generierten Daten verwenden würden, um die Funktionen von Codemods zu erweitern? Das Folgende ist ein Beispiel für solche Daten. Es ist leicht zu erkennen, dass fast alles vorhanden ist, was Sie zum automatischen Korrigieren von Typanmerkungen benötigen!

 $ pyre ƛ Found 2 type errors! testing/utils.py:7:0 Missing return annotation [3]: Returning `SomeClass` but no return type is specified. testing/utils.py:10:0 Missing return annotation [3]: Returning `testing.other.SomeOtherClass` but no return type is specified. 

Pyre führt während der Arbeit eine detaillierte Analyse der Ausführungsreihenfolge jeder Funktion durch. Infolgedessen kann dieses Tool manchmal mit sehr hoher Wahrscheinlichkeit davon ausgehen, dass eine nicht kommentierte Funktion zurückkehren sollte. Dies bedeutet, dass wir dieser Funktion den Rückgabetyp zuweisen, wenn Pyre glaubt, dass die Funktion einen einfachen Typ zurückgibt. Jetzt müssen wir jedoch möglicherweise auch Importbefehle verarbeiten. Dies bedeutet, dass wir wissen müssen, ob etwas lokal importiert oder deklariert wird. Später werden wir kurz auf dieses Thema eingehen.

Welche Vorteile ergeben sich aus dem automatischen Hinzufügen von Typinformationen, die im Code leicht angezeigt werden können? Typen sind Dokumentation! Wenn die Funktion vollständig eingegeben ist, muss der Entwickler ihren Code nicht lesen, um die Funktionen seines Aufrufs und die Funktionen der Verwendung der zurückgegebenen Daten herauszufinden.

 def get_description(page: WikiPage) -> Optional[str]:    if page.draft:        return None    return page.metadata["description"]  # <-    ? 

Viele von uns sind auf ähnlichen Python-Code gestoßen. Die Instagram-Codebasis hat auch etwas Ähnliches. Wenn die Funktion get_description nicht typisiert wäre, müssten Sie mehrere Module untersuchen, um herauszufinden, was sie zurückgibt. Selbst wenn es sich um einfachere Funktionen handelt, deren Rückgabewerte leicht abzuleiten sind, werden ihre typisierten Varianten gleichzeitig leichter wahrgenommen als untypisierte.

Darüber hinaus überprüft Pyre nicht die korrekte Funktion des Funktionskörpers, wenn die Funktion nicht vollständig mit Anmerkungen versehen ist. Im folgenden Beispiel some_function der Aufruf von some_function fehl. Es wäre schön, dies zu wissen, bevor der Code in Produktion geht.

 def some_function(in: int) -> bool:    return in > 0 def some_other_function():    if some_function("bla"): # <-             print("Yay!") 

In diesem Fall können wir einen ähnlichen Fehler feststellen, nachdem der Code in Produktion gegangen ist. Tatsache ist, dass some_other_function keine Annotation vom Rückgabetyp hat. Wenn wir es mit unseren heuristischen Mechanismen unter Verwendung des automatisch abgeleiteten Typs None kommentiert hätten, hätten wir ein Problem mit den Typen entdeckt, bevor es Probleme verursachen könnte. Dies ist natürlich ein künstliches Beispiel, aber auf Instagram sind solche Probleme ernst. Wenn Sie Millionen von Codezeilen haben, verpassen Sie bei der Codeüberprüfung möglicherweise solche Dinge, die in einem einfachen Beispiel völlig offensichtlich erscheinen.

In Instagram erlaubten die oben genannten Methoden, die auf automatisch abgeleiteten Typen basieren, die Eingabe von etwa 10% der Funktionen. Infolgedessen mussten die Benutzer nicht mehr Tausende und Abertausende von Funktionen manuell bearbeiten. Die Vorteile von getipptem Code liegen auf der Hand, aber dies führt im Rahmen unserer Konversation zu einem weiteren wichtigen Vorteil. Eine vollständig typisierte Codebasis eröffnet noch größere Möglichkeiten für die Verarbeitung von Code mithilfe von Codemods.

Wenn wir Typanmerkungen vertrauen, bedeutet dies, dass Pyre uns zusätzliche Möglichkeiten eröffnen kann. Schauen wir uns noch einmal das Beispiel an, in dem wir die Funktionen umbenannt haben. Was ist, wenn die Entität, die wir umbenennen, durch eine Klassenmethode und nicht durch eine globale Funktion dargestellt wird?


Funktion ist eine Klassenmethode

Wenn Sie die von Pyre empfangenen Typinformationen und den Codemodus kombinieren, in dem die Funktionen umbenannt werden, können Sie unerwartet Korrekturen daran vornehmen, wo die Funktion aufgerufen und wo sie deklariert wird! Da wir in diesem Beispiel wissen, was sich auf der linken Seite des a.fn Konstrukts befindet, wissen wir auch, dass es sicher ist, dieses Konstrukt in a.add zu a.add .

Erweiterte statische Analyse



Python verfügt über vier Arten von Bereichen: globaler Bereich, Bereich auf Klassen- und Funktionsebene, verschachtelter Bereich

Die Analyse des Umfangs ermöglicht es uns, noch leistungsfähigere Codemods zu verwenden. Erinnern Sie sich an eines der obigen Beispiele, in denen wir über die Tatsache gesprochen haben, dass das Hinzufügen von Typanmerkungen auch die Notwendigkeit bedeuten kann, mit Importbefehlen zu arbeiten? Wenn das System den Bereich analysiert, bedeutet dies, dass wir dank Importbefehlen, die lokal deklariert sind und welche fehlen, wissen können, welche in der Datei verwendeten Typen in der Datei vorhanden sind. Wenn Sie wissen, dass eine globale Variable von einem Funktionsargument überlappt wird, können Sie vermeiden, dass der Name eines solchen Arguments beim Umbenennen einer globalen Variablen versehentlich geändert wird.

Zusammenfassung


Bei unserer Suche nach Korrektur aller Fehler im Instagram-Code haben wir eines verstanden. Es besteht darin, dass die Suche nach dem Code, der repariert werden muss, oft wichtiger ist als die Suche selbst. Programmierer müssen häufig einfache Aufgaben lösen - wie das Umbenennen von Funktionen, das Hinzufügen von Argumenten zu Methoden oder das Teilen von Modulen in Teile. All dies ist alltäglich, aber die Größe unserer Codebasis bedeutet, dass eine Person nicht jede Zeile finden kann, die geändert werden muss. Deshalb ist es so wichtig, die Fähigkeiten von Codemods mit einer zuverlässigen statischen Analyse zu kombinieren. Auf diese Weise können wir die Teile des Codes, die geändert werden müssen, sicherer finden, was bedeutet, dass wir die Codemodi sicherer und leistungsfähiger machen können.

Liebe Leser! Benutzt du Code Mods?


Source: https://habr.com/ru/post/de473770/


All Articles