17 de mayo de 2006

Escribir mejor código (Parte 1)

Artículo original: Writing better code (Part 1)
http://weblogs.foxite.com/andykramek/archive/2006/03/07/1260.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York

Como todos sabemos, Visual FoxPro brinda un entorno extremadamente rico y variado; pero a veces demasiadas cosas buenas nos llevan a adoptar malos hábitos. Al escribir código existen varias vías de alcanzar el mismo resultado; pero todo frecuentemente existen diferencias significativas en rendimiento y la única vía para asegurar que nuestro código está optimizado para el mejor rendimiento es comprobar, comprobar y volver a comprobar la mayor cantidad de condiciones diferentes que se puedan idear. Habiendo dicho esto, es igualmente importante reconocer que el primer requerimiento de cualquier código es ser funcionalmente correcto. Al decir de Marcia Akins (Microsoft MVP, autora y co-propietaria de Tightline Computers Inc) "... hacer algo mal lo antes posible no es realmente muy útil".

Pero, frecuentemente también, tendemos a lograr la funcionalidad correcta sólo al final de la historia, y una vez que algo está funcionando, simplemente nos movemos al siguiente problema. La realidad es que la mayoría de los desarrolladores típicamente revisan y optimizan su código al mismo tiempo que regresan a el para agregar comentarios (es decir, ¡nunca!).

Sin embargo, al aplicar algunas reglas y técnicas básicas, puede asegurarse de evitar algunos de los problemas más comunes y producir código más eficiente desde la primera vez. A medida de que lo haga con más frecuencia, será  menor el tiempo que necesite emplear en revisar y "tunear" código funcional. Esto tiene dos beneficios para todo desarrollador:

Mientras menos retoques que se haga sobre el código, una vez que está funcionando correctamente, será menor la probabilidad de introducir fallos en código funcional.

Arreglarlo inmediatamente ahorra tiempo, no tener que revisar el código, es siempre más rápido que re-hacer para mejor rendimiento o usabilidad.

El propósito de esta serie de artículos es revisar algunas de estas cosas que hacemos, cuando estamos escribiendo código para asegurar que es nuestro software es tan eficiente y tan usable como sea posible y minimizar la necesidad de revisar código que trabaja bien para optimizarlo. Comenzaremos con algo básico y luego, más adelante en esta serie, tendremos algo más avanzado.

Advierta a sus usuarios; pero no los trate como idiotas

Alguien (lo siento; pero no recuerdo quien) una vez remarcó el deseo de los usuarios finales, no desean ser mostrados como tontos delante de sus jefes al dar respuestas erróneas, y no les deben tratar como idiotas. Desafortunadamente nosotros, como desarrolladores, tendemos en concentrarnos mucho en lo primero, que nos olvidamos de lo segundo. Una de las cosas más básicas que podemos hacer en nuestras aplicaciones es intentar y crear un balance adecuado entre las advertencias relevantes y las "molestas".

Una de las cosas que más me molestan vienen del propio VFP. ¿Ha observado que cuando estamos ejecutando paso a paso desde el depurador y seleccionamos "Reparar" inmediatamente un diálogo nos pregunta "¿Cancelar programa?"? Entiendo que aquí la intención es advertirme en caso, digamos, que haya oprimido inadvertidamente la lista desplegable y seleccionado "Reparar" cuando realmente deseaba otra opción (¿realmente soy tan tonto?) Pero en este diálogo la opción "predeterminada" es "SI", la cual, por una parte no es realmente consistente con la razón de mostrar el diálogo en primer lugar (quiero decir, protección "infalible"). Aún, puede argumentar que tiene sentido porque probablemente haya querido reparar el código cuando oprimí "Reparar".

Sin embargo, si el código en cuestión es una definición de clases, al seleccionar "Reparar" no es suficiente porque tan pronto como trate de modificar en la ventana abierta recibir otro diálogo - y esta vez pregunta:

"¿Quitar la clase de la memoria?"

A ver, un momento, le hemos dicho a VFP que:
  1. Queremos reparar el código que se está ejecutando
  2. Si, realmente queremos cancelar la ejecución del programa
¿y ahora nos pregunta si queremos quitar la clase de memoria? ¿Cómo se supone que la arreglemos si NO lo hacemos? Para hacer las cosas aun peores, ¡la opción seleccionada de forma predeterminada es "Ignore"!

Entonces, si ha tratado de insertar una línea nueva presionando la tecla Intro como el primer paso de su modificación (y ¿Cuán frecuentemente no es lo primero que hacemos?) - este diálogo idiota relampaguea en su pantalla, y se va, seleccionando "ignorar" y no pasa nada. Ahora vea, después de todo, yo soy un desarrollador y seguramente si intento modificar una definición de clase ¿realmente DESEO hacerlo? ¿Que hace a VFP pensar que asume que yo no se lo que estoy haciendo? ¡Esto es realmente muy molesto, por no decir insultante!

Ahora considere ¿Con cuánta frecuencia tiene ese tipo de diálogos en sus aplicaciones? La pregunta clásica es "¿Está seguro?". He aquí un ejemplo, el usuario abre la ventana de búsqueda, encuentra algún valor y trata de encontrar un registro. Entonces tiene que seleccionar, desde las opciones del desarrollador, "Eliminar". Un diálogo diciendo: "Esto va a eliminar el registro, está seguro?" con "NO" como opción predeterminada (Es su turno de protección" infalible", amigos ...) ¿Cuán insultante es esto? Por supuesto, ellos desean eliminar el registro, gastan justamente 20 minutos buscando el registro, y ahora le pregunta si está seguro de que eso era lo que quería hacer?

Por supuesto, oigo que está diciendo, siempre existe la posibilidad de que oprima Eliminar accidentalmente. ¿Pero quién es el culpable? La respuesta, ¡DEL DESARROLLADOR! El desarrollador es el único que  hace posible que oprima "eliminar" por accidente, nadie más. Si la funcionalidad eliminar es tan sensitiva, entonces la interfaz de usuario está mal hecha que permite la casualidad. (usted pregunta "¿Está seguro?" cuando el usuario desea Agregar un registro, o Guardar cambios ...?).

¿Por qué no habilita el botón "eliminar" una posible acción de tal forma que el usuario tiene que hacer algo para iniciar el proceso y no entonces tener que lidiar con "Esto va a eliminar un registro" seguido por "¿Está seguro?", seguido por "Está realmente, realmente seguro" y así hasta el infinito. Al final de este día, el desarrollador, tiene que o ejecutar el comando eliminar o cancelar la operación - mejor que advertir, y darle la opción de cancelar, antes ellos han gastado su tiempo en el proceso.

Informe a sus usuarios; pero no comprometa el rendimiento para hacerlo.


He aquí un código al que llegué recientemente en una aplicación de la vida real. La aplicación en cuestión es una que fue escrita hace tiempo cuyos volúmenes de datos crecen grandemente a través de los años. El código en cuestión es muy común, y simplemente utiliza un WAIT WINDOW con un mensaje indicando el progreso de una operación que estaba ejecutando sobre cada registro en una tabla. La parte relevante está en el lazo SCAN:
*!* Inicializa el contador de registros
lnCnt = 0
lcOfRex = " de " + TRANSFORM( RECCOUNT( ALIAS() ) )
SCAN
  *!* Actualiza el valor mostrado del progreso
  lnCnt = lnCnt + 1
  lcTxt = 'Procesando Registro ' + TRANSFORM( lnCnt ) + lcOfRex
  WAIT lcTxt WINDOW NOWAIT
Ahora, lo interesante acerca este proceso era que se ejecutaba contra una tabla que ahora contiene más de 125.000 registros. ¿Y qué? Escucho que dice, bueno el tiempo que toma para ejecutar el proceso fue cerca de 3 minutos. Pero intente este código en su PC local:
LOCAL lnCnt, lcOfRex, lnSt, lnNum, lcTxt, lnEn
lnCnt = 0
lcOfRex = " de 125000"
lnSt = SECONDS()
FOR lnNum = 1 TO 125000
  lnCnt = lnCnt + 1
  lcTxt = 'Procesando Registro ' + TRANSFORM( lnCnt ) + lcOfRex
  WAIT lcTxt WINDOW NOWAIT
NEXT
lnEn = SECONDS()
? STR( lnEn - lnSt, 8, 4 )
Ahora, en su PC este código tardará unos 32 segundos para ejecutarse y qué hace? ¡NADA de nada! La pantalla que muestra ni siquiera es legible. La única conclusión a la que podemos llegar es que este código inútil ha tomado el 15% del total del tiempo de ejecución. Intente la siguiente versión del mismo código:
LOCAL lnCnt, lcOfRex, lnSt, lnNum, lcTxt, lnEn
lnCnt = 0
lcOfRex = " de 125000"
lnSt = SECONDS()
FOR lnNum = 1 TO 125000
  lnCnt = lnCnt + 1
  IF MOD( lnCnt, 10000 ) = 0
    lcTxt = 'Procesando Registro ' + TRANSFORM( lnCnt ) + lcOfRex
    WAIT lcTxt WINDOW NOWAIT
  ENDIF
NEXT
lnEn = SECONDS()
? STR( lnEn - lnSt, 8, 4 )
Esto, en mi máquina demora menos de 0.3 de un segundo - ¡Es 100 veces más rápido! Ahora, si consideramos el proceso en cuestión, con 125 000 registros empleaba aproximadamente 3 minutos, lo que significa que recorre aproximadamente 700 registros por segundo. ¿Puede el usuario siquiera ver la actualización del mensaje a esa velocidad? tiene alguna utilidad? Por supuesto no, entonces, ¿por qué utilizarlo?

La cuestión es: ¿Cuál es el intervalo razonable para actualizar la ventana?

Desafortunadamente no hay una respuesta "correcta"; pero puedo sugerir aquí, que aplique un poco de sentido común. El primer requerimiento es que necesita tener alguna idea acerca de la longitud total del proceso en cuestión. Obviamente si el proceso se ejecuta durante tres horas, actualizar cada diez segundos es innecesario probablemente, a la inversa si tarda tres minutos, entonces un intervalo de actualización cada 10 segundos parece razonable.

La regla general, que yo suelo utilizar es intentar actualizar la información de usuario 200 veces por proceso (es decir cada 0.5% de cumplimiento). Mi barra de progreso, tiene entonces 200 unidades y yo configuro mi intervalo de actualización calculando el progreso esperado que constituya el 0,5% del total al tomar el número de registros, y el promedio de tiempo para cada proceso.
¿Cómo se el tiempo promedio de las pruebas?

Cuando estoy desarrollando el código, lo pruebo.  Baso mi evaluación del tiempo de procesamiento promedio en una verificación que emplea un volumen de datos que es al menos 50% más largo del que yo espero obtener en producción. Si, esto significa a veces que mi actualizaciones son muy rápidas, al principio,  cuando el sistema primero entra en uso; pero cuando crece el volumen de datos, la muestra se acerca a mi 0,5% de realización. Incluso si estuve mal en mi estimación, y el proceso termina siendo dos veces más largo, por registro, como yo esperaba estoy actualizando aun la visualización cada 1% de camino - lo que en el proceso que tomaba tres horas significa una actualización en la ventana cada 100 segundos.

Todo esto puede sonar muy sencillo y obvio; pero como es tan frecuente en el desarrollo de aplicaciones, algo pequeño hace la diferencia - especialmente cuando son obvios para el usuario final.

No hay comentarios. :

Publicar un comentario