it-swarm.com.de

Verhindern Sie, dass veralteter Code nach Erreichen einer Frist kompiliert wird

In meinem Team haben wir in einem großen monolithischen Projekt (ganze Klassen, Methoden usw.) viele alte Sachen gereinigt.

Während dieser Reinigungsaufgaben habe ich mich gefragt, ob es eine Art Anmerkung oder Bibliothek gibt, die schicker ist als die üblichen @Deprecated. Diese @FancyDeprecated sollte verhindern, dass die Erstellung des Projekts erfolgreich ist, wenn Sie alten, nicht verwendeten Code nach Ablauf eines bestimmten Datums nicht mehr bereinigt haben.

Ich habe im Internet gesucht und nichts gefunden, das über die unten beschriebenen Funktionen verfügt:

  • sollte eine Anmerkung oder ähnliches sein, die in den Code eingefügt werden soll, den Sie vor einem bestimmten Datum löschen möchten
  • vor diesem Datum wird der Code kompiliert und alles funktioniert normal
  • nach diesem Datum wird der Code nicht mehr kompiliert und Sie erhalten eine Meldung, die Sie vor dem Problem warnt

Ich glaube, ich suche ein Einhorn ... Gibt es eine ähnliche Technologie für eine Programmiersprache?

Als Plan B denke ich über die Möglichkeit nach, die Magie mit einigen Komponententests des Codes zu erzeugen, der entfernt werden soll und die zum "Termin" zu scheitern beginnen. Was denkst du darüber? Irgendeine bessere Idee?

68
Arcones

Ich denke nicht, dass dies eine nützliche Funktion wäre, wenn sie das Kompilieren wirklich verbietet. Wenn am 01/06/2018 große Teile des Codes nicht kompiliert werden, was am Tag zuvor kompiliert wurde, wird Ihr Team diese Anmerkung schnell wieder entfernen, Code bereinigt oder nicht.

Sie können dem Code jedoch einige benutzerdefinierte Anmerkungen hinzufügen, z

@Deprecated_after_2018_07_31

und erstellen Sie ein kleines Tool, um nach diesen Anmerkungen zu suchen. (Ein einfacher Einzeiler in grep reicht aus, wenn Sie keine Reflexion verwenden möchten.) In anderen Sprachen als Java kann ein standardisierter Kommentar verwendet werden, der zum "Greppen" geeignet ist, oder eine Präprozessordefinition.

Führen Sie das Tool dann kurz vor oder nach dem jeweiligen Datum aus. Wenn diese Anmerkung weiterhin gefunden wird, erinnern Sie das Team daran, diese Codeteile dringend zu bereinigen.

62
Doc Brown

Dies würde ein Merkmal darstellen, das als Zeitbombe bekannt ist. ERSTELLEN SIE KEINE ZEITBOMBEN.

Code, egal wie gut Sie ihn strukturieren und dokumentieren, wird wird zu einer schlecht verstandenen, fast mythischen Black Box, wenn er über ein bestimmtes Alter hinaus lebt. Das Letzte, was irgendjemand in der Zukunft braucht, ist ein weiterer seltsamer Fehlermodus, der sie zum schlimmsten Zeitpunkt und ohne offensichtliche Abhilfe völlig überrascht . Es gibt absolut keine Entschuldigung dafür, ein solches Problem absichtlich zu verursachen.

Betrachten Sie es so: Wenn Sie organisiert sind und Ihre Codebasis so gut kennen, dass Sie sich um Veralterung kümmern und diese durcharbeiten, benötigen Sie keinen Mechanismus in der Code, der Sie daran erinnert. Wenn dies nicht der Fall ist, sind Sie wahrscheinlich auch in Bezug auf andere Aspekte der Codebasis nicht auf dem neuesten Stand und können wahrscheinlich nicht rechtzeitig und korrekt auf den Alarm reagieren. Mit anderen Worten, Zeitbomben haben für niemanden einen guten Zweck. Sag einfach nein!

283
Kilian Foth

In C # würden Sie ObsoleteAttribute folgendermaßen verwenden:

  • In Version 1 liefern Sie die Funktion aus. Eine Methode, Klasse, was auch immer.
  • In Version 2 wird eine bessere Funktion ausgeliefert, die die ursprüngliche Funktion ersetzen soll. Sie fügen der Funktion ein veraltetes Attribut hinzu, setzen es auf "Warnung" und geben eine Meldung mit der Aufschrift "Diese Funktion ist veraltet. Verwenden Sie stattdessen die bessere Funktion. In Version 3 dieser Bibliothek, die für solche und solche freigegeben wird Bei einem Datum ist die Verwendung dieser Funktion ein Fehler. " Jetzt können Benutzer der Funktion sie weiterhin verwenden, haben jedoch Zeit, ihren Code zu aktualisieren, um die neue Funktion zu verwenden.
  • In Version 3 aktualisieren Sie das Attribut so, dass es eher ein Fehler als eine Warnung ist, und aktualisieren die Meldung mit der Meldung "Diese Funktion ist veraltet. Verwenden Sie stattdessen die bessere Funktion. In Version 4 dieser Bibliothek, die für solche und solche freigegeben wird ein Datum, das diese Funktion werfen wird. " Benutzer, die Ihre vorherige Warnung nicht beachtet haben, erhalten weiterhin eine hilfreiche Meldung, in der sie erfahren, wie das Problem behoben werden kann, und sie müssen es beheben, da ihr Code jetzt nicht kompiliert wird.
  • In Version 4 ändern Sie die Funktion so, dass eine schwerwiegende Ausnahme ausgelöst wird, und ändern die Meldung so, dass die Funktion in der nächsten Version vollständig entfernt wird.
  • In Version 5 entfernen Sie die Funktion vollständig, und wenn sich Benutzer beschweren, haben Sie ihnen drei Release-Zyklen mit fairer Warnung gegeben, und sie können Version 2 immer weiter verwenden, wenn sie sich stark dafür fühlen.

Hier geht es darum, eine Änderung so einfach wie möglich für die betroffenen Benutzer vorzunehmen und sicherzustellen, dass sie die Funktion weiterhin für mindestens eine Version der Bibliothek verwenden können.

71
Eric Lippert

Sie haben falsch verstanden, was "veraltet" bedeutet. Veraltet bedeutet:

verwendbar sein, aber als veraltet angesehen und am besten vermieden werden, typischerweise weil es ersetzt wurde.

Oxford Dictionaries

Per Definition wird ein veraltetes Feature weiterhin kompiliert.

Sie möchten die Funktion an einem bestimmten Datum entfernen . Das ist gut. Die Art und Weise, wie Sie das tun, ist , dass Sie es an diesem Datum entfernen .

Bis dahin als veraltet, veraltet oder wie auch immer Ihre Programmiersprache es nennt, markieren. Geben Sie in der Nachricht das Datum an, an dem sie entfernt wird, und das Objekt, das sie ersetzt. Dadurch werden Warnungen generiert, die darauf hinweisen, dass andere Entwickler eine neue Verwendung vermeiden und die alte Verwendung nach Möglichkeit ersetzen sollten. Diese Entwickler werden es entweder einhalten oder ignorieren, und jemand muss sich mit den Konsequenzen auseinandersetzen, wenn es entfernt wird. (Abhängig von der Situation können es Sie oder die Entwickler sein, die es verwenden.)

26
jpmc26

Vergessen Sie nicht, dass Sie weiterhin in der Lage sein müssen, ältere Versionen des Codes zu erstellen und zu debuggen, um bereits veröffentlichte Versionen der Software zu unterstützen. Wenn Sie einen Build nach einem bestimmten Datum sabotieren, riskieren Sie auch, dass Sie in Zukunft keine legitimen Wartungs- und Supportarbeiten mehr ausführen.

Außerdem scheint es eine triviale Problemumgehung zu sein, die Uhr meiner Maschine ein oder zwei Jahre vor dem Kompilieren zurückzustellen.

Denken Sie daran, "veraltet" ist eine Warnung, dass in Zukunft etwas verschwinden wird. Wenn Sie verhindern möchten, dass Benutzer diese API verwenden, entfernen Sie einfach den zugehörigen Code. Es macht keinen Sinn, Code in der Codebasis zu belassen, wenn ein Mechanismus ihn unbrauchbar macht. Durch das Entfernen des Codes erhalten Sie die gewünschten Überprüfungen zur Kompilierungszeit und es gibt keine einfache Problemumgehung.

Bearbeiten: Ich sehe, dass Sie in Ihrer Frage auf "alten unbenutzten Code" verweisen. Wenn der Code wirklich nbenutzt ist, macht es keinen Sinn, ihn zu verwerfen. Löschen Sie es einfach.

12
bta

Ich habe noch nie eine solche Funktion gesehen - eine Anmerkung, die nach einem bestimmten Datum wirksam wird.

Das @Deprecated kann jedoch ausreichend sein. Fangen Sie Warnungen in CI ab und lassen Sie es ablehnen, den Build zu akzeptieren, falls vorhanden. Dies verlagert die Verantwortung vom Compiler auf Ihre Build-Pipeline, hat jedoch den Vorteil, dass Sie die Build-Pipeline (halb) einfach ändern können, indem Sie zusätzliche Schritte hinzufügen.

Beachten Sie, dass diese Antwort nicht Ihr Problem vollständig löst (z. B. lokale Builds auf Entwicklercomputern würden trotz Warnungen immer noch erfolgreich sein) und davon ausgeht, dass Sie eine CI-Pipeline eingerichtet und ausgeführt haben.

6
Mael

Sie suchen nach Kalendern oder Aufgabenlisten .

Eine andere Alternative besteht darin, benutzerdefinierte Compiler-Warnungen oder Compiler-Meldungen zu verwenden, wenn Sie nur wenige oder gar keine Warnungen in Ihrer Codebasis haben. Wenn Sie zu viele Warnungen haben, müssen Sie zusätzlichen Aufwand (ca. 15 Minuten?) Aufwenden und die Compiler-Warnung im Build-Bericht abrufen, die Ihre kontinuierliche Integration für jeden Build liefert.

Erinnerungen, dass Code repariert werden muss, sind gut und notwendig. Manchmal haben diese Erinnerungen strenge Fristen in der realen Welt, daher kann es auch erforderlich sein, sie auf einen Timer zu setzen.

Ziel ist es, die Benutzer kontinuierlich daran zu erinnern, dass das Problem besteht und innerhalb eines bestimmten Zeitrahmens behoben werden muss - eine Funktion, die den Build zu einem bestimmten Zeitpunkt einfach abbricht, tut dies nicht nur, sondern diese Funktion ist selbst ein Problem, das erforderlich ist innerhalb eines bestimmten Zeitrahmens festgelegt werden.

4
Peter

Eine Möglichkeit, darüber nachzudenken, ist , was Sie unter Zeit/Datum verstehen. Computer wissen nicht, was diese Konzepte sind: Sie müssen irgendwie programmiert werden. Es ist durchaus üblich, Zeiten im UNIX-Format "Sekunden seit der Epoche" darzustellen, und es ist üblich, einen bestimmten Wert über Betriebssystemaufrufe in ein Programm einzugeben . Unabhängig davon, wie häufig diese Verwendung ist, ist es wichtig zu bedenken, dass es nicht die "tatsächliche" Zeit ist: Es ist nur eine logische Darstellung.

Wie andere bereits betont haben, ist es trivial, eine andere Zeit einzugeben und diese "Frist" zu überschreiten, wenn Sie mit diesem Mechanismus eine "Frist" festgelegt haben. Gleiches gilt für ausgefeiltere Mechanismen wie das Abfragen eines NTP - Servers (auch über eine "sichere" Verbindung, da wir unsere eigenen Zertifikate, Zertifizierungsstellen ersetzen oder sogar die Kryptobibliotheken patchen können) Es scheint, dass solche Personen schuld daran sind, Ihren Mechanismus zu umgehen, aber es kann sein, dass dies automatisch und für gute Gründe . Zum Beispiel ist es eine gute Idee, reproduzierbare Builds zu haben, und Tools, die dies unterstützen, können ein solches nicht deterministisches System automatisch zurücksetzen/abfangen Aufrufe. libfaketime macht genau das, Nix setzt alle Zeitstempel aller Dateien auf 1970-01-01 00:00:01, Qemus Aufnahme-/Wiedergabefunktion fälscht alle Hardware-Interaktionen usw.

Dies ähnelt Goodharts Gesetz : Wenn Sie das Verhalten eines Programms von der logischen Zeit abhängig machen, ist die logische Zeit kein gutes Maß für die "tatsächliche" Zeit mehr. Mit anderen Worten, die Leute werden im Allgemeinen nicht mit der Systemuhr herumspielen, aber sie werden es tun, wenn Sie ihnen einen Grund dafür geben.

Es gibt andere logische Darstellungen der Zeit: Eine davon ist die Version der Software (entweder Ihre App oder eine Abhängigkeit). Dies ist eine wünschenswertere Darstellung für eine "Frist" als z.B. UNIX-Zeit, da sie spezifischer für das ist, was Sie interessiert (Ändern von Funktionssätzen/APIs) und daher weniger wahrscheinlich auf orthogonale Bedenken tritt (z. B. wenn Sie mit der UNIX-Zeit herumspielen, um Ihre Frist zu umgehen, kann dies dazu führen, dass Protokolldateien beschädigt werden und Cron-Jobs ausgeführt werden , Caches usw.).

Wie bereits erwähnt, können Sie, wenn Sie die Bibliothek steuern und diese Änderung "pushen" möchten, eine neue Version pushen, die die Funktionen nicht mehr unterstützt (was zu Warnungen führt, damit die Verbraucher ihre Verwendung finden und aktualisieren können), und dann eine andere neue Version, die die Funktion entfernt Funktionen vollständig. Sie können diese unmittelbar nacheinander veröffentlichen, wenn Sie möchten, da (wieder) Versionen nur eine logische Darstellung der Zeit sind und nicht mit der "tatsächlichen" Zeit in Beziehung gesetzt werden müssen. Hier kann die semantische Versionierung hilfreich sein.

Das alternative Modell besteht darin, die Änderung zu "ziehen". Dies ist wie bei Ihrem "Plan B": Fügen Sie der konsumierenden Anwendung einen Test hinzu, der überprüft, ob die Version dieser Abhängigkeit mindestens den neuen Wert aufweist. Wie üblich Rot/Grün/Refaktor, um diese Änderung durch die Codebasis zu verbreiten. Dies ist möglicherweise angemessener, wenn die Funktionalität nicht "schlecht" oder "falsch" ist, sondern nur "schlecht für diesen Anwendungsfall geeignet".

Eine wichtige Frage beim "Pull" -Ansatz ist, ob die Abhängigkeitsversion als "Einheit" ( der Funktionalität ) zählt und daher getestet werden sollte. oder ob es sich nur um ein "privates" Implementierungsdetail handelt, das nur als Teil der tatsächlichen Unit-Tests ( der Funktionalität ) ausgeführt werden sollte. Ich würde sagen: Wenn die Unterscheidung zwischen den Versionen der Abhängigkeit wirklich als Merkmal Ihrer Anwendung gilt, führen Sie den Test durch (indem Sie beispielsweise überprüfen, ob die Version Python>> 3.x ist) Wenn nicht, dann füge den Test nicht hinzu (da er spröde, nicht informativ und zu restriktiv ist). Wenn du die Bibliothek kontrollierst, gehe Wenn Sie die Bibliothek nicht kontrollieren, verwenden Sie einfach die bereitgestellte Version: Wenn Ihre Tests erfolgreich sind, lohnt es sich nicht, sich einzuschränken. Wenn sie nicht erfolgreich sind, ist dies genau dort Ihre "Frist"!

Es gibt einen anderen Ansatz, wenn Sie bestimmte Verwendungen der Funktionen einer Abhängigkeit verhindern möchten (z. B. das Aufrufen bestimmter Funktionen, die mit dem Rest Ihres Codes nicht gut funktionieren), insbesondere wenn Sie die Abhängigkeit nicht kontrollieren: Lassen Sie Ihre Codierungsstandards verbieten Ich rate von der Verwendung dieser Funktionen ab und füge Ihrem Linter Überprüfungen hinzu.

Jedes davon wird unter verschiedenen Umständen anwendbar sein.

3
Warbo

Eine Anforderung besteht darin, einen Zeitbegriff in den Build einzuführen. In C, C++ oder anderen Sprachen/Build-Systemen, die einen C-ähnlichen Präprozessor verwenden1könnte man einen Zeitstempel durch Definitionen für den Präprozessor zur Erstellungszeit einführen: CPPFLAGS=-DTIMESTAMP()=$(date '+%s'). Dies würde wahrscheinlich in einem Makefile passieren.

Im Code würde man dieses Token vergleichen und einen Fehler verursachen, wenn die Zeit abgelaufen ist. Beachten Sie, dass die Verwendung eines Funktionsmakros den Fall erfasst, dass jemand TIMESTAMP nicht definiert hat.

#if TIMESTAMP() == 0 || TIMESTAMP() > 1520616626
#   error "The time for this feature has run out, sorry"
#endif

Alternativ könnte man den fraglichen Code einfach "definieren", wenn die Zeit gekommen ist. Dies würde es dem Programm ermöglichen, zu kompilieren, vorausgesetzt, niemand verwendet es. Angenommen, wir haben einen Header, der eine API definiert, "api.h", und wir erlauben nicht, old() nach einer bestimmten Zeit aufzurufen:

//...
void new1();
void new2();
#if TIMESTAMP() < 1520616626
   void old();
#endif
//...

Ein ähnliches Konstrukt würde wahrscheinlich den Funktionskörper von old() aus einer Quelldatei entfernen.

Natürlich ist dies kein Kinderspiel; man kann einfach ein altes TIMESTAMP definieren, falls der Freitagabend-Notfall-Build an anderer Stelle erwähnt wird. Aber das finde ich ziemlich vorteilhaft.

Dies funktioniert natürlich nur, wenn die Bibliothek neu kompiliert wird - danach existiert der veraltete Code einfach nicht mehr in der Bibliothek. Es würde jedoch nicht verhindern, dass Client-Code mit veralteten Binärdateien verknüpft wird.


1

Sie verwalten dies auf Paket- oder Bibliotheksebene. Sie steuern ein Paket und dessen Sichtbarkeit. Sie können die Sichtbarkeit zurückziehen. Ich habe dies intern bei großen Unternehmen gesehen und es ist nur in Kulturen sinnvoll, die das Eigentum an Paketen respektieren, selbst wenn die Pakete Open Source sind oder kostenlos verwendet werden können.

Dies ist immer chaotisch, da die Kundenteams einfach nichts ändern möchten. Daher benötigen Sie häufig einige Runden nur auf der Whitelist, wenn Sie mit den jeweiligen Kunden zusammenarbeiten, um einen Termin für die Migration zu vereinbaren und ihnen möglicherweise Unterstützung anzubieten.

1
djechlin

In Visual Studio können Sie ein vorgefertigtes Skript einrichten, das nach einem bestimmten Datum einen Fehler auslöst. Dies verhindert das Kompilieren. Hier ist ein Skript, das am oder nach dem 12. März 2018 einen Fehler auslöst ( von hier übernommen ):

@ECHO OFF

SET CutOffDate=2018-03-12

REM These indexes assume %DATE% is in format:
REM   Abr MM/DD/YYYY - ex. Sun 01/25/2015
SET TodayYear=%DATE:~10,4%
SET TodayMonth=%DATE:~4,2%
SET TodayDay=%DATE:~7,2%

REM Construct today's date to be in the same format as the CutOffDate.
REM Since the format is a comparable string, it will evaluate date orders.
IF %TodayYear%-%TodayMonth%-%TodayDay% GTR %CutOffDate% (
    ECHO Today is after the cut-off date.
    REM throw an error to prevent compilation
    EXIT /B 2
) ELSE (
    ECHO Today is on or before the cut-off date.
)

Lesen Sie unbedingt die anderen Antworten auf dieser Seite, bevor Sie dieses Skript verwenden.

0
user2023861