Me interesa saber qué técnica (s) está utilizando para validar el estado interno de un objeto durante una operación que, desde su propio punto de vista, solo puede fallar debido a un mal estado interno o una violación invariable.¿Cómo se valida el estado interno de un objeto?
Mi enfoque principal está en C++, ya que en C# la manera oficial y predominante es lanzar una excepción, y en C++ no es sólo una sola manera de hacer esto (bueno, no realmente en C# o bien, sé ese).
Tenga en cuenta que estoy no hablando de validación de parámetros de función, pero más como verificaciones de integridad invariante de clase.
Por ejemplo, supongamos que queremos un objeto Printer
para Queue
un trabajo de impresión de forma asíncrona. Para el usuario de Printer
, esa operación solo puede tener éxito, porque un resultado de cola asíncrona llega en otro momento. Por lo tanto, no hay un código de error relevante para transmitir a la persona que llama.
Pero para el objeto Printer
, esta operación puede fallar si el estado interno es incorrecto, es decir, la invariante de clase está rota, lo que básicamente significa: un error. Esta condición no es necesariamente de interés para el usuario del objeto Printer
.
Personalmente, tiendo a mezclar tres estilos de validación de estado interno y realmente no puedo decidir cuál es el mejor, si lo hay, cuál es el peor. Me gustaría escuchar su opinión sobre esto y también que comparta sus propias experiencias y pensamientos sobre este asunto.
El primer estilo que utilizo - mejor fallar de una manera controlable que los datos corruptos:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in both release and debug builds.
// Never proceed with the queuing in a bad state.
if(!IsValidState())
{
throw InvalidOperationException();
}
// Continue with queuing, parameter checking, etc.
// Internal state is guaranteed to be good.
}
El segundo estilo que utilizo - mejor accidente incontrolable que los datos corruptos:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in debug builds only.
// Break into the debugger in debug builds.
// Always proceed with the queuing, also in a bad state.
DebugAssert(IsValidState());
// Continue with queuing, parameter checking, etc.
// Generally, behavior is now undefined, because of bad internal state.
// But, specifically, this often means an access violation when
// a NULL pointer is dereferenced, or something similar, and that crash will
// generate a dump file that can be used to find the error cause during
// testing before shipping the product.
}
El tercer estilo Uso - mejor en silencio y en defensa defensiva que datos corruptos:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in both release and debug builds.
// Break into the debugger in debug builds.
// Never proceed with the queuing in a bad state.
// This object will likely never again succeed in queuing anything.
if(!IsValidState())
{
DebugBreak();
return;
}
// Continue with defenestration.
// Internal state is guaranteed to be good.
}
Mi comentario ts a los estilos:
- Creo que prefiero el segundo estilo, donde la falla no está oculta, siempre que una infracción de acceso realmente cause un bloqueo.
- Si no se trata de un puntero NULL involucrado en el invariante, entonces tiendo a inclinarme hacia el primer estilo.
- Realmente no me gusta el tercer estilo, ya que ocultará muchos errores, pero conozco a gente que lo prefiere en el código de producción, porque crea la ilusión de un software robusto que no se cuelga (las funciones simplemente se detienen para funcionar , como en la cola del objeto
Printer
roto).
¿Prefiere alguno de estos o tiene otras formas de lograrlo?
En realidad, no estoy de acuerdo en que NVI sea una buena solución en el caso específico que expuse. Sería bueno si Printer fuera una clase base, pero añadiendo ese cableado antes la necesidad es obvia a menudo es en vano. Si veía la necesidad de derivar de la impresora, entonces me refactorizaría en ese momento. –