Django Workflow

Você, desenvolvedor, já se deparou com cenários específicos em que você precisa controlar um fluxo de informações, mas, suportando diversos status, com diversos efeitos colaterais diferentes, entre cada mudança desses status?

Criar e manter este tipo de estrutura, manualmente, é bem complicado. Formalmente, este tipo de estrutura é chamada de máquina de estado finita ou finite state machines e para os íntimos, FSM.

Inspirados em outros pacotes disponíveis para o Django, construímos nosso próprio gerenciador de máquinas de estados. Este é mais um pacote open-source que disponibilizamos para a comunidade.

Aplicações das FSM

As aplicações das FSMs são diversas. Elas podem controlar como compoenentes internos de uma aplicação reagem a estímulos externos (usuário informa determinada informação) ou mesmo serem editadas pelo próprio usuário. Um exemplo disso são os famigerados BPM (Bussiness Process Management**) onde os próprios usuários definem até certo ponto, o fluxo da informação e por quais verificações esta deve passar antes de permitir a troca de um estado para outro.

Outro exemplo interessante é que algumas AIs são escritas usando os conceitos de FSM para determinar comportamentos de seus agentes, portanto, te garanto, FSMs são bastante flexíveis.

Na realidade, a ideia desta aplicação Django surgiu para atender uma demanda, onde os usuários mais graduados, deveriam poder escolher como a informação fluiria numa aplicação geográfica.

Instalação

Para instalar este pacote é bem simples e você pode usar o pip:

pip install django-workflow-fsm

Agora adicione este pacote ao seu INSTALLED_APPS:

INSTALLED_APPS = (
    # outras apps,
    'workflow',
    # outras apps,
)

Execute suas migrações com ./manage.py migrate

Getting Started

Terminado isto, você precisa definir qual é o modelo que você deseja controlar o status. Basta você herdar de um MixIn para obter a funcionalidade de máquina de status:

# models.py
class Projeto(StateControllerMixIn):
    nome = models.CharField(max_length=128)

Pronto. A configuração básica está pronta!

Depois disso, com toda a certeza, você deve querer editar quais status e como a informação flui entre estes status. Isto é bem fácil.

Nós controlamos o fluxo de informações utilizando três modelos:

  • StateMachine;
  • State
  • Transition;

Vamos construir isso em um passo a passo rápido:

# shell ou fixture ou migração de dados
status_aberto = State.objects.create(code='aberto')
status_em_andamento = State.objects.create(code='em-andamento')
status_fechado = State.objects.create(code='fechado')

# nossos status estão criados. Vamos criar nossa máquina de estado

fsm = StateMachine.objects.create(name='projetos-simples', initial_state=status_aberto)
# na linha acima definimos nossa máquina e o status inicial dela, aberto.

# agora vamos definir as transicoes
aberto_andamento = Transition.objects.create(name='iniciando-projeto',
    machine=fsm,
    from_state=status_aberto,
    to_state=status_em_andamento)

andamento_fechado = Transition.objects.create(name='finalizando-projeto',
    machine=fsm,
    from_state=status_em_andamento,
    to_state=status_fechado)

Agora que nossas transições estão prontas, você pode definir qualquer projeto, com diferentes tipos de máquinas de estado, basta escolher a máquina de estado apropriada, veja só:

projeto = Projeto()
projeto.nome = 'projeto legal'
projeto.save(state_machine=fsm)

Neste momento, todas as ações da máquina de estado estão disponíveis através do mixin que você herdou na construção do modelo Projeto.

Exemplo:

projeto.current_state
# imprime "aberto"
projeto.next
# imprime "em andamento"
projeto.change_to(state_em_andamento)
# projeto será enviado para o estado "em andamento"
projeto.next
# imprime 'fechado'

Não é só isso, existem diversos ganchos que você pode usar, como tarefas específicas a serem disparadas e ações, nas quais você pode associar a um estado, para indicar que esta ação está disponível. Exemplo: no status em andamento do projeto, vocẽ pode criar comentários e apenas neste estado. Portanto, você pode criar uma Action e associá-la ao estado em-andamento.

No seu código, vocẽ pode checar qual action está disponível e renderizar o template como você achar melhor.

Outras coisas legais:

  • Você pode associar permissões a cada estado, ou seja, apenas usuários com determinadas permissões podem trocar o estado da máquina;
  • Você pode associar tasks (ou tarefas) que serão executadas quando um estado da máquina é alterado. Por exemplo, quando a máquina de estado mudar de estado, quero disparar um email para um usuário, informando do ocorrido. Você pode criar isso como uma Task e associar esta task a transição específica.
  • Suporte para tarefas assíncronas. Por padrão, as tarefas são executadas usando o Celery, que é uma dependência do projeto. Caso você não queira executar estas tarefas de forma assícrona, basta desabilitar o Celery.
  • Suporte completo para API REST, usando django-rest-framework

Repositório

Disponível no Github: https://github.com/sigma-geosistemas/django-workflow

Roadmap

  • Suporte completo para tarefas assíncronas e síncronas (hoje só suportamos um modo, queriamos suportar os dois);
  • Melhorar a infraestrutura de testes;
  • Melhor/criar um help/ajuda/getting started;
  • Outras coisitas;

Este é um pacote bem completo para gestão de máquinas de estado. Caso você tenha interesse, dê uma conferida. Estamos a disposição!

Integrar software livre com software proprietário

Antes de falar sobre as diferenças de software livre e proprietário vamos imaginar o seguinte cenário: A empresa X trabalha com gestão de imóveis. Sua carta de clientes soma mais de 10 mil propriedades. Para gerir todos os clientes a empresa usa um software (vamos batizá-lo de “SISTEMA Y”). Para usar esse software a empresa gasta mensalmente 2000 reais. Ao longo do tempo, o “SISTEMA Y” é assimilado e todos os funcionários detém domínio do sistema.

A empresa X vai bem, mas precisa integrar novos serviços ou melhorar aqueles que já oferece. Nesse cenário o software SISTEMA Y desempenha muito bem suas atividades mas não oferece suporte, por exemplo, para gestão de fundiária. Nesse momento a empresa X tem duas opções:

  1. Instalar um novo software proprietário que atenda sua demanda, ou
  2. Integrar um software livre ao seu programa de gestão.

A questão é controversa. Mas entre prós e contras, a integração de softwares proprietários e livres é possível e apresenta benefícios singulares para construir sistemas especialistas de forma rápida e eficiente. Antes de prosseguir vamos falar rapidamente sobre sobre a diferença entre duas classificações:

Como o próprio nome diz o software proprietário é de propriedade de alguém (ou alguma empresa). Isso significa que ele tem um estrutura fechada que, em geral não pode ser modificada nem redistribuída. Os direitos sobre o software são exclusivos de quem o desenvolveu e para utilizá-lo é necessário o pagamento de uma licença. No cenário hipotético com a empresa X, o “SISTEMA Y” seria um software dessa natureza

O software livre, por sua vez, é de natureza oposta. Ele pode ser copiado, alterado e redistribuído livremente. Para utilizá-lo o usuário pode ou não pagar por uma licença, mas independente disso, o código- fonte do software livre está sempre aberto para modificações.

Quebrando barreiras

A integração de softwares livres à softwares proprietários é uma discussão antiga. Nesse artigo, além de explicar a diferença entre os dois modelos de software, vamos discorrer sobre os benefícios dessa integração de softwares livre para empresas que utilizam o um sistema proprietários.

Vamos retomar o exemplo da empresa X. Ao integrar o novo software ao sistema que já opera (SISTEMA Y) a empresa tira a burocracia de acesso. Ou seja, não é necessário outro login. Isso simplifica sutilmente a vida dos colaboradores (que diminuem um procedimento de acesso) e favorece drasticamente o trabalho da equipe de TI (que não vão gerenciar o dobro de cadastros no sistema). No fim estamos falando de economia de desenvolvimento, tempo e dinheiro.

Para o operador do sistema os benefícios dizem respeito a adaptabilidade à uma nova ferramenta de gestão. O software integrado não muda o ambiente tecnológico do colaborador, na verdade ele adiciona uma nova ferramenta nesse universo, a partir da mesma interface. Na prática a integração otimiza a implantação e uso do novo software, a fase de treinamento é menor.

Os usuários, portanto, são mais positivos ao uso de novas ferramentas quando elas estão inseridas no próprio software com o qual já estão seguros. A integração de softwares livres em softwares proprietários promove assim maior eficiência a todo conjunto administrativo da empresa..

Screencasts SIGMA

Olá pessoal, boa tarde!

De forma a nivelar o conhecimento da equipe, estamos com uma prática que tem dado certo. A cada semana, um dos desenvolvedores, discorre sobre algum assunto relacionado ao trabalho, no formato de um screencast.

Nós já realizamos três screencasts na empresa, cada semana sobre um tópico. E estamos gravando este material no Youtube e deixando disponível para todos.

Também usamos nossos repositórios no Gitlab para manter tudo registrado.

Saca só o que já rolou:

E estamos com grandes planos agendados!

Nas próximas semanas:

  • Django Rest Framework (Alexandre Cunha);
  • BDD;
  • Webpack;
  • Marionette;

Também deixamos em aberto a participação da comunidade. Caso tenham interesse, os SCs estão sendo veiculados ao-vivo, pelo Youtube, todos as segundas-feiras (quando possível) ou na terça-feira, as 9:30 da manhã – para dar aquela inspiração e começar bem a semana.

Se inscreva no nosso canal do Youtube e veja as novidades!

Na próxima semana, nosso desenvolvedor Alexandre Cunha vai fazer um Screencast de Django Rest Framework e mostrar alguns cantos mais desconhecidos deste excelente framework. Cola aí com a gente e aprenda mais!

Abraços

Como escrever bons relatórios de bugs

Escrever software é dificil. Manter um software atualizado e em funcionamento
sem defeitos, é ainda mais dificil.

Uma das ferramentas mais importantes para que isso ocorra, isto é, a criação
e manutenção de um bom software, com o mínimo de defeitos (nenhum software é perfeito), é sem dúvida, os relatórios de
defeitos, ou bug reports.

Eles podem ser criados por qualquer pessoa que use de fato o software, qualquer
que ele seja. Alguns relatórios de bugs (chamaremos apenas de bugs, daqui por)
diante serão escritos por desenvolvedores, que notarão comportamentos estranhos
ainda na etapa de desenvolvimento. Outros serão escritos por testadores profissionais,
que ganham o pão procurando a desgraça alheia. E outros bugs, serão encontrados
apenas quando o nosso software estiver em produção – pelo usuário final.

Não importando quem escreveu, todo e qualquer bug merece um relatório e uma
investigação formal. O quanto antes, melhor. Bugs tendem a ficar cada vez mais
caros, conforme o tempo passa. Estima-se que um bug descoberto em produção custa 10 vezes mais o valor do que se ele tivesse sido encontrado antes – durante o desenvolvimento.

A conta é simples: se o bug tivesse sido encontrado durante a etapa de desenvolvimento, o desenvolvedor gastaria 2 horas para resolvê-lo e adicionar
um teste de unidade, afim de garantir que o mesmo não retorne para assombrar ninguém,
anos depois.

Se o bug é encontrado em produção, serão gastos, aproximadamente, 20 horas para
resolvê-lo. Senão mais. Tudo em produção é mais difícil.

Certo, bugs são caros, mas e daí? Bem, se podemos resolver o bug em desenvolvimento
e uma das principais formas de se resolver um bug é criando um bug report,
bons relatórios de bugs facilitam a vida dos desenvolvedores, na hora de encontrar
o danado e resolvê-lo de uma vez por todas.

Maus relatórios de bugs deixarão os desenvolvedores e usuários irritados. Bugs
com títulos e descrições inadequadas serão muito mais difíceis de serem resolvidos,
pois são mais difíceis de serem encontrados, terem o comportamento exato reproduzido,
assim por diante.

Uma máxima importante é:

Um bom relatório de bug descreve o problema de forma que o desenvolvedor familiarizado com o projeto pode entender e resolvê-lo, sem falar com a pessoa que o escreveu.

Esta é uma tradução generalizada de uma [postagem do martiancraft][martincraft-bug-report]. Realmente, devo concordar com o título.

Pequena estória: uma certa vez, trabalhando com um cliente – sem expertise técnica, decidiu que gostaria de escrever seus próprios relatórios de bugs. Ótimo, pensei, uma coisa a menos que terei de fazer. Este cliente nunca havia utilizado ou mesmo visto um relatório de bug.

Quando ele começou a escrevê-los, ele colocava títulos estranhos, descrições que não mencionavam como reproduzir o mesmo e tentava explicar problemas de desenho da tela, utilizando palavras – sem um simples screenshot se quer. Tudo bem, pensei, ele apenas precisa aprender a escrever melhores relatórios de erros e nossa vida será muito mais fácil. Enviei alguns links para ele e a situação melhorou.

Os bugs eram corrigidos mais rapidamente, pois gastávamos menos tempo tentando entender o que ele quis dizer com aquilo. Facilitou a comunicação sobre outras funcionalidades que ele queria para seu produto. Entre diversas outras coisas.

Então, confiem em mim: um bom relatório de erros é essencial para a vida do desenvolvedor e do maior interessado, o cliente.

Então, o que todo bom relatório de bugs deve conter?

Apenas um problema por relatório

Apenas um problema por relatório de defeito. Apenas. Um. Problema. Por. Relatório.

Não existe forma de deixar isto mais claro. Quando você tem um ou mais defeitos em um mesmo relatório de bugs, isto confunde o desenvolvedor, dificulta a correção e dificulta principalmente o teste da correção.

Outra: os dois problemas andam juntos, são corrigidos juntos e aprovados juntos. Em uma equipe, estes problemas poderiam ter sido resolvidos em paralelo, fechados com timelines distintas (um bug era fácil, o outro difícil) e com maior qualidade.

Nunca reporte mais de um bug em um único relatório. Nunca. Mesmo se eles forem relacionados. Cada bug é importante o suficiente para merecer seu próprio relatório.

Título e Descrição

Um bom título é essencial para avaliação rápida do problema. Títulos que não esclarem a situação, ou mascaram o problema real, são as formas erradas de começar a escrever um relatório de erros.

Um bom título ajuda o desenvolvedor a realizar a triagem do bug, algo como: “ah é só um texto na tela”, “faltou uma validação aqui e ali” e “rapaz…tenho menor ideia do que diabos é isso”. Não só ajuda a identificar a criticidade do bug, mas também mais ou menos de onde vem o problema, facilitando a delegação da resolução para um ou outro desenvolvedor ou time.

Já as descrições são os lugares para se dar detalhes sobre o bug. Contexto, informações do ambiente, passos para reprodução do bug, dados para reprodução do bug, temperatura do ar, pressão atmosférica e humor do chefe no dia.

Sem essas informações cruciais, fica muito mais difícil reproduzir o bug, tornando o mais caro para a empresa e para o cliente.

Exemplos:

  • RUIM: “A aplicação não responde”;
  • BOM: “A aplicação não responde após a carga do arquivo XPTO na tela FOO”;

  • RUIM: “Texto fora do padrão”;

  • BOM: “Texto do título ‘Pesquisa’ está desformatado”;

  • RUIM: “Não consigo salvar imagens”;

  • BOM: “Dentro da tela de redimensionar imagens, ao tentar salvar a mesma após cortar 30% da largura da imagem, não consigo salvar as imagens”;

Acho que deu para entender.

Passos detalhados e comportamento esperado

Certo, temos nossos bugs, mas como recriar a situação? A primeira coisa que um desenvolvedor fará, é tentar recriar a situação para ver se o erro encontrado não é esporádico, se tem relação com as marés ou a lua, ou se é realmente um defeito encontrado consistentemente.

Defeitos esporádicos, como, “relatório não pode ser gerado durante os meses que possuem 31 dias” existem e são legítimos, mas o desenvolvedor primeiro irá procurar se ele não vem de uma má configuração ou algum passo errado que o usuário possa ter executado no momento de usar a aplicação.
Defeitos consistentes são rapidamente avaliados pois temos certeza de quais passos realizar para reproduzir o erro. Sem os passos, o desenvolvedor deverá contar com a sorte para reproduzir o danado – tarefa tediosa e que pode não resolver o problema.

Vamos dizer que um erro é encontrado em apenas 1/10 das vezes em que se executam uma tarefa, aleatoriamente. O desenvolvedor testou 9 vezes, mas o erro não se apresentou. O que ele irá pensar? Que não existe erro algum e talvez o usuário estivesse em uma versão antiga, ou o conjunto de dados mudou.

Mesmo que o responsável por reportar o defeito não saiba exatamente quais ações foram realizadas, em detalhes, para incluir no relatório, um conjunto mínimo de passos ajuda muito.

Exemplo:

  • RUIM: “Loguei no sistema e cliquei em imprimir no relatório.”;
  • BOM:
    • Logar no sistema com usuário que tenha permissão para gerar relatórios;
    • Clicar na seção de relatórios;
    • Escolher o relatório de faturamento;
    • Agrupar por mês e filial;
    • Clicar em gerar relatório;
    • Sistema apresenta uma tela azul;
    • Sistema deveria apresentar uma tela com um relatório em PDF, contendo o relatório de faturamento mensal, agrupado por filial;

A diferença entre os dois é gritante.

Quanto ao comportamento esperado, ele também é fundamental para quem for testar o bug e confirmar que o mesmo está resolvido. Além dos passos de testes, o comportamento esperado é o prego no caixão do defunto – ele tratá a certeza para o desenvolvedor de que o problema morreu.

Contexto

Informações de contexto são cada vez mais importantes, conforme os mesmos são distribuídos por uma gama muito maior de clientes – a web, por exemplo.

Um bom contexto contém:

  • Sistema operacional e versão;
  • Navegador utilizado e versão (em aplicações web);
  • Versão do software que está sendo utilizado;
  • Informações relevantes ao uso do sistema (dados, usuários, grupos e permissões, etc);
  • Condições especiais (com GPS do celular ligado, com pouca bateria no notebook, com a rede ligada, etc);

Em casos extremos, como aplicações gráficas ou desktops, qual placa de vídeo, qual resolução utilizada, etc. Quanto maior o número de detalhes, mais fácil.

Prioridade

Não confunda prioridade com criticidade. Prioridade é o quanto um bug impacta no dia a dia do usuário. Um bug crítico em uma funcionalidade usada apenas uma vez no ano, é muito importante, mas um bug de severidade média em uma ferramenta utilizada todos os dias tem prioridade muito maior de resolução.

Screenshots

Ah, os screenshots. Eles são muito úteis para provar que o usuário não está ~~louco~~ sonhando. Brincadeiras à parte, os screenshots colocam o desenvolvedor numa máquina do tempo e na máquina do usuário. Principalmente se o bug envolver qualquer tipo de tela ou interface de usuário.

Não é tão difícil, então, sempre que possível, tire um screenshot, desenhe em cima dele, aponte uma seta para o problema. O desenvolvedor será muito grato.

Quando fechar um bug

Existe ciclo de vida natural de um bug, que varia de empresa para empresa, mas o mais simples geralmente acontece assim:

  1. Defeito é encontrado;
  2. Bug é relatado;
  3. Desenvolvedor faz uma triagem do bug;
  4. Desenvolvedor resolve o bug;
  5. Desenvolvedor altera o status do bug para o relator conferir se o problema realmente foi resolvido;
  6. Relator fecha o bug (ou reabre, caso o conserto não tenha sido muito bom – neste caso, volte para item 3);

Este é o fluxo mínimo que temos de trabalho. Quando o bug resolvido chegar na “mesa” do relator, ele deve separar um tempo para fazer o teste da resolução, o quanto antes melhor e dar andamento nos processos.

Se outro defeito foi encontrado, crie outro bug. A maior parte das ferramentas atuais permitem que você referencie bugs e/ou tarefas, facilitando o desenvolvedor a acompanhar o histórico.

Pensamentos finais

Escrever relatórios de erros não é difícil. É um processo em que se colabora com a construção do software, então deve ser minimamente organizado e coerente. Desenvolvedores não tem bolas de cristal e não são hackers do tipo Matrix, onde vem tudo em código o tempo todo.

Um bom relatório de bugs é certamente apreciado e muito mais legal de ser resolvido do que um mau relatório. Seja bonzinho e nos ajude a te ajudar :D.

Links interessantes

TDD (Desenvolvimento Guiado por Testes)

A primeira pessoa a apresentar este método de desenvolvimento foi Kent Beck, autor do livro Extreme Programming, e relata em um capitulo de seu livro a necessidade de testar prematuramente, frequentemente e automaticamente, destacando que tal abordagem é necessária, pois quanto mais cedo encontrarmos erros mais barato será consertá-lo. Três anos mais tarde ele lança o livro Test Driven Development by example definindo uma estratégia de desenvolvimento que além de permitir detectar erros prematuramente, cria um ambiente de trabalho que favorece o desenho de uma boa arquitetura.

A principal característica do TDD é sua simplicidade, onde a principal regra a seguir é: criar um teste que falhe, escrever o código para o teste passar e em seguida refatorar seu código.

Perceba que não foi dito que uma linguagem é preferível que outras, nem que uma IDE é necessária para trabalhar desta forma. É claro que será necessário um framework de testes e o Kent Beck criou um que foi base para todos os outros que surgiram, o JUnit.

Certo, neste ponto começam a surgir as dúvidas, sobre como definir os métodos de testes, qual a granularidade do meu teste e por onde começar.

Um sistema sempre vai começar a partir dos requisitos ou estórias, uma boa documentação que defina quais serão os comportamentos do software.

Nós, na Sigma, descrevemos o comportamento de nosso sistema utilizando estórias. Então vamos escrever uma estória hipotética que define o comportamento de um sistema.

Eu como comprador

quero adicionar produtos comprados ao estoque

para manter o saldo em estoque atualizado;

Analisando a estória acima podemos extrair algumas entidades que o sistema de almoxarifado deve ter: Produto e Estoque. Além disso podemos visualizar os comportamentos esperados: dar entrada no estoque de um produto, consultar saldo de um produto em estoque;

Então podemos criar um teste que falhe para um dos comportamentos:

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()

Este teste vai falhar, pois não existe uma classe Produto definida em nosso sistema.

import unittest


class Produto(object):
    def __init__(self):
        pass


class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()

O código vai passar então podemos refatorar e criar corretamente um módulo e retirar o código da classe produto do module de teste;

Damos o segundo passo na implementação:

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):
        produto = Produto()
        estoque = Estoque()
        quantidade = 10
        estoque.entrada(produto, quantidade)

Perceba que avançamos alguns passos extras na implementação, pois temos experiência na criação de classes e podemos ser flexíveis e escrever mais trechos de código que temos fluência. Esta flexibilidade não pode ser usada em excesso, pois o principal objetivo ao escrever código que falhe é que esta falha seja fácil de ser encontrada, portanto se eu escrever muitas linhas de código eu posso gastar muito tempo consertando ele. Portanto aprecie com moderação.
O código acima está falhando, pois o método entrada não está implementado.

Portando vamos implementá-lo:

class Estoque(object):

    def __init__(self):
        self._estoque = []

    def entrada(self, produto, quantidade):
        self.estoque.append({
            'produto': produto,
            'quantidade': quantidade
        })

Novamente o teste vai passar, mas como definir que este teste é válido?

Asserções!

Asserção é um método para validar se uma condição de nosso teste é válida e caso ela não seja, minha asserção não será válida e vai lançar uma exceção.

import unittest

class TestEstoque(unittest.TestCase):

    def test_estoque_deve_dar_entrada_em_produto(self):

        # Definições
        produto = Produto()
        estoque = Estoque()
        quantidade = 10

        # Ações
        estoque.baixar(produto, quantidade)

        # Asserção
        saldo = estoque.saldo(produto)
        self.assertTrue(saldo == 10)

Perceba a estrutura de um testes, eu defino o estado inicial para meu teste, tomo uma ação e verifico se meu teste chegou ao estado esperado.

Este artigo serviu de base para o screencast sobre TDD, que foi gravado e está disponível neste link, com a apresentação também disponível neste link.