W moim zespole myjemy wiele starych rzeczy w dużym monolitycznym projekcie (całe klasy, metody itp.).
Podczas tych prac porządkowych zastanawiałem się, czy jest coś w rodzaju adnotacji lub bardziej wyszukanego w bibliotece niż zwykle @Deprecated
. To @FancyDeprecated
powinno zapobiec gromadzeniu projektu od powodzenia, jeśli nie czyścić stare niewykorzystany kod po określonej dacie minęło.
Szukałem w Internecie i nie znalazłem niczego, co ma możliwości opisane poniżej:
- powinna być adnotacją lub czymś podobnym, aby umieścić w kodzie, który chcesz usunąć przed określoną datą
- przed tą datą kod się skompiluje i wszystko będzie działać normalnie
- po tej dacie kod nie będzie się kompilował i wyświetli komunikat ostrzegający o problemie
Myślę, że szukam jednorożca ... Czy istnieje podobna technologia dla dowolnego języka programu?
Jako plan BI myślę o możliwości wykonania magii za pomocą niektórych testów jednostkowych kodu, który ma zostać usunięty, które zaczynają kończyć się niepowodzeniem w „terminie”. Co o tym myślisz? Masz lepszy pomysł?
źródło
Odpowiedzi:
Nie sądzę, aby była to przydatna funkcja, gdy naprawdę zabrania kompilacji. Kiedy na 01.06.2018 duże części kodu nie zostaną skompilowane, co skompilowano dzień wcześniej, Twój zespół szybko usunie tę adnotację, wyczyści kod lub nie.
Możesz jednak dodać niestandardową adnotację do kodu, np
i zbuduj małe narzędzie do skanowania w poszukiwaniu tych adnotacji. (Wystarczy prosta linijka w grep, jeśli nie chcesz używać odbicia). W innych językach niż Java można użyć standardowego komentarza odpowiedniego do „grepping” lub definicji preprocesora.
Następnie uruchom to narzędzie na krótko przed określoną datą lub po niej, a jeśli nadal znajdzie tę adnotację, przypomnij zespołowi, aby pilnie wyczyściła te części kodu.
źródło
Stanowiłoby to cechę znaną jako bomba zegarowa . NIE TWORZENIE BOMB CZASU.
Kod, bez względu na to, jak dobrze zorganizować i dokumentować je, będzie przekształcić się źle zrozumiał niemal mitycznej czarnej skrzynki, jeśli mieszka poza pewnym wieku. Ostatnią rzeczą, jakiej potrzebuje ktoś w przyszłości, jest kolejny dziwny tryb awarii, który zaskakuje go całkowicie w najgorszym możliwym czasie i bez oczywistego lekarstwa. Nie ma absolutnie usprawiedliwienia dla celowego wywołania takiego problemu.
Spójrz na to w ten sposób: jeśli jesteś wystarczająco zorganizowany i zdajesz sobie sprawę ze swojej bazy kodu, że zależy ci na przestarzałości i postępujesz zgodnie z nią, to nie potrzebujesz mechanizmu w kodzie, aby ci to przypominać. Jeśli tak nie jest, istnieje prawdopodobieństwo, że nie będziesz na bieżąco z innymi aspektami bazy kodu i prawdopodobnie nie będziesz w stanie odpowiedzieć na alarm w odpowiednim czasie i poprawnie. Innymi słowy, bomby zegarowe nikomu nie służą. Po prostu powiedz nie!
źródło
W języku C # użyłbyś
ObsoleteAttribute
w następujący sposób:Chodzi tutaj o to, aby dokonać przełomowej zmiany tak bezboleśnie, jak to możliwe, dla użytkowników, których to dotyczy, i zapewnić, że będą mogli nadal korzystać z tej funkcji w co najmniej jednej wersji biblioteki.
źródło
libfoo.so.2
ilibfoo.so.3
mogą współistnieć ze sobą w porządku, twój downstream może nadal używać starej biblioteki, dopóki nie zostanie przełączony.Źle zrozumiałeś, co oznacza „przestarzałe”. Przestarzałe oznacza:
Słowniki Oxford
Z definicji przestarzała funkcja będzie się nadal kompilować.
Chcesz usunąć tę funkcję w określonym dniu. W porządku. W ten sposób usuwasz go w tym dniu .
Do tego czasu oznaczaj jako przestarzałe, przestarzałe lub jak to nazywa Twój język programowania. W wiadomości podaj datę, o której zostanie usunięty, oraz informację, która go zastępuje. Spowoduje to wygenerowanie ostrzeżeń, wskazujących, że inni programiści powinni unikać nowego użycia i w miarę możliwości zastępować stare użycie. Ci programiści albo zastosują się do niego, albo go zignorują i ktoś będzie musiał poradzić sobie z konsekwencjami tego, gdy zostanie on usunięty. (W zależności od sytuacji może to być Ty lub programiści.)
źródło
Nie zapominaj, że musisz zachować możliwość budowania i debugowania starszych wersji kodu, aby obsługiwać wersje oprogramowania, które zostały już wydane. Sabotowanie kompilacji po określonej dacie oznacza, że ryzykujesz również, że nie będziesz w stanie wykonywać legalnych prac konserwacyjnych i wsparcia w przyszłości.
Wydaje się to również trywialnym obejściem, aby ustawić zegar mojej maszyny o rok lub dwa wstecz przed kompilacją.
Pamiętaj, że „przestarzałe” to ostrzeżenie, że coś zniknie w przyszłości. Jeśli chcesz zdecydowanie uniemożliwić innym użytkownikom korzystanie z tego interfejsu API, po prostu usuń powiązany kod . Nie ma sensu pozostawiać kodu w bazie kodu, jeśli jakiś mechanizm sprawia, że jest on bezużyteczny. Usunięcie kodu zapewnia sprawdzanie czasu kompilacji, którego szukasz, i nie ma trywialnego obejścia.
Edycja: Widzę, że w swoim pytaniu masz na myśli „stary nieużywany kod”. Jeśli kod jest naprawdę nieużywany , nie ma sensu go zastępować. Po prostu go usuń.
źródło
Nigdy wcześniej nie widziałem takiej funkcji - adnotacji, która zaczyna obowiązywać po określonej dacie.
@Deprecated
Może być wystarczająca, jednak. Złap ostrzeżenia w CI i niech odmówi zaakceptowania kompilacji, jeśli takie istnieją. Przenosi to odpowiedzialność z kompilatora na potok kompilacji, ale ma tę zaletę, że można (częściowo) łatwo zmienić potok kompilacji, dodając dodatkowe kroki.Zauważ, że ta odpowiedź nie rozwiązuje w pełni twojego problemu (np. Lokalne kompilacje na komputerach programistów nadal by się powiodły, choć z ostrzeżeniami) i zakłada, że masz skonfigurowany i uruchomiony potok CI.
źródło
Szukasz kalendarzy lub list rzeczy do zrobienia .
Inną alternatywą jest użycie niestandardowych ostrzeżeń kompilatora lub komunikatów kompilatora, jeśli zdołasz mieć kilka ostrzeżeń w bazie kodu. Jeśli masz zbyt wiele ostrzeżeń, musisz poświęcić dodatkowy wysiłek (około 15 minut?) I musisz odebrać ostrzeżenie kompilatora w raporcie kompilacji, który twoja ciągła integracja zapewnia dla każdej kompilacji.
Przypomnienia o konieczności naprawienia kodu są dobre i konieczne. Czasami te przypomnienia mają ściśle określone rzeczywiste terminy, więc może być konieczne także ich odmierzenie.
Celem jest ciągłe przypominanie ludziom, że problem istnieje i należy go naprawić w określonym przedziale czasowym - funkcja, która po prostu psuje kompilację w określonym czasie, nie tylko tego nie robi, ale sama funkcja jest problemem, który musi zostać naprawione w określonym przedziale czasowym.
źródło
Jednym ze sposobów myślenia o tym jest to, co rozumiesz przez czas / datę ? Komputery nie wiedzą, jakie są te pojęcia: trzeba je jakoś zaprogramować. Dość często reprezentuje się czasy w formacie UNIX „sekund od epoki” i często podaje się określoną wartość do programu za pomocą wywołań systemu operacyjnego. Jednak bez względu na to, jak powszechne jest to użycie, należy pamiętać, że nie jest to „rzeczywisty” czas: jest to tylko logiczna reprezentacja.
Jak zauważyli inni, jeśli zrobiłeś „termin” za pomocą tego mechanizmu, trywialne jest karmienie w innym czasie i przełamanie tego „terminu”. To samo dotyczy bardziej skomplikowanych mechanizmów, takich jak żądanie serwera NTP (nawet przez „bezpieczne” połączenie, ponieważ możemy zastąpić własne certyfikaty, urzędy certyfikacji, a nawet załatać biblioteki kryptograficzne). Na początku może się wydawać, że takie osoby są winne obejścia twojego mechanizmu, ale może się zdarzyć, że zrobi to automatycznie i nie bez powodu . Na przykład dobrym pomysłem jest posiadanie odtwarzalnych kompilacji , a narzędzia, które mogą to pomóc, mogą automatycznie resetować / przechwytywać takie niedeterministyczne wywołania systemowe. libfaketime robi dokładnie to,ustawia znaczniki czasu wszystkich plików na
1970-01-01 00:00:01
, funkcja nagrywania / odtwarzania Qemu udaremnia wszelkie interakcje sprzętowe itp.Jest to podobne do prawa Goodharta : jeśli uzależnisz zachowanie programu od czasu logicznego, wówczas czas logiczny przestanie być dobrą miarą „rzeczywistego” czasu. Innymi słowy, ludzie na ogół nie będą bałagać się zegarem systemowym, ale zrobią to, jeśli dasz im powód.
Istnieją inne logiczne reprezentacje czasu: jednym z nich jest wersja oprogramowania (aplikacja lub zależność). Jest to bardziej pożądana reprezentacja dla „ostatecznego terminu” niż np. Czas UNIX, ponieważ jest bardziej specyficzna dla rzeczy, na których Ci zależy (zmiana zestawów funkcji / interfejsów API), a zatem mniej prawdopodobne jest deptanie ortogonalnych problemów (np. Majstrowanie przy czasie UNIX do obejście terminu może spowodować uszkodzenie plików dziennika, zadań cron, pamięci podręcznych itp.).
Jak powiedzieli inni, jeśli kontrolujesz bibliotekę i chcesz „wypchnąć” tę zmianę, możesz wypchnąć nową wersję, która przestaje działać (powodując ostrzeżenia, aby pomóc konsumentom znaleźć i zaktualizować ich użycie), a następnie inną nową wersję, która usuwa funkcje całkowicie. Możesz je opublikować bezpośrednio po sobie, jeśli chcesz, ponieważ (ponownie) wersje są tylko logiczną reprezentacją czasu, nie muszą być powiązane z „faktycznym” czasem. Pomocna może być tutaj wersja semantyczna.
Alternatywnym modelem jest „pociągnięcie” zmiany. To jest jak Twój „plan B”: dodaj test do aplikacji konsumującej, która sprawdza, czy wersja tej zależności jest co najmniej nową wartością. Jak zwykle czerwony / zielony / refaktor do propagowania tej zmiany przez bazę kodu. Może to być bardziej odpowiednie, jeśli funkcjonalność nie jest „zła” lub „zła”, ale tylko „źle pasuje do tego przypadku użycia”.
Ważnym pytaniem związanym z podejściem „pull” jest to, czy wersja zależności liczy się jako „jednostka” ( funkcjonalności ), a zatem zasługuje na przetestowanie; czy to tylko „prywatna” szczegółów wdrażania, które powinny być wykonywane jedynie w ramach rzeczywistego urządzenia ( funkcjonalności ) testów. Powiedziałbym: jeśli różnica między wersjami zależności naprawdę liczy się jako funkcja twojej aplikacji, to wykonaj test (na przykład sprawdzając, czy wersja Pythona to> = 3.x). Jeśli nie, to nie rób tegododaj test (ponieważ będzie on kruchy, pozbawiony informacji i nadmiernie ograniczający); jeśli kontrolujesz bibliotekę, idź ścieżką „push”. Jeśli nie kontrolujesz biblioteki, po prostu użyj dowolnej dostarczonej wersji: jeśli testy przejdą pomyślnie, nie warto się ograniczać; jeśli nie przejdą, to właśnie tam jest twój „ostateczny termin”!
Istnieje inne podejście, jeśli chcesz zniechęcić do niektórych zastosowań funkcji zależności (np. Wywoływania niektórych funkcji, które nie działają dobrze z resztą kodu), zwłaszcza jeśli nie kontrolujesz zależności: zabroń standardów kodowania / zniechęcaj do korzystania z tych funkcji i dodawaj je do linera.
Każdy z nich będzie miał zastosowanie w różnych okolicznościach.
źródło
Zarządzasz tym na poziomie pakietu lub biblioteki. Kontrolujesz pakiet i kontrolujesz jego widoczność. Możesz cofnąć widoczność. Widziałem to wewnętrznie w dużych firmach i ma to sens tylko w kulturach, które szanują własność pakietów, nawet jeśli pakiety są otwarte lub bezpłatne.
Jest to zawsze bałagan, ponieważ zespoły klientów po prostu nie chcą niczego zmieniać, więc często potrzebujesz kilku rund tylko białej listy, pracując z konkretnymi klientami, aby uzgodnić termin migracji, oferując im wsparcie.
źródło
Jednym z wymagań jest wprowadzenie pojęcia czasu do kompilacji. W C, C ++ lub innych językach systemami wykorzystującymi C-jak preprocesor / build 1 , można wprowadzić znacznik czasu poprzez określa dla preprocesora w czasie kompilacji:
CPPFLAGS=-DTIMESTAMP()=$(date '+%s')
. Prawdopodobnie stanie się tak w makefile.W kodzie można by porównać ten token i spowodować błąd, jeśli czas się skończy. Zauważ, że użycie makra funkcji łapie przypadek, którego ktoś nie zdefiniował
TIMESTAMP
.Alternatywnie można po prostu „zdefiniować” dany kod, kiedy nadejdzie czas. Pozwoliłoby to na kompilację programu, pod warunkiem, że nikt go nie używa. Załóżmy, że mamy nagłówek definiujący interfejs API „api.h” i
old()
po pewnym czasie nie zezwalamy na wywołanie :Podobna konstrukcja prawdopodobnie wyeliminowałaby
old()
ciało funkcji z jakiegoś pliku źródłowego.Oczywiście nie jest to głupi dowód; można po prostu zdefiniować stary
TIMESTAMP
na wypadek awaryjnej kompilacji w piątek wieczorem, o której mowa w innym miejscu. Ale myślę, że jest to raczej korzystne.Działa to oczywiście tylko wtedy, gdy biblioteka jest ponownie kompilowana - po tym czasie przestarzały kod po prostu już nie istnieje w bibliotece. Nie zapobiegnie to jednak łączeniu się kodu klienta z przestarzałymi plikami binarnymi.
1 C # obsługuje tylko prostą definicję symboli preprocesora, bez wartości liczbowych, co sprawia, że ta strategia nie jest wykonalna.
źródło
TIMESTAMP
rekompilację całego używanego kodu na każdej kompilacji. Obezwładni także narzędzia takie jakccache
. Oznacza to, że typowy czas kompilacji przyrostowych kompilacji znacznie się wydłuży w zależności od tego, na ile podstawy kodu wpływają funkcje przestarzałe w ten sposób.TIMESTAMP
wartość przez, powiedzmy, 86400, aby uzyskać codzienną ziarnistość, a zatem mniejszą liczbę ponownych kompilacji.W programie Visual Studio można skonfigurować skrypt przed kompilacją, który generuje błąd po określonej dacie. Zapobiegnie to kompilacji. Oto skrypt, który zgłasza błąd 12 marca 2018 r. Lub później ( pobrany stąd ):
Przed użyciem tego skryptu przeczytaj pozostałe odpowiedzi na tej stronie.
źródło
Rozumiem cel tego, co próbujesz zrobić. Ale jak wspomniano inni, system kompilacji / kompilator prawdopodobnie nie jest odpowiednim miejscem do wymuszenia tego. Sugerowałbym, że bardziej naturalną warstwą do egzekwowania tej zasady są albo SCM, albo zmienne środowiskowe.
Jeśli zrobisz to drugie, w zasadzie dodaj flagę funkcji, która oznacza przebieg przed deprecjacją. Za każdym razem, gdy budujesz przestarzałą klasę lub wywołujesz przestarzałą metodę, zaznacz flagę funkcji. Po prostu zdefiniuj pojedynczą funkcję statyczną
assertPreDeprecated()
i dodaj ją do każdej przestarzałej ścieżki kodu. Jeśli jest ustawiony, zignoruj potwierdzenia połączeń. Jeśli nie jest to wyjątek. Gdy data upłynie, odznacz flagę funkcji w środowisku wykonawczym. Wszelkie długotrwałe przestarzałe wywołania kodu pojawią się w logach środowiska wykonawczego.W przypadku rozwiązania opartego na SCM założę, że używasz git i git-flow. (Jeśli nie, logikę można łatwo dostosować do innych VCS). Utwórz nowy oddział
postDeprecated
. W tej gałęzi usuń cały przestarzały kod i zacznij pracę nad usunięciem wszelkich referencji, aż się skompiluje. Wszelkie normalne zmiany są nadal wprowadzane wmaster
oddziale. Przechowywać łączenie Obojętnie Nie nieaktualnych związanych zmiany kodu wmaster
plecy wpostDeprecated
celu zminimalizowania problemów integracyjnych.Po zakończeniu daty wycofania utwórz nowy
preDeprecated
oddział zmaster
. Następnie połączyćpostDeprecated
z powrotemmaster
. Zakładając, że Twoje wydanie wychodzi pozamaster
gałąź, powinieneś teraz używać nieaktualnej gałęzi po dacie. Jeśli zdarzy się nagły wypadek lub nie będziesz w stanie dostarczyć wyników na czas, zawsze możesz wycofać siępreDeprecated
i wprowadzić niezbędne zmiany w tej gałęzi.źródło