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:
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:- 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";
- 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.
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.).
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 } }
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:- 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;
- Gerar uma saída em formato .SVG, para que a qualidade seja independente de zoom;
- 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">
Se isso não é WYSIWYG, então nada é, não é mesmo? |