Si tuviera que elegir sus técnicas favoritas (inteligentes) para la codificación defensiva, ¿cuáles serían? Aunque mis lenguajes actuales son Java y Objective-C (con experiencia en C ++), no dude en responder en cualquier lenguaje. El énfasis aquí estaría en técnicas defensivas inteligentes distintas de las que ya conocemos más del 70% de nosotros. Así que ahora es el momento de profundizar en su bolsa de trucos.
En otras palabras, trate de pensar en algo más que este ejemplo poco interesante :
if(5 == x)
en lugar deif(x == 5)
: para evitar asignaciones no intencionadas
Estos son algunos ejemplos de algunas mejores prácticas de programación defensiva intrigantes (ejemplos específicos del lenguaje están en Java):
- Bloquee sus variables hasta que sepa que necesita cambiarlas
Es decir, puede declarar todas las variables final
hasta que sepa que tendrá que cambiarlo, momento en el que puede eliminar el final
. Un hecho comúnmente desconocido es que esto también es válido para los parámetros del método:
public void foo(final int arg) { /* Stuff Here */ }
- Cuando sucede algo malo, deja un rastro de evidencia detrás
Hay una serie de cosas que puede hacer cuando tiene una excepción: obviamente, iniciar sesión y realizar algunas tareas de limpieza serían algunas. Pero también puede dejar un rastro de evidencia (por ejemplo, establecer variables en valores centinela como "NO SE PUEDE CARGAR ARCHIVO" o 99999 sería útil en el depurador, en caso de que catch
supere un bloque de excepción ).
- Cuando se trata de consistencia: el diablo está en los detalles
Sea tan coherente con las otras bibliotecas que está utilizando. Por ejemplo, en Java, si está creando un método que extrae un rango de valores, haga que el límite inferior sea inclusivo y el límite superior sea exclusivo . Esto lo hará consistente con métodos como el String.substring(start, end)
que opera de la misma manera. Encontrará que todos estos tipos de métodos en Sun JDK se comportan de esta manera, ya que realiza varias operaciones, incluida la iteración de elementos consistentes con las matrices, donde los índices son desde cero ( inclusive ) hasta la longitud de la matriz ( exclusivo ).
¿Cuáles son algunas de tus prácticas defensivas favoritas?
Actualización: si aún no lo ha hecho, siéntase libre de intervenir. Estoy dando la oportunidad de recibir más respuestas antes de elegir la respuesta oficial .
fuente
Respuestas:
En c ++, una vez me gustó redefinir nuevo para que proporcionara un poco de memoria adicional para detectar errores de publicación en vallas.
Actualmente, prefiero evitar la programación defensiva a favor del desarrollo impulsado por pruebas . Si detecta errores de forma rápida y externa, no necesita enturbiar su código con maniobras defensivas, su código es SECO y termina con menos errores de los que tiene que defenderse.
Como escribió WikiKnowledge :
fuente
SQL
Cuando tengo que borrar datos, escribo
Cuando lo ejecute, sabré si olvidé o fallé la cláusula where. Tengo una seguridad Si todo está bien, destaco todo después de los tokens de comentario '-' y lo ejecuto.
Editar: si estoy eliminando una gran cantidad de datos, usaré count (*) en lugar de solo *
fuente
Asigne una porción razonable de memoria cuando se inicie la aplicación. Creo que Steve McConnell se refirió a esto como un paracaídas de memoria en Code Complete.
Esto se puede usar en caso de que algo grave salga mal y se le solicite que finalice.
La asignación de esta memoria por adelantado le proporciona una red de seguridad, ya que puede liberarla y luego usar la memoria disponible para hacer lo siguiente:
fuente
En cada instrucción de cambio que no tiene un caso predeterminado, agrego un caso que aborta el programa con un mensaje de error.
fuente
Cuando maneja los diversos estados de una enumeración (C #):
Luego, dentro de alguna rutina ...
En algún momento agregaré otro tipo de cuenta a esta enumeración. Y cuando lo haga, me olvidaré de arreglar esta declaración de cambio. Por lo tanto, se
Debug.Fail
bloquea horriblemente (en modo de depuración) para llamar mi atención sobre este hecho. Cuando agrego elcase AccountType.MyNewAccountType:
, el horrible bloqueo se detiene ... hasta que agregue otro tipo de cuenta y me olvide de actualizar los casos aquí.(Sí, el polimorfismo es probablemente mejor aquí, pero este es solo un ejemplo fuera de mi cabeza).
fuente
Cuando imprimo mensajes de error con una cadena (particularmente una que depende de la entrada del usuario), siempre uso comillas simples
''
. Por ejemplo:Esta falta de comillas
%s
es realmente mala, porque decir que el nombre de archivo es una cadena vacía o solo un espacio en blanco o algo así. El mensaje impreso sería, por supuesto:Entonces, siempre es mejor hacer:
Entonces, al menos, el usuario ve esto:
Creo que esto hace una gran diferencia en términos de la calidad de los informes de errores enviados por los usuarios finales. Si hay un mensaje de error de aspecto extraño como este en lugar de que suene algo genérico, es mucho más probable que lo copie / pegue en lugar de simplemente escribir "no abriría mis archivos".
fuente
Seguridad de SQL
Antes de escribir cualquier SQL que modifique los datos, envuelvo todo en una transacción revertida:
Esto le impide ejecutar una eliminación / actualización incorrecta de forma permanente. Y puede ejecutar todo y verificar recuentos de registros razonables o agregar
SELECT
declaraciones entre su SQL y elROLLBACK TRANSACTION
para asegurarse de que todo se vea bien.Cuando estás completamente seguro de que no lo que esperaba, cambie la
ROLLBACK
aCOMMIT
y correr de verdad.fuente
Para todos los idiomas:
Reduzca el alcance de las variables al mínimo posible requerido. Evite las variables que se proporcionan para llevarlas a la siguiente declaración. Las variables que no existen son variables que no necesita comprender y de las que no puede hacerse responsable. Use Lambdas siempre que sea posible por la misma razón.
fuente
En caso de duda, bombardear la aplicación!
Verifique todos y cada uno de los parámetros al comienzo de todos y cada uno métodos (ya sea que lo codifique explícitamente usted mismo o use una programación basada en contratos, no importa aquí) y bombardee con la excepción correcta y / o un mensaje de error significativo si existe alguna condición previa para el código no se cumplen.
Todos conocemos estas condiciones previas implícitas cuando escribimos el código , pero si no se verifican explícitamente, estamos creando laberintos para nosotros mismos cuando algo sale mal más tarde y las pilas de docenas de llamadas a métodos separan la aparición del síntoma y la ubicación real. donde no se cumple una condición previa (= donde realmente está el problema / error).
fuente
param.NotNull("param")
lugar deif ( param == null ) throw new ArgumentNullException("param");
En Java, especialmente con colecciones, utilice la API, por lo que si su método devuelve la lista de tipos (por ejemplo), intente lo siguiente:
¡No permitas que nada escape de tu clase que no necesitas!
fuente
en Perl, todos lo hacen
me gusta
Esto hace que el código muera por cualquier advertencia de compilación / tiempo de ejecución. Esto es principalmente útil en la captura de cadenas sin inicializar.
fuente
C#:
fuente
En c # comprobación de string.IsNullOrEmpty antes de realizar cualquier operación en la cadena como length, indexOf, mid, etc.
[Editar]
Ahora también puedo hacer lo siguiente en .NET 4.0, que además verifica si el valor es solo un espacio en blanco
fuente
if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */
.En Java y C #, asigne a cada subproceso un nombre significativo. Esto incluye hilos del grupo de subprocesos. Hace que los volcados de pila sean mucho más significativos. Se necesita un poco más de esfuerzo para dar un nombre significativo incluso a los subprocesos del grupo de subprocesos, pero si un grupo de subprocesos tiene un problema en una aplicación de larga ejecución, puedo hacer que se produzca un volcado de la pila (usted sabe acerca de SendSignal.exe , ¿verdad? ), tome los registros y, sin tener que interrumpir un sistema en ejecución, puedo decir qué hilos son ... lo que sea. Bloqueado, goteando, creciendo, sea cual sea el problema.
fuente
Con VB.NET, active Option Explicit y Option Strict de manera predeterminada para todo Visual Studio.
fuente
C ++
luego reemplace todas sus llamadas ' delete pPtr ' y ' delete [] pPtr ' con SAFE_DELETE (pPtr) y SAFE_DELETE_ARRAY (pPtr)
Ahora, por error, si usa el puntero 'pPtr' después de eliminarlo, obtendrá el error 'violación de acceso'. Es mucho más fácil de arreglar que las corrupciones aleatorias de memoria.
fuente
delete
. El usonew
está bien, siempre que el nuevo objeto sea propiedad de un puntero inteligente.Con Java, puede ser útil utilizar la palabra clave afirmar, incluso si ejecuta código de producción con las afirmaciones desactivadas:
Incluso con las afirmaciones desactivadas, al menos el código documenta el hecho de que se espera que param se establezca en algún lugar. Tenga en cuenta que esta es una función auxiliar privada y no un miembro de una API pública. Usted solo puede llamar a este método, por lo que está bien hacer ciertas suposiciones sobre cómo se usará. Para los métodos públicos, probablemente sea mejor lanzar una excepción real para la entrada no válida.
fuente
No encontré la
readonly
palabra clave hasta que encontré ReSharper, pero ahora la uso instintivamente, especialmente para las clases de servicio.fuente
En Java, cuando algo está sucediendo y no sé por qué, a veces usaré Log4J así:
de esta manera obtengo un seguimiento de la pila que me muestra cómo llegué a una situación inesperada, digamos un bloqueo que nunca se desbloqueó, algo nulo que no puede ser nulo, y así sucesivamente. Obviamente, si se produce una Excepción real, no necesito hacer esto. Esto es cuando necesito ver lo que está sucediendo en el código de producción sin realmente molestar a nada más. Yo no quiero lanzar una excepción y no coger uno. Solo quiero que se registre un seguimiento de la pila con un mensaje apropiado para indicarme qué está sucediendo.
fuente
Si está utilizando Visual C ++, utilice la palabra clave de anulación siempre que anule el método de una clase base. De esta manera, si alguna vez alguien cambia la firma de la clase base, arrojará un error del compilador en lugar de que se llame silenciosamente al método incorrecto. Esto me habría salvado varias veces si hubiera existido antes.
Ejemplo:
fuente
He aprendido en Java a casi nunca esperar indefinidamente a que se desbloquee un bloqueo, a menos que realmente espere que pueda tomar un tiempo indefinidamente largo. Si, de manera realista, el bloqueo se desbloquea en segundos, entonces esperaré solo un cierto período de tiempo. Si el bloqueo no se desbloquea, entonces me quejo y vuelco la pila a los registros, y dependiendo de lo que sea mejor para la estabilidad del sistema, continúe como si el bloqueo estuviera desbloqueado o continúe como si el bloqueo nunca se desbloqueara.
Esto ha ayudado a aislar algunas condiciones de carrera y condiciones de pseudo-punto muerto que eran misteriosas antes de comenzar a hacer esto.
fuente
C#
sealed
mucho las clases para evitar introducir dependencias donde no las quería. Permitir la herencia debe hacerse explícitamente y no por accidente.fuente
Cuando emite un mensaje de error, al menos intente proporcionar la misma información que tenía el programa cuando tomó la decisión de lanzar un error.
"Permiso denegado" le dice que hubo un problema de permiso, pero no tiene idea de por qué o dónde ocurrió el problema. "No se puede escribir el registro de transacciones / mi / archivo: sistema de archivos de solo lectura" al menos le permite saber la base sobre la cual se tomó la decisión, incluso si es incorrecta, especialmente si es incorrecta: ¿nombre de archivo incorrecto? abrió mal? otro error inesperado? - y le permite saber dónde estaba cuando tuvo el problema.
fuente
En C #, use la
as
palabra clave para emitir.lanzará una excepción si obj no es una cadena
dejará un como nulo si obj no es una cadena
Todavía debe tener en cuenta nulo, pero eso suele ser más sencillo que buscar excepciones de lanzamiento. A veces quieres un comportamiento tipo "lanzar o explotar", en cuyo caso
(string)obj
se prefiere la sintaxis.En mi propio código, encuentro que uso la
as
sintaxis aproximadamente el 75% del tiempo y la(cast)
sintaxis aproximadamente el 25%.fuente
is/instanceof
vienen a la mente.Prepárate para cualquier entrada, y cualquier entrada que reciba que sea inesperada, volcar en registros. (Dentro de lo razonable. Si está leyendo contraseñas del usuario, ¡no las arroje a los registros! Y no registre miles de este tipo de mensajes en registros por segundo. Razonar sobre el contenido, la probabilidad y la frecuencia antes de iniciar sesión) .)
No solo estoy hablando de la validación de entrada del usuario. Por ejemplo, si está leyendo solicitudes HTTP que espera que contengan XML, esté preparado para otros formatos de datos. Me sorprendió ver respuestas HTML donde esperaba solo XML, hasta que miré y vi que mi solicitud estaba pasando por un proxy transparente del que no estaba al tanto y que el cliente alegaba ignorancia, y el proxy agotó el tiempo de espera tratando de completar el solicitud. Por lo tanto, el proxy devolvió una página de error HTML a mi cliente, confundiendo al cliente que solo esperaba datos XML.
Por lo tanto, incluso cuando cree que controla ambos extremos del cable, puede obtener formatos de datos inesperados sin involucrar a ninguna villanía. Esté preparado, codifique a la defensiva y proporcione resultados de diagnóstico en caso de entrada inesperada.
fuente
Intento utilizar el enfoque de Diseño por contrato. Se puede emular el tiempo de ejecución por cualquier idioma. Cada idioma admite "afirmar", pero es fácil y conveniente escribir una mejor implementación que le permita administrar el error de una manera más útil.
Entre los 25 errores de programación más peligrosos la "Validación de entrada incorrecta" es el error más peligroso en la sección "Interacción insegura entre componentes".
Agregar afirmaciones de precondición al comienzo de los métodos es una buena manera de asegurarse de que los parámetros sean consistentes. Al final de los métodos escribo postcondiciones , que comprueban que la salida es lo que se pretende que sea.
Para implementar invariantes , escribo un método en cualquier clase que verifique la "consistencia de clase", que debería llamarse automáticamente por macro precondición y postcondición.
Estoy evaluando la Biblioteca de Contratos de Código .
fuente
Java
La API de Java no tiene ningún concepto de objetos inmutables, ¡lo cual es malo! Final puede ayudarte en ese caso. Etiquete cada clase que es inmutable con final y prepare la clase en consecuencia .
A veces es útil usar final en variables locales para asegurarse de que nunca cambien su valor. Encontré esto útil en construcciones de bucle feas, pero necesarias. Es demasiado fácil reutilizar accidentalmente una variable a pesar de que es una constante.
Usar copia de defensa en tus captadores. A menos que devuelva un tipo primitivo o un objeto inmutable, asegúrese de copiar el objeto para no violar la encapsulación.
Nunca use clon, use un constructor de copia .
Aprenda el contrato entre equals y hashCode. Esto se viola muy a menudo. El problema es que no afecta su código en el 99% de los casos. Las personas sobrescriben iguales, pero no les importa el código hash. Hay instancias en las que su código puede romperse o comportarse de manera extraña, por ejemplo, use objetos mutables como claves en un mapa.
fuente
Olvidé escribir
echo
en PHP demasiadas veces:Me llevaría una eternidad tratar de descubrir por qué -> baz () no estaba devolviendo nada cuando, de hecho, ¡no lo estaba haciendo eco! : -S Así que hice una
EchoMe
clase que podría envolverse alrededor de cualquier valor que debería repetirse:Y luego, para el entorno de desarrollo, utilicé un EchoMe para envolver cosas que deberían imprimirse:
Usando esa técnica, el primer ejemplo que faltaba
echo
ahora arrojaría una excepción ...fuente
AnObject.ToString
lugar deWriteln(AnObject.ToString)
?C ++
Cuando escribo nuevo, debo escribir eliminar de inmediato. Especialmente para matrices.
C#
Verifique si hay valores nulos antes de acceder a las propiedades, especialmente cuando usa el patrón Mediator. Los objetos se pasan (y luego se deben lanzar usando como, como ya se ha señalado), y luego se verifica contra nulo. Incluso si cree que no será nulo, verifique de todos modos. Me ha sorprendido
fuente
Utilice un sistema de registro que permita ajustes dinámicos del nivel de registro del tiempo de ejecución. A menudo, si tiene que detener un programa para habilitar el registro, perderá cualquier estado raro en el que se produjo el error. Debe poder activar más información de registro sin detener el proceso.
Además, 'strace -p [pid]' en Linux mostrará que desea que el sistema llame a un proceso (o hilo de Linux). Puede parecer extraño al principio, pero una vez que se acostumbre a las llamadas del sistema que generalmente realizan las llamadas libc, encontrará que esto es invaluable para el diagnóstico de campo.
fuente