Mejores prácticas de programación defensiva (inteligente) favoritas [cerrado]

148

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 de if(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 finalhasta 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 catchsupere 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 .

Ryan Delucchi
fuente
Me gustaría representar al ignorante 30% de nosotros que quizás no conozca las técnicas simples . ¿Alguien tiene un vínculo con las técnicas "obvias" que todos deberían conocer como base?
elliot42
Relacionado: stackoverflow.com/questions/163026/…
Bill the Lizard
¿Por qué elegirías una respuesta oficial? Eso suena totalmente impuro.
bzlm
Bueno, cuando se trata de programación, incluso la inteligencia debería quedar en segundo plano en honor a "la regla del menor asombro". Para mí, romper con el espíritu de Stack Overflow de marcar la mejor respuesta violaría el protocolo Stack Overflow (por lo tanto, romper dicha regla). Aparte de eso: me gusta el cierre :-)
Ryan Delucchi

Respuestas:

103

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 :

Evite la programación defensiva, falle 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 hacer frente de alguna manera con eso.

Joe portador de almas
fuente
8
La programación defensiva está tratando de lidiar con condiciones ilegales introducidas por otras partes de un programa. Manejar la entrada incorrecta del usuario es algo completamente diferente.
Joe Soul-bringer
55
Errr ... tenga en cuenta que esta definición de programación defensiva ni siquiera se acerca a la definición implícitamente utilizada en la pregunta.
Sol
15
Nunca evite la programación defensiva. La cuestión no es "compensar" las fallas en los datos, sino protegerse de los datos maliciosos diseñados para hacer que su código haga cosas que se supone que no debe hacer. Ver Desbordamiento de búfer, inyección SQL. Nada falla más rápido que una página web bajo XSS pero no es bonito
Jorge Córdoba
66
Yo diría que los procedimientos de "falla rápida" son una forma de programación defensiva.
Ryan Delucchi el
66
@ryan tiene toda la razón, fallar rápido es un buen concepto defensivo. Si el estado en el que se encuentra no es posible, no intente seguir cojeando, ¡FALLE RÁPIDO Y FUERTE! Extra importante si usted está dirigido por metadatos. La programación defensiva no es solo verificar sus parámetros ...
Bill K
75

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

Editar: si estoy eliminando una gran cantidad de datos, usaré count (*) en lugar de solo *

John MacIntyre
fuente
Escribí esto en mi iPhone, donde no puedo seleccionar texto. Si alguien pudiera marcar mi código como código, sería realmente apreciado. Gracias.
John MacIntyre
66
Yo sólo lo envuelve en una transacción para que pueda deshacer si meto la pata ...
rmeador
36
COMIENZA LA TRANSACCIÓN | TRANSACCIÓN
EN
3
+1 Sí, supongo que esto podría reemplazarse con una transacción, pero me gusta la simplicidad de hacerlo de esta manera.
Ryan Delucchi el
3
Usar una transacción es mejor; significa que puedes ejecutarlo docenas de veces y ver los efectos reales , hasta que estés seguro de que lo has hecho bien y puedes comprometerte.
Ryan Lundy
48

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:

  • Guarde todos los datos persistentes.
  • Cierra todos los archivos apropiados
  • Escribir mensajes de error en un archivo de registro
  • Presentar un error significativo al usuario.
LeopardSkinPillBoxHat
fuente
He visto esto llamado un fondo de día lluvioso.
zócalo
42

En cada instrucción de cambio 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);
}
Diomidis Spinellis
fuente
2
O un lanzamiento en un lenguaje moderno y moderno.
Tom Hawtin - tackline
2
Assert tiene la ventaja de que sus efectos pueden deshabilitarse globalmente en tiempo de compilación. Sin embargo, en algunas situaciones, el lanzamiento puede ser más apropiado, si su idioma lo admite.
Diomidis Spinellis
2
Assert tiene otra ventaja que lo hace útil para el código de producción: cuando algo sale mal, le dice exactamente qué falló y de qué línea del programa provino el error. ¡Un informe de error como ese es maravilloso!
Mason Wheeler
2
@Diomidis: Otro ángulo es: Assert tiene la desventaja de que sus efectos pueden desactivarse globalmente en tiempo de compilación.
Desilusionado el
1
lanzar es de hecho mejor. Y luego se asegura de que su cobertura de prueba sea adecuada.
Dominic Cronin
41

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

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Luego, 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 arreglar esta declaración de cambio. Por lo tanto, se Debug.Failbloquea horriblemente (en modo de depuración) 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 me olvide de actualizar los casos aquí.

(Sí, el polimorfismo es probablemente mejor aquí, pero este es solo un ejemplo fuera de mi cabeza).

Kyralessa
fuente
44
La mayoría de los compiladores son lo suficientemente inteligentes como para emitir una advertencia si no maneja algunas enumeraciones en un bloque de mayúsculas y minúsculas. Pero establecer el valor predeterminado para fallar sigue siendo una buena forma: una enumeración es solo un número y si se daña la memoria, puede aparecer un valor no válido.
Adam Hawes
en c, solía agregar un tipo de cuenta no válida al final. Esto es útil a veces.
Ray Tayek
1
@ Adam - los compiladores emitirán una advertencia si recompilas todo . Si agrega nuevas clases y solo vuelve a compilar parcialmente, es posible que no note algo como lo anterior, y el caso predeterminado lo salvará. No solo fallará en silencio.
Eddie el
44
La ruptura está implícita en los comentarios, Slashene. : P
Ryan Lundy
2
Después de la depuración debe ser un 'lanzar una nueva NotSupportedException ()' para el código de producción.
user7116
35

Cuando imprimo mensajes de error con una cadena (particularmente una 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 %ses 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:

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 ''

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".

Nik Reiman
fuente
44
bueno, también he visto este problema
Alex Baranosky
28

Seguridad de SQL

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

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 de forma permanente. Y puede ejecutar todo y verificar recuentos de registros razonables o agregar SELECTdeclaraciones 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 ROLLBACKa COMMITy correr de verdad.

amsimmon
fuente
¡Me golpeaste al máximo en este! :-)
Howard Pinsley
2
Solía ​​hacer esto todo el tiempo, pero causa tanta sobrecarga adicional en los clústeres de base de datos que tuve que parar.
Zan Lynx
25

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.

revs le dorfier
fuente
55
¿Qué significa exactamente la parte de evasión? A veces introduzco variables que solo viven hasta la siguiente línea. Sirven como nombre para una expresión, lo que hace que el código sea más legible.
zoul
44
Sí, yo tampoco estoy de acuerdo. Con expresiones muy complejas, a menudo es una buena idea dividirlas en dos o, a veces, más cortas y simples usando variables temporales. Es menos propenso a errores en el mantenimiento y el compilador optimizará las temperaturas
Cruachan
1
Estoy de acuerdo en que hay casos excepcionales en los que declaro variables para mayor claridad (y, a veces, para crear un objetivo para la depuración). Mi experiencia es que la práctica general es errar en la dirección opuesta.
dkretz
Estoy de acuerdo con ambos puntos de vista; aunque cuando divido las expresiones en variables temporales, trato de hacerlo dentro de un ámbito separado. Las lambdas son excelentes para esto, al igual que los métodos auxiliares.
Erik Forbes el
19

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

revs peSHIr
fuente
Y, por supuesto: utilice un código genérico (una pequeña biblioteca, métodos de extensión en C #, lo que sea) para facilitar esto. Así que puedes escribir algo parecido a un glike en param.NotNull("param")lugar deif ( param == null ) throw new ArgumentNullException("param");
peSHIr
2
¡O use programación basada en contratos, como Spec #!
bzlm
Depende de qué aplicación sea. Espero que la gente que escribe el código para los aviones y los marcapasos de vuelo por cable no piense como peSHIr.
MarkJ 03 de
66
@ MarkJ: Realmente no lo entiendes, ¿verdad? Si bombardea temprano (= durante el desarrollo y las pruebas) nunca debería bombardear cuando está en producción. ¡Así que realmente espero que hagan un programa como este!
peSHIr
Debo admitir que no me gusta mucho, especialmente para los métodos privados y protegidos. Motivos: a) abarrota el código con comprobaciones que no se parecen en absoluto a los requisitos comerciales b) esas comprobaciones son difíciles de probar para métodos no públicos c) es inútil en muchos casos, por ejemplo, ya que un valor nulo hará que el método falle de todos modos dos líneas más tarde
Erich Kitzmueller
18

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:

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

¡No permitas que nada escape de tu clase que no necesitas!

David Grant
fuente
+1 En C # hay colecciones de solo lectura para esto.
tobsen
1
+1 para Java. Lo uso todo el tiempo
Fortyrunner
+1 ... Hago esto mucho (aunque desearía que más personas recuerden hacer esto).
Ryan Delucchi
Solo asegúrate de que nada más tenga una instancia de la variable de lista subyacente, o esto no te servirá de mucho ...
GreenieMeanie
55
Para su información, Collections.unmodifiableList devuelve una vista inmutable de la lista, no una copia inmutable . Entonces, si la lista original se modifica, también lo hará la vista.
Zarkonnen
17

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 en la captura de cadenas sin inicializar.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here
Eric Johnson
fuente
Ojalá esto fuera un poco más anunciado ...
DJG
16

C#:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}
CMS
fuente
Lo mismo en Java, y probablemente en la mayoría de los lenguajes OO.
MiniQuark
Esto es bueno en Objective-C, donde es posible enviar mensajes a cero ("métodos de llamada de un objeto nulo", con una cierta licencia). Llamar a [nil isEqualToString: @ "Moo"] devuelve falso.
zoul
No estoy de acuerdo con el ejemplo de C #. Una solución mejor es usar "if (myString ==" someValue ")". Tampoco hay excepción de referencia nula, y ciertamente más legible.
Dan C.
13
Este ejemplo solo le permite ocultar una situación potencialmente peligrosa en su programa. Si no esperaba que fuera nulo, QUIERE que arroje una excepción, y si esperaba que fuera nulo, debe manejarlo como tal. Esta es una mala práctica.
rmeador
2
En C #, siempre uso string.Equals (<string1>, <string2>). No importa si alguno de ellos es nulo.
Darcy Casselman
15

En c # comprobación 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 un espacio en blanco

string.IsNullOrWhiteSpace(myString)
revs Binoj Antony
fuente
77
Eso no es ser defensivo, es ignorar el problema. Debe ser if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
enashnash
14

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.

Eddie
fuente
Y, en Windows, ¡también para C ++! (habilitado con lanzamiento especial de excepción SEH y siguiente captura).
Brian Haak
12

Con VB.NET, active Option Explicit y Option Strict de manera predeterminada para todo Visual Studio.

ChrisA
fuente
Aunque estoy trabajando con un código anterior, entonces no tengo la opción estrictamente activada (provoca demasiados errores de compilación para corregir), tener ambas opciones activadas puede (y en mi caso habría ahorrado mucho corazón) dolor.
Kibbee
1
Por cierto, para cualquiera que convierta código antiguo que no usó Option Strict para usarlo, tenga en cuenta que en el caso de los elementos que se convirtieron automáticamente a String, no están usando ToString (); Están usando un yeso para encadenar. En mis primeros días .NET los cambiaría para usar ToString (), y rompería las cosas, especialmente con las enumeraciones.
Ryan Lundy el
10

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

Nitin Bhide
fuente
1
Mejor usar una plantilla. Tiene alcance y sobrecarga.
Estaba a punto de decir eso. Use una plantilla en lugar de una macro. De esa manera, si alguna vez necesita recorrer el código, puede hacerlo.
Steve Rowe el
Aprendí cuál era más fácil de depurar por el camino difícil en la escuela. Es un error que no he cometido desde entonces. :)
Greg D
3
O use Smart Pointers ... O diablos, evite nuevo / eliminar tanto como pueda de todos modos.
Arafangion
1
@Arafangion: solo evita delete. El uso newestá bien, siempre que el nuevo objeto sea propiedad de un puntero inteligente.
Alexandre C.
10

Con Java, puede ser útil utilizar la palabra clave afirmar, incluso si ejecuta código de producción con las afirmaciones 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 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.

Outlaw Programmer
fuente
En .NET, Debug.Assert hace lo mismo. Siempre que tenga un lugar en el que piense "Esta referencia no puede ser nula aquí, ¿verdad?", Puede poner un Depuración. Agregue allí, de modo que si puede ser nulo, pueda corregir el error o cambiar sus suposiciones.
Ryan Lundy el
1
+1, métodos públicos debe tirar en los fracasos del contrato, soldados deben valer
user7116
9

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

readonly var prodSVC = new ProductService();
JMS
fuente
Utilizo la palabra clave 'final' equivalente de Java en todos los campos que puedo. Realmente le ahorra hacer movimientos de cabeza hueca como no establecer un campo / escribir interruptores confusos que pueden establecer campos varias veces. Es menos probable que marque variables / parámetros locales, pero supongo que no podría doler.
Outlaw Programmer
C # no le permite marcar variables locales como de solo lectura, por lo que ni siquiera tenemos la opción ...
Jason Punyon
No estoy seguro de lo que significa solo lectura aquí, pero la final de Java no es suficiente para mí. Simplemente significa que el puntero a un objeto no ha cambiado, pero no hay forma de evitar cambios en el objeto mismo.
Slartibartfast
En C #, una estructura de solo lectura evitaría que se cambie.
Samuel
9

En Java, cuando algo está sucediendo y no sé por qué, a 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 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.

Eddie
fuente
Hmm, esto es bastante bueno, pero me preocupa que pueda causar cierta mezcla de código funcional y de manejo de errores. Si bien el mecanismo try-catch puede ser torpe, obliga a uno a hacer una ejecución de redireccionamiento de excepción de un bloque (try) a otro (catch), que es donde está todo el manejo de errores
Ryan Delucchi
1
Esto no se usa para el manejo de errores, sino solo para diagnósticos . Esto le permite ver la ruta por la cual su código entró en una situación inesperada sin interrumpir el flujo de su código. Lo uso cuando el método en sí puede manejar la situación inesperada, pero aún así no debería suceder.
Eddie el
2
también puede usar la nueva Excepción ("mensaje"). printStackTrace (); No es necesario tirar ni atrapar, pero aún obtienes un buen stacktrace en el registro. Obviamente, esto no debería estar en el código de producción, pero puede ser muy útil para la depuración.
Jorn
9

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:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}
Steve Rowe
fuente
para Java, esta es la clase Foo {void doSomething () {}} class Bar {@Override void doSomething () {}} Dará una advertencia si falta la anotación (por lo que no puede anular en silencio un método que no hizo significa) y cuando la anotación está ahí pero no anula nada.
Jorn
Agradable ... no sabía acerca de esto ... es una pena que parece ser específica Microsoft ... pero algunas buenas #defines puede ayudar a hacer que sea portátil
e.tadeu
8

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.

Eddie
fuente
1
Las esperas con tiempos de espera utilizados como este son un signo de otros problemas peores.
dicroce
2
En un sistema que debe tener un alto tiempo de actividad, es mejor al menos obtener información de diagnóstico para que pueda encontrar el problema. A veces prefiere salir de un hilo o devolver una respuesta a la persona que llama cuando el sistema está en un estado conocido, por lo que espera en un semáforo. Pero no quieres ahorcarte para siempre
Eddie
8

C#

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

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.

Joe McMahon
fuente
1
Al escribir el código para un mensaje de error, lea el mensaje y pregunte qué desea saber a continuación , luego agréguelo. Repita hasta que no sea razonable. Por ejemplo: "Fuera de rango". ¿Qué está fuera de rango? "Foo cuenta fuera de rango". ¿Cuál fue el valor? "El recuento de foo (42) está fuera de rango". ¿Cuál es el rango? "El número de foo (42) está fuera del rango (549 a 666)".
HABO
7

En C #, use la aspalabra clave para emitir.

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

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 assintaxis aproximadamente el 75% del tiempo y la (cast)sintaxis aproximadamente el 25%.

Matt Briggs
fuente
Correcto, pero ahora tiene que verificar si es nulo antes de usar la referencia.
Brian Rasmussen
77
No lo entendí. Me parece una mala decisión, preferir el nulo. Obtendrá problemas en algún lugar durante el tiempo de ejecución sin ninguna pista sobre el motivo original.
Kai Huppmann el
Si. Esto solo es útil si realmente quieres nulo cuando no es el tipo correcto. Útil en algunos casos: en Silverlight, donde la lógica de control y el diseño a menudo están separados, la lógica quiere usar el control "Arriba" solo si es un botón. Si no, es como si no existiera (= nulo).
Sander
2
Esto parece tan defensivo como las grandes ventanas de salón de baile en una fortaleza. ¡Pero es una vista encantadora!
Pontus Gagge
1
Esto me recuerda a alguien que no usó excepciones porque 'rompieron programas'. Si desea cierto comportamiento cuando un objeto no es una clase que espera, creo que codificaría esto de otra manera. is/instanceofvienen a la mente.
Kirschstein el
6

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.

Eddie
fuente
6

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 .

zen
fuente
6

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.

user45947
fuente
5

Olvidé escribir echoen PHP demasiadas veces:

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

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 EchoMeclase que podría envolverse alrededor de cualquier valor que debería repetirse:

<?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");
  }
}

Y luego, para el entorno de desarrollo, utilicé un EchoMe para envolver cosas que deberían imprimirse:

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

Usando esa técnica, el primer ejemplo que faltaba echoahora arrojaría una excepción ...

demasiado php
fuente
¿No debería el destructor verificar $ this-> impreso! == verdadero?
Karsten
Debería considerar usar un sistema de plantillas, incrustar php en html es subóptimo en casi todos los sistemas.
Cruachan
Tal vez me estoy perdiendo algo? Pero me parece que esto está tratando de compensar la codificación: en AnObject.ToStringlugar de Writeln(AnObject.ToString)?
Desilusionado el
Sí, pero el error es mucho más fácil de hacer en PHP
demasiado php
4

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

mmr
fuente
Me gusta tu primer punto. Hago cosas similares cuando, por ejemplo, escribo un método que devuelve una colección de algo. Creo la colección en la primera línea e inmediatamente escribo la declaración de devolución. Todo lo que queda es completar cómo se llena la colección.
Outlaw Programmer
55
En C ++, cuando escribe new, debe asignar inmediatamente ese puntero a un AutoPtr o contenedor contado por referencia. C ++ tiene destructores y plantillas; úselos sabiamente para manejar la eliminación automáticamente.
An̲̳̳drew
4

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.

dicroce
fuente