Pregunta

  

Duplicar posible:
   defensiva de programación

Hemos tenido una gran discusión esta mañana sobre el tema de la programación defensiva. Tuvimos una revisión de código, donde un puntero fue aprobada y no se comprobó si era válido.

Algunas personas sintieron que era necesario solamente un cheque de puntero nulo. Interrogué si podría ser verificado en un nivel superior, en lugar de cada método se pasa a través, y que la comprobación de nula fue un cheque muy limitada si el objeto en el otro extremo del punto de no cumplir con ciertos requisitos.

Yo entiendo y acepto que un cheque por nula es mejor que nada, pero se siente a mí que la comprobación sólo para nula proporciona una falsa sensación de seguridad, ya que está limitado en su alcance. Si desea asegurarse de que el puntero se puede utilizar, comprobar si hay más de la nula.

¿Cuáles son sus experiencias sobre el tema? ¿Cómo se escribe en las defensas de su código para los parámetros que se pasan a los métodos subordinados?

¿Fue útil?

Solución

En código completo 2, en el capítulo sobre la gestión de errores, me presentaron a la idea de barricadas. En esencia, una barricada es un código que valida rigurosamente todas las entradas que entra en ella. Código dentro de la barricada puede asumir que cualquier entrada inválida ya ha sido tratado, y que las entradas que se reciben son buenas. En el interior de la barricada, código sólo tiene que preocuparse acerca de los datos no válidos que se le pasan por otros códigos dentro de la barricada. Hacer valer las condiciones y las pruebas unitarias juiciosa puede aumentar su confianza en el código de barricadas. De esta manera, se programa muy defensiva en la barricada, pero en menor medida en el interior de la barricada. Otra forma de pensar en ello es que en la barricada, siempre se maneja correctamente los errores, y en el interior de la barricada se limitan a afirmar las condiciones en su versión de depuración.

Por lo que el uso de punteros primas va, por lo general lo mejor que puede hacer es afirmar que el puntero no es nulo. Si sabe lo que se supone que es en el que la memoria entonces se podría asegurar que el contenido es consistente de alguna manera. Esto plantea la pregunta de por qué los que la memoria no está envuelto en un objeto que se puede verificar que es la consistencia en sí.

Así que, ¿por qué estás usando un puntero prima en este caso? ¿Sería mejor utilizar una referencia o un puntero inteligente? Hace el puntero contiene datos numéricos, y si es así, ¿sería mejor que lo envuelve en un objeto que logró el ciclo de vida de ese puntero?

La respuesta a estas preguntas puede ayudar a encontrar una manera de ser más defensivo, en el que se va a terminar con un diseño que es más fácil de defender.

Otros consejos

La mejor manera de estar a la defensiva no es para comprobar punteros para nula en tiempo de ejecución, pero a evitar el uso de punteros que pueden ser nula, para empezar

Si el objeto que se pasa en el mosto no sea nulo, utilice una referencia! O pasarlo por valor! O utilizar un puntero inteligente de algún tipo.

La mejor manera de hacerlo programación defensiva es atrapar a sus errores en tiempo de compilación. Si se considera un error de un objeto a ser nula o punto a la basura, entonces usted debe hacer esas cosas errores de compilación.

En última instancia, usted no tiene manera de saber si un puntero apunta a un objeto válido. Así que en lugar de la comprobación de un caso esquina específica (que es mucho menos común que los realmente peligrosos, los punteros que apuntan a inválido objetos), hacen que el error imposible mediante el uso de un tipo de datos que garantiza la validez.

No puedo pensar en otro idioma de la generalidad que le permite atrapar la mayor cantidad de errores en tiempo de compilación como lo hace C ++. utilizar esa capacidad.

No hay manera de comprobar si un puntero es válido.

En todo serio, que depende de la cantidad de errores que le gustaría tener que han infligido a usted.

Comprobación de un puntero nulo es definitivamente algo que consideraría necesario pero no suficiente. Hay un montón de otros principios sólidos puede utilizar a partir de los puntos de entrada de su código (por ejemplo, la validación de entrada = hace que la punta puntero a algo útil) y los puntos de salida (por ejemplo, se pensaba que el puntero señaló algo útil, pero pasó a la causa su código para lanzar una excepción).

En resumen, si se asume que todo el mundo llama el código que va a hacer todo lo posible para arruinar su vida, probablemente se encontrará una gran cantidad de los peores culpables.

EDITAR para mayor claridad: algunas otras respuestas están hablando de pruebas unitarias. Creo firmemente que el código de prueba es a veces más valioso que el código que se está probando (dependiendo de quién está midiendo el valor). Dicho esto, también creo que las pruebas son unidades también necesaria pero no suficiente para la codificación defensiva.

Ejemplo concreto: considerar una tercera parte método de búsqueda que se documenta para devolver una colección de valores que responden a su petición. Por desgracia, lo que no era claro en la documentación de ese método es que el desarrollador original decidió que sería mejor volver un nulo en lugar de una colección vacía si no coincide con su petición.

Así que ahora, se llama a la unidad de prueba del método de pensamiento defensivo y también (lo que falta tristemente una comprobación interna puntero nulo) y boom! NullPointerException que, sin una comprobación interna, que no hay manera de hacer frente a:

defensiveMethod(thirdPartySearch("Nothing matches me")); 
// You just passed a null to your own code.

Soy un gran fan de la "deja que choque" escuela de diseño. (Negación: no trabajo en equipo médico, aviónica, o software relacionados con la energía nuclear.) Si su programa explota, que estimulan al depurador y averiguar por qué. Por el contrario, si el programa sigue funcionando después de que se han detectado parámetros ilegales, en el momento en que se bloquea es probable que tenga ni idea de lo que salió mal.

Buena código se compone de muchas pequeñas funciones / métodos, y la adición de una docena de líneas de parámetro de comprobación de cada uno de esos fragmentos de código hace que sea más difícil de leer y más difícil de mantener. Debe ser sencillo.

Puede que sea un poco extremo, pero no me gusta programación defensiva, creo que es la pereza que se ha introducido el principio.

En este ejemplo en particular, no hay sentido en afirmar que el puntero no es nulo. Si quieres un puntero nulo, no hay mejor manera de hacer cumplir en realidad (y documentar con claridad al mismo tiempo), que utilizar una referencia en su lugar. Y es la documentación que realmente se hace cumplir por el compilador y no cuesta una ziltch en tiempo de ejecución !!

En general, tiendo a no utilizar los tipos 'en bruto' directamente. Vamos a ilustrar:

void myFunction(std::string const& foo, std::string const& bar);

¿Cuáles son los valores posibles de foo y bar? Bueno, eso es más o menos limitado sólo por lo que un std::string puede contener ... que es bastante vaga.

Por otro lado:

void myFunction(Foo const& foo, Bar const& bar);

es mucho mejor!

  • si la gente erróneamente invertir el orden de los argumentos, que es detectado por el compilador
  • cada clase es el único responsable de comprobar que el valor es correcto, los usuarios no están burdenned.

Tengo una tendencia a favorecer Tipificación estricta. Si tengo una entrada que debe estar compuesto únicamente de caracteres alfabéticos y ser de hasta 12 caracteres, prefiero crear una pequeña clase envolviendo una std::string, con un método simple que se usa internamente validate para comprobar las asignaciones, y pasar esa clase alrededor de su lugar . De esta manera sé que si puedo probar la rutina de validación de la ONCE, no tiene que preocuparse realmente acerca de todos los caminos por los que ese valor puede llegar a mí> será validado cuando me llega.

Por supuesto, eso no me que el código no debe ser probado. Es sólo que estoy a favor de una fuerte encapsulación, y validación de una entrada es parte de encapsulación conocimientos en mi opinión.

Y como ninguna regla puede venir sin una interfaz excepción ... expuesta está necesariamente hinchado con código de validación, porque nunca se sabe lo que podría venir sobre ti. Sin embargo, con objetos auto-validación en su lista de materiales es bastante transparente en general.

"Las pruebas unitarias que verifican el código hace lo que debe hacer"> "código de producción tratando de comprobar su no hacer lo que no supone su hacer".

Yo ni siquiera comprobar NULL a mí mismo, a menos que su parte de una API publicada.

Depende en gran medida; es el método en cuestión cada vez llamado por el código externo a su grupo, o se trata de un método interno?

Para los métodos internos, puede probar lo suficiente como para hacer de este un punto discutible, y si usted está construyendo código donde el objetivo es más alto rendimiento posible, puede que no desee para pasar el tiempo en la comprobación de entradas eres muy seguros tienen razón.

Para los métodos visibles externamente - si usted tiene cualquiera - siempre se debe revisar sus entradas. Siempre.

Desde el punto de vista de depuración, es más importante que su código es a prueba de rápido. El anterior falla el código, el más fácil de encontrar el punto de falla.

Para los métodos internos, que normalmente se pegan a afirma para este tipo de controles. Eso no obtener errores recogidos en las pruebas unitarias (que tiene buena cobertura de la prueba, ¿verdad?) O al menos en las pruebas de integración que se están ejecutando con las afirmaciones sobre.

comprobación de puntero nulo es sólo la mitad de la historia también debe asignar un valor nulo a cada puntero no asignada .
API más responsable hará lo mismo.
la comprobación de un puntero nulo viene muy barato en ciclos de CPU, que tiene una aplicación estrellarse su vez entregados a usted y su empresa puede costar dinero y reputación.

puede saltarse los controles de puntero nulo si el código está en una interfaz privada tiene un control completo de y / o comprobar NULL mediante la ejecución de una prueba de unidad o alguna prueba de depuración de construcción (por ejemplo, afirman)

Hay algunas cosas en el trabajo aquí en esta pregunta que me gustaría para hacer frente a:

  1. directrices de codificación deben especificar que sea lidiar con una referencia o un valor directamente en lugar de utilizar punteros. Por definición, los punteros son los tipos de valor que acaba de celebrar una dirección en la memoria - (. Rango de memoria direccionable, plataforma, etc) validez de un puntero es específica de la plataforma y significa muchas cosas
  2. Si usted se encuentra cada vez que necesite un puntero por cualquier razón (como por objetos dinámicamente generadas y polimórficas) considerar el uso de punteros inteligentes. punteros inteligentes que dan muchas ventajas con la semántica de los punteros "normales".
  3. Si un tipo, por ejemplo, tiene un estado de "no válido", entonces el propio tipo debería prever esto. Más específicamente, se puede implementar el patrón NullObject que especifica cómo un "mal definidos" o "no inicializado" se comporta objeto (tal vez por lanzar excepciones o proporcionando no-op funciones miembro).

Puede crear un puntero inteligente que hace el defecto NullObject que tiene este aspecto:

template <class Type, class NullTypeDefault>
struct possibly_null_ptr {
  possibly_null_ptr() : p(new NullTypeDefault) {}
  possibly_null_ptr(Type* p_) : p(p_) {}
  Type * operator->() { return p.get(); }
  ~possibly_null_ptr() {}
  private:
    shared_ptr<Type> p;
    friend template<class T, class N> Type & operator*(possibly_null_ptr<T,N>&);
};

template <class Type, class NullTypeDefault>
Type & operator*(possibly_null_ptr<Type,NullTypeDefault> & p) {
  return *p.p;
}

A continuación, utilice la plantilla possibly_null_ptr<> en caso de que usted apoya punteros nulos posiblemente a tipos que tienen un defecto derivado de "comportamiento nulo". Esto hace que sea explícito en el diseño que no es un comportamiento aceptable para los "objetos nulos", y esto hace que su práctica defensiva documentado en el código - y más concreta -. Que una directriz o práctica general

Puntero sólo debe utilizarse si se necesita para hacer algo con el puntero. Tal como aritmética de punteros a transversal alguna estructura de datos. A continuación, si es posible, que debe ser encapsulado en una clase.

Si el puntero se pasa a la función de hacer algo con el objeto al que apunta, a continuación, pasa una referencia en su lugar.

Uno de los métodos de programación defensiva es afirmar casi todo lo que pueda. Al comienzo del proyecto es molesto, pero más tarde es un buen complemento de la unidad de pruebas.

Un número de dirección de respuesta a la pregunta de cómo escribir las defensas en su código, pero no se dijo mucho acerca de "cómo la defensiva debe ser?". Eso es algo que tiene que evaluar en base a la criticidad de los componentes de software.

Estamos haciendo software de vuelo y los impactos de un rango de error de software desde una molestia menor a la pérdida de las aeronaves / tripulación. Categorizamos diferentes piezas de software en base a sus potenciales impactos adversos que afecta a las normas, pruebas, etc. Es necesario evaluar cómo se utilizará el software y los impactos de los errores y establecer qué nivel de la defensiva que desea (y puede permitirse) de codificación. El href="http://en.wikipedia.org/wiki/DO-178B" rel="nofollow estándar DO-178B llama a esto "Diseño nivel de garantía".

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top