Boas práticas de desenvolvimento na Plataforma Innovaro ERP

Introdução

Este documento foi criado com o objetivo de melhorar a legibilidade dos códigos desenvolvidos na Plataforma Innovaro ERP. Ao seguir as sugestões deste documento é promovido um padrão de codificação que beneficia todos os desenvolvedores, usando um estilo único de codificação, de fácil leitura. Os esforços para seguir estas sugestões irão aumentar o valor do código desenvolvido, principalmente nos ciclos de manutenção e depuração.

É desnecessário dizer que as convenções de códigos propostas são baseadas primariamente em questões de gosto. Embora acreditemos neste argumento, e admiremos o estilo promovido neste documento, apoiamos os padrões aqui sugeridos não porque acreditamos que eles são certos e outros são errados, mas porque acreditamos na eficácia de utilizar um padrão único, adotado pela maioria dos desenvolvedores. A mente humana se adapta às normas e encontra meios de reconhecer rapidamente padrões familiares, o que resulta em uma assimilação mais rápida e com menor esforço. É o desejo de criar um padrão que facilite a leitura dos códigos para o maior número de pessoas, a maior motivação por trás deste documento.

Para chegar a este ideal, devemos buscar a criação de códigos auto-explicativos, objetivos e bem organizados, que não necessitem de documentação externa ou explicações pessoais, garantindo a continuidade da solução mesmo na ausência dos seus autores. Qualquer documentação necessária deve se encontrar dentro do próprio código através do uso do JSDoc, das propriedades de help ou de comentários no código.

Se as sugestões parecerem estranhas para você em um primeiro momento, pedimos para tentar utilizá-las por algum tempo. Estamos certo que de você se acostumará com elas ao longo do tempo.

Este documento é vivo, recebendo contribuições conforme a cultura da empresa evolui. Boa leitura!

Convenções de códigos JavaScript

Adotamos as convenções sugeridas por Douglas Crockford em http://javascript.crockford.com/code.html, com as exceções abaixo:

    • usamos o "var" dentro de blocos, principalmente em laços "for". Acreditamos que o uso de variáveis privadas sem a declaração de var é arriscada e a proximidade da definição da variável com o seu uso evita declarações de variáveis privadas sem o uso do "var";

    • utilizamos a notação de propriedades privadas prefixado-as com "_", apesar de também adotarmos a sugestão de propriedades privadas em http://javascript.crockford.com/private.html. O uso do padrão sugerido por Crockford é conhecido por não ser eficiente, não sendo viável a sua aplicação irrestrita. O artigo http://erik.eae.net/archives/2009/11/09/21.12.16/ explica esta ineficiência com maiores detalhes.

Além das sugestões de Douglas Crockford, adotamos as seguintes recomendações.

Nomes de métodos de objetos

Os nomes de métodos devem ser verbos no imperativo.

Exemplo:

//

parcelas.calcula();

//

pedido.aprova();

Nomes com siglas

Devem seguir o estilo camelo, exceto siglas frequentemente utilizadas em maiúsculas em outras APIs, como JSON, XMLHttpRequest, etc.

Errado:

var aliquotaICMS = 17;

function pegaBaseDeCalculoIPI() {

}

Correto:

var aliquotaIcms = 17;

function pegaBaseDeCalculoIpi() {

}

Nomes de campos e tabelas

Por razões históricas, todos os campos e tabelas do sistema de ERP são definidos em letras maiúsculas e todos os campos de infra-estrutura são definidos usando o mesmo padrão de propriedades JavaScript, prefixadas com a letra "i".

Os nomes de DataSets são escritos seguindo o estilo camelo. Os nomes das propriedades de DataSets que são campos da tabela são escritos em minúsculo.

Errado:

var chave = dbCache.get(session.userKey, "CCustRes");

ds.CHAVE = connection.createKey();

ds.cCustRes = session.userKey.cCustRes;

var Entidade = dbCache.getTable("Entidade");

var igroupuser = dbCache.getTable("IGROUPUSER");

Correto:

var chave = dbCache.get(session.userKey, "CCUSTRES");

ds.chave = connection.createKey();

ds.ccustres = session.userKey.ccustres;

var entidade = dbCache.getTable("ENTIDADE");

var iGroupUser = dbCache.getTable("iGroupUser");

Códigos redundantes

Tenha atenção para evitar códigos desnecessários. Sempre opte pela simplicidade e legibilidade.

Errado:

if (valor > 200 && data < hoje) {

var resultado = true;

} else {

var resultado = false;

}

if (resultado === true) {

}

Correto:

var resultado = valor > 200 && data < hoje;

if (resultado) {

}

Duplicação de Códigos

Não duplique códigos que podem ser reutilizados através de funções ou bibliotecas. Detecte a necessidade de criação de funções ou objetos para eliminar o código repetido. Tenha em mente que quanto menor o código, mais fácil será sua a compreensão e manutenção. Somente quebre esta regra por motivos fortes de perfomance ou legibilidade.

Errado:

var recurso = dbCache.getTable("RECURSO");

var chavesRecursos = [];

recurso.first();

while (!recurso.eof) {

if (!recurso.nome) {

chavesRecursos.push(recurso.chave);

}

recurso.next();

}

var entidade = dbCache.getTable("ENTIDADE");

var chavesEntidades = [];

entidade.first();

while (!entidade.eof) {

if (!entidade.chave) {

chavesEntidades.push(entidade.chave);

}

entidades.next();

}

Correto:

function pegaChavesDosRegistrosSemNome(nomeTabela) {

var chaves = [];

var ds = dbCache.getTable(nomeTabela);

ds.indexFieldNames = "NOME";

ds.find("");

while (!ds.eof && !ds.nome) {

chaves.push(ds.chave);

ds.next();

}

return chaves;

}

var chavesRecursos = pegaChavesDosRegistrosSemNome("RECURSO");

var chavesEntidades = pegaChavesDosRegistrosSemNome("ENTIDADE");

Bibliotecas

Utilize e crie bibliotecas para garantir uma máxima reutilização do código fonte. Caso as classes e funções sejam válidas para um único cliente, crie uma biblioteca na base do cliente com chave Custom. Caso sejam de uso geral, analise e discuta a possibilidade de se criar a biblioteca em um produto, tornando-a parte do Sistema Innovaro ERP.

Bibliotecas de uso geral criadas com chave negativa devem possuir sua interface escrita em Inglês, exceto nos casos em que a realidade da biblioteca seja de termos exclusivamente locais. As interfaces das classes e funções criadas devem ser muito bem elaboradas e discutidas, pois serão eternizadas apartir do momento em que elas passarem a ser utilizadas.

Importante: jamais utilize classes, métodos e propriedades de códigos de terceiros que não sejam documentadas como públicas. APIs privadas podem ser alteradas ou excluídas a qualquer momento, sem nenhum tipo de comunicação prévia, levando os códigos que fazem uso destas APIs a deixarem de funcionar.

Lógica positiva

Em blocos condicionais, dê preferência em utilizar a lógica positiva, evitando o uso de operadores de negação ou diferença.

Errado:

if (!ds.findKey(key)) {

doOne();

} else {

doTwo();

}

if (!expression && (value1 !== value2)) {

doOne();

} else {

doTwo();

}

Correto:

if (ds.findKey(key)) {

doTwo();

} else {

doOne();

}

if (expression || (value1 === value2)) {

doTwo();

} else {

doOne();

}

Expressões de controle de laços

Em uma condição com várias expressões dentro de um laço, as primeiras expressões da condição devem ser as que possam ser avalidas mais rapidamente e caso este não seja um fator determinável ou significativo, devem ser as que representam a maioria das situações testadas.

Errado:

for (titulo.first(); !titulo.eof; titulo.next()) {

if (outroDataSet.find(titulo.chave) && (ds.pessoa === chavePessoa)) {

}

}

Correto:

for (titulo.first(); !titulo.eof; titulo.next()) {

if ((ds.pessoa === chavePessoa) && outroDataSet.find(titulo.chave)) {

}

}

Documentação

Os códigos JavaScript devem ser documentados através da sintaxe JSDoc. A página http://usejsdoc.org/ contém a documentação de todas as tags suportadas.

Exemplo:

/**

* Cria uma âncora que quando clicada direciona o usuário para uma Interação, Atividade ou executa uma função desejada.

* @param {string} name Nome da âncora.

* @param {string|function()} nextInteractionNameOrFunction Nome da interação, atividade ou função para onde o usuário será direcionado.

* @param {number} processKey Chave do processo para onde o link irá apontar.

* @param {boolean} [newTab] Indica se deve ser criada uma nova aba para a Interação ou Atividade seguinte.

* @example

* var lnk = this.link("Nome do link","Interação");

* lnk.write();

*/

Process.prototype.link = function(name, nextInteractionNameOrFunction, processKey, newTab) {

};

As tags abaixo do JSDoc não devem ser utilizadas em códigos do Innovaro ERP:

    • @fileoverview: o seu uso polui a documentação gerada, visto que a maioria dos arquivos não são incluídos pelos usuários da API e quando a inclusão é realizada, ela é feito por meio da chave do arquivo.

    • @author: o uso da tag não traz benefício prático visto que todos os códigos são de propriedade da Innovaro.

Ao adicionar as tags de modificadores de acesso (@private ou @protected), coloque-as após a descrição do membro, seus parâmetros e retorno. O JSDoc não permite comentários nesta tag, portanto ela somente pode ser seguida por outras tags, nunca por um texto livre.

O JSDoc não permite que o nome de um parâmetro seja "arguments", portanto ele não deve ser utilizado em APIs públicas. Como o seu uso prejudica a legibilidade do código, é recomendado que ele seja evitado de uma forma geral.

Convenções de códigos SQL

Nomes de campos e tabelas

Em expressões SQL, os nomes de campos e tabelas são escritos seguindo a mesma padronização utilizada na definição dos mesmos: os campos e tabelas do sistema de ERP são em letras maiúsculas e os de infra-estrutura utilizam o estilo camelo, prefixadas com a letra "i".

Errado:

var ds = database.query("Select e.chave From Entidade e Where e.Codigo = 'Empresa Licenciada'");

var ds = database.query("Select INAME From igroupuser Where iKEY = 1234");

Correto:

var ds = database.query("Select e.CHAVE From ENTIDADE e Where e.CODIGO = 'Empresa Licenciada'");

var ds = database.query("Select iName From iGroupUser Where iKey = 1234");

Indentação

Utilize quebras de linhas e alinhamentos conforme exemplo abaixo, observando que as condições são isoladas umas das outras, e que toda vez que fechamos a string do query, a reabrimos na mesma linha.

Com esta padronização facilitamos a leitura e manutenção da expressão SQL, permitindo a fácil visualização e manutenção das condições. A análise de erros, logs e profiler também se tornam mais simples, pois a identação utilizada é enviada para o servidor de banco de dados.

Exemplo:

var ds = database.query("

select PESSOA, RECURSO, sum( QUANTIDADE) as QUANTIDADE, sum( TOTAL) as TOTAL

from PEDIDO

where EMISSAOMOV >= " + this.emissaoInicial.toSqlString() + "

and EMISSAOMOV <= " + this.emissaoFinal.toSqlString() + "

group by PESSOA, RECURSO

");

Alterações na base de dados

Toda alteração no banco de dados deve ser realizada através de alterações em DataSets, gravadas automaticamente quando a propriedade DataSet.automaticApplyUpdates está habilitada, ou manualmente através do métodos DatabaseProxy.applyUpdates ou DataSet.applyUpdates. Jamais utilize os comandos INSERT, UPDATE ou DELETE.

A não adoção desta recomendação poderá causar inconsistências na base de dados, incluindo a perda dos dados alterados.

Errado:

database.executeSQL("Update PEDIDO set DISPONIVEL = 123456 Where CHCRIACAO = 123");

Correto:

var ds = database.query("Select CHAVE, VERSAO, DISPONIVEL From PEDIDO Where CHCRIACAO = 123");

for (ds.first(); !ds.eof; ds.next()) {

ds.disponivel = 123456;

}

ds.applyUpdates();

Utilização do CAST

Sempre faça uso do CAST explícito, pois a forma implícita, sem definição do tipo, não é suportada por todos os bancos de dados.

Errado:

Select g.CHAVE, "Co-Participação" as UTILIZACAO From SGUIA g Where g.PEDIDOCOPARTICIP in (-1)

Correto:

Select g.CHAVE, Cast("Co-Participação"as Varchar(20)) as UTILIZACAO From SGUIA g Where g.PEDIDOCOPARTICIP in (-1)

Web Framework

Uso de HTML e CSS

Evite utilizar HTML ou CSS, pois o seu uso elimina o benefício do Web Framework em compatibilizar o processo ou relatório com todos os navegadores suportados pelo Sistema Innovaro ERP. Ao fazer uso destas linguagens, o desenvolvedor assume a responsabilidade de realizar testes em todos os navegadores suportados pelo sistema, além assumir o risco de realizar futuras manutenções por problemas causados pelas atualizações dos navegadores.

Esta restrição é maior para relatórios, pois ao fazer uso de HTML ou CSS, o desenvolvedor está restringindo a mídia do relatório para telas de navegadores, removendo a possibilidade do relatório ser exportado para arquivos textos, planilhas, PDF ou outras mídias que eventualmente o framework possa dar suporte.

Poder de acesso dos usuários

O desenvolvedor possui a responsabilidade de realizar os filtros e as verificações necessárias para garantir que o usuário de um processo ou relatório somente veja e altere as informações que ele tenha acesso, de acordo com as permissões definidas no processo “Permissões”.

A verificação do poder de acesso deve ser realizada com base na classe do registro a ser exibido ou alterado e em situações pertinentes, com base nas classes dos registros vinculados ao registro. Por exemplo, um relatório que exiba o faturamento deve testar o campo CLASSE da tabela PEDIDO, assim como o campo CLASSE dos registros vinculados através dos campos NUCLEO, LOCESCRITU, ESTABELECI ou RECURSO.

Desempenho

Cache Local

Não obtenha informações do banco de dados que já estejam no cache local.

Um dos principais recursos do Sistema Innovaro ERP é a tecnologia de cache local. Ela permite que os querys no banco de dados retornem apenas dados referentes à movimentação, pois todos os dados cadastrais já estão na estação local. A utilização do cache local reduz significativamente o consumo de processamento do banco de dados e a utilização do meio de comunicação entre a estação e o servidor, além de permitir uma codificação mais simples e elegante.

Errado:

var chaveCliente = 123456;

var entidade = database.query("

select NOME

from ENTIDADE

where CHAVE = " + chaveCliente + "

");

var nomeCliente = entidade.nome;

//

var pedido = database.query("

select p.CHAVE, p.CHCRIACAO, e.NOME, e.LOGRADOURO, e.OBSERVACAO

from PEDIDO p

left outer join ENTIDADE e on (e.CHAVE = p.REPRESENTA)

where EMISSAO = '01/01/2004'

");

var s = pedido.nome + "," + pedido.observacao;

Correto:

var chaveCliente = 123456;

var nomeCliente = chaveCliente.nome;

//

var pedido = database.query("

select p.CHAVE, p.CHCRIACAO, p.REPRESENTA

from PEDIDO p

where EMISSAO = '01/01/2004'

");

var s = pedido.representa.nome + "," + pedido.representa.observacao;

Cópias de DataSets

Não faça cópias de DataSet desnecessárias, pois normalmente é um processo oneroso que pode ser substituído por uma lógica mais elaborada.

Esta orientação deve principalmente ser levada em consideração para clones do cache local, pois o método copy() copia todos os registros independente da visão de classes utilizada. No exemplo abaixo, será feita uma cópia da tabela ENTIDADE.

Errado:

var ds = classes.getCachedDataSet(-2007878000 /* Disponíveis */);

var filtrado = new DataSet();

filtrado.copy(ds);

filtrado.first();

while (!filtrado.eof) {

if (filtrado.estabeleci === 187721 /* Estabelecimento Teste */){

filtrado.next();

} else {

filtrado.del();

}

}

for (filtrado.first(); !filtrado.eof; filtrado.next()) {

// Processa informações do registro

}

Correto:

var ds = classes.getCachedDataSet(-2007878000 /* Disponíveis */);

ds.indexFieldNames = "ESTABELECI";

ds.find(187721 "/* Estabelecimento Teste */");

while (!ds.eof && ds.estabeleci === 187721 "/* Estabelecimento Teste */") {

// Processa informações do registro

ds.next();

}

Uso de chaves

Considerações gerais

Chaves são um recurso limitado do sistema e não devem ser desperdiçadas. De uma forma geral:

  1. Não devem ser criadas chaves que não serão de fato gravadas no banco de dados.

  2. Abertura de operações nunca devem criar chaves, pois uma operação pode ser aberta diversas vezes após a sua criação, sem que sejam efetivadas alterações. É importante que sejam criados testes de integração que garantam que a abertura de operações não criem chaves. A API session.limitKeyCreation() pode ser utilizada para limitar a criação de chaves nos testes.

Sincronismo de dataSets

Ao sincronizar dataSets, tenha cuidado em garantir que o dataSet a ser atualizado não está com insertWithKey ativo, pois o append() irá gerar uma chave que será logo em seguida sobreposta pela chave da origem dos dados.

Errado:

for (origem.first(); !origem.eof; origem.next() {

if (!destino.findKey(origem.chave) {

destino.append();

}

destino.copyRecord(origem);

destino.post();

}

Correto:

var bkpInsertWitkKey = destino.insertWithKey;

destino.insertWithKey = false;

try {

for (origem.first(); !origem.eof; origem.next() {

if (!destino.findKey(origem.chave) {

destino.append();

}

destino.copyRecord(origem);

destino.post();

}

} finally {

destino.insertWithKey = bkpInsertWitkKey;

}

Sequencia das chaves

As chaves geradas pelo sistema não segue uma ordem crescente, desta forma nenhum algoritmo deve assumir essa premissa para indicar a ordem de inserção de registros no banco.

Referências bibliográficas