Complejo del programador sobreprotector.

Últimamente he pensado que soy un programador sobreprotector, le impongo muchos límites a mi código (clases/métodos/funciones/…), le digo con que otros códigos juntarse y cómo comportarse ante las malas influencias. Un ejemplo es: http://www.javamexico.org/blogs/rodrigo_salado_anaya/ayuda_me_urge_ejemp...

Si algún medio le manda un String nulo a mi método, debería de generar una excepción. Eso lleva a que el código que lo implementa cuide ese detalle, así delegando esas obligaciones en el momento adecuado.

Y porque en el momento adecuado, bueno porque si el método que manda la ruta se da cuenta que es nula antes de mandarla, podría hacer otro intento para generar la correcta y no tener que esperar la excepción del validador para decir “a pus si cierto, te la mande nula verdad :S”
En estos tiempo no debería de existir null, con la cantidad de RAM que tenemos, el GC nos la pellizca (¡Claro es broma!)
Y bueno ya me decidí a no validar eso, pero aun así me quedo con la espinita y ¿Si yo no soy el que escribe el código de los dos métodos? ¿Si simplemente falla él envió de la ruta entre los dos medios? No puedo ser tan indulgente con esos detalles.

Existe una sentencia en Java desde la 1.4 que se llama assert y sirve mucho para hacer estupideces (lo dice la experiencia) y para hacer aseveraciones del tipo booleano.

Lo siguiente es un ejemplo de su uso y como hacer sus pruebas unitarias. Antes debes decirle a la JVM que habilite esa funcionalidad, pero mejor leer este artículo: http://download.oracle.com/javase/6/docs/technotes/guides/language/asser...

También puedes tomarlo como un ejemplo de cómo hacer pruebas a excepciones con JUnit y por medio de anotaciones (versión probada: 4.8.2)

Código Test

public class ValidadorTest {
    private Validador v = new Validador();
   
    @Test (expected=AssertionError.class)
    public void rutaNula(){
        v.directorioValido(null);
    }
   
    @Test (expected=AssertionError.class)
    public void sinRuta(){
        v.directorioValido("");
    }
   
    @Test (expected=AssertionError.class)
    public void rutaVacia(){
        v.directorioValido(" ");
    }
   
    @Test
    public void validaDirectorio(){
        assertFalse("Ruta extraña", v.directorioValido("#$%&"));
        assertFalse("No es directorio", v.directorioValido("C:/directorio/archivo.txt"));
        assertTrue("Es directorio", v.directorioValido("C:/"));
        assertTrue("Es directorio", v.directorioValido("C:/"));
    }
}

Código Java

public class Validador {    
    public boolean directorioValido(String ruta){
        assert  ruta != null : "ruta nula";
        if(ruta == null){
            throw new NullPointerException("Ruta nula");
        }
        assert  !ruta.trim().isEmpty();
        if(!ruta.trim().isEmpty()){
            throw new MyEmptyPathException("Ruta vacía");
        }
        return  !new File(ruta).exists() ||
                !new File(ruta).isDirectory()
                ? false : true;
    }
}

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

No me parece que sea

No me parece que sea sobreprotector.

Aunque tu lógica parece buena y la intención también lo es, tiene una falla grave en el uso del assert y no es técnica.

El assert no debe de ser usado en código que sirva para hacer una validación. La razón es muy simple, los asserts pueden ser deshabilitados y si están deshabilitados ya no te protegen de nada:

$cat A.java
import static java.lang.System.out;
class A {
   void something( Object arg ) {
       assert arg != null : "args no debe de ser null";
       out.println( arg.toString() );// por decir algo
    }
    public static void main( String ... args ) {
        new A().something( null );
    }
}
$javac A.java
$java -ea A
Exception in thread "main" java.lang.AssertionError: args no debe de ser null
        at A.something(A.java:4)
        at A.main(A.java:8)
$java A
Exception in thread "main" java.lang.NullPointerException
        at A.something(A.java:5)
        at A.main(A.java:8)

De nada sirvió. Cuando el assert estuvo habilitado hizo su trabajo bien, pero en la segunda ejecución sin el assert, sale un error que no nos ayuda en mucho..

Cuando lo que intentes es validar la entrada, lo que tienes que poner es un if sí o sí:

$cat A.java
import static java.lang.System.out;
class A {
   void something( Object arg ) {
       // validar que arg no sea null
       if ( arg == null ) {
           throw new NullPointerException("arg no debe de ser null");
       }
       // lame...
       //assert arg != null : "args no debe de ser null";
       out.println( arg.toString() );// por decir algo
    }
    public static void main( String ... args ) {
        new A().something( null );
    }
}
$javac A.java
$java A
Exception in thread "main" java.lang.NullPointerException: arg no debe de ser null
        at A.something(A.java:6)
        at A.main(A.java:13)
$java -ea A
Exception in thread "main" java.lang.NullPointerException: arg no debe de ser null
        at A.something(A.java:6)
        at A.main(A.java:13)
$

En ambas ejecuciones obtuvimos el mensaje correcto.

Es decir, la práctica de validar es muy buena. La siguiente vez que alguien programe usando esta clase, le lanzará un NPE cuando le pase un null y le va a decir la línea exacta y el mensaje preciso. Debe de ser una RuntimeException ( Recordar que NullPointerException es una subclase de RuntimeException ) para dejar claro que el error es de programación ( o sea que se puede evitar poniendo código ).

Al poner la validación así con un if nos aseguramos que se ejecute SIEMPRE.

Entonces, ¿cuando debe de usarse el assertion? La respuesta es: cuando quieres confirmar que lo que tu crees de tu código es cierto, pero no debe de reemplazar la validación.

Piensas: " yo creo que en este punto el valor no es nulo", puedes hacer un assertion y confirmarlo. Si tienes un error en producción puedes habilitar los asserts incluso por un paquete en específico y confirmar que lo que tu crees de tu código ( que un valor no es nulo ) es en realidad así. Si no lo es, el assert te dirá donde fallaste. Es por esto también útil poner un mensaje que haga sentido.

Entonces, este ejemplillo debería de quedar así:

class A {
   void something( Object arg ) {
       // validar que arg no sea null
       if ( arg == null ) {
           throw new NullPointerException("arg no debe de ser null");
       }
       // Si llegamos aquí es porque arg no es null
       // a ver si es cierto.
       assert arg != null : "A caray?! algo raro paso!!";
       out.println( arg.toString() );// por decir algo
    }
    public static void main( String ... args ) {
        new A().something( new Object()  );
        new A().something( null );
    }
}

Este es solo un ejemplo por supuesto, pero supongamos que es uno de esos códigos kilometricos, alguien puede introducir un bug sin querer.
Supongamos que con el tiempo y los cambios alguien introduce algun bug por error:

$cat A.java ; javac A.java ; java  A ; echo ; echo ; echo Revisemos eso ; echo ; echo ; java -ea A  
import static java.lang.System.out;
class A {
    void something( Object arg ) {
        // validar que arg no sea null
        if ( arg == null ) {
            throw new NullPointerException("arg no debe de ser null");
        }
        // blah, blah blha
        // mil versiones después y varios desarrolladores después
        // imaginemos que este método tiene no sé mil lineas o algo así:
        arg = null; // ouch!!! alguien metio el bug
        // y el código sigue hasta antes de usarlo
        // a ver si es cierto.
        assert arg != null : "A caray?! algo raro paso!!";
        out.println( arg.toString() );// por decir algo
    }
    public static void main( String ... args ) {
        new A().something( new Object()  );
        new A().something( null );
    }
}
Exception in thread "main" java.lang.NullPointerException
        at A.something(A.java:15)
        at A.main(A.java:18)

Revisemos eso

Exception in thread "main" java.lang.AssertionError: A caray?! algo raro paso!!
        at A.something(A.java:14)
        at A.main(A.java:18)

En este ejemplo tuvimos un NullPointerException en la primera ejecución, el programador diría "NooooOOooooo pues si yo lo valideeeEEEEeeeeEe, no debería de ser nuuUUUuuuulll" entonces en la segunda ejecución habilitamos el assert y vemos que efectivamente, hicimos la validación pero algo raro pasó en el camino.

Bueno esa es la idea.

Otras dos cosas a considerar al usar assert es que no debe de haber efectos secundarios al usarlo ( precisamente para que no cambie el comportamiento cuando esté deshabilitado ) y que la validación debe de preferencia ser costosa. Si es costosa, hace algo que vale la pena y que es complejo y que además se puede quitar en producción. Si no es costosa ( args != null no es costoso ) mejor sería o no ponerlo o dejarlo en el código mismo.

...
void something( SomeComplexObject o ) {
   if ( o == null ) {
       throw new NullPointerException( "o no debe de ser null");
   }
   ...
   assert metodoQueHaceUnaRevisionCostosaDeO( o ); // quizá se conecta a la base de datos, coincilia estado de resultaso etc.
   o.someBusinessMethod();
}

La ganancia es que si por ejemplo lo que creíamos de o no es cierto, podemos invocar un método que haga muchas cosas para tratar de determinar donde está el error, pero que podemos deshabilitar al probar en producción.

Saludos!

Imagen de Sr. Negativo

Sobreprotector ??

Últimamente he pensado que soy un programador sobreprotector, le impongo muchos límites a mi código (clases/métodos/funciones/…), le digo con que otros códigos juntarse y cómo comportarse ante las malas influencias ...

No creo que seas sobreprotector. Creo es algo que siempre pasa. Tratamos de hallar la mejor manera de resolver un problema.

Además la culpa de la "ola del TDD" la tiene @OscarRyz por sus post sobre TDD y el Refactoring (risas). No , no es cierto (es broma). Me sirvieron de mucho para mejorar mi diseño de programas.

Hay muchos detractores de está técnica. "Es mu dificil programar de esa manera" , "Quita mucho tiempo...me urge", "¿Para qué me sirve?, las pruebas siempre son al final ..."

Siempre es bueno aprender cosas nuevas y útiles. No creen?

Imagen de rodrigo salado anaya

Gracias…

@OscarRyz, gracias me sirvió para tener más claro el tema, edite el post para dejarlo con tus consejos.
@Sr. Negativo yeap, aprender cosas nuevas es la neta.

Saludos a todos.