Python Pragmático

Uma comparação entre processos de desenvolvimento

Atenção: este post é mais sobre processos de desenvolvimento do que sobre Python, no entanto o código apresentado pode ajudar o leitor a aprender formas mais “pythônicas” de resolver problemas. No final do artigo existe um link para um projeto GitHub com todo o código demonstrado além de um PDF com slides resumindo o conteúdo.


Comparação entre duas abordagens diferentes para resolver o mesmo problema. A primeira usando eXtreme Go Horse (XGH, ou seja, fazendo a primeira coisa que vem à cabeça), e a segunda usando Test-Driven Development, modularização, conceitos funcionais etc.


Planejamento da Sprint

Hoje é dia de planejamento de Sprint! Durante os trabalhos, no backlog priorizado do projeto, existe a seguinte história de usuário:

Quero saber quanto pago em média por uma hora de cada tipo de serviço que preciso contratar, independentemente da forma de contratação.

Certo, a reunião acaba rapidamente e um felizardo fica com essa tarefa.


Metodologia XGH, Segunda-feira

Na parte da tarde o desenvolvedor começa a analisar a tarefa, e descobre que existem duas fontes de dados para essa demanda:

  1. Base de contratos do Sistema de Compras;
  2. Base de funcionários do Sistema de RH.

Analisando o Sistema de Compras, ele descobre que já existe uma API REST que retorna uma lista no seguinte formato:


Metodologia XGH, Terça-feira

Mãos à obra! Ele então cria a API principal e já começa a consumir os dados do Sistema de Compras!

$ python -m venv ~/.pyenv/pyprag
$ source ~/.pyenv/pyprag/bin/activate

$ pip install -r xgh/api/requirements.txt

$ export FLASK_APP=xgh/api.py
$ flask run


Metodologia XGH, Quarta-feira

O desenvolvedor continua então melhorando o código gerando as médias para cada tipo de serviço:


Metodologia XGH, Quinta-feira

Chegou a hora do Sistema de RH! Analisando a integração, o desenvolvedor descobre que existe uma API que retorna dados em XML (ugh) com o seguinte formato:

E logo ele se depara com um problema: como mapear cargos com tipos de serviço (repare que isso aconteceu na quinta-feira!)? Ele então levanta a bola para o facilitador da equipe e prossegue para o desenvolvimento do código que trabalha esses dados:


Metodologia XGH, Sexta-feira

Chegou o dia da apresentação! Mas ainda existe uma pendência na demanda. Mas a culpa é do pessoal de negócio que ainda não terminou o mapeamento que foi solicitado ontem, certo? Além disso, vamos parar para analisar alguns pontos fracos da nossa solução:

  1. Código confuso e procedural, nada pythônico;
  2. Nenhuma cobertura com testes automatizados;
  3. Não permite reaproveitamento das integrações;
  4. Mudanças no Sistema de RH ou de Compras impactam mais do que deviam, já que todo o código está embolado em uma coisa só.

Resultado? Código ruim e cliente insatisfeito. Mas deve ter uma forma melhor de fazer tudo isso, certo?


“Boas práticas”, Segunda-feira

Qual o motivo das aspas? Seria presunçoso dizer que o que segue é corroborado pela literatura, trata-se boa parte de opinião do autor resultante do seu trabalho, por isso as aspas duplas.

A reunião de planejamento ocorre exatamente da mesma forma.

A primeira coisa que o desenvolvedor decide fazer é quebrar a demanda em tarefas, para analisar o risco de cada uma delas. Veja o resultado:

  • Integração com o Sistema de Compras [Risco Alto]
  • Integração com o Sistema de RH [Risco Alto]
  • Calculadora de custo médio por tipo de serviço [Risco Alto]

Mas como assim tudo possui alto risco? É isso mesmo, com o pouco conhecimento que o desenvolvedor tem neste início de Sprint, tudo pode dar errado. Ele então decide começar pela integração com o Sistema de Compras, mas não já codificando, ele quer apenas diminuir o risco da subtarefa. Em conversa com os mantenedores do sistema, ele descobre que já existe uma API que retorna XML, e ela retorna os dados do jeito que ele precisa. Com isso, ele reduz o risco da subtarefa para médio.

  • Integração com o Sistema de RH [Risco Alto]
  • Calculadora de custo médio por tipo de serviço [Risco Alto]
  • Integração com o Sistema de Compras [Risco Médio]

“Boas práticas”, Terça-feira

O próximo item da lista é o Sistema de RH. Durante a análise o desenvolvedor já identifica que a API não retorna os dados compatíveis com o que ele precisa (note que isso está acontecendo na terça!), e já adiciona uma nova subtarefa com risco alto, pois está fora do seu controle:

  • Mapeamento de Cargos para Tipo de Serviço [Bloqueada]
  • Integração com o Sistema de RH [Risco Alto]
  • Calculadora de custo médio por tipo de serviço [Risco Alto]
  • Integração com o Sistema de Compras [Risco Médio]

Em conversa com o facilitador e os gestores do negócio, lhe prometem uma planilha que faz o mapeamento entre cargos e tipos de serviço. Com isso ele julga que o risco da integração com o sistema de RH pode ser reduzido:

  • Mapeamento de Cargos para Tipo de Serviço [Bloqueada]
  • Calculadora de custo médio por tipo de serviço [Risco Alto]
  • Integração com o Sistema de RH [Risco Médio]
  • Integração com o Sistema de Compras [Risco Médio]

Como não é possível fazer nada com subtarefas bloqueadas, o próximo item da lista, por ordem decrescente de risco, é a calculadora de custo médio. Mas como implementá-la sem a integração com os outros sistemas? Neste ponto o desenvolvedor analisa as entradas e saídas desse componente:

Entradas:

  • Lista de funcionários
  • Lista de contratos
  • Mapeamento de cargo para tipo de serviço

Saídas:

  • Custo médio por tipo de serviço

Com essa quantidade de entradas, o desenvolvedor conclui que ele está ferindo o princípio da responsabilidade única do SOLID, e decide quebrar ainda mais as tarefas:

  • Mapeamento de Cargos para Tipo de Serviço [Bloqueada]
  • Calculadora de custo médio por tipo de serviço [Risco Alto]
  • ___ Processador de planilha de mapeamento de cargos [Bloqueada]
  • ___ Calculadora de custo médio por funcionários [Risco Médio]
  • ___ Calculadora de custo médio por compras [Risco Médio]
  • ___ Agregador de custos médios [Risco Pequeno]
  • Integração com o Sistema de RH [Risco Médio]
  • Integração com o Sistema de Compras [Risco Médio]

Dessa maneira ele então decide implementar a Calculadora de custo médio por funcionários, que possui a seguinte interface:

Entradas:

  • Lista de funcionários
  • Dicionário de cargo para tipo de serviço

Saída: Dicionário de tipo de serviço para custo total e peso

$ pip install -r boaspraticas/requirements.txt


“Boas práticas”, Quarta-feira

A primeira coisa do dia (depois do café) é dar uma olhada na lista de subtarefas atualizada:

  • Mapeamento de Cargos para Tipo de Serviço [Bloqueada]
  • Calculadora de custo médio por tipo de serviço [Risco Alto]
  • ___ Processador de planilha de mapeamento de cargos [Bloqueada]
  • ___ Calculadora de custo médio por funcionários [CONCLUÍDA]
  • ___ Calculadora de custo médio por compras [Risco Médio]
  • ___ Agregador de custos médios [Risco Pequeno]
  • Integração com o Sistema de RH [Risco Médio]
  • Integração com o Sistema de Compras [Risco Médio]

O próximo passo então é a calculadora de custo médio por compras. Veja as entradas e saídas deste componente:

Entrada: Lista de contratos

Saída: Dicionário de tipo de serviço para custo total e peso

E o desenvolvimento do componente usando TDD:

Finalizado este componente, o desenvolvedor olha novamente para a lista de subtarefas:

  • Mapeamento de Cargos para Tipo de Serviço [Bloqueada]
  • Calculadora de custo médio por tipo de serviço [Risco Alto]
  • ___ Processador de planilha de mapeamento de cargos [Bloqueada]
  • ___ Calculadora de custo médio por funcionários [CONCLUÍDA]
  • ___ Calculadora de custo médio por compras [CONCLUÍDA]
  • ___ Agregador de custos médios [Risco Pequeno]
  • Integração com o Sistema de RH [Risco Médio]
  • Integração com o Sistema de Compras [Risco Médio]

Seguindo a análise de risco, o próximo item a ser implementado é a integração com o Sistema de RH:

Entradas: N/A

Saídas: Lista de funcionários

E assim ele implementa a integração:


“Boas práticas”, Quinta-feira

Boas notícias! Acaba de chegar no e-mail do desenvolvedor uma planilha com o mapeamento de cargos para tipo de serviço! Ele vai então e atualiza a lista de subtarefas:

  • Mapeamento de Cargos para Tipo de Serviço [CONCLUÍDA]
  • Calculadora de custo médio por tipo de serviço [Risco Pequeno]
  • ___ Processador de planilha de mapeamento de cargos [Risco Pequeno]
  • ___ Calculadora de custo médio por funcionários [CONCLUÍDA]
  • ___ Calculadora de custo médio por compras [CONCLUÍDA]
  • ___ Agregador de custos médios [Risco Pequeno]
  • Integração com o Sistema de RH [CONCLUÍDA]
  • Integração com o Sistema de Compras [Risco Médio]

Bem melhor! O desenvolvedor decide então atuar pela parte da manhã na última subtarefa de risco médio: integração com o sistema de compras.

Entradas: N/A

Saídas: Lista de contratos

E chega na implementação abaixo:

Agora faltam apenas subtarefas de baixo risco! Se a análise do desenvolvedor estiver correta, nada o impedirá de entregar a demanda amanhã!

  • Mapeamento de Cargos para Tipo de Serviço [CONCLUÍDA]
  • Calculadora de custo médio por tipo de serviço [Risco Pequeno]
  • ___ Processador de planilha de mapeamento de cargos [Risco Pequeno]
  • ___ Calculadora de custo médio por funcionários [CONCLUÍDA]
  • ___ Calculadora de custo médio por compras [CONCLUÍDA]
  • ___ Agregador de custos médios [Risco Pequeno]
  • Integração com o Sistema de RH [CONCLUÍDA]
  • Integração com o Sistema de Compras [CONCLUÍDA]

Ele decide então partir para o processador da planilha de mapeamento:

E agora falta apenas o agregador de custos médios e o endpoint Flask:


“Boas práticas”, Sexta-feira

E assim a demanda pode ser entregue! Comparando as duas estratégias pode-se identificar vários benefícios com a segunda abordagem:

  • Existe uma grande modularização, o que estimula o reuso dos componentes por funcionalidades futuras;
  • A cobertura de testes unitários que foi feita para os componentes de regra de negócio ajudam também na manutenção futura (devido a eventuais defeitos ou mudanças);
  • A criação de componentes dedicados para as integrações facilita não apenas no reuso, mas a ajustar o sistema caso os componentes dependentes mudem a forma de entrega dos dados, por exemplo.

Mas nem tudo são flores, é evidente que a quantidade de código escrito para a segunda abordagem é muito maior, mas será que vale a pena trocar algumas linhas de código extra pelos benefícios listados acima? O autor acredita que sim, vale!

Caso tenha encontrado algum erro ou saiba de alguma maneira melhor para lidar com qualquer parte deste artigo, não exite em deixar um comentário! Sua contribuição construtiva é muito importante.

Todo o código usado neste artigo está disponível aqui: https://github.com/samuelgrigolato/palestra-pythonpragmatico. Na raíz do projeto também encontra-se um PDF com slides que resumem o conteúdo.