8 de setembro de 2014

O "Using" é somente "Using"???

Fala pessoas!

Hoje vou falar de uma coisa simples e besta mas que é, no mínimo, interessante.
Antes de iniciarmos, vamos rever algumas coisas que já sabemos:

  1. Todos nós já sabemos que podemos realizar a passagem de parâmetros para um FORM usando as opções USING e CHANGING. 
  2. Também sabemos que temos a opção de passar variáveis por REFERÊNCIA ou por VALOR, assim como em inúmeras outras linguagens de programação. 
  3. Finalmente, sabemos que o "USING" é destinado aos parâmetros de ENTRADA e, por "pseudo-lógica", não tem seu valor alterado, já o CHANGING é destinado aos parâmetros de SAÍDA e tem seu valor alterado. 

Tudo certo quanto ao nosso conhecimento? Nem tanto...
Seja esse código abaixo:

DATA: lv_num1 TYPE i.

  lv_num1 = 10.
  WRITE: lv_num1, ' = variável início'.
  NEW-LINE.

  lv_num1 = 10.
  PERFORM z_using USING lv_num1.
  WRITE: lv_num1, ' = variável using'.
  NEW-LINE.

  lv_num1 = 10.
  PERFORM z_using_value USING lv_num1.
  WRITE: lv_num1, ' = variável using value'.
  NEW-LINE.

  lv_num1 = 10.
  PERFORM z_changing CHANGING lv_num1.
  WRITE: lv_num1, ' = variável changing'.
  NEW-LINE.

  lv_num1 = 10.
  PERFORM z_changing_value CHANGING lv_num1.
  WRITE: lv_num1, ' = variável changing value'.
  NEW-LINE.

FORM z_using USING pe_num TYPE i.
  pe_num = pe_num * 2.
ENDFORM.

FORM z_using_value USING VALUE(pe_num) TYPE i.
  pe_num = pe_num * 2.
ENDFORM.

FORM z_changing CHANGING pc_num TYPE i.
  pc_num = pc_num * 2.
ENDFORM.

FORM z_changing_value CHANGING VALUE(pc_num) TYPE i.
  pc_num = pc_num * 2.
ENDFORM.


Esse exemplo (mais besta do que simples) testa as possibilidades de parâmetros com a utilização do USING e CHANGING, mesclando as passagens por REFERÊNCIA e VALOR.
Apenas lendo o código, podemos definir o resultado como:

10 = variável início
10 = variável using
10 = variável using value
20 = variável changing
20 = variável changing value

Certo??? Não!!!!
O retorno do programa é:

10 = variável início
20 = variável using
10 = variável using value
20 = variável changing
20 = variável changing value

Reparem que a utilização do USING por REFERÊNCIA realiza a alteração do valor.

Aí você me pergunta: mas como assim a passagem utilizando USING, ou seja, passando um valor de ENTRADA, tem seu valor alterado?
Então eu respondo: Simples, porque sim (eu sei que a resposta é cretina, mas é a pura realidade, dessa vez o "Porque sim Zequinha" é válido).

Por definição, para que o valor não seja alterado quando utilizado via USING, o mesmo deve ser passado por VALOR (explicitando o VALUE) e não por REFERÊNCIA (implícito).
Resumindo, tanto os parâmetros de USING ou CHANGING podem ter o seu valor alterado.

Com isso, você começa a se questionar:
Então meu código funcionará corretamente, caso eu simplesmente arrancar todos os CHANGINGs do código?
Se você quiser alterara a variável de origem, sim.

E eu posso fazer isso?
Sim e não. Sim, irá funcionar. Não, pois isso envolve a metodologia de programação, sendo um modo para passagem de entrada e outro para passagem de saída. É quase igual a você entrar no seu carro pela porta do passageiro. Você pode fazer isso e funciona do mesmo modo, mas você tem um porta ao seu lado que serve para isso.

E o que isso vai mudar minha vida?
Em absolutamente nada!

Mas se isso não vai mudar nada, então por que é interessante eu saber disso?
Somente para você saiba que quando um determinado valor estiver sendo alterado inexplicavelmente, vale a pena verificar se você usou a variável desse valor como passagem de entrada (USING) em um FORM e realizou algo com ela.

Até mais e obrigado pelos peixes.

25 de junho de 2014

Relatório de request

Fala pessoas.

Nada melhor do que a necessidade (ou a falta de paciência para atividades repetitivas) para nos forçar a achar outras soluções.

Atualmente estou em um projeto onde há várias frentes de trabalho distintas (que não se comunicam, diga-se de passagem) atuando em um mesmo processo ao mesmo tempo, ou seja, vários e vários ABAPs atualizando o mesmo código durante um determinado tempo. Com isso, é comum que esse processo ora tenha a regra que A + B = 10, ora A + B = 17 ou ainda A + B = (12 * 3,14) + √42.

Isso acaba gerando uma situação onde um processo, validado em ambiente QAS há dois dias, não apresente o resultado esperado e validado, e a primeira necessidade é realizar uma verificação de versão de objetos, para verificar quem foi o último ABAP que alterou a regra, para depois disso descobrir o porquê (viu como uma boa comunicação e um processo de documentação centralizado são úteis?!).

Bom, mas você pode dizer que realizar uma verificação de versão de objetos é simples, e eu concordo com isso, mas com uma ressalva: verificar a versão de um objeto é simples, verificar a versão de 5 objetos é simples, mas verificar a versão de mais de 40 objetos distintos espalhados entre mais de 15 requests (e o cenário pode piorar cada vez mais) é um trabalho oneroso (para não dizer um puta saco).

Pensando que isso tem se tornado uma constante no meu dia-a-dia, fui forçado a desenvolver uma solução inteligente (mas não tão quanto eu gostaria por falta de tempo) para esse problema: um relatório de requests, contendo objetos e comparação de transporte, onde eu possa obter de forma simples qual a última request transportada para determinado objeto. Simples não?



Como vou postar o fonte para quem tenha interesse, vou analisar os pontos interessantes desse processo:

Para iniciar o processo, me baseei em duas premissas: 1 - preciso obter essa informação de transporte partindo de DEV e sem me conectar a outros ambientes; 2 - preciso me basear na request (ou requests) para pontos inicial da minha busca.

Logo no início, me vi com algumas dúvidas quanto aos ambientes: quantos são os ambientes (nesse cliente eu sei quantidade, mas e em um próximo), qual a rota de transporte? 
Para isso utilizei uma function muito útil: TMW_GET_TARGET_SYSTEMS. Essa function fornece uma lista de ambiente, numerando a rota de transporte. As descrições dos ambientes, que não são fornecidas por essa função, consegui obter na tabela TMSCSYS. Esse é o ponto onde o processo não ficou interessante, pois, por questões de tempo, acabei restringindo o número máximo de ambiente (feio, eu sei), mas logo vou atualizar o fonte. 

Após resolver as informações sobre ambientes e rotas, utilizei as tabelas E070, E07T e E071 para obter as informações de objetos de cada request. Para evitar informações não necessárias nesse momento, acabei excluindo as informações de objetos CORR (textos) e TABU (TVDIR e TDDAT).

Bom, já tinha as informações dos ambientes e dos objetos das request, hora de obter os dados cruzados. Obtendo as mesmas tabelas acima, fiz uma busca inversa verificando quais outras request possuem os mesmo objetos. De posso de todos os dados, usei a function STRF_READ_COFILE para me fornecer o log de transporte de cada request e usando para comparação de datas de transporte as seguintes regras:
Para ambiente 0 (DEV comumente) obtive a data da função E (Criação de versões após exportação)
Para demais ambientes obtive a data da função G (Geração Abaps e telas) ou A (Ativação de objetos de data dictionary).

Com todas as informações foi somente realizar o cruzamento de informações, verificando se determinado objeto possui uma data de transporte posterior à data presente na minha request e pronto. Para facilitar a visualização coloquei alguns ícones. Acabei realizando todo o desenvolvimento sem objetos, para permitir uma fácil migração entre ambiente.

Às vezes a sua necessidade mais banal pode gerar alguns resultados interessantes. Ainda pretendo atualizar o relatório para conter mais algumas informações, mas por enquanto está me atendendo e espero que possa auxilia-los. 

Caso queiram baixar o fonte (e atualizar para inserir maiores informações - me mandem a versão atualizada depois), vou deixar o link aqui: fontes.

Até mais e obrigado pelos peixes.

28 de abril de 2014

Loops encadeados: a maldição

Fala pessoas!

Olha, teve 1 acesso desde o último post. Será que alguém errou algum endereço e caiu aqui?

Bom, hoje vou falar de uma coisa simples e besta, mas que tenho visto muitas vezes essa coisa simples e besta se repetindo da forma mais dantesca o possível: Loop encadeado (ou vulgo loop dentro de loop).

Por conceito, o loop é um processo que navega por todos os registros de uma tabela interna. Simples não? Conceitualmente sim e praticamente também. Então qual o problema? O problema está na necessidade de analisar os dados que serão processados antes da construção do loop.

Vejam esses dois exemplos:

  1. um loop encadeado com 10 registros na primeira tabela e 20 na segunda tabela. A execução deste loop encadeado resultaria em 200 processos (ou 125 na base 13, certo?).

  2. um loop encadeado de gente grande (mas não tão), com 96.701 registros na primeira tabela e 1.504.265 na segunda. A execução deste belo loop encadeado resultaria em simples 145.463.929.765 processos. Imagine que legal fazer um loop encadeado da forma mais hedionda o possível e se deparar com uma quantidade de registros dessas. Será muito legal justificar que seu report vai demorar só 2 anos, 7 meses, 19 dias, 21 horas e 42 minutos para terminar.
Mas com certeza, tem alguém pensando que isso é puro exagero e que mesmo um loop encadeado mais tosco o possível não pode demorar tanto assim, que ele sempre fez assim e sempre funcionou. No exemplo abaixo eu fiz um teste de tempo de execução para o exemplo 2 (olha que legal, o primeiro tempo apresentado é o do loop tosco):



Então, vamos ver os tipos mais comuns de loops encadeados, do pior para o melhor:

1 - Loop preguiça - para pouco, realmente poucos dados
 Esse é o tipo de loop mais simples e que tem a pior performance possível. É somente um loop com outro interno usando um IF para comparação dos dados. Esse caso representa o primeiro item da imagem de medição de tempo.

UNASSIGN: <gf_ekko>.
LOOP AT gt_ekko
ASSIGNING <gf_ekko>.
    UNASSIGN: <gf_ekpo>.
    LOOP AT gt_ekpo
    ASSIGNING <gf_ekpo>.
        IF <gf_ekko>-ebeln = <gf_ekpo>-ebeln.
            gs_lista-ebeln = <gf_ekko>-ebeln.
            gs_lista-qtd = 1.
            COLLECT gs_lista INTO gt_lista.
        ENDIF.
    ENDLOOP.
ENDLOOP.

2 - Loop segundo grau - para poucos ou um pouco mais de poucos registros. Nesse caso, o segundo loop contém um WHERE, o que permite uma localização rápida do registro inicial que contém os dados necessários. Esse caso representa o segundo item da imagem de medição de tempo.

UNASSIGN: <gf_ekko>.
LOOP AT gt_ekko
ASSIGNING <gf_ekko>.
    UNASSIGN: <gf_ekpo>.
    LOOP AT gt_ekpo
    ASSIGNING <gf_ekpo>
      WHERE ebeln = <gf_ekko>-ebeln.
        IF <gf_ekko>-ebeln = <gf_ekpo>-ebeln.
            gs_lista-ebeln = <gf_ekko>-ebeln.
            gs_lista-qtd = 1.
            COLLECT gs_lista INTO gt_lista.
        ENDIF.
    ENDLOOP.
ENDLOOP.

3 - Loop plus plus - para poucos ou muitos dados. Este processo usa no início do segundo loop um read table com a opção transporting no fields, o que permitem um posicionamento inicial do dados desejado e transporta essa posição para o segundo loop usando a opção from sy-tabix. Esse processo se torna mais rápido porque usa o posicionamento do índice dos registros para localizar as informações e não propriamente os dados dos registros. Esse caso representa o terceiro item da imagem de medição de tempo.

UNASSIGN: <gf_ekko>.
LOOP AT gt_ekko
ASSIGNING <gf_ekko>.

    READ TABLE gt_ekpo
    TRANSPORTING NO FIELDS
      WITH KEY ebeln = <gf_ekko>-ebeln
        BINARY SEARCH.

    IF sy-subrc EQ 0.
        UNASSIGN: <gf_ekpo>.
        LOOP AT gt_ekpo
        ASSIGNING <gf_ekpo>
          FROM sy-tabix.
            IF <gf_ekko>-ebeln = <gf_ekpo>-ebeln.
                gs_lista-ebeln = <gf_ekko>-ebeln.
                gs_lista-qtd = 1.
                COLLECT gs_lista INTO gt_lista.
            ELSE.
                EXIT.
            ENDIF.
        ENDLOOP.
    ENDIF.
ENDLOOP.

Como pode ser notado, o loop mais elaborado, que permite uma melhor performance em qualquer dos casos, não é tão mais complexo ou trabalhoso para ser criado. Agora a escolha de utilização fica a gosto de cada um.

Seguem mais algumas dicas para sempre serem usadas para se trabalhar com grandes quantidade de dados:
  1. SORT: sempre ordene os seus dados para permitir uma melhor performance

  2. BINARY SEARCH: sempre use o binary search no read table. Ele permite uma melhor performance para obter a informação desejada. Obs: sempre ordene os dados baseado nos campos que serão usados como chave no read table, pois caso os dados estejam usando outra ordem e o comando read table esteja usando o binary search, o retorno poderá ser informado como não encontrado, por mais que a informação exista. Vejam o conceito da busca binária aqui (é bem interessante e vale a pena o conhecimento e entendimento).

Antes que eu esqueça, vocês podem reparar que eu usei FIELD-SYMBOLS em todos os exemplo, ao invés de WORKAREAS ou HEADERS. Isso por um simples motivos: porque workareas e headers sempre (sempre) apresentam performance menor, para qualquer situação. Depois monto um post explicando o porque usar field-symbols.

Caso se interessem, segue abaixo os arquivos dos reports utilizados para medição: fontes.

Até mais e obrigado pelos peixes.

3 de abril de 2014

FOR ALL ENTRIES: pode até ser seu colega, mas cuidado com ele

Fala pessoas (olha, um arbusto seco rolando ao vento...)

Vamos iniciar nossa série sobre performance, que terá um total de partes equivalente ao quadrado do grau de inclinação do rabo do hipopótamo.

Não vou abordar os conceitos iniciais de performance, como selecionar somente os campos necessários no select, não usar tabela com cabeçalho, não contratar aborígenes de Papua para ficar imputando NFe e outros. Mas caso achem necessário, deixe comentários no post solicitando os conceitos básicos de performance (ou seja, como não tem ninguém lendo, não vou ter que fazer).

Inicialmente, vamos falar de FOR ALL ENTRIES.
Esse prezado e dileto comando pode até ser nosso colega, mas não devemos confiar nele para cuidar da nossa coleção de 151 pokémons.

Tenho dois casos para demonstrar que nem sempre a utilização do FOR ALL ENTRIES (trataremos de FAE daqui em diante para economizar letras, pois meu estoque de "L" está baixo) é a mais indicada. Para ambos os exemplos eu fiz duas comparações de tempos, uma com a utilização de FAE e outra sem, sempre usando os mesmos dados.

No primeiro caso, temos a utilização de FAE para grande quantidade de dados. Quando há uma quantidade de dados baixa, o processo de FAE é um processo benéfico que garante acesso único a base de dados, mas quando há uma grande quantidade de dados, a execução do FAE se torna mais lenta que o processo de select individual, pois há mais informação para ser carregada para memória (e quanto maior a carga de dados para a memória, maior a alocação física e menor a performance, progressivamente):


Nesse caso, para o período de 2014, como há poucos registros, a obtenção de dados com FAE é realizada mais rápida, porém, no período de 2011 a obtenção com FAE é mais lenta que um select dentro de um loop. Ok, eu sei que não se usa select dentro de loop, mas quando não há como reduzir a massa de dados ou segmentar o processo, é preferível isso à um DUMP.

No segundo, temos a utilização de FAE derivado de um select sem a utilização de campos chaves (sejam PK, FK ou IDX). Nesse caso a utilização do FAE se torna um pouco maior do que sem o mesmo:


Bom, vale lembrar que para usar o FAE, SEMPRE, repito SEMPRE, deve ser verificado se a tabela de referencia possui conteúdo, pois caso a tabela esteja vazia isso forçará o select a trazer absolutamente todos os registros da tabela acessada..

Caso se interessem, segue abaixo os arquivos dos reports utilizados para medição: fontes.

Até mais e obrigado pelos peixes.

1 de abril de 2014

Antes de falarmos de performance

Fala pessoal (cri cri cri)...

Estive pensando (sim, as vezes faço isso), e acho interessante abordarmos outro assunto antes de falarmos de performance. Vamos falar de padronização!

Quantas vezes nós (abaps amaldiçoados) nos deparamos com as mais diversas, e diga-se criativa às vezes, formas de declaração de itens no código fonte. Entendo que você pode justificar que como o desenvolvimento é seu você desenvolver como quer, mas imagine que um outro abap irá realizar uma manutenção no seu código, ele pode ficar com dúvida sobre qual a finalidade de tabela "tb_table" ou ainda não saber direito para que server a variável "baleia".

Além do mais, é importante se acostumar a desenvolver baseado em padrões de nomenclaturas, pois muitas empresas possui um workbook interno (documento empresarial contendo a descrição, normalização e metodologia de todos os padrões de programação que você deve utilizar). Então porque não ter um workbook próprio e usa-lo no dia a dia?

Inicialmente você não precisa criar todos os padrões possíveis a imagináveis para começar a utilizar seu workbook. Comece pelos itens mais simples e do cotidiano e vá evoluindo aos poucos. Para criar uma padronização, pense em duas coisas: clareza e simplicidade (lembre-se que outras pessoas precisam identificar de forma rápida para qual objetivo existe o objeto)! Veja alguns exemplos de padronização que eu uso:

gt_notas
G = declaração global - T = Tabela interna - NOTAS = conteúdo existente

ls_material
L = declaração local - S = estrutura interna (workarea) - MATERIAL = conteúdo existente

lv_tabix
L = declaração local - V = variável - TABIX =  número do loop atual

Esses são apenas exemplos simples que eu uso no cotidiano. A padronização pode ser criada do modo que desejado, desde que seja interpretável de forma simples por outras pessoas (não usem runas Angerthas ou klingon para isso).

No início você irá se atrapalhar um pouco para lembrar o seu novo método de padronização, mas depois de pouco tempo isso se tornará nativo.

Até mais e obrigado pelos peixes.

31 de março de 2014

Retomando atividades

Olá pessoas (por mais que não tenha ninguém lendo isso ainda).

Estou retomando o start do site (porque nem ao menos ele começou a ter conteúdo), mas como todo começo é confuso, não quero gerar uma quantidade X de conteúdo somente para postar e gerar massa de dados.

Como há diversos locais bons (e alguns nem tanto) para consulta de código ABAP, inicialmente vou abordar alguns pontos de performance, assunto que pode ser muito útil mas que nem sem tem a devida atenção.

Obviamente vou passar pela trivialidade de postar códigos para iniciantes, mas antes quero organizar como isso será postado.

Então vou iniciar com uma série de processos simples para obter uma melhor performance para execução de seu código, que pode ser aplicada em diversas áreas.

Até a próxima e obrigado pelos peixes.

PS: sei que o correto é "adeus e obrigado pelos peixes", mas como pretendo retornar com conteúdo regular, deixemos o "adeus" para o último post.

27 de setembro de 2012

Primeiro post

Olá pessoal,

Este blog será para compartilhar algumas dicas de programação ABAP e lógica de programação, que eu encontro no meu dia-a-dia em meus clientes.

Primeiro vou formatar e arrumar esse layout e depois venho com as publicações.
Espero que gostem.