Evita que el código en desuso se compile después de alcanzar una fecha límite [cerrado]

67

En mi equipo, hemos estado limpiando muchas cosas antiguas en un gran proyecto monolítico (clases completas, métodos, etc.).

Durante esas tareas de limpieza, me preguntaba si hay algún tipo de anotación o biblioteca más sofisticada que la habitual @Deprecated . Este @FancyDeprecated debería evitar que la compilación del proyecto tenga éxito si no ha limpiado el código antiguo no utilizado después de que haya pasado una fecha en particular.

He estado buscando en Internet y no encontré nada que tenga las capacidades que se describen a continuación:

  • debe ser una anotación, o algo similar, para colocar en el código que se pretende eliminar antes de una fecha en particular
  • antes de esa fecha, el código se compilará y todo funcionará normalmente
  • después de esa fecha, el código no se compilará y le mostrará un mensaje que le advierte sobre el problema

Creo que estoy buscando un unicornio ... ¿Existe alguna tecnología similar para algún lenguaje de programa?

Como plan B, estoy pensando en la posibilidad de realizar la magia con algunas pruebas unitarias del código que se pretende eliminar y que comienza a fallar en el "plazo". ¿Qué piensas sobre esto? ¿Alguna idea mejor?

    
pregunta Arcones 08.03.2018 - 09:31

12 respuestas

61

No creo que esta sea una característica útil cuando realmente prohíbe la compilación. Cuando al 01/06/2018 no se compile una gran parte del código que se compiló el día anterior, su equipo volverá a eliminar esa anotación rápidamente, ya sea que el código esté limpio o no.

Sin embargo, puede agregar alguna anotación personalizada al código como

@Deprecated_after_2018_07_31

y construye una pequeña herramienta para buscar esas anotaciones. (Un simple forro en grep lo hará, si no desea utilizar la reflexión). En otros lenguajes que no sean Java, se puede usar un comentario estandarizado adecuado para "grepping" o una definición de preprocesador.

Luego, ejecuta esa herramienta poco antes o después de la fecha en particular, y si aún encuentra esa anotación, recuérdale al equipo que limpie esas partes del código con urgencia.

    
respondido por el Doc Brown 08.03.2018 - 10:31
284

Esto constituiría una característica conocida como bomba de tiempo . NO CREAR BOMBAS DE TIEMPO.

El código, no importa lo bien que lo estructure y lo documente, se convertirá en una caja negra casi mítica mal entendida si vive más allá de cierta edad. Lo último que alguien necesita en el futuro es otro modo de fallo extraño que los atrapa totalmente por sorpresa, en el peor momento posible, y sin un remedio obvio. No hay absolutamente ninguna excusa para producir intencionalmente este problema.

Mírelo de esta manera: si está organizado y es lo suficientemente consciente de su código como para preocuparse por la obsolescencia y lo sigue, no necesita un mecanismo dentro de el código para recordarte. Si no lo está, es probable que tampoco esté actualizado sobre otros aspectos del código base y que probablemente no pueda responder a la alarma de manera oportuna y correcta. En otras palabras, las bombas de tiempo no tienen un buen propósito para nadie. ¡Solo di no!

    
respondido por el Kilian Foth 08.03.2018 - 10:21
69

En C # usarías el ObsoleteAttribute de la siguiente manera:

  • En la versión 1, envías la función. Un método, clase, lo que sea.
  • En la versión 2, se envía una función mejor para reemplazar la función original. Pone un atributo Obsoleto en la función, configúrelo como "advertencia" y le da un mensaje que dice "Esta función está en desuso. Utilice la función mejor en su lugar. En la versión 3 de esta biblioteca, que se publicará en tal y tal Una fecha, el uso de esta característica será un error ". Ahora los usuarios de la función aún pueden usarla, pero tienen tiempo para actualizar su código para usar la nueva función.
  • En la versión 3, actualiza el atributo para que sea un error en lugar de una advertencia, y actualiza el mensaje para que diga "Esta función está obsoleta. Utilice la mejor función en su lugar. En la versión 4 de esta biblioteca, que se publicará en tal y tal fecha, esta característica lanzará ". Los usuarios que no prestaron atención a su advertencia anterior aún reciben un mensaje útil que les dice cómo solucionar el problema, y deben solucionarlo, porque ahora su código no se compila.
  • En la versión 4, cambia la función para que produzca una excepción fatal, y cambia el mensaje para indicar que la función se eliminará por completo en la próxima versión.
  • En la versión 5, elimina la función por completo, y si los usuarios se quejan, bueno, les dio tres ciclos de lanzamiento de advertencia justa, y siempre pueden seguir usando la versión 2 si se sienten muy convencidos al respecto.

La idea aquí es hacer que un cambio de ruptura sea lo más sencillo posible para los usuarios afectados, y garantizar que puedan continuar usando la función para al menos una versión de la biblioteca.

    
respondido por el Eric Lippert 08.03.2018 - 18:36
23

Has entendido mal lo que significa "en desuso". Medios en desuso:

  

se puede utilizar, pero se considera obsoleto y es mejor evitarlo, generalmente porque ha sido reemplazado.

Oxford Dictionaries

Por definición, una función en desuso aún se compilará.

Está buscando eliminar la función en una fecha específica. Esta bien. La forma en que lo haces es eliminarlo en esa fecha .

Hasta entonces, marque como obsoleto, obsoleto o como lo llame su lenguaje de programación. En el mensaje, incluya la fecha en que se eliminará y la cosa que lo reemplaza. Esto generará advertencias, indicando que otros desarrolladores deben evitar el uso nuevo y deben reemplazar el uso antiguo siempre que sea posible. Esos desarrolladores lo cumplirán o lo ignorarán, y alguien tendrá que lidiar con las consecuencias cuando se elimine. (Dependiendo de la situación, podría ser usted o los desarrolladores que lo estén utilizando).

    
respondido por el jpmc26 08.03.2018 - 20:20
12

No olvide que debe conservar la capacidad de compilar y depurar versiones anteriores del código para admitir versiones del software que ya se han lanzado. Sabotear una compilación después de una fecha determinada significa que también corre el riesgo de evitar realizar tareas de mantenimiento y soporte legítimas en el futuro.

Además, parece una solución trivial para hacer retroceder el reloj de mi máquina uno o dos años antes de compilar.

Recuerde, "en desuso" es una advertencia de que algo va a desaparecer en el futuro. Cuando desee impedir a la gente que use esa API, simplemente elimine el código asociado . No tiene sentido dejar el código en la base del código si algún mecanismo lo hace inutilizable. La eliminación del código le brinda los controles de tiempo de compilación que está buscando y no tiene una solución trivial.

Editar: veo que se refiere al "código antiguo no utilizado" en su pregunta. Si el código realmente es no utilizado , no tiene sentido desecharlo. Solo bórralo.

    
respondido por el bta 08.03.2018 - 18:20
6

Nunca había visto una característica así, una anotación que comienza a tener efecto después de una fecha en particular.

Sin embargo, el @Deprecated puede ser suficiente. Capture las advertencias en CI y haga que se niegue a aceptar la compilación si hay alguna presente. Esto cambia la responsabilidad del compilador a su canal de compilación, pero tiene la ventaja de que puede (semi) alterar fácilmente el canal de compilación agregando pasos adicionales.

Tenga en cuenta que esta respuesta no resuelve completamente su problema (por ejemplo, las compilaciones locales en las máquinas de los desarrolladores aún tendrían éxito, aunque con advertencias) y asume que tiene un canal de configuración de CI configurado y en funcionamiento.

    
respondido por el Mael 08.03.2018 - 09:44
4

Está buscando calendarios o listas de tareas .

Otra alternativa es usar advertencias de compilación personalizadas o mensajes de compilación, si logras tener pocas advertencias, si las hay, en tu base de código. Si tiene demasiadas advertencias, tendrá que dedicar un esfuerzo adicional (¿unos 15 minutos?) Y tendrá que seleccionar la advertencia del compilador en el informe de compilación que su integración continua se entrega en cada compilación.

Los recordatorios de que el código debe repararse son buenos y necesarios. A veces, estos recordatorios tienen plazos estrictos en el mundo real, por lo que también podría ser necesario ponerlos en un temporizador.

El objetivo es recordar continuamente a las personas que el problema existe y debe solucionarse dentro de un período de tiempo determinado: una característica que simplemente rompe la compilación en un momento específico no solo no lo hace, sino que esa característica es en sí misma un problema. que necesita ser arreglado dentro de un plazo determinado.

    
respondido por el Peter 08.03.2018 - 12:46
3

Una forma de pensar sobre esto es a qué te refieres con hora / fecha ? Las computadoras no saben cuáles son estos conceptos: tienen que ser programados de alguna manera. Es bastante común representar veces en el formato UNIX de "segundos desde la época", y es común introducir un valor particular en un programa a través de llamadas del sistema operativo. Sin embargo, no importa cuán común sea este uso, es importante tener en cuenta que no es el momento "real": es solo una representación lógica.

Como han señalado otros, si creó un "plazo límite" utilizando este mecanismo, es trivial alimentar en un momento diferente y romper ese "plazo límite". Lo mismo ocurre con mecanismos más elaborados, como solicitar un servidor NTP (incluso a través de una conexión "segura", ya que podemos sustituir nuestros propios certificados, autoridades de certificados o incluso parchar las bibliotecas criptográficas). Al principio, puede parecer que estas personas tienen la culpa de trabajar en torno a su mecanismo, pero puede darse el caso de que se haga automáticamente y por buenas razones . Por ejemplo, es una buena idea tener compilaciones reproducibles , y las herramientas para ayudar a esto podrían restablecer / interceptar automáticamente tales llamadas de sistema no deterministas. libfaketime hace exactamente eso, Nix establece todo las marcas de tiempo del archivo a 1970-01-01 00:00:01 , función de grabación / reproducción de Qemu hace que toda la interacción de hardware, etc. .

Esto es similar a ley de Goodhart : si la conducta de un programa depende de la hora lógica, entonces el tiempo lógico deja de ser una buena medida del tiempo "real". En otras palabras, la gente generalmente no se mete con el reloj del sistema, pero lo hará si le das una razón para hacerlo.

Hay otras representaciones lógicas del tiempo: una de ellas es la versión del software (ya sea su aplicación o alguna dependencia). Esta es una representación más deseable para una "fecha límite" que, por ejemplo, El tiempo de UNIX, ya que es más específico para lo que le interesa (cambiar los conjuntos de funciones / API) y, por lo tanto, es menos probable que pisotee los problemas ortogonales (por ejemplo, jugar con el tiempo de UNIX para solucionar su fecha límite podría terminar rompiendo los archivos de registro y los trabajos cron , cachés, etc.).

Como han dicho otros, si controla la biblioteca y desea "impulsar" este cambio, puede impulsar una nueva versión que desaprueba las características (lo que provoca advertencias, para ayudar a los consumidores a encontrar y actualizar su uso), y luego otra nueva versión lo que elimina las características por completo. Puede publicarlos inmediatamente uno tras otro si lo desea, ya que (nuevamente) las versiones son simplemente una representación lógica del tiempo, no es necesario que estén relacionadas con el tiempo "real". Las versiones semánticas pueden ayudar aquí.

El modelo alternativo es "arrastrar" el cambio. Esto es como su "plan B": agregue una prueba a la aplicación consumidora, que comprueba que la versión de esta dependencia es al menos el nuevo valor. Como de costumbre, rojo / verde / refactor para propagar este cambio a través del código base. Esto puede ser más apropiado si la funcionalidad no es "mala" o "incorrecta", sino simplemente "una mala adaptación para este caso de uso".

Una pregunta importante con el enfoque "pull" es si la versión de dependencia cuenta como una "unidad" ( de funcionalidad ), y por lo tanto merece ser probado; o si es solo un detalle de implementación "privada", que solo debe ejercerse como parte de la unidad real ( de funcionalidad ) pruebas. Yo diría: si la distinción entre las versiones de la dependencia realmente cuenta como una característica de su aplicación, entonces haga la prueba (por ejemplo, verificando que la versión de Python es > = 3.x). Si no, entonces no agregue la prueba (ya que será frágil, poco informativa y demasiado restrictiva); Si controla la biblioteca, vaya por la ruta "push". Si no controla la biblioteca, simplemente use la versión que se proporciona: si sus pruebas pasan, entonces no vale la pena restringirse; si no se aprueban, ¡ese es tu "plazo" justo allí!

Hay otro enfoque, si desea desalentar ciertos usos de las características de una dependencia (por ejemplo, llamar a ciertas funciones que no funcionan bien con el resto de su código), especialmente si no controla la dependencia: tenga su los estándares de codificación prohíben / desalientan el uso de estas funciones y agregan comprobaciones a su indicador.

Cada uno de estos será aplicable en diferentes circunstancias.

    
respondido por el Warbo 09.03.2018 - 15:52
1

Usted administra esto a nivel de paquete o biblioteca. Usted controla un paquete y controla su visibilidad. Eres libre de retraer la visibilidad. Lo he visto internamente en grandes empresas y solo tiene sentido en culturas que respetan la propiedad de los paquetes, incluso si los paquetes son de código abierto o de uso gratuito.

Esto siempre es complicado porque los equipos de clientes simplemente no quieren cambiar nada, por lo que a menudo necesita algunas rondas de listas blancas solo cuando trabaja con los clientes específicos para acordar una fecha límite para migrar, posiblemente ofreciéndoles asistencia.

    
respondido por el djechlin 08.03.2018 - 18:16
1

Un requisito es introducir una noción de tiempo en la compilación. En C, C ++, u otros lenguajes / sistemas de compilación que utilizan un preprocesador tipo C 1 , se podría introducir una marca de tiempo a través de las definiciones para el preprocesador en el momento de la compilación: CPPFLAGS=-DTIMESTAMP()=$(date '+%s') . Esto probablemente sucederá en un makefile.

En el código, uno compararía ese token y causaría un error si se acabara el tiempo. Tenga en cuenta que al utilizar una función, la macro detecta el caso de que alguien no haya definido TIMESTAMP .

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

Alternativamente, uno podría simplemente "definir" el código en cuestión cuando llegue el momento. Eso permitiría que el programa se compile, siempre que nadie lo use. Supongamos que tenemos un encabezado que define una api, "api.h", y no permitimos llamar a old() después de un tiempo determinado:

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

Una construcción similar probablemente eliminaría el cuerpo de la función de old() de algún archivo fuente.

Por supuesto, esto no es a prueba de tontos; uno puede simplemente definir un antiguo TIMESTAMP en el caso de la compilación de emergencia del viernes por la noche mencionada en otra parte. Pero eso es, creo, bastante ventajoso.

Esto obviamente funciona solo cuando la biblioteca se vuelve a compilar, después de eso el código obsoleto simplemente ya no existe en la biblioteca. Sin embargo, no evitaría que el código del cliente se vincule a binarios obsoletos.

1 C # solo admite la definición simple de símbolos de preprocesador, sin valores numéricos, lo que hace que esta estrategia no sea viable.     
respondido por el Peter A. Schneider 09.03.2018 - 13:09
0

En Visual Studio, puede configurar una secuencia de comandos de compilación previa que arroja un error después de una fecha determinada. Esto evitará la compilación. Aquí hay un script que arroja un error a partir del 12 de marzo de 2018 ( tomado desde aquí ):

@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.
)

Asegúrese de leer las otras respuestas en esta página antes de usar este script.

    
respondido por el user2023861 12.03.2018 - 16:02
-1

Entiendo el objetivo de lo que estás tratando de hacer. Pero como otros han mencionado, el sistema de compilación / compilador probablemente no sea el lugar correcto para hacer cumplir esto. Sugeriría que la capa más natural para hacer cumplir esta política es la SCM o las variables de entorno.

Si hace esto último, básicamente agregue un indicador de característica que marca una ejecución previa a la depreciación. Cada vez que construya la clase en desuso o llame a un método en desuso, verifique el indicador de característica. Simplemente defina una sola función estática assertPreDeprecated() y agregue esto a cada ruta de código en desuso. Si está configurado, ignora las llamadas de alerta. Si no se tira en una excepción. Una vez que la fecha pase, desactive la marca de característica en el entorno de ejecución. Cualquier llamada en desuso persistente al código aparecerá en los registros de tiempo de ejecución.

Para una solución basada en SCM, asumiré que estás usando git y git-flow. (Si no, la lógica es fácilmente adaptable a otros VCS). Crea una nueva rama postDeprecated . En esa rama, elimine todo el código en desuso y comience a trabajar para eliminar cualquier referencia hasta que se compile. Todos los cambios normales se siguen realizando en la rama master . Continúe fusionando cualquier cambio de código relacionado no desaprobado en master nuevamente en postDeprecated para minimizar los desafíos de integración.

Después de que finalice la fecha de eliminación, cree una nueva rama preDeprecated a partir de master . Luego, vuelva a fusionar postDeprecated en master . Suponiendo que su lanzamiento se salga de la rama master , ahora debería usar la rama en desuso después de la fecha. Si hay una emergencia, o no puede entregar los resultados a tiempo, siempre puede revertir a preDeprecated y realizar los cambios necesarios en esa rama.

    
respondido por el user79126 08.03.2018 - 14:46

Lea otras preguntas en las etiquetas