Procesos y funciones y eventos

Procesos

Sin duda es el objeto más poderoso de Velneo a la hora de crear funcionalidad en nuestras aplicaciones. Tiene la capacidad de ejecutarse en cualquier plano, admite cualquier origen (ninguno, ficha o lista) y cualquier destino (ninguno, ficha o lista), pueden recibir datos de entrada, si los ejecutamos con comandos de objeto, se podrán asignar valores a variables locales de los mismos antes de su ejecución y además puede devolver cualquier valor de cualquier variable local declarada en el objeto como si se tratase de parámetros de retorno. Tanta potencia requiere control para no hacer un mal uso de los procesos.

Aplica el criterio de responsabilidad única

Cuando estamos desarrollando una funcionalidad es fácil caer en la tentación de escribir un proceso largo que contiene toda la funcionalidad. Sin embargo esa es un mala praxis. Cuando más largo es un proceso más complicado es de leer, entender y mantener. Por ese motivo es conveniente usar el criterio de responsabilidad única. En lugar de tener un mega proceso es mejor:

  • Crear un proceso principal que se encargue de llamar a otros procesos.

  • Cada uno de los procesos llamados debería realizar una único función. No debemos confundir función con cálculo, es decir un proceso puede calcular muchos valores pero siempre que se realicen sobre la misma información.

Tampoco debemos caer en el error opuesto, es decir, atomizar tanto nuestros procesos que al final tengamos un grupo de procesos encadenados difíciles de analizar y comprender. Por ejemplo, no es fácil de mantener un proceso A que llama a un proceso B que a su vez llama a los procesos C1 y C2 y cada uno de estos llamada otros procesos. Esta jerarquía de procesos hace complicado seguirlo y mantenerlos.

Por lo tanto nuestro objetivo debe ser siempre buscar el equilibrio entre responsabilidad única y evitar el exceso de atomización, para ellos podemos recurrir a combinaciones de procesos y funciones que faciliten la legibilidad del código.

Otro problema que plantea la aplicación de la responsabilidad única es la necesidad de pasar información de un proceso a otro, algo que se evita cuando todo está en el mismo proceso. En este punto volvemos a repetir la palabra equilibrio, es decir debemos aplicar el criterio de responsabilidad única cuando un proceso va a ser llamado por otros y es mejor tener pequeñas piezas de código que realizan funciones concretas con un bajo nivel jerárquico y sin complejidades a la hora de pasar información.

Separa interfaz de proceso

Uno de los aspectos más importantes a la hora de optimizar un proceso es separar la parte de interacción con el usuario a través de la interfaz de la aplicación de reglas de negocio, cálculos y otras operaciones transaccionales automáticas que no requieren interacción.

El problema de que todo esté junto es que nos imposibilita la ejecución de un proceso en 3º plano, perdiendo la posibilidad de optimizar la parte de aplicaciones de reglas de negocio, cálculos y otras operaciones transaccionales.

Por este motivo y aunque requiera algo más de programación siempre es conveniente tener separada en un proceso independiente la parte de interfaz. Un ejemplo de buena práctica podría ser el siguiente esquema de ejecución:

  • Un proceso LLAMADOR lanza la interfaz donde se pide la información al usuario.

  • El proceso LLAMADOR realiza las verificaciones oportunas avisando al usuario en caso de error.

  • Si todo es correcto lanza en 3º plano un proceso CALCULADOR que realiza las operaciones transaccionales.

  • Al finalizar el proceso CALCULADOR en 3º plano el proceso de interfaz recupera la información relevante como el estado final, errores en caso de que los haya, registros creados, etc.

  • El proceso LLAMADOR muestra al usuario el resultado final del proceso ejecutado.

En el ejemplo anterior solo hay 2 procesos LLAMADOR y CALCULADOR, el primero se encarga de la interacción con el usuario a través de la interfaz tanto antes como después de que finalice la transacción, mientras que el segundo proceso se ejecuta de forma optimizada en el servidor ya que no utiliza nada de interfaz.

Este mismo esquema podemos realizarlo de forma similar sustituyendo el proceso LLAMADOR por un formulario que realizar toda la parte de interfaz con manejadores de evento del formulario.

Evita la complejidad ciclomática

La complejidad ciclomática es una métrica del software que proporciona una medición cuantitativa de la complejidad lógica de un programa. Es una de las métricas de software de mayor aceptación, ya que ha sido concebida para ser independiente del lenguaje.

Traducido a lenguaje Velneo es un valor que se calcula en base a la cantidad de niveles que se establecen en un proceso.

Sin duda alguna cuando vemos un proceso así no es fácil saber que hace cada línea del proceso ya que cuando estamos en 1º, 2º o 3º nivel de jerarquía todavía podemos controlarlo, pero cuando los niveles siguen creciendo nos obliga a leer todo el código secuencialmente para saber bajo qué condiciones se ejecuta las líneas de ese nivel.

Los comandos if y los subprocesos que generar muchos comandos de instrucción nos añaden complejidad ciclomática a los procesos, por ese motivo debemos tratar de simplificarlos al máximo y en estos casos aplicar el criterio de responsabilidad única puede ser de gran ayuda, así como el uso de funciones que simplifican la lectura del código.

Las verificaciones primero

Cuando tenemos que hacer verificaciones para decidir si vamos o no a ejecutar un parte del código del proceso, siempre que sea posible aplica el criterio de las verificaciones primero y en caso de error finaliza el proceso.

El primero verifica y si no hay error aceptar el formulario:

¿Cuándo es mejor un proceso que una función?

Existen diferentes motivos por los que un proceso puede ser más conveniente que una función:

  • Cuando queremos ejecutar un código con un origen ficha o lista.

  • Cuando queremos recuperar una ficha o lista de retorno.

  • Si queremos que el código se puede ejecutar en un plano diferente al del código lanzador.

  • Cuando no queremos tener límite de parámetros.

  • Cuando queremos que el orden de los parámetros no influya.

  • Cuando queremos que existan parámetros opcionales independientemente de su posición.

  • Cuando queremos poder recuperar no un único valor de retorno sino todos los valores que sean necesarios.

  • Cuando queremos que el código quede integrado en la transacción en curso aunque estemos ejecutando en 1º plano.

¿Cuándo debo usar el comando ejecutar proceso?

El comando de instrucción es más limitado que disparar objeto, sin embargo cuenta con la ventaja de la sencillez.

  • Cuando ya estoy en el origen ficha o lista y no necesito pasarle parámetros.

  • Cuando necesito ejecutar el proceso en 2º plano.

¿Cuándo debo usar el comando disparar objeto con un proceso?

El comando disparar objeto requiere más líneas de código que ejecutar proceso sin embargo cuenta con ventajas funcionales que nos motivan a usarlo cuando:

  • Cuando quiero pasarle parámetros al proceso.

  • Cuando necesito recuperar parámetros o valores calculados en el proceso ejecutado.

Funciones

La función es un contenedor de código sin origen. Podríamos decir que una función es como un proceso sin origen, pero la gran diferencia es que mientras el proceso puede ser ejecutado desde una acción, otro proceso, función, manejador o trigger, la función se puede ejecutar en cualquier fórmula lo que le da una potencia de ejecución que no tiene el proceso. Una función puede ser ejecutada en cualquier ámbito de nuestra aplicación.

Acorta código

Uno de sus usos más interesantes es la posibilidad de evitar código repetido. Una función permite lanzar código pasándole parámetros para que ejecute una funcionalidad retornando un valor que podemos capturar para su reutilización.

Esto nos permite mover código repetido en un proceso, función, manejador de evento o evento de tabla a una función que será llamada desde diferentes puntos. La ventaja es que la llamada a una función se realiza con un única línea de código, en el siguiente ejemplo se ve la llamada a 2 funciones.

Sin embargo, ejecutar un proceso con paso de parámetros requiere varias líneas de proceso. En el siguiente ejemplo vemos la llamada a 2 procesos con los comandos de instrucción de manejador de objeto.

Ten en cuenta el número limitado de parámetros

Una de las limitaciones de las funciones es que admite un máximo de 10 parámetros. No es una gran limitación, pero debemos tenerla en cuenta a la hora de establecer la estrategia de paso de muchos parámetros a una función.

Tener una función con muchos parámetros no es cómodo, por lo que en la medida de los posible es mejor crear funciones con pocos parámetros.

Si tenemos que pasar más de 10 parámetros y no podemos hacerlo con un proceso tenemos 2 opciones, utilizar el 10º parámetros para pasar muchos valores o pasar un único parámetro con todos los valores. Esta segunda opción tiene la ventaja de que es más homogénea, es decir, no hay unos parámetros que se pasan directamente y otros agrupados sino que todos se pasan agrupados.

Ese parámetro con múltiples valores puede tener los valores aplicando un formato JSON o XML o CSV, por ejemplo. Una vez recibido el parámetro la función comienza descomponiendo dichos valores en las diferentes variables locales o en una variable global de tipo array.

Documenta los parámetros en el inicio de la función

Pensando siempre en la mantenibilidad del código y que cualquier desarrollador puede necesitar usar la función es importante describir correctamente los parámetros que recibe la función y el valor que devuelve.

Usa buenas descripciones en las variables locales que sean parámetros

Cuando usamos una función tras seleccionarla de la lista nos encontraremos que en el lugar donde tenemos que escribir los parámetros nos aparecerán unos textos correspondiente a las descripciones de las variables locales de la función. Es fundamental que esas descripciones sean lo más cortas posibles a la vez que cumplan la función de describir con precisión el dato que debemos pasar a la función. Si podemos utilizar una palabra es mejor que dos o más, pero lo más importante es que se describa bien el parámetro.

Ten en cuenta que en 1º plano genera una transacción independiente

Velneo tiene un sistema transaccional automático que se encarga de englobar en una única transacción todas las operaciones realizadas a partir de que ya exista una transacción abierta. Esto es totalmente aplicable a las funciones cuando se ejecutan en el servidor. Sin embargo, cuando una función transacciona y se ejecuta en 1º plano, su transacción no queda agrupada con la que ya estuviese en abierta en curso, sino que se crea una independiente.

Este funcionamiento debemos tenerlo en cuenta para evitar cuando sea preciso, cambiando en ese caso la función por un proceso o para forzarlo cuando nos interese cambiando un proceso por una función.

¿Cuándo es mejor una función que un proceso?

Existen diferentes motivos por los que una función es más conveniente que un proceso:

  • Cuando queremos lanzar código desde una fórmula debemos usar una función.

  • Si queremos que se puede ejecutar el código remotamente desde otro servidor a través de una función remota.

  • Cuando queremos reducir el código de llamada a una línea.

  • Cuando el código no tiene origen y necesitamos pasarle parámetros.

  • Cuando queremos que genere una transacción independiente al ejecutarlo en 1º plano.

Conexiones de evento

Una gran parte de la potencia y funcionalidad de la interfaz de una aplicación viene dada por el uso de señales que nos permiten lanzar código en un momento determinado de la aplicación. Las conexiones de evento son muy potentes, pero también debemos usarlas con precaución para no abusar de ellas y producir el efecto no deseado en nuestra interfaz.

Evita el uso de la conexión pérdida de foco

Aunque es una tendencia natural usar esta señal, no es la más recomendable ya que existen muchas formas de perder el foco, cambiar de control con tabulación, con intro, pulsar una opción del botón de menú del control, pulsar una tecla de función que activa un botón, cambiar de aplicación, etc.

El problema es que no siempre nos vamos a encontrar con que el funcionamiento es el esperado, aunque detrás del comportamientos siempre hay una explicación lógica. Por este motivo es necesario trabajar con esta señal con precaución. Funciona y funciona bien, pero hay mucha casuística que se debe tener en cuenta.

Por ejemplo al pulsar un botón del formulario utilizando una tecla aceleradora, aunque se ejecuta el botón nuestro control no pierde foco ya que así es el funcionamiento de las señales en Qt. Por este motivo en ocasiones no es suficiente con la señal de pérdida de foco, además hay que hacer controles adicionales al aceptar o cerrar el formulario.

Value changed es una buena opción

Habitualmente es más recomendable usar la señal value changed que la de pérdida de foco para detectar si se han realizado cambios en los datos de un control. Es una señal que nos garantiza detectar cuando el valor del campo ha cambiado tanto si es con una opción de localidad o alta de maestro a través del botón de menú del control, como si es por una acción del usuario con el teclado a escribir un nuevo valor o con el ratón al pulsar algún botón arriba o abajo o de selección de una lista en vista de datos.

Lo que tenemos que tener presente es que si el cambio de valor del control se realiza mediante programación la señal no se disparará. Es decir, si el usuario cambia el valor manualmente si se dispara, pero si el cambio es realizado por un manejador de evento programado la señal no se va a disparar. Es fácil de gestionar, pero siempre que tengamos claro su funcionamiento.

Mejor usar "Ratón: botón soltado" que "Ratón: botón pulsado"

Estas señales aunque parezcan similares tienen una gran diferencia. El botón pulsado se dispara cuando el usuario pulsa el botón, aunque pulse y no suelte el botón del ratón la señal se habrá disparado, sin embargo si antes de soltar el botón se desplaza fuera del botón la señal ya se habría disparado cuando el usuario realmente a cambiado de opinión al tratar de desplazar el ratón fuera del botón.

Por este motivo es más recomendable utilizar la señal botón soltado que garantiza que el usuario pulsó y soltó el botón de ratón sobre el control. En el caso de controles de tipo botón ya existe una señal específica con el nombre “Botón pulsado”.

Incompatibilidad entre "Ítem: simple clic" e "Ítem: doble clic"

En las rejillas, por ejemplo, nos encontramos que podemos aplicar ambas señales, sin embargo debemos tener en cuenta que si declaramos las 2 señales nos vamos a encontrar con que al hacer simple clic se dispara la señal correspondiente, sin embargo hacer doble clic también se va a disparar la señal de simple clic, algo que puede no ser lo esperado, pero que debemos tenerlo en cuenta.

Onclose solo está disponible en el objeto marco en uso

Cuando tratamos de controlar el cierre de la aplicación contamos con la señal Onclose disponible en el objeto marco que se esté usando. Esta señal nos permite cancelar su cierre con el comando de instrucción set retorno proceso = NO.

En el caso de los formularios en cuadro de diálogo aunque no disponemos de la señal podemos evitar su cierre quitando la barra de título de la ventana, o quitando el icono de cerrar ; opciones que podemos desactivar en la propiedad estilo del formulario.

Una vez que no hay botón cerrar en el título de la ventana podemos poner un botón "Cerrar" o "Cancelar" en el formulario con el que tendremos control absoluto sobre la acción del usuario.

Controlar el cierre de un formulario en vista

En el caso de que queramos controlar el cierre de un formulario abierto en vista, podemos controlarlo a través de la señal Vista cerrada.

Cuando se dispara la señal podemos utilizar funciones del API de Velneo a través de JavaScript para saber que formulario es el que está activo y por lo tanto el que está tratando de cerrar el usuario.

Manejadores de evento

Los manejadores de evento tienen la ventaja de ser código “conectado” al objeto al que pertenece, de tal forma que un manejador de evento de un formulario tiene control sobre el registro editado y todos los controles de la interfaz, y un manejador de evento de una rejilla sobre la lista de registros y sus columnas.

Al estar conectado el manejador de evento es usado para aplicar funcionalidades de avanzadas de interfaz que no podríamos lograr con procesos o funciones.

Un manejador puede llamar a otro del mismo objeto salvo en el marco AUTOEXEC

Un comando usado habitualmente y que nos ayuda a tener código de responsabilidad única es “Interfaz: ejecutar manejador de evento”, este comando permite hacer llamadas de un manejador a otro teniendo siempre presente que comparten el registro o la lista de origen del objeto así como las variables locales y las cestas.

Sin embargo, hay una excepción, el marco AUTOEXEC aunque permite la creación de conexiones y manejadores de evento no permite que un manejador de evento llame a otro. En este caso particular tendremos que hacer uso de funciones o procesos para evitar código repetido.

Las variables locales son compartidas entre los manejadores

Una funcionalidad muy cómoda cuando trabajamos con los manejadores de objetos es que las variables locales declaradas en el objeto son compartidas por todos los manejadores, eso significa que podemos almacenar valores en variables locales para posteriormente utilizarlas en otro manejador. Esta funcionalidad es aplicable dentro del objeto, es decir a nivel de una tabla, un formulario, una rejilla, etc.

Debemos tener en cuenta que si un objeto está instanciado más de una vez, por ejemplo el usuario abre el formulario de dos clientes distintos, aunque el objeto es el mismo cada formulario tiene su propio ámbito de ejecución, y por lo tanto las variables de un formulario son comunes para todos sus manejadores, pero las variables locales de un formulario no son accesibles para los manejadores que están asociados al otro formulario.

Las cestas locales son compartidas entre los manejadores

Las cestas locales tienen un ámbito y una persistencia asociada a la ejecución del manejador que la crea, sin embargo sin un manejador de objeto crea una cesta local y llamamos desde ese manejador a otro manejador que utiliza un cesta con el mismo identificador, la cesta es compartida por ambos manejadores. Al finalizar la ejecución del manejador que creó la cesta el objeto será destruido de tal forma que al volver a lanzar el mismo manejador se creará una nueva cesta local.

Aplica el criterio de responsabilidad única y evita código repetido

Los manejadores de evento al igual que las otras piezas de código en Velneo permiten escribir todo el código que necesites, aunque no es recomendable hacer código largo ya que dificulta su legibilidad y mantenibilidad.

Gracias a la compartición del origen, variables y cestas, es muy sencillo evitar el código repetido en los manejadores de evento, ya que podemos hacer que un manejador llame a otro. Aplicando el mismo criterio podemos evitar que los manejadores hagan múltiples cosas, por ejemplo verificaciones, transacciones, cambiar el estado de la interfaz, etc. Es recomendable crear pequeños manejadores de evento con responsabilidad única que son llamados desde otros manejadores de evento.

Más información

Última actualización