http://weblogs.foxite.com/andykramek/archive/2006/03/20/1308.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York
En el tercer artículo de esta serie voy a hablar sobre Procedimientos y Funciones. Visual FoxPro, como sus ancestros FoxPro y FoxBase admite dos tipos diferentes de declaración y llamada de código.
Crear un procedimiento
Un procedimiento es sencillamente un bloque de código que es llamado por un nombre. Puede; pero no se requiere, que acepte uno o más parámetros y la razón para la creación de un procedimiento, es evitar la necesidad de escribir el mismo código muchas veces. Los procedimientos son llamados empleando el comando DO, y, de forma predeterminada, todos los parámetros se pasan por referencia. Por tanto no existe la necesidad de tener valores de retorno en los procedimientos - pueden modificar cualquier valor y el código llamado que lo pasa.
He aquí un ejemplo sencillo del tipo de código que podría estar en un procedimiento. Todo lo que hace es aceptar un número de registro y Alias de tabla. Valida que el número de registro es válido en el contexto de una tabla específica y, si es así, mueve el puntero de registro. Si falla algo, o si no es válido, se genera un error:
******************************************************************** *** Nombre.....: GOSAFE *** Autor...: Andy Kramek & Marcia Akins *** Fecha.....: 03/20/2006 *** Aviso...: Copyright (c) 2006 Tightline Computers, Inc *** Compilador.: Visual FoxPro 09.00.0000.3504 for Windows *** Función.: Si el registro especificado es válido para el alias *** ........: especificado va hasta el, de lo contrario genera un error ******************************************************************** PROCEDURE gosafe PARAMETERS tnRecNum, tcAlias TRY ********************************* *** Comprobación de los parámetros ********************************* *** Debemos recibir un número de registro! IF VARTYPE( tnRecNum ) # "N" OR EMPTY( tnRecNum ) ERROR "Debe pasar un número de registro a GoSafe" ENDIF *** Si no se ha especificado un alias, asume el alias actual lcAlias = IIF( VARTYPE( tcAlias ) # "C" OR EMPTY( tcAlias ), ; LOWER( ALIAS()), LOWER( ALLTRIM( tcAlias )) ) IF EMPTY( lcAlias ) OR NOT USED( lcAlias ) *** No hay tabla! ERROR "Debe especificar, o seleccionar una tabla " + ; "abierta al llamar a GoSafe" ENDIF ********************************* *** Verifica que el número de registro es válido para el alias ********************************* IF BETWEEN( tnRecNum, 1, RECCOUNT( lcAlias ) ) *** Este está bien GOTO (tnRecNum) IN (lcAlias) ELSE *** No, el número de registro no es bueno ERROR "El registro " + TRANSFORM( tnRecNum ) + ; " no es válido para la tabla " + lcAlias ENDIF CATCH TO loErr MESSAGEBOX( loErr.Message, 16, "GoSafe ha fallado" ) ENDTRY RETURN ENDPROCPara llamar al procedimiento utilizamos
SET PROCEDURE TO procfile ADDITIVE *** Guarda el puntero de registro en Account lcOldAlias = ‘account’ lnOldRec = RECNO( lcOldAlias ) <<más código aquí>> *** Restaura el puntero de registro DO GoSafe WITH lnOldRec, lcOldAliasEste procedimiento en particular no modifica nada y de hecho (como todos los métodos, funciones y procedimientos de VFP) en realidad devuelve .T. No hay necesidad, o incluso posibilidad de capturar ese valor. Vea que si desea pasar valores a un procedimiento por valor, en lugar de por referencia, entonces tenemos que pasarlos como valores reales y no como variables (Si, podríamos incluso cambiar la configuración de UDFPARMS pero no está recomendado, porque tiene otros efectos colaterales no deseados. Además, utilizar una solución global para un tema local es generalmente una mala idea). Entonces, para pasar un registro de número, "por valor" a este procedimiento podríamos utilizar:
DO GoSafe WITH INT( lnOldRec ), lcOldAlias
Crear una función
El otro método para llamar a código es crear una función. La diferencia básica entre un procedimiento y una función es que la función SIEMPRE devuelve un valor y por tanto siempre que llame a una función, ya sea una función nativa o una propia, siempre debe verificar los resultados. Las funciones se llaman finalizándolas con dos paréntesis. Como los procedimientos, las funciones pueden aceptar uno o más parámetros; pero a diferencia de los procedimientos, los parámetros pasados a las funciones son, de forma predeterminada, pasados por valor. La consecuencia es que esta funciones no modifican los valores en el código que los llama. (Si, esto es un comportamiento exactamente opuesto al comportamientos de los procedimientos). Veamos una sencilla función que devuelve el tiempo como una cadena de caracteres después de unos segundos.
******************************************************************** *** Nombre.....: GETTIMEINWORDS *** Autor...: Andy Kramek & Marcia Akins *** Fecha.....: 03/20/2006 *** Aviso...: Copyright (c) 2006 Tightline Computers, Inc *** Compilador.: Visual FoxPro 09.00.0000.3504 for Windows *** Función.: Devolver la cantidad de Días/Horas y minutos a *** ........: partir de una cantidad en segundos *** Valor devuelto.: Cadena de caracteres ******************************************************************** FUNCTION GetTimeInWords( tnElapsedSeconds, tlIncludeSeconds ) LOCAL lcRetval, lnDays, lnHrs, lnMins *** Inicializa las variables STORE '' TO lcRetval STORE 0 TO lnDays, lnHrs, lnMins*** Handle the Days first lnDays = INT( tnElapsedSeconds / 86400 ) IF lnDays > 0 lcRetVal = PADL( lnDays, 3 ) + ' Days ' ENDIF *** Calcula las horas lnHrs = INT(( tnElapsedSeconds % 86400 ) / 3600 ) IF lnHrs > 0 lcRetVal = lcRetVal + PADL( lnHrs, 2, '0' ) + ' Hrs ' ENDIF *** Ahora los minutos lnMins = INT(( tnElapsedSeconds % 3600 ) / 60 ) *** Verifica los segundos IF tlIncludeSeconds *** Si deseamos los segundos, los agrega explícitamente lcRetVal = lcRetVal + PADL( lnMins, 2, '0') + ' Min ' lcRetVal = lcRetVal + PADL( INT( tnElapsedSeconds % 60 ), 2, '0' )+' Sec ' ELSE *** Redondea por exceso los minutos UP Si >= 30 segundos lnMins = lnMins + IIF( INT( tnElapsedSeconds % 60 ) >= 30, 1, 0 ) lcRetVal = lcRetVal + PADL( lnMins, 2, '0') + ' Min ' ENDIF RETURN lcRetVal ENDPROC
Como puede ver, a diferencia del procedimiento, este código realmente crea, y devuelve explícitamente una cadena de caracteres a lo que sea que lo llama. (El fundamento detrás de esta función es, por supuesto, que si obtiene la diferencia entre dos valores DateTimes el resultado está en segundos). Entonces, cuando llama a esta función el valor devuelto debe ser controlado de alguna forma. Lo podemos mostrar:
? GetTimeInWords( 587455, .T. ) && Muestra: 6 Días 19 Horas 10 Min 55 Sego lo guarda como variable:
lcTime = GetTimeInWords( 587455 )o en el portapapeles (para pegarlo en alguna otra aplicación, por ejemplo)
_cliptext = GetTimeInWords( 587455 )Recuerde que el comportamiento predeterminado de Visual Foxpro es que los parámetros se pasan por valor a una función para que si necesita pasar los valores por referencia (y esto es lo que surge más comúnmente al tratar con matrices) entonces, debe pasar la referencia precedida por un signo "@", de esta forma:
DIMENSION gaMyArray[3,2] luRetVal = SomeFunction( @gaMyArray )
Entonces, ¿cuál es la diferencia?
Aunque yo declaro este código como una FUNCTION y el código GoSafe como un PROCEDURE, en la práctica a VFP no le importa. Podemos llamar código que está declarado como un procedimiento como si fuera una función (y vice-versa). Así podemos llamar al procedimiento "GoSafe" referido antes, de esta forma:
llStatus = GoSafe( lnOldRec, lcOldAlias )El valor devuelto en llStatus podría, dada la forma en que está escrito el procedimiento SIEMPRE ser .T.; pero si modificamos el código sólo un poco, tendríamos el valor devuelto sea o no exitoso el procedimiento al colocar el puntero del registro correctamente. La única modificación necesaria es verificar el éxito después de mover el puntero del registro, de esta forma:
IF BETWEEN( tnRecNum, 1, RECCOUNT( lcAlias ) ) *** Esto está OK GOTO (tnRecNum) IN (lcAlias) *** Verifica que tenemos el registro correcto *** Si está bien, da valor de retorno = .T. STORE ( RECNO( lcAlias ) = tnRecNum ) TO llRetVal ELSE *** No, el puntero de registro está mal ERROR "registro " + TRANSFORM( tnRecNum ) + ; " no es válido para la tabla " + lcAlias ENDIF CATCH TO loErr MESSAGEBOX( loErr.Message, 16, "GoSafe Failed" ) *** Fuerza el valor devuelto a .F. en cualquier caso de error llRetVal = .F. ENDTRY *** Devuelve el valor RETURN llRetValPor supuesto podríamos ejecutar la función GetTimeInWords llamándola como procedimiento, de esta forma:
DO GetTimeInWords WITH 587455, .T.Pero no haríamos muy bien, porque la función no hace nada aparte de devolver un valor - el que en este caso no podemos detectar. Lo interesante de todo esto es:
- Primero, VFP en realidad no le importa si se ha escrito como PROCEDURE o como FUNCTION. Solamente es código para ser ejecutado. ¿Qué importancia tiene si el código es llamado como procedimiento (empleando DO gosafe) o como función (llStatus = gosafe())?
- Segundo, si está llamando una función es vitalmente importante verificar el valor devuelto. Mientras los procedimientos usualmente encierran su propio control de errores (como el ejemplo que se ha brindado), el valor devuelto por una función es usualmente la única indicación de que realizó o no lo que se esperaba. Esto es válido si la función en cuestión está definida en un archivo de procedimiento, como un programa stand-alone, un método en un objeto o una función Visual FoxPro nativa.
Ahora, puede pensar que todo esto es muy básico, y obviamente, porqué yo le molesto con esto. Pero usted sabe, una de las cosas más comunes que he visto en varios foros es un mensaje que dice algo como:
“TableUpdate no funciona”
Aparte del comentario obvio, de que no hay un error en la operación ya que es una función TableUpdate() que ha sido verificada para la función actual de VFP, esto significa realmente que "No puedo hacer que trabaje TableUpdate". Generalmente la función viene junto con estas líneas:
"Cuando los usuarios cambian los datos, sus cambios son visibles en el formulario; pero no son guardados en la base de datos. No hay un error; pero la tabla no se actualiza. Ayúdenme por favor."
Y cuando la persona publica su código (frecuentemente, después de habérselo pedido muchas veces - ¿por qué a algunas personas no les gusta publicar su código?) nos encontramos esto:
=TableUpdate( .T. )Ahora, esta única línea de código, que existe en todas las versiones del archivo de ayuda de VFP (si, incluyendo VFP 9.0) creo que es responsable del mayor gasto de tiempo del desarrollador, que cualquier otra cosa que se haya escrito ¿Qué está mal aquí? Veamos:
Primero, TableUpdate() es una función, la que, como hemos podido ver, significa que debe devolver un valor. ¿Dónde se verifica este valor? Incluso el archivo de ayuda dice claramente que se devuelve un valor, no indica en ningún ejemplo del archivo de Ayuda que necesita verificarlo. Sin embargo, está claro, cuando lee la letra pequeña, porqué es imperativo verificar el valor devuelto:
Valores devueltos¿Qué significa esto? Simplemente que si falla una llamada a TableUpdate() al actualizar un registro, la única forma en que lo puede saber es verificando el valor devuelto.
Lógico. TABLEUPDATE( ) devuelve verdadero (.T.) si se confirman los cambios realizados en todos los registros; de lo contrario, TABLEUPDATE( )devuelve falso (.F.).
Segundo, se pasa solamente uno de los CUATRO parámetros posibles para TableUpdate(), ni siquiera el nombre de la tabla que se supone que sea actualizada.
Tercero, utiliza el viejo (VFP 3.0, VFP 5.0) valores lógicos para el primer parámetro, independientemente del hecho que la posibilidad de utilizar funcionalidad extendida se introdujo en la versión 6.0 De hecho, ¡ la única cosa cierta en este ejemplo es la ortografía de la palabra "TableUpdate"!
¿Cómo debe verse este ejemplo? Pues así:
*** Hubo cambios solamente en el registro actual llOk = TABLEUPDATE( 1, .F., 'employee' ) IF llOk *** TableUpdate Exitoso ? 'Actualizado el valor cLastName: ' ?? cLastName && Muestra el valor atual cLastName (Jones) ELSE *** Falló TableUpdate - ¿POR QUÉ? AERROR( laErr ) MESSAGEBOX( laErr[ 1, 2], 16, 'Falló TableUpdate ' ) ENDIFPara ver más detalles del uso de TableUpdate() (y TableRevert()) vea mi artículo del blog "Handling buffered data in Visual FoxPro" de Diciembre de 2005.
Nota de la traductora: El artículo referido, será traducido al español y publicado como: Controlar datos en buffer Visual FoxPro
Conclusión
El punto aquí es que debe tener el hábito, si aun no lo tiene, de verificar los valores devueltos de llamadas de funciones - incluso cuando SEPA de antemano (¿cuántas veces tiene un comando SEEK y luego no verifica el resultado? ¡Sea honesto!)
La única excepción posible que puedo pensar, podría ser TableRevert() porque no estoy seguro de cómo podría fallar, o incluso que podría usted hacer si falla. Parece más un comando o procedimiento, que una verdadera función, por supuesto, devuelve un valor (por ejemplo, la cantidad de registros revertidos).
No hay comentarios. :
Publicar un comentario
Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.