¿Qué es un schrödinbug?


52

Esta página wiki dice:

Un schrödinbug es un error que se manifiesta solo después de que alguien que lee el código fuente o usa el programa de una manera inusual se da cuenta de que nunca debería haber funcionado, en ese momento el programa deja de funcionar rápidamente para todos hasta que se arregla. El archivo Jargon agrega: "Aunque ... esto suena imposible, sucede; algunos programas han albergado schrödinbugs latentes durante años".

De lo que se habla es muy vago.

¿Puede alguien dar un ejemplo de cómo es un schrödinbug (como en una situación ficticia / de la vida real)?


15
Tenga en cuenta que la cita se cuenta en broma.

11
Creo que comprenderías mejor shrodinbug si supieras sobre el gato de Shrodinger: en.wikipedia.org/wiki/Shrodingers_cat
Eimantas

1
@Eimantas Ahora estoy más confundido, pero ese es un artículo interesante :)

Respuestas:


82

En mi experiencia, el patrón es este:

  • El sistema funciona, a menudo durante años.
  • Se informa un error
  • El desarrollador investiga el error y encuentra un poco de código que parece estar completamente defectuoso y declara que "nunca podría haber funcionado"
  • El error se soluciona y la leyenda del código que nunca podría haber funcionado (pero funcionó durante años) crece

Seamos lógicos aquí. Código que nunca podría haber funcionado ... nunca podría haber funcionado . Si se hizo el trabajo a continuación, la declaración es falsa.

Así que voy a decir que un error exactamente como se describe (es decir, observar que el código defectuoso deja de funcionar) no tiene sentido.

En realidad, lo que sucedió es una de dos cosas:

1) El desarrollador no ha entendido completamente el código . En este caso, el código generalmente es un desastre y en algún lugar tiene una sensibilidad importante pero no obvia a alguna condición externa (por ejemplo, una versión o configuración específica del sistema operativo que gobierna cómo funciona alguna función de alguna manera menor pero significativa). Esta condición externa se altera (por ejemplo, mediante una actualización o cambio del servidor que se cree que no está relacionado) y al hacerlo hace que el código se rompa.

Luego, el desarrollador mira el código y, sin comprender el contexto histórico o no tener tiempo para rastrear cada posible dependencia y escenario, declaró que nunca podría haber funcionado y lo reescribe.

En esta situación, lo que hay que entender aquí es que la idea de que "nunca podría haber funcionado" es probablemente falsa (porque lo hizo).

Eso no quiere decir que reescribirlo sea algo malo; a menudo no lo es, mientras que es bueno saber exactamente qué estaba mal, a menudo lleva mucho tiempo y reescribir la sección de código a menudo es más rápido y le permite estar seguro de que ha solucionado los problemas.

2) En realidad nunca funcionó, nadie lo ha notado . Esto es sorprendentemente común, particularmente en sistemas grandes. En este caso, alguien nuevo comienza y comienza a mirar las cosas de la forma en que nadie lo hacía antes, o un proceso de negocio cambia y trae un caso secundario previamente menor al proceso principal, y algo que nunca funcionó realmente (o funcionó algunos pero no todos el tiempo) se encuentra y se informa.

El desarrollador lo mira y declara "nunca podría haber funcionado", pero los usuarios dicen "tonterías, lo hemos estado usando durante años" y tienen razón, pero son algo que consideran irrelevante (y generalmente no mencionan hasta que desarrollador encuentra el estado exacto momento en el que se van "oh sí, nosotros hacemos que ahora y no lo hicieron antes") ha cambiado.

Aquí el desarrollador tiene razón: nunca podría haber funcionado y nunca funcionó.

Pero en cualquier caso, una de las dos cosas es cierta:

  • La afirmación "nunca podría haber funcionado" es cierta y nunca ha funcionado: la gente pensaba que sí
  • Funcionó y la afirmación "nunca podría haber funcionado" es falsa y se debe a una falta (generalmente razonable) de comprensión del código y sus dependencias

1
Me pasa tan a menudo
Génesis

2
Gran penetración en el realismo de estas situaciones
StuperUser

1
Supongo que generalmente es el resultado de un momento "WTF". Tuve eso una vez. Volví a leer el código que escribí y me di cuenta de que un error que se notó recientemente debería haber hecho que la aplicación se descompusiera. En realidad, después de una inspección adicional, otro componente que escribí fue tan bueno que compensó los errores.
Thaddee Tyl

1
@Thaddee: lo he visto antes, pero también he visto dos errores en los módulos de código que se llamaban entre sí para cancelarse, por lo que realmente funcionó. Mira a cualquiera de ellos y estaban rotos, pero juntos estaban bien.
Jon Hopkins

77
@ Jon Hopkins: También recibí un caso de 2 errores que se cancelaban entre sí, y eso es realmente sorprendente. Encontré un error, pronuncié la famosa frase "nunca podría haber funcionado", busqué más profundamente para averiguar por qué funcionó de todos modos, y encontré otro error que corrigió el primero, al menos en la mayoría de los casos. Realmente me sorprendió el descubrimiento, y el hecho de que con solo UNO de los errores, ¡la consecuencia habría sido catastrófica!
Alexis Dufrenoy

54

Como todos mencionan un código que nunca debería haber funcionado, les daré un ejemplo con el que me encontré hace unos 8 años en un proyecto VB3 que se estaba convirtiendo a .net. Desafortunadamente, el proyecto tuvo que mantenerse actualizado hasta que se completó la versión .net, y yo fui el único que incluso comprendió remotamente VB3.

Había una función muy importante que se llamaba cientos de veces para cada cálculo: calcular el interés mensual para los planes de pensiones a largo plazo. Reproduciré las partes interesantes.

Function CalculateMonthlyInterest([...], IsYearlyInterestMode As Boolean, [...]) As Double
    [about 30 lines of code]
    If IsYearlyInterestMode Then
        [about 30 lines of code]
        If Not IsYearlyInterestMode Then
            [about 30 lines of code (*)]
        End If
    End If
End Function

La parte marcada con una estrella tenía el código más importante; fue la única parte que realizó el cálculo real. Claramente esto nunca debería haber funcionado, ¿verdad?

Tomó mucha depuración, pero finalmente encontré la causa: IsYearlyInterestModeera Truey Not IsYearlyInterestModetambién era cierto. Esto se debe a que, en algún punto de la línea, alguien lo convierte en un número entero, luego, en una función que se supone que lo establece en verdadero, lo incrementa (si es 0 False, se establecería en 1, que es VB True, por lo que puedo ver la lógica allí), luego vuélvalo a un booleano. Y me quedé con una condición que nunca puede suceder y, sin embargo, sucede todo el tiempo.


77
Epílogo: nunca arreglé esa función; Acabo de parchear el sitio de la llamada fallida para enviar 2 como todos los demás.
configurador

¿quiere decir que se usa cuando la gente malinterpreta el código?
Pacerier

1
@Pacerier: más a menudo cuando el código es un desastre que solo funciona correctamente por accidente. En mi ejemplo, ningún desarrollador estaba destinado IsYearlyInterestModea evaluar como verdadero y no verdadero; el desarrollador original que agregó algunas líneas (incluida una de las ifs en realidad no entendió cómo funciona, simplemente funcionó, por lo que fue lo suficientemente bueno.
configurador

16

No conozco un ejemplo del mundo real, pero para simplificarlo con una situación de ejemplo:

  • Un error no se nota por un tiempo, porque la aplicación no ejecuta el código en condiciones que hacen que falle.
  • Alguien lo nota haciendo algo fuera del uso normal (o inspeccionando la fuente).
  • Ahora que se notó el error, la aplicación falla hasta condiciones normales también, hasta que se corrige el error.

Esto puede suceder porque el error corromperá algún estado de la aplicación que causará fallas en las condiciones normales anteriores.


44
Una explicación es que hubo fallas aleatorias en el software, que nadie fue capaz de vincularse mentalmente. Por lo tanto, se pensaba que esos errores eran de causa natural (como fallas aleatorias de hardware). Una vez que se lee el código fuente, las personas ahora pueden relacionar todos los errores aleatorios anteriores con esta única causa, y se darán cuenta de que nunca debería haber funcionado en primer lugar.
rwong

44
Una segunda explicación es que hay una parte en el software que se implementa con un patrón de cadena de responsabilidad. Cada controlador está escrito de manera robusta, a pesar de que un controlador tiene un error crítico. Ahora, el primer controlador siempre fallará, pero debido a que el segundo controlador (que se superpone en responsabilidad) intenta realizar la misma tarea, la operación general parece haber tenido éxito. Si hay algún cambio en el segundo módulo, como un cambio en el área de responsabilidad, podría causar una falla general, aunque el error real está en una ubicación diferente.
rwong

13

Un ejemplo de la vida real. No puedo mostrar el código, pero la mayoría de la gente se relacionará con esto.

Tenemos una gran biblioteca interna de funciones de utilidad donde trabajo. Un día estoy buscando una función para hacer una cosa en particular, y Frobnicate()trato de usarla. Uh-oh: resulta que Frobnicate()siempre devuelve un código de error.

Al profundizar en la implementación, encuentro algunos errores lógicos básicos Frobnicate()que hacen que siempre falle. En el control de origen, puedo ver que la función no se ha modificado desde que se escribió, lo que significa que la función nunca ha funcionado como se esperaba. ¿Por qué nadie se ha dado cuenta de esto? Busco en el resto del alistamiento de fuentes y encuentro que todas las personas que llaman Frobnicate()están ignorando el valor de retorno (y por lo tanto contienen errores sutiles propios). Si cambio esas funciones para verificar el valor de retorno como deberían, entonces también comienzan a fallar.

Este es un caso común de la condición # 2 que Jon Hopkins mencionó en su respuesta, y es deprimentemente común en grandes bibliotecas internas.


... lo cual es una buena razón para evitar escribir una biblioteca interna siempre que se pueda usar una externa. Será más probado y, por lo tanto, tendrá muchas menos sorpresas desagradables (es preferible tener bibliotecas de código abierto, porque de todos modos puede solucionarlas).
Jan Hudec

Sí, pero si los programadores ignoran los códigos de retorno, no es culpa de la biblioteca. (Por cierto, ¿cuándo fue la última vez que revisó el código de acceso de printf()?)
JensG

Esto es exactamente por qué se inventaron las excepciones verificadas.
Kevin Krumwiede

10

Aquí hay un Schrödinbug real que vi en algún código del sistema. Un demonio raíz necesita comunicarse con un módulo del núcleo. Entonces el código del núcleo crea algunos descriptores de archivo:

int pipeFDs[1];

luego configura la comunicación a través de una tubería que se conectará a una tubería con nombre:

int pipeResult = pipe(pipeFDs);

Esto no debería funcionar. pipe()escribe dos descriptores de archivo en la matriz, pero solo hay espacio para uno. Pero durante unos siete años se hizo el trabajo; la matriz se encontraba antes de un espacio no utilizado en la memoria que se convirtió en un descriptor de archivo.

Entonces, un día, tuve que portar el código a una nueva arquitectura. Dejó de funcionar y se descubrió el error que nunca debería haber funcionado.


5

Un corolario del Schrödinbug es el Heisenbug , que describe un error que desaparece (u ocasionalmente aparece) cuando se intenta investigar y / o solucionarlo.

Los Heisenbugs son pequeños monstruos inteligentes y míticos que corren y se esconden cuando se carga un depurador, pero salen de la carpintería una vez que dejas de mirar.

En realidad, estos parecen ser causados ​​por uno u otro de los siguientes:

  • El impacto de esa optimización, donde el código compilado -DDEBUGse optimiza a un nivel diferente de la versión de lanzamiento
  • diferencias sutiles de tiempo debido a que los buses de comunicación del mundo real o las interrupciones son sutilmente diferentes de las cargas simuladas "perfectas" simuladas

Ambos destacan la importancia de probar el código de lanzamiento en el equipo de lanzamiento, así como la prueba de unidad / módulo / sistema utilizando emuladores.


¿Por qué no noté la respuesta de S.Lote y el comentario de Delnan antes de publicar esto?
Andrew

Tengo poca experiencia pero he encontrado un par de esto. Estaba trabajando en un entorno Android NDK. Cuando el depurador encontró un punto de interrupción, solo detuvo los hilos de Java, no los de C ++, haciendo posible algunas llamadas porque los elementos se inicializaron en C ++. Si se deja sin depurador, el código Java iría más rápido que C ++ e intentaría usar valores que aún no se inicializaron.
MLProgrammer-CiM

Descubrí un Heisenbug en nuestro uso de la API de la base de datos de Django hace unos meses: Cuándo DEBUG = True, cambia el nombre de los "parámetros" arg a una consulta SQL sin formato. Lo habíamos estado utilizando como una palabra clave arg para mayor claridad debido a la duración de la consulta, que se rompió por completo cuando llegó el momento de DEBUG = False
ingresar al

2

He visto algunos Schödinbugs y siempre por la misma razón:

La política de la compañía requería que todos supusieran usar un programa.
Nadie realmente lo usó (principalmente porque no había entrenamiento para ello).
Pero no pudieron decirle a la gerencia esto. Así que todos tenían que decir "He estado usando este programa durante 2 años y nunca encontré este error hasta hoy".
El programa nunca funcionó realmente, excepto por una minoría de usuarios (incluidos los desarrolladores que lo escribieron).

En un caso, el programa había sido objeto de muchas pruebas, pero no en la base de datos real (que se consideró demasiado sensible, por lo que se utilizó una versión falsa).


1

Tengo un ejemplo de mi propia historia, esto fue hace unos 25 años. Yo era un niño haciendo programación gráfica rudimentaria en Turbo Pascal. TP tenía una biblioteca llamada BGI que incluía algunas funciones que le permitían copiar una región de la pantalla en un bloque de memoria basado en puntero y luego borrarlo en otra parte. Combinado con xor-blitting en una pantalla en blanco y negro, podría usarse para hacer una animación simple.

Quería ir un paso más allá y hacer sprites. Escribí un programa que dibujó grandes bloques y controles para colorearlos, como lo hizo, los reprodujo como píxeles, produciendo un programa de dibujo simple para crear sprites, que luego podría copiar en la memoria. Solo había un problema, para usar estos sprites borrados, tendrían que guardarse en un archivo para que otros programas pudieran leerlos. Pero TP no tenía forma de serializar la asignación de memoria basada en puntero. Los manuales indicaron que no podían escribirse para archivar.

Se me ocurrió una pieza de código que, con éxito, escribió en el archivo. Y comencé a escribir un programa de prueba que borró un sprite de mi programa de dibujo en un segundo plano, en mi camino a crear un juego. Y funcionó muy bien. Al día siguiente, sin embargo, dejó de funcionar. No mostraba nada más que un desastre confuso. Nunca funcionó de nuevo. Creé un nuevo sprite, y funcionó perfectamente, hasta que no lo hizo, y fue un desastre confuso nuevamente.

Me llevó mucho tiempo, pero finalmente descubrí lo que estaba sucediendo. El programa de dibujo no estaba, como pensaba, guardando los datos de píxeles copiados en el archivo, estaba guardando el puntero en sí. Cuando el siguiente programa leyó el archivo, terminó con un puntero al mismo bloque de memoria, que todavía contenía lo que el último programa había escrito allí (esto estaba en MS-DOS, la administración de memoria era inexistente). Pero funcionó ... justo hasta que reiniciaste, o ejecutaste cualquier cosa que había reutilizado esa misma área de memoria, y luego tuviste un lío confuso porque estabas bloqueando un montón de datos completamente no relacionados con el bloque de memoria de video.

Nunca debería haber funcionado, ni siquiera debería haber funcionado (y en cualquier sistema operativo real no lo habría hecho), pero aún así funcionó, y una vez que se rompió, se mantuvo roto.


0

Esto sucede todo el tiempo cuando las personas usan depuradores.

El entorno de depuración es diferente del entorno de producción real, sin depurador.

Ejecutar con un depurador puede enmascarar cosas como desbordamientos de pila porque los marcos de pila del depurador enmascaran el error.


No creo que se refiera a la diferencia entre el código que se ejecuta en un depurador y cuando se compila.
Jon Hopkins

26
Eso no es un schrödinbug, es un heisenbug .

@delnan: Está al límite, OMI. Encuentro que es algo indeterminado porque hay grados de libertad desconocidos. Me gusta reservar heisenbug para cosas en las que medir una cosa realmente perturba a otra (es decir, condiciones de carrera, configuración del optimizador, limitaciones de ancho de banda de red, etc.)
S.Lott

@ S.Lott: La situación que describe implica que la observación cambie las cosas jugando con los marcos de la pila o similares. (El peor ejemplo que vi fue que el depurador ejecutaría pacíficamente y "correctamente" cargas de valores de registro de segmento no válidos en modo de un solo paso. El resultado fueron algunas rutinas en el RTL que se enviaron a pesar de cargar un puntero de modo real mientras estaba en modo protegido Dado que solo se estaba copiando y no desreferenciando, se comportó perfectamente.)
Loren Pechtel

0

Nunca he visto un verdadero schrodinbug y no creo que puedan existir; encontrarlo no romperá las cosas.

Más bien, algo cambió que expuso un error que ha estado al acecho durante años. Lo que haya cambiado sigue cambiando y, por lo tanto, el error sigue apareciendo mientras que al mismo tiempo alguien lo encuentra.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.