In meinem Team haben wir viele alte Sachen in einem großen monolithischen Projekt aufgeräumt (ganze Klassen, Methoden usw.).
Während dieser Reinigungsarbeiten habe ich mich gefragt, ob es eine Art Annotation oder Bibliothek gibt, die schicker ist als üblich @Deprecated
. Dies @FancyDeprecated
sollte verhindern, dass das Erstellen des Projekts erfolgreich ist, wenn Sie alten, nicht verwendeten Code nach Ablauf eines bestimmten Datums nicht bereinigt haben.
Ich habe im Internet gesucht und nichts gefunden, was die unten beschriebenen Funktionen aufweist:
- sollte eine Anmerkung oder ähnliches in dem Code sein, den Sie vor einem bestimmten Datum löschen möchten
- vor diesem Datum wird der Code kompiliert und alles wird normal funktionieren
- Nach diesem Datum wird der Code nicht kompiliert und es wird eine Meldung angezeigt, die Sie über das Problem warnt
Ich glaube, ich bin auf der Suche nach einem Einhorn ... Gibt es eine ähnliche Technologie für eine Programmiersprache?
Als Plan überlege ich mir die Möglichkeit, die Magie mit einigen Unit-Tests des Codes, der entfernt werden soll, zu machen, die zum "Stichtag" anfangen zu scheitern. Was denkst du darüber? Irgendeine bessere Idee?
quelle
Antworten:
Ich denke nicht, dass dies ein nützliches Feature wäre, wenn es das Kompilieren wirklich verbietet. Wenn am 01/06/2018 große Teile des Codes nicht kompiliert werden können, der am Vortag kompiliert wurde, wird Ihr Team diese Annotation schnell wieder entfernen, den Code bereinigen oder nicht.
Sie können jedoch dem Code eine benutzerdefinierte Anmerkung wie hinzufügen
Erstellen Sie ein kleines Tool, um nach diesen Anmerkungen zu suchen. (Ein einfacher Einzeiler in grep wird es tun, wenn Sie keine Reflektion verwenden möchten). In anderen Sprachen als Java kann ein für "grepping" geeigneter standardisierter Kommentar oder eine Präprozessordefinition verwendet werden.
Führen Sie das Tool dann kurz vor oder nach dem jeweiligen Datum aus. Wenn diese Anmerkung weiterhin angezeigt wird, erinnern Sie das Team daran, diese Codeteile dringend zu bereinigen.
quelle
Dies würde ein Merkmal darstellen, das als Zeitbombe bekannt ist . KEINE ZEITBOMBEN ERSTELLEN.
Code, egal wie gut Sie ihn strukturieren und dokumentieren, wird zu einer missverstandenen, 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 völlig überrascht, zum schlimmsten Zeitpunkt und ohne offensichtliche Abhilfe einfängt. Es gibt absolut keine Entschuldigung dafür, ein solches Problem absichtlich zu produzieren.
Betrachten Sie es so: Wenn Sie so gut organisiert sind und sich Ihrer Codebasis bewusst sind, dass Sie sich für die Veralterung interessieren und sich daran halten, brauchen Sie keinen Mechanismus im Code, der Sie daran erinnert. Andernfalls sind Sie möglicherweise auch in anderen Aspekten der Codebasis nicht auf dem neuesten Stand und können möglicherweise nicht rechtzeitig und korrekt auf den Alarm reagieren. Mit anderen Worten, Zeitbomben haben für niemanden einen guten Zweck. Sag einfach nein!
quelle
In C # würden Sie
ObsoleteAttribute
folgendermaßen vorgehen:Die Idee dabei ist, eine grundlegende Änderung für die betroffenen Benutzer so einfach wie möglich zu gestalten und sicherzustellen, dass sie die Funktion für mindestens eine Version der Bibliothek weiterhin verwenden können.
quelle
libfoo.so.2
undlibfoo.so.3
können gut miteinander koexistieren, können Ihre Downstreams weiterhin die alte Bibliothek verwenden, bis sie umgeschaltet werden.Sie haben falsch verstanden, was "veraltet" bedeutet. Veraltet bedeutet:
Oxford Wörterbücher
Per Definition wird ein veraltetes Feature immer noch kompiliert.
Sie möchten die Funktion an einem bestimmten Datum entfernen . Das ist gut. Auf diese Weise entfernen Sie es an diesem Datum .
Markieren Sie es bis dahin als veraltet, veraltet oder wie auch immer Ihre Programmiersprache es nennt. Geben Sie in der Nachricht das Datum an, an dem sie entfernt wird, und das Datum, an dem sie ersetzt wird. Dadurch werden Warnungen generiert, die darauf hinweisen, dass andere Entwickler eine neue Verwendung vermeiden und die alte nach Möglichkeit ersetzen sollten. Diese Entwickler werden es entweder einhalten oder ignorieren, und jemand wird sich mit den Konsequenzen davon auseinandersetzen müssen, wenn es entfernt wird. (Abhängig von der Situation sind es möglicherweise Sie oder die Entwickler, die es verwenden.)
quelle
Vergessen Sie nicht, dass Sie die Fähigkeit behalten 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 durchführen.
Außerdem scheint es eine triviale Lösung zu sein, die Uhr meines Computers ein oder zwei Jahre vor dem Kompilieren zurückzusetzen.
Denken Sie daran, "veraltet" ist eine Warnung, dass in Zukunft etwas weggehen wird. Wenn Sie verhindern möchten, dass Benutzer diese API verwenden, entfernen Sie einfach den zugehörigen Code . Es hat keinen Sinn, Code in der Codebasis zu belassen, wenn ein Mechanismus ihn unbrauchbar macht. Wenn Sie den Code entfernen, erhalten Sie die gesuchten Überprüfungen zur Kompilierungszeit und haben keine triviale Problemumgehung.
Bearbeiten: Ich sehe, dass Sie in Ihrer Frage auf "alten unbenutzten Code" verweisen. Wenn der Code wirklich nicht verwendet wird , macht es keinen Sinn, ihn zu missbrauchen. Löschen Sie es einfach.
quelle
Ich habe noch nie zuvor eine solche Funktion gesehen - eine Anmerkung, die nach einem bestimmten Datum wirksam wird.
Das
@Deprecated
kann aber ausreichen. Fangen Sie Warnungen in CI ab und lehnen Sie ab, 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 Ihr Problem nicht vollständig löst (z. B. würden lokale Builds auf Entwicklermaschinen trotz Warnungen weiterhin erfolgreich sein) und davon ausgehen, dass eine CI-Pipeline eingerichtet und ausgeführt wird.
quelle
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ätzliche Anstrengungen unternehmen (etwa 15 Minuten?) Und die Compiler-Warnung im Build-Bericht aufgreifen, die Ihre fortlaufende Integration für jeden Build liefert.
Erinnerungen, dass Code repariert werden muss, sind gut und notwendig. Manchmal haben diese Erinnerungen strenge reale Fristen, so dass es auch notwendig sein kann, sie auf einen Timer zu setzen.
Das Ziel ist es, die Leute 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, erledigt dies nicht nur, sondern diese Funktion ist selbst ein Problem, das behoben werden muss innerhalb eines bestimmten Zeitrahmens festgelegt werden.
quelle
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 verwendet wird, ist es wichtig zu berücksichtigen, dass es sich nicht um die "tatsächliche" Zeit handelt, sondern nur um eine logische Darstellung.
Wie andere darauf hingewiesen haben, ist es trivial, eine andere Zeit einzugeben und diese "Frist" zu überschreiten, wenn Sie mit diesem Mechanismus eine "Frist" festgelegt haben. Das gleiche gilt für aufwändigere Mechanismen wie das Fragen an einen NTP-Server (auch über eine "sichere" Verbindung, da wir unsere eigenen Zertifikate, Zertifizierungsstellen oder sogar die Kryptobibliotheken ersetzen können). Auf den ersten Blick mag es so aussehen, als ob solche Personen daran schuld sind, an Ihrem Mechanismus herumzuarbeiten, aber es kann sein, dass dies automatisch und aus guten Gründen geschieht . Zum Beispiel ist es eine gute Idee, reproduzierbare Builds zu haben , und Tools, die dies unterstützen, könnten solche nicht deterministischen Systemaufrufe automatisch zurücksetzen / abfangen. libfaketime macht genau das,
1970-01-01 00:00:01
Setzt alle Zeitstempel der Datei auf , die Aufnahme- / Wiedergabefunktion von Qemu fälscht alle Hardware-Interaktionen usw.Dies ähnelt dem Goodhartschen 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 sich im Allgemeinen nicht mit der Systemuhr anlegen, 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. die UNIX-Zeit, da sie spezifischer für das ist, was Sie interessieren (Ändern von Feature-Sets / APIs) und daher weniger wahrscheinlich auf orthogonalen Bedenken herumtrampeln (z. B. mit der UNIX-Zeit herumspielen) Die Umgehung Ihrer Frist kann dazu führen, dass Protokolldateien, Cronjobs, Caches usw. beschädigt werden.
Wie andere bereits gesagt haben, 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, um den Verbrauchern das Auffinden und Aktualisieren ihrer Verwendung zu erleichtern) Funktionen vollständig. Sie können diese sofort nacheinander veröffentlichen, wenn Sie möchten, da (wieder) Versionen lediglich eine logische Darstellung der Zeit sind, müssen sie nicht mit der "tatsächlichen" Zeit zusammenhängen. Die semantische Versionierung kann hier Abhilfe schaffen.
Das alternative Modell ist das "Ziehen" der Änderung. Dies ähnelt Ihrem "Plan B": Fügen Sie der konsumierenden Anwendung einen Test hinzu, der überprüft, ob die Version dieser Abhängigkeit mindestens dem neuen Wert entspricht. 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 ) gilt oder nicht und daher einen Test verdient. oder ob es sich nur um ein "privates" Implementierungsdetail handelt, das nur im Rahmen tatsächlicher Unit ( of Functional ) -Tests 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 (überprüfen Sie beispielsweise, ob die Python-Version> = 3.x ist). Wenn nicht, dann nichtFügen Sie den Test hinzu (da er spröde, nicht aussagekräftig und zu restriktiv ist). Wenn Sie die Bibliothek kontrollieren, gehen Sie die "Push" -Route hinunter. Wenn Sie die Bibliothek nicht kontrollieren, verwenden Sie einfach die mitgelieferte Version: Wenn Ihre Tests erfolgreich sind, lohnt es sich nicht, sich einzuschränken. Wenn sie nicht bestehen, ist das genau dort Ihre "Frist"!
Es gibt einen anderen Ansatz, wenn Sie bestimmte Verwendungszwecke 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 von diesen ist unter verschiedenen Umständen anwendbar.
quelle
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 frei verwendet werden können.
Dies ist immer problematisch, da die Kundenteams einfach nichts ändern möchten. Daher benötigen Sie häufig einige Runden der Whitelist, wenn Sie mit den jeweiligen Kunden zusammenarbeiten, um eine Frist für die Migration zu vereinbaren und ihnen möglicherweise Unterstützung anzubieten.
quelle
Eine Anforderung besteht darin, einen Zeitbegriff in den Build einzuführen. In C, C ++ oder anderen Sprachen / baut Systeme , die eine C-ähnliche Präprozessor verwenden 1 , könnte man einen Zeitstempel durch definiert für den Preprozessor auf Kompilierzeit vorstellen:
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 abfängt, den jemand nicht definiert hat
TIMESTAMP
.Alternativ könnte man den fraglichen Code einfach "definieren", wenn die Zeit gekommen ist. Das würde das Programm kompilieren lassen, vorausgesetzt, niemand benutzt es. Angenommen, wir haben einen Header, der eine API definiert, "api.h", und wir erlauben
old()
nach einer bestimmten Zeit keinen Aufruf mehr :Ein ähnliches Konstrukt würde wahrscheinlich den
old()
Funktionskörper von einer Quelldatei entfernen.Natürlich ist dies kein Narrensicher. man kann einfach ein altes
TIMESTAMP
für den an anderer Stelle erwähnten Freitag-Nacht-Notfallbau definieren . 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 der Client-Code mit veralteten Binärdateien verknüpft wird.
1 C # unterstützt nur die einfache Definition von Präprozessorsymbolen, keine numerischen Werte, wodurch diese Strategie nicht praktikabel ist.
quelle
TIMESTAMP
bei jedem Build neu kompiliert wird. Es wird auch Werkzeuge wie außer Gefecht setzenccache
. Dies bedeutet, dass die typische Kompilierungszeit für inkrementelle Builds erheblich zunimmt, je nachdem, wie stark die Codebasis von Funktionen betroffen ist, die auf diese Weise veraltet sind.TIMESTAMP
Wert durch 86400 dividiert wird, um eine tägliche Granularität und damit weniger Neukompilierungen zu erzielen .In Visual Studio können Sie ein vorab erstelltes 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 ):
Lesen Sie die anderen Antworten auf dieser Seite, bevor Sie dieses Skript verwenden.
quelle
Ich verstehe das Ziel dessen, was Sie versuchen zu tun. Aber wie andere bereits erwähnt haben, ist das Build-System / der Compiler wahrscheinlich nicht der richtige Ort, um dies durchzusetzen. Ich würde vorschlagen, dass die natürlichere Ebene zur Durchsetzung dieser Richtlinie entweder die SCM- oder die Umgebungsvariablen ist.
Wenn Sie Letzteres tun, fügen Sie im Grunde genommen ein Feature-Flag hinzu, das einen Vor-Verfalls-Lauf kennzeichnet. Überprüfen Sie jedes Mal, wenn Sie die veraltete Klasse erstellen oder eine veraltete Methode aufrufen, das Feature-Flag. Definieren Sie einfach eine einzelne statische Funktion
assertPreDeprecated()
und fügen Sie diese jedem veralteten Codepfad hinzu. Wenn dies festgelegt ist, ignorieren Sie Assert-Aufrufe. Wenn es keine Ausnahme gibt. Wenn das Datum abgelaufen ist, deaktivieren Sie das Feature-Flag in der Laufzeitumgebung. Veraltete Aufrufe des Codes werden in den Laufzeitprotokollen angezeigt.Für eine SCM-basierte Lösung gehe ich davon aus, dass Sie Git und Git-Flow verwenden. (Wenn nicht, ist die Logik leicht an andere VCS anpassbar). Erstellen Sie eine neue Niederlassung
postDeprecated
. Löschen Sie in diesem Zweig den gesamten veralteten Code und beginnen Sie mit dem Entfernen aller Verweise, bis dieser kompiliert ist. Alle normalen Änderungen werden weiterhin an dermaster
Zweigstelle vorgenommen. Führen Sie alle nicht veralteten Codeänderungenmaster
wieder in zusammen,postDeprecated
um die Integrationsprobleme zu minimieren.Erstellen Sie nach Ablauf des Verfallsdatums einen neuen
preDeprecated
Zweig vonmaster
. DannpostDeprecated
wieder zusammenführen inmaster
. Angenommen, Ihre Freigabe verlässt denmaster
Zweig, sollten Sie jetzt den nach dem Datum veralteten Zweig verwenden. Wenn es einen Notfall gibt oder Sie die Ergebnisse nicht rechtzeitig bereitstellen können, können Sie jederzeit zupreDeprecated
diesem Zweig zurückkehren und die erforderlichen Änderungen vornehmen.quelle