Quitar IF

Iniciado por n-utz, 24 Febrero 2018, 15:19 PM

0 Miembros y 1 Visitante están viendo este tema.

n-utz

Holas buenas,

Estoy intentando empezar a seguir la filosofía "real devs don't use if's", y me encuentro con códigos en los que no se como quitar el condicional, por eso les traigo uno, a ver si me tiran ideas y me abren un poco más la cabeza:

public static GregorianCalendar traerFecha (int año, int mes, int dia) {
GregorianCalendar fecha = null;

if (esFechaValida(año, mes, dia))
fecha = new GregorianCalendar(año, mes - 1, dia);

return fecha;
}


Creo que el codigo se entiende bastante bien, tiene nombres predictivos. Se le pasa un año, mes y dia, y te devuelve un GregorianCalendar con la fecha, pero solo si los parametros pasados son correctos (mes del 1 al 12, dia correcto dentro de ese mes, se contempla año bisiesto). Al mes le resto uno porque GregorianCalendar tiene los meses desfazados hacia abajo, el 0 es Enero.

Y les dejo otro de plus, que es justamente el que consume al que llama el código de arriba.


public static boolean esFechaValida (int año, int mes, int dia) {
int[] meses = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
boolean response = false;

if (esBisiesto(año))
meses[1] = 29;

if (mes >= 1 && mes <= 12)
response = meses[mes - 1] >= dia;

return response;
}


Si pueden tirarme ideas sobre como podria quitar los condicionales buenisimos, también agradecido si me recomiendan lecturas. Estoy yendo de a poco, para mi los casos en donde se pueden aplicar polimorfismo son más fáciles.

srWhiteSkull

#1
sinceramente es una filosofía absurda, no se donde o quien te ha recomendado eso

como es java podrías prescindir de la función esFechaValida() y simplemente usar un try/catch para GregorianCalendar

otra forma es que la vista o en la entrada que tome el dia,  mes y año restrinja los valores fuera de rango

si te refieres a las operaciones ternarias estos son if

n-utz

Los puristas de OO como los smalltalkers sostienen que se puede programar sin usar un solo condicional, solo objetos enviando mensajes a otros objetos.

Obviamente es un extremo, no busco hacer todo el código fuente sin poner condicionales, pero mientras más evites, mejor. Hay libros que hablan de esto como Clean Code, y plugins de coverages que te calculan la diversidad de flujos debido a condicionales que tiene tu programa. Igualmente, para gustos...

Es un trabajo práctico para la facultad, no hay vista, es solo consola. Y el try-catch para Gregorian como sería? Acabo de crear un objeto con fecha el 30 de Febrero del 2017 y me dejo sin problemas.

Gracias por la respuesta!

srWhiteSkull

#3
Código ("java") [Seleccionar]

...
try {
  fecha = new GregorianCalendar(año, mes - 1, dia);

} catch(Exception e) {
  // codigo para tratar la excepción (opcional)
}
...


Las condiciones siempre estarán presentes pues son la forma menos costosa de reconducir el flujo del programa y muy útiles en la validación de datos. Las excepciones en teoría son para que en el caso de que hubieses previsto todos los escenarios, validando, pudieras resolverlo de una forma elegante ante un imprevisto con un mensajito o similar sin que el programa se interrumpiera. Luego una vez se depura y se resuelve el problema y a sabiendas de que ya no puede repetirse prescindir de esta o mantenerla si se tratara de una parte critica del programa.

El abuso de condiciones tiene por supuesto repercusión en el rendimiento por ello el diseño de un programa debe ser inteligente y previsorio. Normalmente si improvisamos en los desarrollos, lo normal, esto no es importante porque una vez finalizamos podemos pasar a optimizarlo.

https://docs.oracle.com/javase/tutorial/essential/exceptions/handling.html

engel lex

no conseguí nada sobre esta "filosofía" (llamemoslo creencia, ya que una folosofia es "conjunto sistemático de los razonamientos expuestos por un pensador" y esto no creo que sea de un "pensador)

if es uno de los elementos más optimizados de la informática, cualquier otra via de toma de decisiones (literalmente 1 ciclo de procesador)

try/catch a pesar de ser "poco costoso" si tiene un esfuerzo porque obliga en runtime a hacer un "mini ambiente virtual" donde las cosas pasen "sin consecuencias" para en caso que falle, no afectar el resto del codigo (por lo menos cambiar valor de variables), si fue exitoso, este ambiente debe cerrarse...

hacerlo por metodos esotericos u objetos usualmente solo empeora el rendimiento y legibilidad, como digo "no ayuda a nadie"...



El problema con la sociedad actualmente radica en que todos creen que tienen el derecho de tener una opinión, y que esa opinión sea validada por todos, cuando lo correcto es que todos tengan derecho a una opinión, siempre y cuando esa opinión pueda ser ignorada, cuestionada, e incluso ser sujeta a burla, particularmente cuando no tiene sentido alguno.

Eleкtro

#5
Cita de: n-utz en 25 Febrero 2018, 18:10 PMY el try-catch para Gregorian como sería?

Si tienes pensado reemplazar condicionales por bloques try/catch con la idea de que así lograrás micro-optimizar el rendimiento... vas muy equivocado, haciendo eso conseguirás precisamente todo lo contrario que si usases un If. En las mismas condiciones, el tiempo total de ejecución de un try/catch es mayor que el de un If, sobre todo si en lugar de controlar una excepción específica lo que controlas es la clase base para todos los tipos de excepciones (clase Exception en Java) debe tomarse aun más tiempo para evaluar. Y en la misma cantidad, el abuso de bloques try/catch es más perjudicial para el rendimiento que el abuso de (anidación o no de) condicionales.

Aparte de que le estarias dando un uso inapropiado, ya que un bloque try/catch sirve para controlar cosas que escapan de tu control dentro del flujo normal de tu programa, y no es el caso.

Cita de: n-utz en 25 Febrero 2018, 18:10 PMIgualmente, para gustos...

Esa "filosofía" es de lo más absurdo, y esto no es cuestión de gustos, sino de hechos. No creo que se pueda añadir mucho más a lo que ya te ha explicado todo el mundo, deberías hacer caso a lo que te están advirtiendo y aconsejando los demás compañeros, por que ellos no dicen habladurías, en cambio esa filosofía si, y si te acostumbras a esas habladurías pues estarás siguiendo un mal camino en el ámbito de la programación.

Saludos!








engel lex

CitarEn las mismas condiciones, el tiempo total de ejecución de un try/catch es mayor que el de un If, sobre todo si en lugar de controlar una excepción específica lo que controlas es la clase base para todos los tipos de excepciones (clase Exception en Java) debe tomarse aun más tiempo para evaluar.

sumemos a esto... tu programas sin if, pero entonces tendrás que rehacer el lenguaje desde 0, ya que de nada vale si tu programas sin if en una librería hecha con ifs XD es como decir "se debe programar en java completamente en español"
El problema con la sociedad actualmente radica en que todos creen que tienen el derecho de tener una opinión, y que esa opinión sea validada por todos, cuando lo correcto es que todos tengan derecho a una opinión, siempre y cuando esa opinión pueda ser ignorada, cuestionada, e incluso ser sujeta a burla, particularmente cuando no tiene sentido alguno.

Orubatosu

Solo le veo sentido a esta "filosofía" para buscar opciones alternativas en escenarios donde hay una gran cantidad de toma de decisiones y el resultado es fijo para cada escenario.

En esos casos una alternativa es usar algo parecido a una tabla de verdad, una matriz donde cada "if" corresponda a un numero y obtener el resultado accediendo a la tabla

Por poner un ejemplo simple, imaginemos un "stick" digital con 9 posiciones, donde 0 es reposo y luego cada número corresponde a una dirección (cuadro direcciones y las diagonales). Implementar un cursor en ese escenario puede hacerse mediante nueve preguntas de tipo if-else, o puede resolverse con una matriz de 9 elementos por dos. (matriz bidimensional). El movimiento del cursor puede sacarse de sumar el valor almacenado en la matriz para cada caso.

Este sería un ejemplo sencillo donde se puede evitar el if, pero por lo demás no le veo sentido a insistir en no usar una herramienta legítima. Otro tema sería usar un "goto" que eso si que es aberrante en casi todos los casos.

La idea de la matriz puede ayudarte en muchos casos, pero no en todos
"When People called me freak, i close my eyes and laughed, because they are blinded to happiness"
Hideto Matsumoto 1964-1998

Serapis

#8
Si los 'if' fueran ineficaces ni siquiera formarían parte de la lógica... pero es que la lógica se basa exclusivamernte en los condicinales... a nivel 'atómico', son puertas AND, OR y XOR... lo que al final configuran los 'if'... cuando hacemos un if, estamos haciendo una comparación que a nivel de diseño, es una resta, y cuyo resultado arroja uno de 3 valores <0, 0 ó >0. Como resultado uno o más bits del registro de estado del procesador (en los x86), se activa y luego es cuando el programador puede 'test'ear el resultado.

Gran parte de la lógica digital descansa precisamente en esto... y en los saltos (al caso condicionados por el resultado de la comparación).

....

Esto no quiere decir que lo que te han señalado sobre el uso y abuso de 'IF', como que es mala programación no sea correcto, siempre que se entienda sin ambigüedad a que se quiere referir..., y parta ello te expongo claramente el ejemplo de 'no usar + ifs' de los necesarios...

Por ejemplo, en el caso de calcular fechas... dado que es complejo, verificar en concreto si un día, de un mes, de un año cae por ejemplo es tal o cual día ó si es válido (existe dicho día para dicho mes), exige usando IFs, muchas comprobaciones... entonces al caso es que es preferible resolverlo de forma más arítmetica que lógica...

En otras ocasones es haciendo uso de las operaciones lógicas más elementales: AND, OR, XOR qwue al fin y al cabo, bien entendidos son otra forma de condicionales.

Lo que se hace en general es calcular, y al final basta uno solo o dos condiconales.

Una foma sencilla de usar 'If's y no sobrecargar con cada llamada es entender que todos los meses tienen un nínimo de 28 días, luego no es preciso hacer un cácluclo total y abusivo con cada fecha que se llame...

Por claridad creamos una estructura que comprenda los datos precisos.

Estructura Fecha
   byte día
   byte mes
   entero año
fin estructura


También por claridad, no se consideran (en la función) los casos en que mes, día o año estén fuera de rango...
   Si ((f.año > x) y (f.año < y)) //x,y representan un límite impuesto para el año....
   Si (f.mes > 0) y (f.Mes < 12)  // mes va en el rango 1-12, descartar cualquier otra posibilidad (aunque podría ir en el rango 0-11).
   Si (f.dia > 0) y (f.dia < 32)  // se supone que día está en el rango 1-31


buleano = Funcion ValidarFecha(fecha f)
 Si (f.dia <= 28)
    devolver TRUE
 Sino
   devolver ValidarFechaAFondo(f)
 Fin si
Fin funcion


Aquí es cuando nos meteríamos en verificar si el día es 29, 30 ó 31 en base al mes y en caso de ser febrero el mes, en si es bisiesto.

buleano = Funcion ValidarFechaAFondo(fecha f)
   byte mes = f.mes

   Si (mes = 2) // febrero
       Si (f.Dia <30)
           Si AñoEsbisiesto(f.año)
               devolver TRUE
           fin si
        fin si
   Sino
       // el 'ó', es también un condicional... así esta línea tiene 4 condicionales.
       Si (mes = 4) ó (mes = 6) ó (mes = 9) ó (mes = 11)
           Si (f.dia < 31)
               devolver TRUE
           Fin si
       Sino  //enero, marzo, mayo, julio, agosto, octubre y diciembre tienen 31 días.
           devolver TRUE  
       Fin si
   Fin si
   
   devolver FALSE  
Fin Funcion


Por ejemplo: si queremos hacer algo basado en si un mes tiene 30, 31, 28, ó 29 días... sería relativamente sencillo, crear un array... que aunque largo, contuviera ya la solución, así se ahorrarían muchísimos 'IF's, y solo sería preciso una comprobación posterior para el caso de febrero cuando se señale el día 29 y no para 30 y 31

Nota ahora como el siguiente código ahorra mucho código a cambio de ocupar más memoria, con un array...

  //                                          mes , día
  Array de bytes DiaMesValido(0 a 12, 0 a 2) // el mes 0, no se usa...

Funcion RellenarArrayValido
  // Enero
  DiaMesValido(1,0) = 1   // día 29
  DiaMesValido(1,1) = 1   // día 30
  DiaMesValido(1,2) = 1   // día 31
  // Marzo
  DiaMesValido(3,0) = 1   // día 29
  DiaMesValido(3,1) = 1   // día 30
  DiaMesValido(3,2) = 1   // día 31
  // Mayo
  DiaMesValido(5,0) = 1   // día 29
  DiaMesValido(5,1) = 1   // día 30
  DiaMesValido(5,2) = 1   // día 31
  // Julio
  DiaMesValido(7,0) = 1   // día 29
  DiaMesValido(7,1) = 1   // día 30
  DiaMesValido(7,2) = 1   // día 31
  // Agosto
  DiaMesValido(8,0) = 1   // día 29
  DiaMesValido(8,1) = 1   // día 30
  DiaMesValido(8,2) = 1   // día 31
  // Octubre
  DiaMesValido(10,0) = 1   // día 29
  DiaMesValido(10,1) = 1   // día 30
  DiaMesValido(10,2) = 1   // día 31
  // Diciembre
  DiaMesValido(12,0) = 1   // día 29
  DiaMesValido(12,1) = 1   // día 30
  DiaMesValido(12,2) = 1   // día 31

  // Mes de 29 días: Calcular bisiesto
  // Febrero
  DiaMesValido(2,0) = 255   // día 29
  DiaMesValido(2,1) = 0      // día 30
  DiaMesValido(2,2) = 0      // día 31

  / Meses de 30 días: Abril, Junio, Septiembre y Noviembre
  // Abril
  DiaMesValido(4,0) = 1   // día 29
  DiaMesValido(4,1) = 1   // día 30
  DiaMesValido(4,2) = 0   // día 31
  // Junio
  DiaMesValido(6,0) = 1   // día 29
  DiaMesValido(6,1) = 1   // día 30
  DiaMesValido(6,2) = 0   // día 31
  // Septiembre
  DiaMesValido(9,0) = 1   // día 29
  DiaMesValido(9,1) = 1   // día 30
  DiaMesValido(9,2) = 0   // día 31
  // Noviembre
  DiaMesValido(11,0) = 1   // día 29
  DiaMesValido(11,1) = 1   // día 30
  DiaMesValido(11,2) = 0   // día 31
Fin funcion

// Al inicializar el componente se crea el array (estático) una única vez.
funcion Inicializar
  llamada a la funcion RellenarArrayValido
fin funcion


La función de validación (es la misma que la previa):

buleano = Funcion ValidarFecha(fecha f)
 Si (f.dia <= 28)
    devolver TRUE
 Sino
   devolver ValidarFechaAFondo(f)
 Fin si
Fin funcion


Y finalmente la función de validación 'a fondo' ahora queda así:

buleano = Funcion ValidarFechaAFondo(fecha f)
   byte resultado

   resultado = DiaMesValido(f.mes, f.dia - 29)  
   Si (resultado = 1)
       devolver TRUE
   Si (resultado = 255)  // caso de febrero, día 29...
       Si AñoEsbisiesto(f.año)
           devolver TRUE        
       Fin si
   Fin si

   devolver FALSE
fin funcion


Incluso si comparas la primera función (que yo expongo) con tú código, ésta es mucho más óptima, sigue sin necesitar entrar en consideraciones extras si el día es inferior a 29, ya que todos los meses tienen esos días... pero en la 2ª función que expongo...

Como se puede ver, se ha limitado mucho la cantidad de los 'If's usados. Hay otras opciones igualmente aceptables, pero la idea simplemente es la demostrar que el número de condicionales puede ser limitado de diferentes maneras, en el ejemplo mediante el uso de un array pre-configurado con las soluciones...
Ante una miríada de llamadas, se puede ver que solo se va a calcular si el año es bisiesto, tan solo cuando el mes introducido es febrero y el día 29 (tampoco si se indica día 30, ni 31 para febrero), y no con cada fecha que se recibe....

La función AñoEsBisiesto... queda a tu esfuerzo, baste saber que cada 4 años es bisiesto, y que los acabados en 00, no lo son, pero cada 400 años, el que acabe enn 00 si lo es, etc... posiblemente en wikipedia esté bien explicado, si lo precisas...

Otros casos para evitar IFs conllevan usar aritmética, o solucionando casos con AND, OR y XOR (pueden testear varios bits a la vez, en vez de verificar cada caso con un 'if suelto').

Serapis

Cita de: Orubatosu en 26 Febrero 2018, 14:10 PM
Otro tema sería usar un "goto" que eso si que es aberrante en casi todos los casos.
Es la misma tontería... un goto, es un Jump a nivel ensamblador... es inaceptable evadir su uso.

Lo que hacen los lenguajes de alto nivel es darle estructura a los saltos y delimitar el punto al que salta... por ejemplo para un bucle, simplemente se le permite 'salir del bucle', no saltar a cualquier otro punto si se usa, solo salta fuera dle bucle.

Bucle para k desde x hasta y
   ...
   Si (k es múltiplo de 7)
        Salir del bucle // es un jump, un goto...
   fin si
Fin bucle


Los goto, fueron condenados, no por el goto en sí, que son imprescindibles, si no porque lo hacían en la misma manera que se usan en ensamblador... un salto incondicional a una dirección específica sin que quede claro, el flujo, algo legítimo en ensamblador, queda sin embargo anodino en un lenguaje de alto nivel... especialmente cuando el programador no tiene/tenía la base suficiente, fabricando código altamente espagueti... (enredado).