language-agnostic

language agnostic - Prácticas recomendadas de programación defensiva favoritas(inteligentes)



language-agnostic (30)

¡En caso de duda, bombardear la aplicación!

Compruebe todos y cada uno de los parámetros al principio de cada uno de los métodos (ya sea que lo codifique explícitamente o utilizando la programación basada en contratos no importa aquí) y bombardee con la excepción correcta y / o el mensaje de error significativo si se requiere alguna condición previa para el código no se cumplen.

Todos sabemos acerca de 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 va mal y pilas de docenas de llamadas a métodos separan la ocurrencia del síntoma y la ubicación real donde no se cumple una precondición (= donde realmente está el problema / error).

Si tuviera que elegir sus técnicas favoritas (inteligentes) para la codificación defensiva, ¿cuáles serían? Aunque mis idiomas actuales son Java y Objective-C (con antecedentes en C ++), no dude en responder en cualquier idioma. El énfasis aquí estaría en las técnicas de defensa inteligentes que no sean las que el 70% de nosotros ya conocemos. Así que ahora es el momento de profundizar en su bolsa de trucos.

En otras palabras, intente pensar en otro que no sea este ejemplo poco interesante :

  • if(5 == x) lugar de if(x == 5) : para evitar la asignación involuntaria

Aquí hay algunos ejemplos de algunas de las mejores prácticas de programación defensivas intrigantes (los ejemplos específicos del lenguaje están en Java):

- Bloquee sus variables hasta que sepa que debe cambiarlas

Es decir, puede declarar todas las variables final hasta que sepa que tendrá que cambiarlas, y en ese momento puede eliminar la 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 algo malo sucede, 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 a los valores centinela como "IMPOSIBLE CARGAR ARCHIVO" o 99999 sería útil en el depurador, en caso de que pasen por alto un bloqueo de catch excepción).

- Cuando se trata de consistencia: el diablo está en los detalles

Sea tan consistente 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 exclusivo . Esto lo hará consistente con métodos como String.substring(start, end) que funciona 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 coherentes con las matrices, donde los índices van desde Cero ( inclusive ) hasta la longitud de la matriz ( exclusiva ).

¿Cuáles son algunas de tus prácticas defensivas favoritas?

Actualización: si aún no lo has hecho, siéntete libre de avisar. Estoy dando la oportunidad de que lleguen más respuestas antes de elegir la respuesta oficial .


Al imprimir mensajes de error con una cadena (particularmente uno que depende de la entrada del usuario), siempre uso comillas simples '''' . Por ejemplo:

FILE *fp = fopen(filename, "r"); if(fp == NULL) { fprintf(stderr, "ERROR: Could not open file %s/n", filename); return false; }

Esta falta de comillas alrededor de %s es realmente mala, porque decir nombre de archivo es una cadena vacía o simplemente un espacio en blanco o algo así. El mensaje impreso sería, por supuesto,:

ERROR: Could not open file

Entonces, siempre es mejor hacer:

fprintf(stderr, "ERROR: Could not open file ''%s''/n", filename);

Entonces, al menos, el usuario ve esto:

ERROR: Could not open file ''''

Considero que esto marca 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 divertido como este en lugar de algo genérico, entonces es mucho más probable que lo copien / peguen en lugar de simplemente escribir "no abriría mis archivos".


Aprendí en Java a casi nunca esperar indefinidamente para desbloquear un bloqueo, a menos que realmente espere que pueda llevar un tiempo indefinidamente largo. Si es realista, la cerradura debe desbloquearse en segundos, entonces esperaré solo por un cierto período de tiempo. Si el bloqueo no se desbloquea, entonces me quejo y descargo la pila a los registros, y dependiendo de lo que sea mejor para la estabilidad del sistema, continúe como si el bloqueo se desbloqueara, o continúe como si el bloqueo nunca se desbloqueara.

Esto ha ayudado a aislar algunas condiciones de carrera y condiciones de seudo-interbloqueo que eran misteriosas antes de comenzar a hacer esto.


Asigne un pedazo razonable de memoria cuando comience 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 requiera que finalice.

Asignar esta memoria por adelantado le proporciona una red de seguridad, ya que puede liberarla y luego usar la memoria disponible para hacer lo siguiente:

  • Guarde todos los datos persistentes
  • Cierre todos los archivos apropiados
  • Escribir mensajes de error en un archivo de registro
  • Presentar un error significativo al usuario

C ++

When I type new, I must immediately type delete. Especially for arrays.

DO#

Check for null before accessing properties, especially when using the Mediator pattern. Objects get passed (and then should be cast using as, as has already been noted), and then check against null. Even if you think it will not be null, check anyway. I''ve been surprised.


C ++

#define SAFE_DELETE(pPtr) { delete pPtr; pPtr = NULL; } #define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

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á un error de ''infracción de acceso''. Es mucho más fácil de arreglar que las corrupciones de memoria aleatorias.


Con Java, puede ser útil utilizar la palabra clave assert, incluso si ejecuta el código de producción con las aserciones desactivadas:

private Object someHelperFunction(Object param) { assert param != null : "Param must be set by the client"; return blahBlah(param); }

Incluso con las afirmaciones desactivadas, al menos el código documenta el hecho de que se espera que param esté establecido en algún lugar. Tenga en cuenta que esta es una función auxiliar privada y no un miembro de una API pública. Este método solo puede ser llamado por usted, por lo que está bien hacer ciertas suposiciones sobre cómo se usará. Para los métodos públicos, probablemente sea mejor lanzar una verdadera excepción para los datos no válidos.


Con VB.NET, tiene Opción explícita y Opción estricta activada por defecto para todo Visual Studio.


Cuando emita un mensaje de error, al menos intente proporcionar la misma información que el programa cuando tomó la decisión de arrojar un error.

El "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 que se tomó la decisión, incluso si está mal, especialmente si es incorrecta: ¿nombre de archivo incorrecto? abrió mal? otro error inesperado? - y le permite saber dónde estaba cuando tuvo el problema.


Cuando maneja los diversos estados de una enumeración (C #):

enum AccountType { Savings, Checking, MoneyMarket }

Entonces, dentro de alguna rutina ...

switch (accountType) { case AccountType.Checking: // do something case AccountType.Savings: // do something else case AccountType.MoneyMarket: // do some other thing default: --> Debug.Fail("Invalid account type."); }

En algún momento agregaré otro tipo de cuenta a esta enumeración. Y cuando lo haga, me olvidaré de corregir esta declaración de cambio. Entonces, Debug.Fail cuelga horriblemente (en modo Debug) para llamar mi atención sobre este hecho. Cuando agrego el case AccountType.MyNewAccountType: el horrible bloqueo se detiene ... hasta que agregue otro tipo de cuenta y olvide actualizar los casos aquí.

(Sí, el polimorfismo es probablemente mejor aquí, pero esto es solo un ejemplo fuera de lo común).


DO#

  • Verifique los valores no nulos para los parámetros de tipo de referencia en el método público.
  • Uso mucho las clases sealed para evitar introducir dependencias donde no las quería. Permitir herencia debe hacerse explícitamente y no por accidente.

DO#:

string myString = null; if (myString.Equals("someValue")) // NullReferenceException... { } if ("someValue".Equals(myString)) // Just false... { }


En C #, use la palabra clave as para lanzar.

string a = (string)obj

lanzará una excepción si obj no es una cadena

string a = obj as string

dejará un como nulo si obj no es una cadena

Aún debe tener en cuenta la nulidad, pero eso normalmente es más directo y luego buscar excepciones de conversión. A veces se desea un comportamiento de tipo "emitir o explotar", en cuyo caso se prefiere la sintaxis (string)obj .

En mi propio código, encuentro que utilizo la sintaxis as 75% del tiempo y la sintaxis (cast) alrededor del 25%.


En Java y C #, otorgue a cada hilo un nombre significativo. Esto incluye subprocesos de 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 para subprocesos de grupo de subprocesos, pero si un grupo de subprocesos tiene un problema en una aplicación de larga ejecución, puedo causar un volcado de pila (usted sabe acerca de SendSignal.exe , ¿verdad? ), agarrar los registros, y sin tener que interrumpir un sistema en ejecución, puedo decir qué hilos son ... lo que sea. En punto muerto, goteando, creciendo, sea cual sea el problema.


En Java, cuando algo está sucediendo y no sé por qué, algunas veces usaré Log4J así:

if (some bad condition) { log.error("a bad thing happened", new Exception("Let''s see how we got here")); }

de esta forma obtengo un seguimiento de la pila que me muestra cómo llegué a la situación inesperada, por ejemplo, un bloqueo que nunca se desbloqueó, algo nulo que no puede ser nulo, y así sucesivamente. Obviamente, si se lanza una Excepción real, no necesito hacer esto. Aquí es cuando necesito ver qué está sucediendo en el código de producción sin perturbar realmente nada más. No quiero lanzar una excepción y no atrapé una. Solo quiero que se registre un seguimiento de la pila con un mensaje apropiado para informarme sobre lo que está sucediendo.


En Java, especialmente con colecciones, haga uso de la API, de modo que si su método devuelve el tipo de lista (por ejemplo), intente lo siguiente:

public List<T> getList() { return Collections.unmodifiableList(list); }

¡No permita que escape nada de su clase que no necesita!


En c ++, una vez me gustó la redefinición de nuevos para que proporcionara algo de memoria extra para detectar los errores de la valla.

Actualmente, prefiero evitar la programación defensiva a favor del desarrollo impulsado por prueba . Si detecta errores de forma rápida y externa, no necesita ensuciar su código con maniobras defensivas, su código es DRY -er y usted termina con menos errores de los que tiene que defenderse.

Como WikiKnowledge escribió :

Evite la programación defensiva, fallar rápido en su lugar.

Por programación defensiva me refiero al hábito de escribir código que intenta compensar alguna falla en los datos, de escribir código que asume que las personas que llaman pueden proporcionar datos que no se ajustan al contrato entre la persona que llama y la subrutina y que la subrutina debe lidiar de alguna manera con eso.


En cada instrucción switch que no tiene un caso predeterminado, agrego un caso que aborta el programa con un mensaje de error.

#define INVALID_SWITCH_VALUE 0 switch (x) { case 1: // ... break; case 2: // ... break; case 3: // ... break; default: assert(INVALID_SWITCH_VALUE); }


En comprobación de c # de string.IsNullOrEmpty antes de realizar cualquier operación en la cadena como length, indexOf, mid, etc.

public void SomeMethod(string myString) { if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty { // Also implies that myString.Length == 0 //Do something with string } }

[Editar]
Ahora también puedo hacer lo siguiente en .NET 4.0, que además verifica si el valor es solo espacio en blanco

string.IsNullOrWhiteSpace(myString)


Java

The java api has no concept of immutable objects, which is bad! Final can help you in that case. Tag every class that is immutable with final and prepare the class accordingly .

Sometimes it is useful to use final on local variables to make sure they never change their value. I found this useful in ugly, but necessary loop constructs. Its just to easy to accidently reuse a variable even though it is mend to be a constant.

Use defense copying in your getters. Unless you return a primitive type or a immutable object make sure you copy the object to not violate encapsulation.

Never use clone, use a copy constructor .

Learn the contract between equals and hashCode. This is violated so often. The problem is it doesn''t affect your code in 99% of the cases. People overwrite equals, but don''t care about hashCode. There are instances in wich your code can break or behaves strange, eg use mutable objects as keys in a map.


No encontré la palabra clave readonly hasta que encontré ReSharper, pero ahora lo uso instintivamente, especialmente para las clases de servicio.

readonly var prodSVC = new ProductService();


Para todos los idiomas:

Reduzca el alcance de las variables al mínimo requerido. Evite las variables que se acaban de proporcionar para llevarlas a la siguiente declaración. Las variables que no existen son variables que no necesita comprender y no puede responsabilizarse por ellas. Use Lambdas siempre que sea posible por el mismo motivo.


Si está utilizando Visual C ++, utilice la palabra clave de anulación siempre que anule el método de una clase base. De esta forma, si alguna vez alguien cambiara la firma de la clase base, arrojaría un error de compilación en lugar de llamar al método incorrecto. Esto me hubiera salvado algunas veces si hubiera existido antes.

Ejemplo:

class Foo { virtual void DoSomething(); } class Bar: public Foo { void DoSomething() override { /* do something */ } }


en Perl, todos lo hacen

use warnings;

me gusta

use warnings FATAL => ''all'';

Esto hace que el código muera por cualquier advertencia de compilación / tiempo de ejecución. Esto es principalmente útil para capturar cadenas no inicializadas.

use warnings FATAL => ''all''; ... my $string = getStringVal(); # something bad happens; returns ''undef'' print $string . "/n"; # code dies here


SQL

Cuando tengo que borrar datos, escribo

select * --delete From mytable Where ...

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 comentarios ''-'' y lo ejecuto.

Editar: si borro muchos datos, usaré el recuento (*) en lugar de solo *


Seguridad SQL

Antes de escribir cualquier SQL que modifique los datos, envuelvo todo en una transacción retrotraída:

BEGIN TRANSACTION -- LOTS OF SCARY SQL HERE LIKE -- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID ROLLBACK TRANSACTION

Esto le impide ejecutar una eliminación / actualización incorrecta permanentemente. Y puede ejecutar todo y verificar los registros razonables o agregar SELECT entre su SQL y ROLLBACK TRANSACTION para asegurarse de que todo se ve bien.

Cuando estés completamente seguro de que hace lo que esperabas, cambia el ROLLBACK a COMMIT y corre de verdad.


Be prepared for any input, and any input you get that is unexpected, dump to logs. (Within reason. If you''re reading passwords from the user, don''t dump that to logs! And don''t log thousands of these sorts of messages to logs per second. Reason about the content and likelihood and frequency before you log it.)

I''m not just talking about user input validation. For example, if you are reading HTTP requests that you expect to contain XML, be prepared for other data formats. I was surprised to see HTML responses where I expected only XML -- until I looked and saw that my request was going through a transparent proxy I was unaware of and that the customer claimed ignorance of -- and the proxy timed out trying to complete the request. Thus the proxy returned an HTML error page to my client, confusing the heck out of the client that expected only XML data.

Thus, even when you think you control both ends of the wire, you can get unexpected data formats without any villainy being involved. Be prepared, code defensively, and provide diagnostic output in the case of unexpected input.


I forgot to write echo in PHP one too many times:

<td><?php $foo->bar->baz(); ?></td> <!-- should have been --> <td><?php echo $foo->bar->baz(); ?></td>

It would take me forever to try and figure out why ->baz() wasn''t returning anything when in fact I just wasn''t echoing it! :-S So I made an EchoMe class which could be wrapped around any value that should be echoed:

<?php class EchoMe { private $str; private $printed = false; function __construct($value) { $this->str = strval($value); } function __toString() { $this->printed = true; return $this->str; } function __destruct() { if($this->printed !== true) throw new Exception("String ''$this->str'' was never printed"); } }

And then for the development environment, I used an EchoMe to wrap things which should be printed:

function baz() { $value = [...calculations...] if(DEBUG) return EchoMe($value); return $value; }

Using that technique, the first example missing the echo would now throw an exception ...


I try to use Design by Contract approach. It can be emulated run time by any language. Every language supports "assert", but it''s easy and covenient to write a better implementation that let you manage the error in a more useful way.

In the Top 25 Most Dangerous Programming Errors the "Improper Input Validation" is the most dangerous mistake in the "Insecure Interaction Between Components" section.

Adding precondition asserts at the beginning of the methods is a good way to be sure that parameters are consistent. At the end of methods i write postconditions , that check that output is what''s inteded to be.

In order to implement invariants , I write a method in any class that checks "class consistence", that should be called authomatically by precondition and postcondition macro.

I''m evaluating the Code Contract Library .


when getting a table from a dataset

if( ds != null && ds.tables != null && dt.tables.Count > 0 && ds.tables[0] != null && ds.tables[0].Rows > 0 ) { //use the row; }