domingo, 10 de agosto de 2014

Usando Graphviz para Visualização e Gerenciamento de Dependências

O Gerenciamento de Dependências (Dependency Management) é uma atividade muito útil não apenas em programação e projeto de software, mas em qualquer atividade minimamente complexa em que se deva construir ou executar alguma coisa.

Especificamente em se tratando de software, inúmeras características de qualidade em arquitetura, como modularidade, arquitetura em camadas, information hiding e gerenciamento da complexidade em geral são, em sua essência, atividades de detecção e organização de dependências.

Para que a discussão que se segue seja baseada em um exemplo concreto, vou considerar uma situação real que me aconteceu hoje:

Sou cadastrado no site Coursera, que é um site agregador de cursos online para educação à distância (a ideia me agrada, embora ainda não tenha me engajado em acompanhar de fato algum curso, até agora). Sendo assim, recebi de forma inesperada um email oferecendo vários cursos, e me interessei pelo curso chamado "Data Scientist's Toolbox", da Universidade Johns Hopkins.

Rapidamente descobri que existem cursos relacionados a esse, mas logo em seguida descobri que os cursos não só eram relacionados, como faziam parte de uma série de cursos, e que havia relações de dependência entre eles. Ou seja, era sugerido, ou mesmo requerido, que para cursar determinados cursos, os pré-requisitos já tivessem sido cumpridos.

As dependências são apresentadas em um PDF, que não é exatamente uma forma legível de apresentar uma lista exaustiva de dependências:

Cada curso pode ter dependências "hard"
ou "soft" com relação a outros cursos.


Impaciente para fazer o malabarismo mental - e receoso de esquecer alguma dependência por "erro de leitura" - decidi fazer um gráfico das dependências.

A forma mais fácil de fazer um gráfico é desenhar o gráfico, mas essa é a forma que eu menos gosto de usar, evitando fazer qualquer coisa à mão sempre que possível. Em especial, não só o melhor layout é difícil de aceitar, como qualquer modificação posterior pode envolver redesenhar tudo novamente. Como um bom nerd, me recuso a fazer isso, e depois de muito pesquisar descobri a ferramenta Graphviz, que transforma a descrição de um grafo, sob a forma de um arquivo de texto simples em alguma linguagem de descrição de grafos (a mais comum é a linguagem DOT), na imagem de um grafo propriamente dito, com layout automático configurável.

Bom, o gráfico gerado pelo Graphviz ficou assim:

Dependências "hard" mostradas em preto;
Dependências "soft" mostradas em cinza;
A seta se origina na dependência em direção ao elemento dependente

Se o leitor quiser saber como esse gráfico foi gerado (com qual programa, e a partir de qual arquivo-fonte), isso será explicado no final da postagem. Agora vou comentar sobre como a visualização desse gráfico ajuda na análise do problema, ou seja, no gerenciamento de dependências propriamente dito.

A primeira coisa que chama atenção é que TODOS os elementos dependem de "Data Scientist's Toolbox", e também de "R Programming". Isso polui muito o gráfico, dificultando ver as outras dependências entre os módulos. Antes de começar a reorganizar graficamente essas dependências, convém fazer duas considerações:
  1. Muitas vezes, se A depende de B e B depende de C, não necessariamente precisamos concluir que A depende diretamente de C. No gráfico, não é possível afirmar que os módulos que dependem de "R Programming" dependem direta ou indiretamente de "Data Scientist's Toolbox";
  2. No caso da dependência entre "R Programming" e "Data Scientist's Toolbox", ela é qualitativamente diferente das outras (porque é "soft"), portanto temos mais um motivo para não colapsar inconsequentemente as dependências entre " ' Toolbox" e o restante dos módulos.
Ainda assim, como casualmente TODOS os módulos que dependem de um também dependem de outro, vamos colapsá-los em um sub-grafo chamado "Basics":

Com a criação do elemento "Basics", o gráfico fica muito mais limpo e legível.

Bom, agora ficou bem mais fácil perceber as "dependências herdadas" mencionadas acima. Essas dependências são da forma "A depende de B que depende de C, mas A também depende de C", conforme visualizado no gráfico por uma seta "pulando" diretamente de um elemento a outro, mas também "passando" por um elemento intermediário. A conclusão correta é que, se A depende de B que depende de C, não é necessário afirmar de forma explícita - e redundante - que A depende de C. Portanto essas dependências redundantes podem ser e serão removidas:


 Agora sim, podemos dizer que o gráfico está em sua forma "canônica", com todas as redundâncias agrupadas, colapsadas ou removidas. Podemos então, tirar algumas conclusões diretas a partir da visualização do gráfico:
  • O módulo mais avançado é "Practical Machine Learning", requerendo no mínimo outros três módulos sequenciais ("R Programming", "Statistical Inference" e "Regression Models"), mas preferivelmente outros cinco módulos (os já citados, mais " ' Toolbox" que é soft e "Exploratory..." que além de soft pode ser cursado em paralelo com "Statistical..." ou "Regression...");
  • "Getting and Cleaning Data" não depende nem é pré-requisito dos outros módulos exceto o básico. Portanto, se meu interesse maior for esse, eu posso ver que vai dar pouco trabalho e que não preciso me preocupar em cursar ou não os outros módulos;
  • Não existe uma ordem pré-definida para cursar os módulos que dependem apenas do módulo básico. Essa é uma característica benéfica de descrever um processo em forma de dependências, ao invés de "etapas" (este, depois este, depois este, etc.).
Uma coisa fundamental que deve ser dita, e que diferencia entre a visualização de dependências e o verdadeiro gerenciamento de dependências, é que, ao contrário deste caso em que as dependências já vieram prontas, a maior utilidade do método é quando estamos analisando as dependências de algo que nós estamos desenvolvendo: ao detectarmos dependências espúrias, podemos intervir para eliminá-las, melhorando a qualidade do desenvolvimento.

Como gerar os gráficos

O primeiro passo é criar um arquivo na linguagem DOT. O arquivo que gerou o penúltimo gráfico encontra-se abaixo, com comentários no formato // destacando os pontos chave:
 
digraph g {
rankdir = "LR"   // faz com que as dependências ocorram "left to right"
label = "Data Science Course Series\nJohns Hopkins University"

node [shape="rectangle"]  // faz com que o formato padrão dos nós seja um retângulo

// definição de variáveis do tipo "node", com as respectivas labels
toolbox     [label="Data Scientist's\nToolbox"]
r           [label="R Programming"]
getting     [label="Getting and\nCleaning Data"]
explore     [label="Exploratory\nData Analysis"]
reproduce   [label="Reproducible\nResearch"]
statistical [label="Statistical\nInference"]
regression  [label="Regression\nModels"]
machine     [label="Practical\nMachine Learning"]
products    [label="Developing\nData Products"]


// clusters são definidos com subgrafos cujo nome têm o prefixo "cluster"
subgraph cluster_basics {
    style=filled
    fillcolor="#ffffff"
    
    label="Basics"
    
    toolbox -> r [color="#cccccc"]   // cor cinza simbolizando "soft"
}

explore -> machine [color="#cccccc"]  // "soft"
explore -> products [color="#cccccc"]  // "soft"


// hard dependencies
subgraph hard {  

    // "ltail" (logic tail) posiciona explicitamente a origem da seta

    r -> getting [ltail=cluster_basics]
    r -> explore [ltail=cluster_basics]
    r -> reproduce [ltail=cluster_basics]
    r -> statistical [ltail=cluster_basics]
    
    r -> regression [ltail=cluster_basics]
    statistical -> regression
    
    r -> machine [ltail=cluster_basics]
    regression -> machine
    
    r -> products [ltail=cluster_basics]
    reproduce -> products
}


}

Esse arquivo precisa ser interpretado por algum programa que vai gerar a imagem propriamente dita. No site do Graphviz há instaladores para todas as plataformas. No Linux, usei a linha de comando da seguinte forma:

dot -T svg -o "graph.svg" "graph.gv" 

No Windows, tenho usado a interface gráfica GVEdit. Tudo isso baixado e instalado a partir do site de forma bastante tranquila.

WYSIWYG com três truques malandros

Evidentemente a construção de um arquivo desses é muito melhor quando é interativa: a cada elemento adicionado, geramos um novo grafo e vemos se ele está crescendo da forma como desejamos. Que eu saiba, não há um editor que faça isso de forma contínua, tipo uma "IDE" para a linguagem DOT, mas é possível fazer algo bem parecido da seguinte forma:
  1. Configurar alguma mini-IDE (editor de texto que tenha o comando "Run" para executar scripts, eu uso o Geany) para rodar a linha de comando acima com um atalho de teclado;
  2. Gerar uma saída em formato .SVG, para que a qualidade seja independente de zoom;
  3. Criar um HTML estático que mostre o SVG em um elemento de imagem (largura e altura proporcionais ao tamanho da janela), e que se auto-atualize usando uma tag
    <meta http-equiv="refresh" content="1">
     
Assim, posso trabalhar com a tela dividida ao meio, e cada vez que aperto F5 o arquivo DOT é salvo, o SVG é gerado, e sua exibição é, em no máximo um segundo, atualizada na janela do navegador:

Se isso não é WYSIWYG, então nada é, não é mesmo?