Estoy escribiendo una implementación Java de un juego de cartas, así que creé un tipo especial de Colección que llamo Zona. Todos los métodos de modificación de la Colección de Java no son compatibles, pero hay un método en la API de Zona move(Zone, Card)
, que mueve una Tarjeta de la Zona dada a sí misma (lograda por técnicas de paquete privado). De esta manera, puedo asegurarme de que no se saquen cartas de una zona y simplemente desaparezcan; solo se pueden mover a otra zona.
Mi pregunta es, ¿qué tan necesario es este tipo de codificación defensiva? Es "correcto", y se siente como la práctica correcta, pero no es como si la API de Zone fuera a formar parte de alguna biblioteca pública. Es solo para mí, por lo que es como si estuviera protegiendo mi código de mí mismo cuando probablemente podría ser más eficiente simplemente usando Colecciones estándar.
¿Hasta dónde debo llevar esta idea de Zona? ¿Alguien puede darme algún consejo sobre cuánto debería pensar en preservar los contratos en las clases que escribo, especialmente para aquellos que realmente no estarán disponibles públicamente?
fuente
Respuestas:
No voy a abordar el problema del diseño, solo la cuestión de si hacer las cosas "correctamente" en una API no pública.
Ese es exactamente el punto. Tal vez hay codificadores que recuerdan los matices de cada clase y método que escribieron y nunca los llaman por error con el contrato incorrecto. No soy uno de ellos A menudo olvido cómo se supone que el código que escribí funciona unas horas después de haberlo escrito. Después de pensar que lo has hecho bien una vez, tu mente tenderá a cambiar de marcha al problema en el que estás trabajando ahora .
Tienes herramientas para combatir eso. Estas herramientas incluyen (sin un orden particular) convenciones, pruebas unitarias y otras pruebas automatizadas, verificación de precondiciones y documentación. Yo mismo he encontrado que las pruebas unitarias son invaluables porque ambas te obligan a pensar en cómo se usará tu contrato y a proporcionar documentación más adelante sobre cómo se diseñó la interfaz.
fuente
Usualmente sigo algunas reglas simples:
IllegalArgumentException
).assert input != null
).Si un cliente está realmente interesado, siempre encontrará una manera de hacer que su código se comporte mal. Siempre pueden hacerlo a través de la reflexión, al menos. Pero esa es la belleza del diseño por contrato . No aprueba el uso de su código, por lo que no puede garantizar que funcione en tales escenarios.
En cuanto a su caso específico, si
Zone
se supone que no debe ser utilizado y / o al que no acceden personas ajenas, haga que la clase sea privada (y posiblementefinal
), o preferiblemente, use las colecciones que Java ya le proporciona. Se prueban y no tiene que reinventar la rueda. Tenga en cuenta que esto no le impide usar aserciones en todo el código para asegurarse de que todo funcione como se espera.fuente
La programación defensiva es algo muy bueno.
Hasta que comience a interferir en la escritura del código. Entonces no es tan bueno.
Hablando un poco más pragmáticamente ...
Parece que estás a punto de llevar las cosas demasiado lejos. El desafío (y la respuesta a su pregunta) radica en comprender cuáles son las reglas o requisitos comerciales del programa.
Usando su API de juego de cartas como ejemplo, hay algunos entornos donde todo lo que se puede hacer para evitar las trampas es crítico. Pueden estar involucrados grandes cantidades de dinero real, por lo que tiene sentido establecer una gran cantidad de cheques para asegurarse de que no se pueda hacer trampa.
Por otro lado, debe tener en cuenta los principios SÓLIDOS, especialmente la responsabilidad individual. Pedirle a la clase contenedor que audite eficazmente hacia dónde van las tarjetas puede ser un poco demasiado. Puede ser mejor tener una capa de auditoría / controlador entre el contenedor de la tarjeta y la función que recibe las solicitudes de movimiento.
En relación con esas inquietudes, debe comprender qué componentes de su API están expuestos públicamente (y, por lo tanto, son vulnerables) frente a lo que es privado y menos expuesto. No soy un defensor total de un "revestimiento exterior duro con un interior blando", pero el mejor retorno de su esfuerzo es endurecer el exterior de su API.
No creo que el usuario final previsto de una biblioteca sea tan crítico con la determinación sobre la cantidad de programación defensiva que implementa. Incluso con los módulos que escribo para mi propio uso, aún pongo una medida de verificación en el lugar para asegurarme de que en el futuro no haya cometido un error inadvertido al llamar a la biblioteca.
fuente
La codificación defensiva no es solo una buena idea para el código público. Es una gran idea para cualquier código que no se descarte de inmediato. Claro, ya sabe cómo se debe llamar ahora , pero no tiene idea de qué tan bien recordará estos seis meses a partir de ahora cuando regrese al proyecto.
La sintaxis básica de Java le brinda mucha defensa integrada en comparación con un lenguaje de nivel inferior o interpretado como C o Javascript, respectivamente. Suponiendo que nombra sus métodos con claridad y no tiene una "secuencia de métodos" externa, probablemente pueda salirse con la simple especificación de argumentos como un tipo de datos correcto e incluir un comportamiento sensato si los datos escritos correctamente aún pueden ser inválidos.
(Por otro lado, si las Cartas siempre tienen que estar en la zona, creo que obtienes mejores resultados al hacer que todas las cartas en juego sean referenciadas por una colección global a tu objeto de Juego, y que Zone sea una propiedad de cada carta, pero como no sé qué hacen tus zonas además de tener cartas, es difícil saber si eso es apropiado).
fuente
CardDescriptor
que contenga una carta, su ubicación, estado boca arriba / abajo, o incluso rotación para juegos que se preocupan por eso. Esas son todas propiedades mutables que no alteran la identidad de una tarjeta.Primero cree una clase que mantenga una lista de Zonas para que no pierda una Zona o las cartas en ella. Luego puede verificar que hay una transferencia dentro de su ZoneList. Esta clase probablemente será una especie de singleton, ya que solo necesitará una instancia, pero es posible que desee conjuntos de zonas más adelante, así que mantenga abiertas sus opciones.
En segundo lugar, no haga que Zone o ZoneList implemente Collection o cualquier otra cosa a menos que espere necesitarla. Es decir, si una Zona o ZoneList se pasará a algo que espera una Colección, entonces impleméntela . Puede deshabilitar varios métodos haciendo que arrojen una excepción (UnimplementedException, o algo así) o haciendo que simplemente no hagan nada. (Piense mucho antes de usar la segunda opción. Si lo hace porque es fácil, descubrirá que le faltan errores que podría haber detectado desde el principio).
Hay preguntas reales sobre lo que es "correcto". Pero una vez que descubras qué es, querrás hacer las cosas de esa manera. En dos años te habrás olvidado de todo esto, y si intentas usar el código, te enojarás mucho con el tipo que lo escribió de una manera tan contradictoria y no explicó nada.
fuente
La codificación defensiva en el diseño de API generalmente se trata de validar la entrada y seleccionar cuidadosamente un mecanismo de manejo de errores adecuado. Cosas que otras respuestas mencionan también son dignas de mención.
Esto no es realmente de lo que se trata su ejemplo. Estás limitando tu superficie API, por una razón muy específica. Como GlenH7 menciona, cuando el juego de cartas se va a usar en un juego real, con un mazo ('usado' y 'no usado'), una mesa y manos, por ejemplo, definitivamente querrá poner cheques en su lugar para asegurarse de que cada uno La tarjeta del set está presente una y solo una vez.
Que hayas diseñado esto con "zonas", es una elección arbitraria. Dependiendo de la implementación (una zona solo puede ser una mano, un mazo o una mesa en el ejemplo anterior) podría muy bien ser un diseño completo.
Sin embargo, esa implementación suena como un tipo derivado de un
Collection<Card>
conjunto de tarjetas más parecido, con una API menos restrictiva. Por ejemplo, cuando desea construir una calculadora de valor de mano, o una IA, seguramente quiere ser libre de elegir qué y cuántas de cada cartas iterará.Por lo tanto, es bueno exponer una API tan restrictiva, si el único objetivo de esa API es asegurarse de que cada tarjeta siempre esté en una zona.
fuente