Limitación de X++

Si bien es muy probable que ya muchos lo sepan, pero me gustaría comentar ciertas limitaciones del entorno de programación lenguaje X++ que en un comienzo yo no conocía y que pueden ser muy útiles de saber para otros desarrolladores.

Debe existir unas cuantas más, así que conocen bienvenido el comentario.

No se puede reutilizar el código de un formulario por el siempre hecho de que los formularios no son verdaderas clases

Es decir si hacen la simple prueba de cambiar el extends ObjectRun por object, implementar un interfaz o cambiar nombre no verá ningún resultado palpable.

Tampoco se puede implementar un comportamiento genérico para cierto formulario, aunque hay algunos trucos para lograrlo.

En fin, resumiendo, los forms no son clases, sino simplemente un esquema de árbol que especifica como debe construirse una interfaz grafica a partir de objetos concretos con botones, cajas de texto, etc.

Estas limitaciones también aplican a los formularios que tienen el mismo comportamiento.

Esta forma de construcción de estos elementos me hace recordar un poco a la programación orientada a prototipos, claro que es una versión simplificada y muy práctica.

Ciertas clases no son accesibles al programador X++

Si bien gran parte de la lógica del sistema está escrita en X++, existen clases de kernel que no pueden ser modificadas.
Un ejemplo de ellas son todas las que empiezan con x*. XRecord, XSession, etc.

El límite no es del todo claro. Algunas veces y se pueden llegar a encontrar puertitas para introducir código en procesos centrales del sistema.

Todas las tablas comparten una definición común (Common)

Esta definición permite que todas las tablas, por ejemplo, puedan llevar un campo que identifique la empresa, fecha y hora de creación, modificación, ect.

Pero a pesar de que ayudaría mucho, no existe un mecanismo para agregar campos por defecto a nuestras tablas modificando la tabla base common.

Un formulario/reporte es un objeto indivisible

Usando el comparador se pueden detectar algunas limitaciones en la detección de cambios.
Por ejemplo si modificamos un método dentro de una tabla, funcio no distinge capas.

El lenguaje X++ no es 100% orientado a objetos


No existe el polimorfismo.

Bug: Compilación en cadena

Supongamos que tenemos un proyecto modificaciones sobre PurchFormLetter que es una clase base sobre la que heredan otras y modificamos en método Update.

Si exportamos el proyecto y lo importamos en otro ambiente entoces puede dar error, porque AX no recompila correctamente las clases hijas

Pararse sobre el objeto y utilizar compilación en cadena.

Funciones incorporadas útiles

curExt()
daraAreaId()

changeCompany()
{
}

Programar pensando en migrar I

Una buena forma de evitarse problemas al momento de migrar...

Si se definen nuevos valores en los base enums tamar en cuenta que en una proxima versión pueden existir nuevos valores.

Por eso como política siempre conviente sumar 100 al Id del último enum existente.

Lo pequeño puede ser grabe!

Si alguna vez reciben un error del estilo "Pequeño error de importación"

Hay que estar atento, pero seguramente se trate de un campo en un formulario que perdió su referencia el campo de la tabla que tiene en la propiedad "DataSource"

Esto sucede por un error en el órden de importación de los elementos dentro de un proyecto.



La "solución" es volver a importar el proyecto en cuestión.

El error fue comprobado en AX3 y AX4

Compilación en cadena

Cuando modificamos una clase base del sistema y a su vez una clase que la hereda. En algunas ocacciones el compilador no respete la secuencia de compilación generando errores al usuario cuando un proceso intenta utilizar metodos de la clase hija.

Esto fue comprobado en AX3 y AX4 esperemos no ocurra en AX2009

La solución que adoptamos para este problema es incluir en los proyectos que tengan esta característica un Job de compilado en cadena.

Este tipo de Job se ejecutan automáticamente al importar el proyecto en el que están contenidos

(Funcionalidad que será descripta es otra entrada)

Ej:


static void MPH_AUTORUN_20090630_000_CF(Args _args)
{
;
SysCompilerOutput::compileForward(className2Id('InventMovement'));
}

Best Practices de Desarrollo II

Tratar de evitar

* Guardar datos de estructura en la base de datos como por elemplo FieldId, TableId, ect.
Estos pueden cambiar el futuras versiones del estándar o pueden gererarse diferentes errores por numeraciones dispares al momento de migrarar datos cuando se mantienen dos versiones de AX simultáneamente.

* Referencias a RecId, ya que puede ser reenumerado. (Especial cuidado en Axapta 3.0)

Secuencias Numéricas

Información general:

El método “allocateNumAndVoucher” se utiliza para asignar una secuencia numérica de Voucher y Número Interno en todos los módulos de AX.

En los parámetros de cada módulo el método “allowedNumSeqGroup” se utiliza para determinar que secuencias numéricas pueden usarse dentro de los grupos de secuencias numéricas del módulo en cuestión.

Todas las clases que empiezan con la forma “NumberSeqReference_[MODULO]” definen secuencias numéricas que podrán ser parametrizadas desde los parámetros.

Ejemplo:


Trabajar con Axapta 3.0 y Dynamics AX 4

Hay cientas consideraciones básicas se resulta necesario mantener dos versiones de AX simultáneamente (en nuestro caso es AX3 y AX4)

* Siempre desarrollar sobre AX3 las funcionalidades compartidas, ya que los XPO de AX3 pueden ser importados en AX4, pero no visecersa.

* Análizar simultáneamente las clases utilizadas para comprobar cambios de AX4 que pueden ayudar a la solución del problema y que en AX3 aún no estaban implementados.

* Cualquier FIX a un error de desarrollo debe hacerse si o si en ambas versiones.

* Ciertos requirimientos nuevos pueden hacerse sólo en AX4 si el cliente de AX3 no los necesita.

Bug: Ax permite modificar una capa de aplicación incorrecta

Debido a un error del sistema (al crear una tabla nueva) AX se confunde y considera que ya la tiene definida en otra capa (VAR en este caso). Esto es un problema porque agrega campos que no deben existir y no permite borrarlos porque están definidos en una capa en la que no tenemos acceso.

En el ambiente de desarrollo:


Luego de importar en el ambiente de Testing:

Generó dos nuevos campos TransferValueFrom y TransferValueTo que pertenecen a otra tabla!


Para “palear” el problema lo que estamos haciendo es importar una tabla “_bug” para que cuando el sistema le asigne el identificador (TableId), le vincule esos dos campos a esta tabla, saltando el error y al momento de crear la tabla que nos interesa, no se los cree.

Best Practices de Desarrollo I

Código fuente:

* Al escribir mensages que requieran mostrar información de variables (además de texto fijo) usar siempre la función strfmt().

Ej: throw error("El pedido de compra Nº %1 no puede procesarse",purchTable.PurchId);

* Usar etiquetas y no como en el ejemplo anterior!

Ej: throw error("@MPH23721",purchTable.PurchId);

Ineficiencia crónica

Algunos ejemplitos de lo que NO se debe hacer!

Son más que obvios, pero lamentablemente son casos reales que encontramos...

1)
while select inventTrans
where inventTrans.InventTransId == custInvoiceTrans.InventTransId &&
inventTrans.InvoiceId == custInvoiceTrans.InvoiceId
{
counter = counter + 1;
}

Y el sum??

2)
// Truco para traer un registro vacio
select tmpTable where 1 == 2;

Que tal este truco... tmpTable.clear();

Best Practices Caseritas - Home Made Best Practices

Se pueden leer cientos de tutoriales que te dicen como hay que trabajar, pero muchos de ellos son muy "olvidables" o dificiles de aplicar.
Por eso, la idea es hacer una síntesis de lo que realmente sirve y detectamos que valen la pena.
Todo es discutible y dependiendo del grado de profesionalismo algunos de estos pueden no aplicar, jeje

* Cada objeto de la aplicación que sea modificado conviene incluirlo en un proyecto.
Esto no es capricho!! es que tenemos una herramienta que recorre lo proyectos y te puede dar una lista de todos lo proyectos que usan un objeto determinado.

* Convención de nombrado de los proyectos.
(Empresa)_(Fecha)_(Indicador de Solicitud)_(Nombre Explicativo)
Ej: MPH_20090622_001_SourceCodeMix

Indicador de Solicitud: Las modificaciones realizadas por nosotros (sin que sea un requisito de un usuario en cuestión) tiene la denominación:

09x donde 0 = Ale, 1= Nico1, 2 = Nico2, 3 = Dani y 4 = [Nuevo]

Si somos tan originales de inventar más de un proyecto por día, entonces nos ponemos de acuerdo a los gritos!

Claro, el criterio puede variar según la cantidad de desarrolladores!

* Entornos

Desarrollo (Capa de USR)

Todos (o casi todos) nuestros desarrollos se hacen en estos ambientes.
A veces esto no se puede hacer porque el error o dato concreto depende del dato del cliente en concreto, así que so usan lo ambientes del prueba del cliente o... Productivo??? (con mucho cuidad claro, pero si hay urgencia..)

Testing (Capa de USR y CUS)

Para pruebas o si no se quiere interferir con los otros desarrolladores. Todas las pruebas se hacen en capa USR, pero se deben borrar al abandonar el ambiente.
Luego de aquí se genera un versión definitiva para productivo.

Productivo (Capa CUS)

Nunca, pero nunca se desarrolla en este ambiente!! Sólo se usa para tener un reflejo real del ambiente de productivo y para ver el posible impacto de la aplicación de un desarrollo al ser importado en el ambiente real.

* Convención de nombrado

Todas las tablas y clases que creamos tendrá el prefijo de la companía para la que se realizan.

Personalmente encuentro esta convención bastante desagradable, ya que al estar en una capa distinta a la del sistema debería bastar para distingir un objeto propio de uno del sistema.
Pero a sistema heredado no se le miran los dientes... Así que continuamos con la convención!

Otra cosa que es muy frea es que se pierde la lógica de Ax que relaciona por módulo y que permite un seguimiento rápido por nombre.

Ej: MPH_SecurityInjection

* Si se quiere hacer una funcionalidad paralela a la del sistema nunca duplicar un método, sino modificarlo. Si quiere mantener ambas versiones crear una clase heredada que pise el método con el mismo nombre pero que tenga un funcionalidad distinta