SEFAZ-BA SGF DTI GETEC Secretaria da Fazenda do Estado da Bahia Superintendência da Gestão Fazendária Diretoria de Tecnologia da Informação Gerência de Tecnologia BOAS PRÁTICAS DE PROGRAMAÇÃO VISUAL BASIC Versão 01.00.00 Salvador (Ba), Maio de 2002. Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação ÍNDICE 1. OBJETIVOS .............................................................................................................................. 3 2. UTILIZAÇÃO DE COMPONENTES ..................................................................................... 3 2.1. 2.2. 2.3. 2.4. INTERFACE ............................................................................................................................... 3 COMPATIBILIDADE BINÁRIA..................................................................................................... 3 EARLY BIND VERSUS LATE BIND .............................................................................................. 4 LIBERAÇÃO DE OBJETOS .......................................................................................................... 5 3. ACESSO A DADOS .................................................................................................................. 5 4. TRATAMENTO DE ERROS ................................................................................................... 7 5. OBSERVAÇÕES GERAIS....................................................................................................... 8 Página 2 841073270 Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação 1. OBJETIVOS Este documento foi gerado com o intuito de alertar os desenvolvedores de sistemas sobre algumas práticas que devem ser evitadas durante o processo de codificação e outras que devem ser adotadas visando não só o incremento da performance dos sistemas, como também a melhoria da qualidade do código fonte. Vale ressaltar que algumas recomendações são flexíveis, devendo ser analisadas pela equipe de CD da GETEC as situações em que elas não se aplicam. 2. UTILIZAÇÃO DE COMPONENTES 2.1. Interface Talvez a melhor definição para interface de um componente é que ele é um contrato assinado entre duas partes, o componente e a aplicação cliente que irá utilizá-lo. A interface de um componente é representada pela assinatura de todos os métodos públicos. Uma assinatura de método é determinada pelo nome do método, o tipo de retorno e o nome e tipo dos parâmetros do método. Quando ocorre uma alteração na assinatura do método, diz–se que houve quebra de contrato, ou melhor, quebra de interface. A exclusão de um método também provoca a quebra de interface, entretanto a inclusão de um novo método, para o Visual Basic, não representa quebra de interface. Vale salientar que alterações em métodos privados ou propriedades do componente não interferem em sua interface, portanto não há quebra da mesma. 2.2. Compatibilidade binária Todas as classes possuem um identificador numérico denominado CLSID, quando o componente é instalado e registrado no Windows, o CLSID é gravado no registro do Windows e é usado para requisitar ao COM a criação de uma instância da classe. No momento em que um projeto é compilado fazendo referencia a uma DLL, o CLSID das classes desta DLL são armazenados na aplicação compilada, e quando é executado um New <ObjectName> , o CLSID armazenado é utilizado para solicitar ao COM que crie o objeto. Além do CLSID, as classes também possuem um identificador de programa ou PROGID, diferente do CLSID, que é representado por uma sequência numérica, o PROGID é representado pelo nome do componente (por exemplo, Excel.Application). Ele também é armazenado no registro do Windows no momento em que o componente é instalado, e permite que um componente possa ser criado utilizando a função CreateObject(). Exemplo utilizando o CLSID: Dim objXL as Excel.Application Set objXL = New Excel.Application Exemplo utilizando o PROGID: Dim objXL as Excel.Application Set objXL = CreateObject(“Excel.Application”) A diferença entre a criação de objetos utilizando o New/CLSID e CreatObject/PROGID é que este último não precisa conhecer o CLSID em tempo de compilação, permitindo que e o Página 3 841073270 Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação CLSID possa ser alterado. Será discutido no próximo tópico como isso pode ser feito (Early bind versus late bind). O CLSID de um componente é gerado no momento em quem ele é compilado. Entretanto existe a possibilidade de recompilar um componente e manter o CLSID. No Visual Basic existem duas opções para manter a compatibilidade de um componente com a versão anterior: Compatibilidade binária e Compatibilidade de projeto, sendo que só nos interessa a primeira. Na tela de propriedades do projeto, na aba Componets, encontra-se a caixa Version Compatibility, contém as opções de compatibilidade. No Compatibility: deve ser utilizada quando ocorre uma quebra de interface. Toda vez que um componete é compilado com esta opção, uma nova type library é gerada, incluindo novos CLSIDs. Binary Compatibility: Quando um componente é compilado com esta opção, se nenhuma quebra de interface for detectada, o componente irá manter a type library e os CLSIDs de suas classes. Com isso, todas as aplicações que utilizam este componente, continuarão funcionando com a nova versão, já que os CLSIDs foram mantidos. 2.3. Early bind versus late bind Quando falamos em utilização de componentes, sempre nos deparamos com a forma de declarar os objetos. Existem duas formas distintas que se referem ao momento em que a ligação (binding) é feita com a interface do componente, são elas: Early-bind e Late-bind. - Exemplo de early bind: Dim obj As New Project1.Class1 - Exemplo de late bind: Dim obj As Object Set obj = CreateObject(“Project1.Class1”) No early-bind, ou ligação prévia, a ligação com a interface do componente é feita no momento da declaração da variável. Neste momento o compilador já pode checar se todos os métodos da interface estão sendo invocados corretamente (nome do método, tipo dos parâmetros, tipo de retorno, etc.). Já no late-bind, ou ligação tardia, a ligação é feita com uma interface padrão do VB (Dispatch interface), com isso, a checagem dos métodos só pode ser feita em tempo de execução. Isto gera um grande problema já que alguns erros que seriam capturados durante a compilação só vão ocorrer durante a execução do programa, portanto aplicação deverá ser bem testada. Os desenvolvedores VB foram habituados a utilizar o early-bind principalmente pelo fato de que com o early-bind é possível ter o code-insight (aquela funcionalidade que mostra todos os métodos e atributos de um determinado componente após digitar o “.”), mas o early-bind não possui somente vantagens. Um grande problema é que, para utilizar o early-bind, devemos adicionar o componente na lista de referências do projeto VB. Neste momento estamos vinculando o projeto ao componente, mais especificamente, àquela versão do componente. Como já foi dito, todo componente possui um identificador único, o CLSID e quando ocorre uma quebra de interface do componente e o componente é novamente compilado, um novo CLSID é gerado. No momento em que ele é redistribuído, as aplicações que não forem recompiladas, apontando para o novo componente, e redistribuídas, não irão funcionar. Agora, imagine o custo de redistribuição de aplicativos em uma organização de grande porte se um componente que é utilizado por todas as aplicações tiver quebra de interface? Vale ressaltar que se a nova interface alterar a assinatura de algum método que é utilizado por uma aplicação, evidentemente esta aplicação precisa ser alterada para funcionar com o novo método. Página 4 841073270 Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação 2.4. Liberação de objetos O Visual Basic tem o cuidado de liberar a memória utilizada por uma variável quando o escopo em que ela foi criada deixa de existir. Entretanto, é melhor atribuir explicitamente as variáveis que representam objetos com o valor “Nothing” pelas seguintes razões: É uma boa técnica de programação; É melhor liberar recursos quando você não precisa mais deles do que esperar a saída do escopo, principalmente quando estes objetos mantêm conexões com o banco abertas; Ainda existem alguns erros no ADO que causam alguns problemas (Ex. perda de informação no objeto Err) se não for atribuído explicitamente para “Nothing”, antes de levantar um erro; Fechar Recordsets e Connections antes de atribuir “Nothing” fazem com que pool de conexões trabalhe corretamente. 3. ACESSO A DADOS O modo como os dados são acessados e tratados no servidor de Banco de Dados, é um ponto fundamental para a performance das aplicações. A seguir, seguem algumas recomendações sobre o assunto: Quando for usar o ADO para acessar dados do SQL Server, utilize o modelo chamado “firehouse cursor”. O “firehouse cursor” é a maneira mais eficiente para mover dados do SQL Server para a aplicação cliente. Essencialmente, este método envia os dados requisitados para buffer de output no SQL Server. Quando o buffer estiver cheio, o servidor aguarda até o cliente retirar os dados e envia mais dados para o buffer. Este processo se repete até que todos os dados requisitados sejam enviados ao cliente. Uma outra vantagem desse método é que os registros selecionados só são bloqueados (lock) enquanto estiverem sendo movidos para o buffer. Quando um ADO Recordset é aberto e é utilizada sua configuração padrão, o método “firehouse cursor” é usado. Para especificar manualmente, basta setar as seguintes propriedades do Recordset: CursorType = adForwadOnly CursorLocation = adUseServer LockType = adLockReadOnly CacheSize = 1 Evidentemente, existem situações em que a utilização de “firehouse cursor” não é a melhor opção, podemos citar quando o recordset precisa ser varrido inúmeras vezes, neste caso o tipo de cursor (CursorType) deve ser adStatic. Procure minimizar o número de acessos ao servidor de banco. Uma maneira de melhorar a performance das aplicações é tentar diminuir o número de acessos que ela faz ao Banco de Dados. Muitas vezes, a mesma informação é utilizada diversas vezes e em lugares distintos da aplicação, nestes casos procure manter esta informação na aplicação e não ir busca-la, sempre que necessário, no Banco de Dados. Evite também acessos ao banco de dados dentro de laços. O código a seguir deve ser evitado: Página 5 841073270 Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação For i = 1 to UBound(MyArray) MySQL = “SELECT name from User Where UserId = ” & _ CSTR(MyArray[i]) SET MyRecordSet = MyConn.Execute(MySQL) MyData[i] = MyRecordSet(0) SET MyRecordSet =Nothing Next O código abaixo pode ser mais complexo, entretanto tem o mesmo efeito e faz um único acesso ao banco de dados: MyWhere = ”” For i = 1 to UBound(MyArray) If (MyWhere = ””) Then MyWhere = “Where UserId in (” End If MyWhere = MyWhere & CStr(MyArray[I]) & _ IIf(i < UBound(MyArray), “,”, “)”) Next MySQL = “SELECT name from User ” & MyWhere SET MyRecordSet = MyConn.Execute(MySQL) i = 1 While not MyRecordSet.EOF MyData[i] = MyRecordSet(0) i = i + 1 MyRecordSet.MoveNext Loop SET MyRecordSet =Nothing Procure tirar vantagens das views. Quando a aplicação faz uma consulta (Select) no banco de dados, o SQL Server precisa compilar e gerar um plano de execução para a consulta. Quando a consulta é muito complexa pode acabar gerando um overhead muito grande para a aplicação. Nestes casos uma das soluções é criar uma view. Uma view, por já ter sido compilada, já tem seu plano de execução gerado melhorando a performance das aplicações. Salientamos que a criação de views deve ser discutida com a Administração de Dados e com a GETEC, evitando que sejam criadas indiscriminadamente. Não utilize o DAO para acessar ao SQL Server. A performance do DAO, ao acessar o SQL Server, é indiscutivelmente baixa. Utilize o ADO. Utilize o OLE DB Provider para criar conexões utilizando o ADO. Quando for abrir uma conexão com banco, utilizando o ADO, utilize o OLE DB Provider. Para isso, basta anexar à string de conexão o seguinte parâmetro: Página 6 841073270 Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação “provider=sqloledb”. O OLE DB é garantindo uma melhor performance. muito mais eficiente que o ODBC Provider, Procure utilizar “Stored Procedures” para agrupar comandos SQL. Quando possível, tente agrupar comandos SQL em Stored Procedures. Esta prática, não só reduz significativamente o tráfego de rede, como também melhora a velocidade de execução do código SQL já que este código já foi compilado e já possui plano de execução. Novamente, saliento que esta prática deve ser discutida com a Administração de Dados e com a GETEC, evitando que sejam criadas stored procedures indiscriminadamente. Tente limitar a quantidade de linhas retornadas de uma consulta. Quando for acessar ao SQL Server, evite fazer consultas que retornem dados que não são necessários. Utilize cláusulas where que delimitem o subconjunto de linhas que realmente são importantes para o escopo da aplicação. Não use o ADO para criar, concluir ou abortar transações. Procure encapsular as transações dentro de Stored Procedures. Evite utilizar os métodos do ADO para manipular transações. Não use o método “MoveFirst” em Recordsets que utilize cursor “ForwardOnly” do tipo Na verdade, quando este método é ativado, ele re-executa a consulta e repopula o Recordset, causando um overhead sem necessidade na aplicação e no servidor SQL. Utilize, quando possível, Recordsets desconectados. Essa é uma boa prática, já que contribui para a redução de carga no servidor SQL. Utilize a opção “adExecuteNoRecords” quando for executar stored procedures que não retornem linhas. Quando uma Stored Procedure é executa utilizando o objeto Command, ele aguarda que um conjunto de linhas seja retornado pela procedure. Para isso, uma boa quantidade de código extra é executada, criando uma overhead. Quando uma procedure não retorna linhas, tem-se a opção de sinalizar ao Command que a procedure não retorna linhas e evitar que este código extra seja executado, para tal, basta setar a opção adExecutenoRecords do Command: Cmd.CommandType = adCmdStoredProc + adExecutenoRecords Considere utilizar “output parameters” em stored procedures. Quando for executar uma Stored Procedure que retorne uma única linha, prefira retornar estes valores via parâmetros de saída da procedure. Esta prática elimina todo o código extra que o ADO tem que executar para retornar um conjunto de linhas (vide tópico anterior). 4. TRATAMENTO DE ERROS Página 7 841073270 Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação Este tópico será acrescentado futuramente, quando o novo componente de tratamento de erros estiver disponível. 5. OBSERVAÇÕES GERAIS Neste tópico veremos algumas práticas, de âmbito geral, que devem ser adotadas: Não utilizar SelectionFormula no Crystal Reports; O SelelectionFormula do Crystal apresenta a peculiaridade de trazer todos os registros da seleção e filtra-los no cliente. Essa prática deve ser abolida, ao invés disso utilize os critérios de filtro na cláusula Where, ou stored procedure ou recordset desconectado como fonte de dados para o relatório. Não colocar Message Box dentro de escopos de transação; Outra prática, não muito comum, e que geralmente ocorre acidentalmente, e que deve ser evitada, é a existência de interação com o usuário dentro de escopos de transação. Antes disso, deve ser concluída a transação, seja por commit ou rollback. A situação típica ocorre quando algum erro precisa ser exibido ao usuário e esta exibição é feita antes de finalizar a transação. Nestes casos a transação perdura, mantendo todos os locks no banco de dados até que o usuário interaja com a aplicação e ela possa continuar sua seqüência de execução. Evitar criação de tabela temporária dentro de transações; A criação de tabelas temporárias bloqueia alguns recursos do TEMPDB do SQL Server. Se a criação for feita em estado de transação, estes bloqueios irão persistir enquanto durar a mesma. O problema ocorre quando existem muitas transações que tentam criar tabelas temporárias, isso pode gerar não só o bloqueio destas transações como também afetar a performance do Banco, já que o TEMPDB é um banco compartilhado para todo o servidor SQL. Tipificar as variáveis declaradas, não use o tipo “Variant” indiscriminadamente; Quando você declara uma variável, você pode dar-lhe um tipo de dados específico, tal como String ou Double. Entretanto, você não é obrigado a fazer isto, se você não fizer, o VB irá declará-lo como do tipo Variant. Para o exemplo, as seguintes declarações são equivalentes: DIM MyVar DIM MyVar as Variant Um variável do tipo Variant pode armazenar qualquer tipo de dados. Isto pode parecer útil, mas possui algumas desvantagens. A maior delas é desempenho, em run time o VB terá de executar uma quantidade extra de código cada vez que você acessa à variável para determinar que tipo de dados ela está armazenando, e que tipo de dados você está atribuindo a ela. Podem existir casos onde será necessária a utilização de variáveis do tipo Variant, nesses casos declare-a explicitamente. Isto fará com que o código fique mais fácil de interpretar e eliminar erros no futuro (depuração). Acrescentar diretiva “Option Explicit” no início dos módulos; Página 8 841073270 Secretaria da Fazenda do Estado da Bahia 29/05/2017 DTI - Diretoria de Tecnologia da Informação Além de ser uma boa técnica de programação, forçar a declaração das variáveis acaba forçando também a tipificação. Se uma variável não é declarada explicitamente, o tipo que ela assumirá é Variant (vide observação anterior). Essa prática acaba prevenindo que ocorra o seguinte erro: o programador inicializa uma variável (não declarada) e, algumas linhas depois, a utiliza novamente, mas escreve o nome da variável errado. Neste caso, ao compilar o programa, não será apresentado nenhum erro, entretanto ele poderá não funcionar corretamente. Utilizando a diretiva “Option Explicit”, este problema é evitado, já que o compilador informará a tentativa de utilização de variáveis não declaradas. Procure centralizar a lógica de acesso a dados; Modularização sempre foi uma boa técnica de programação, e porque não modularizar o acesso a dados? Esta técnica acaba trazendo muitos benefícios, entre eles (o principal) facilita a troca dos componentes de acesso a dados. Muito esforço foi gasto para alterar o código das aplicações quando foi trocado o RDO pelo ADO, se o acesso a dados de todas aplicações estivessem centralizados, esse esforço seria minimizado. Outro benefício seria a facilidade de depuração e padronização dos sistemas, já que todos usariam o mesmo módulo (componente) de acesso a dados. Elimine o código fonte sem uso; Não deixe código “morto” em sua aplicação. Todo código comentado e que não será mais utilizado deve ser eliminado, isto facilita a leitura e entendimento do código pela equipe de manutenção e evita que erros antigos voltem a acontecer em caso de eliminação acidental do caractere que delimita o comentário, ocasionando a inclusão de código indevido na aplicação. Não instancie ou destrua objetos dentro de um loop; Outra pratica que deve ser abolida é instanciar ou destruir objetos dentro de laços. O VB executa uma boa quantidade de código interno para instanciar ou destruir objetos, portanto fazer isso dentro de laços acaba gerando uma sobrecarga muito grande para a aplicação. Página 9 841073270