Em minha equipe, limpamos muitas coisas antigas em um grande projeto monolítico (classes inteiras, métodos etc.).
Durante as tarefas de limpeza, fiquei pensando se existe algum tipo de anotação ou biblioteca mais sofisticada do que o habitual @Deprecated
. Isso @FancyDeprecated
deve impedir que a compilação do projeto seja bem-sucedida se você não tiver limpado o código antigo não utilizado após uma data específica.
Estive pesquisando na Internet e não encontrei nada com os recursos descritos abaixo:
- deve ser uma anotação ou algo semelhante para colocar no código que você deseja excluir antes de uma data específica
- antes dessa data, o código será compilado e tudo funcionará normalmente
- após essa data, o código não será compilado e receberá uma mensagem avisando sobre o problema
Acho que estou procurando um unicórnio ... Existe alguma tecnologia semelhante para qualquer linguagem de programa?
Como plano, estou pensando na possibilidade de fazer a mágica com alguns testes de unidade do código que se pretende remover que começam a falhar no "prazo". O que você pensa sobre isso? Alguma ideia melhor?
fonte
Respostas:
Eu não acho que isso seria um recurso útil quando realmente proíbe a compilação. Quando em 01/06/2018 grandes partes do código não serão compiladas no dia anterior, sua equipe removerá rapidamente essa anotação novamente, com o código limpo ou não.
No entanto, você pode adicionar uma anotação personalizada ao código, como
e crie uma pequena ferramenta para procurar essas anotações. (Um liner simples no grep fará isso, se você não quiser utilizar a reflexão). Em outras linguagens que não Java, um comentário padronizado adequado para "grepping" ou uma definição de pré-processador pode ser usada.
Em seguida, execute a ferramenta pouco antes ou depois da data específica e, se ela ainda encontrar essa anotação, lembre a equipe de limpar essas partes do código urgentemente.
fonte
Isso constituiria um recurso conhecido como bomba-relógio . NÃO CRIE BOMBAS DE TEMPO.
O código, não importa quão bem você o estruture e documente, se transformará em uma caixa preta quase mítica, mal compreendida, se viver além de uma certa idade. A última coisa que alguém no futuro precisa é de outro modo de falha estranho que os pega totalmente de surpresa, no pior momento possível e sem um remédio óbvio. Não há desculpa para produzir intencionalmente esse problema.
Veja da seguinte maneira: se você está organizado e ciente de sua base de código o suficiente para se importar com a obsolescência e segui-la, não precisa de um mecanismo no código para lembrá-lo. Caso contrário, é provável que você também não esteja atualizado sobre outros aspectos da base de códigos e provavelmente não poderá responder ao alarme em tempo hábil e corretamente. Em outras palavras, as bombas-relógio não servem para ninguém. Apenas diga não!
fonte
Em C #, você usaria
ObsoleteAttribute
o seguinte:A idéia aqui é fazer uma mudança de última hora o mais simples possível para os usuários afetados e garantir que eles possam continuar usando o recurso em pelo menos uma versão da biblioteca.
fonte
libfoo.so.2
elibfoo.so.3
pode coexistir um com o outro, o seu downstream pode continuar usando a biblioteca antiga até que eles sejam trocados.Você entendeu mal o que significa "obsoleto". Descontinuado significa:
Oxford Dictionaries
Por definição, um recurso descontinuado ainda será compilado.
Você está procurando remover o recurso em uma data específica. Isso é bom. O jeito que você faz isso é removê-lo nessa data .
Até lá, marque como obsoleto, obsoleto ou como sua linguagem de programação chamar. Na mensagem, inclua a data em que será removida e o que a substitui. Isso gerará avisos, indicando que outros desenvolvedores devem evitar novos usos e substituir os antigos sempre que possível. Esses desenvolvedores o cumprirão ou o ignorarão, e alguém terá que lidar com as consequências disso quando for removido. (Dependendo da situação, pode ser você ou os desenvolvedores que estão usando.)
fonte
Não esqueça que você precisa manter a capacidade de criar e depurar versões mais antigas do código para oferecer suporte a versões do software que já foram lançadas. Sabotar uma construção depois de uma certa data significa que você também corre o risco de se impedir de realizar trabalhos de manutenção e suporte legítimos no futuro.
Além disso, parece uma solução trivial definir o relógio da minha máquina um ano ou dois antes de compilar.
Lembre-se de que "descontinuado" é um aviso de que algo vai desaparecer no futuro. Quando você deseja impedir com força as pessoas de usarem essa API, basta remover o código associado . Não faz sentido deixar o código na base de código se algum mecanismo o tornar inutilizável. A remoção do código fornece as verificações em tempo de compilação que você procura e não possui uma solução trivial.
Editar: vejo que você se refere ao "código antigo não utilizado" na sua pergunta. Se o código realmente não for usado , não há sentido em preteri-lo. Apenas apague.
fonte
Eu nunca vi esse recurso antes - uma anotação que começa a entrar em vigor após uma data específica.
O
@Deprecated
pode ser suficiente, no entanto. Capture avisos no IC e recuse aceitar a compilação, se houver algum. Isso muda a responsabilidade do compilador para seu pipeline de construção, mas tem a vantagem de que você pode (semi) facilmente alterar o pipeline de construção adicionando etapas adicionais.Observe que esta resposta não resolve completamente o seu problema (por exemplo, as compilações locais nas máquinas dos desenvolvedores ainda teriam êxito, embora com avisos) e pressupõe que você tenha um pipeline de CI configurado e em execução.
fonte
Você está procurando calendários ou listas de tarefas .
Outra alternativa é usar avisos personalizados do compilador ou mensagens do compilador, se você conseguir ter poucos ou nenhum aviso na sua base de código. Se você tiver muitos avisos, precisará de um esforço adicional (cerca de 15 minutos?) E precisará receber o aviso do compilador no relatório de compilação que sua integração contínua fornece em cada compilação.
Lembretes de que o código precisa ser corrigido são bons e necessários. Às vezes, esses lembretes têm prazos estritos no mundo real, portanto, também é necessário colocá-los em um cronômetro.
O objetivo é lembrar continuamente as pessoas de que o problema existe e precisa ser corrigido dentro de um determinado período de tempo - um recurso que simplesmente interrompe a construção em um horário específico, não apenas não faz isso, mas esse recurso é um problema que precisa ser resolvido. ser corrigido dentro de um determinado período.
fonte
Uma maneira de pensar sobre isso é o que você quer dizer com hora / data ? Os computadores não sabem quais são esses conceitos: eles precisam ser programados de alguma forma. É bastante comum representar tempos no formato UNIX de "segundos desde a época", e é comum alimentar um valor específico em um programa por meio de chamadas do SO. No entanto, não importa quão comum seja esse uso, é importante ter em mente que não é a hora "real": é apenas uma representação lógica.
Como outros já apontaram, se você fez um "prazo" usando esse mecanismo, é trivial alimentar um horário diferente e quebrar esse "prazo". O mesmo vale para mecanismos mais elaborados, como solicitar a um servidor NTP (mesmo em uma conexão "segura", pois podemos substituir nossos próprios certificados, autoridades de certificação ou até corrigir as bibliotecas de criptografia). A princípio, pode parecer que essas pessoas são as culpadas por contornar seu mecanismo, mas pode ser o caso de ser feito automaticamente e por boas razões . Por exemplo, é uma boa ideia ter construções reproduzíveis e ferramentas para ajudar a isso podem redefinir / interceptar automaticamente chamadas de sistema não determinísticas. libfaketime faz exatamente isso,define todos os carimbos de data e hora do arquivo
1970-01-01 00:00:01
, o recurso de gravação / reprodução do Qemu falsifica toda a interação do hardware, etc.Isso é semelhante à lei de Goodhart : se você faz o comportamento de um programa depender do tempo lógico, então o tempo lógico deixa de ser uma boa medida do tempo "real". Em outras palavras, as pessoas geralmente não mexem com o relógio do sistema, mas sim se você lhes der um motivo.
Existem outras representações lógicas do tempo: uma delas é a versão do software (seu aplicativo ou alguma dependência). Essa é uma representação mais desejável para um "prazo" do que, por exemplo, o horário do UNIX, já que é mais específica para o que lhe interessa (alteração de conjuntos de recursos / APIs) e, portanto, é menos provável que atropele preocupações ortogonais (por exemplo, mexendo no tempo do UNIX para contornar o prazo pode acabar quebrando arquivos de log, tarefas cron, caches, etc.).
Como já foi dito, se você controla a biblioteca e deseja "enviar" essa alteração, pode enviar uma nova versão que descontinua os recursos (causando avisos, para ajudar os consumidores a encontrar e atualizar seu uso) e, em seguida, outra nova versão que remove o apresenta inteiramente. Você pode publicá-las imediatamente uma após a outra, se quiser, uma vez que (novamente) as versões são meramente uma representação lógica do tempo, elas não precisam estar relacionadas ao tempo "real". O controle de versão semântico pode ajudar aqui.
O modelo alternativo é "puxar" a mudança. É como o seu "plano B": adicione um teste ao aplicativo consumidor, que verifica se a versão dessa dependência é pelo menos o novo valor. Como sempre, vermelho / verde / refatorar para propagar essa alteração pela base de código. Isso pode ser mais apropriado se a funcionalidade não for "ruim" ou "incorreta", mas apenas "inadequada para este caso de uso".
Uma questão importante com a abordagem "pull" é se a versão de dependência conta ou não como uma "unidade" ( de funcionalidade ) e, portanto, merece teste; ou se é apenas um detalhe de implementação "particular", que deve ser exercido apenas como parte dos testes reais de unidade ( de funcionalidade ). Eu diria: se a distinção entre as versões da dependência realmente conta como um recurso do seu aplicativo, faça o teste (por exemplo, verificando se a versão do Python é> = 3.x). Se não, então nãoadicione o teste (pois será quebradiço, pouco informativo e excessivamente restritivo); se você controlar a biblioteca, desça a rota "push". Se você não controla a biblioteca, basta usar a versão fornecida: se seus testes forem aprovados, não vale a pena se restringir; se eles não passarem, esse é o seu "prazo final" aí mesmo!
Existe outra abordagem, se você deseja desencorajar certos usos dos recursos de uma dependência (por exemplo, chamar determinadas funções que não funcionam bem com o restante do seu código), especialmente se você não controla a dependência: proibir seus padrões de codificação / desencoraje o uso desses recursos e adicione verificações ao seu linter.
Cada um deles será aplicável em diferentes circunstâncias.
fonte
Você gerencia isso no nível do pacote ou da biblioteca. Você controla um pacote e sua visibilidade. Você é livre para retirar a visibilidade. Eu já vi isso internamente em grandes empresas e isso só faz sentido em culturas que respeitam a propriedade de pacotes, mesmo que os pacotes sejam de código aberto ou de uso gratuito.
Isso é sempre confuso, porque as equipes do cliente simplesmente não querem mudar nada; portanto, muitas vezes você precisa de algumas rodadas de lista de permissões apenas enquanto trabalha com clientes específicos para acordar um prazo para migrar, possivelmente oferecendo suporte a eles.
fonte
Um requisito é introduzir uma noção de tempo na compilação. Em C, C ++, ou outros idiomas sistemas que utilizam um pré-processador C-like / construir uma , pode-se introduzir um carimbo de tempo através define para o pré-processador em tempo de compilação:
CPPFLAGS=-DTIMESTAMP()=$(date '+%s')
. Isso provavelmente aconteceria em um makefile.No código, seria possível comparar esse token e causar um erro se o tempo acabar. Observe que o uso de uma macro de função captura o caso que alguém não definiu
TIMESTAMP
.Como alternativa, pode-se simplesmente "definir" o código em questão quando chegar a hora. Isso permitiria que o programa fosse compilado, desde que ninguém o usasse. Digamos, temos um cabeçalho que define uma API, "api.h" e não permitimos chamadas
old()
depois de um certo tempo:Uma construção semelhante provavelmente eliminaria
old()
o corpo da função de algum arquivo de origem.Claro que isso não é à prova de idiotas; pode-se simplesmente definir um antigo
TIMESTAMP
no caso da construção de emergência de sexta à noite mencionada em outro lugar. Mas acho que isso é bastante vantajoso.Obviamente, isso funciona apenas quando a biblioteca é recompilada - depois disso, o código obsoleto simplesmente não existe mais na biblioteca. Porém, isso não impediria que o código do cliente vinculasse a binários obsoletos.
1 C # suporta apenas a definição simples de símbolos de pré-processador, sem valores numéricos, o que torna essa estratégia inviável.
fonte
TIMESTAMP
a ser recompilado a cada compilação. Também incapacitará ferramentas comoccache
. Isso significa que o tempo de compilação típico para compilações incrementais aumentará significativamente, dependendo de quanto da base de código é afetada pelos recursos preteridos dessa maneira.TIMESTAMP
valor por, digamos, 86400, para obter uma granularidade diária e, portanto, menos recompilações.No Visual Studio, você pode configurar um script de pré-compilação que gera um erro após uma determinada data. Isso impedirá a compilação. Aqui está um script que lança um erro em ou após 12 de março de 2018 ( extraído daqui ):
Certifique-se de ler as outras respostas nesta página antes de usar este script.
fonte
Entendo o objetivo do que você está tentando fazer. Mas, como outros já mencionaram, o sistema de compilação / compilador provavelmente não é o lugar certo para aplicar isso. Eu sugiro que a camada mais natural para aplicar essa política seja o SCM ou as variáveis de ambiente.
Se você fizer o último, adicione basicamente um sinalizador de recurso que marca uma execução de pré-reprovação. Toda vez que você constrói a classe reprovada ou chama um método obsoleto, verifique o sinalizador do recurso. Apenas defina uma única função estática
assertPreDeprecated()
e adicione-a a todo caminho de código obsoleto. Se estiver definido, ignore as chamadas de confirmação. Se não é uma exceção. Depois que a data passar, desmarque o sinalizador de recurso no ambiente de tempo de execução. Quaisquer chamadas obsoletas remanescentes para o código serão exibidas nos logs de tempo de execução.Para uma solução baseada em SCM, presumo que você esteja usando git e git-flow. (Caso contrário, a lógica é facilmente adaptável a outros VCS). Crie uma nova ramificação
postDeprecated
. Nesse ramo, exclua todo o código descontinuado e comece a remover todas as referências até compilar. Quaisquer alterações normais continuam sendo feitas namaster
ramificação. Mantenha integrando quaisquer alterações não-obsoleto relacionados código namaster
volta parapostDeprecated
minimizar os desafios de integração.Após o término da data de descontinuação, crie uma nova
preDeprecated
ramificação a partir demaster
. Em seguida, mesclarpostDeprecated
novamentemaster
. Supondo que sua liberação saia damaster
ramificação, agora você deve usar a ramificação pós-obsoleta após a data. Se houver uma emergência ou você não conseguir fornecer resultados a tempo, sempre poderá reverterpreDeprecated
e fazer as alterações necessárias nessa ramificação.fonte