Como criar classes em JavaScript (ES6) e Node.js

Existe muita treta no mercado a respeito do JavaScript ser ou não ser orientado à objetos e não vou entrar nesta discussão. A ideia deste artigo é pura e simplesmente apresentar como usar classes e alguns pequenos recursos ligados à orientação à objetos disponibilizados para JS (e consequentemente Node.js) na versão 6 de 2015.

Há quem diga também que JavaScript é melhor aproveitado no paradigma funcional do que no OO, mas como um profissional que trabalhou mais de uma década com Java e C#, confesso que volta e meia me pego usando OO em aplicações Node.js e saber usar dos recursos OO disponibilizados na especificação ES6 é uma mão na roda.

Sem mais delongas, vamos ao código!

Criando classes em JavaScript

Para quem não lembra ou ainda não aprendeu, uma classe é uma especificação, um tipo novo de objeto da sua aplicação. Por exemplo, uma classe Pessoa (inicie classes sempre com letra maiúscula e no singular) irá definir propriedades e funções comuns a pessoas da sua aplicação. Assim, quando for criar pessoas usando esta classe, elas sempre possuirão a mesma estrutura.

A primeira recomendação é que você use uma classe por arquivo JS, tranformando-o em um módulo JS que deverá ser requerido/importado onde se desejar usar essa classe. Esse arquivo deve ter o mesmo nome da classe, como abaixo, onde crio uma classe Cliente.

O próximo passo é criar o construtor dessa classe, uma função especial que inicializa um objeto deste tipo, usando argumentos e processamentos internos para definir as suas propriedades.

Esse construtor acima espera nome, idade e e-mail e os utiliza para definir as propriedades homônimas de todo cliente criado a partir dessa classe. Internamente, o construtor repassa esses valores para as propriedades do objeto, iniciadas com ‘this.’. Variáveis precedidas por ‘this.’ são propriedades do objeto e serão replicadas em todas variáveis que instanciarmos como Cliente mais tarde.

Note também que ele inicializa uma propriedade dataCadastro, de maneira automática e transparente, pegando a data e hora atuais. Esse tipo de processamento pode ser realizado no construtor inclusive para validar e transformar dados passados como argumento (que tal adicionar validações usando Joi?).

O uso da palavra reservada constructor somente pode ser usada nessa função e ela é disparada automaticamente quando criamos um novo objeto Cliente usando a keyword new, como em outras linguagens orientadas a objeto (Java, C#, etc).

Para usar essa classe que criamos, usei o require no módulo Cliente.js em uma constante, e essa constante representa a classe em si. Usando o operador new, instanciei um novo cliente com nome Luiz, idade 31 e e-mail mostrado acima. Se você voltar no trecho de código anterior, onde declarei o construtor, verá que cada um desses argumentos será colocado em uma propriedade interna do cliente e isso se torna evidente quando você imprime o objeto cliente1 no console.

Objeto cliente impresso no console

Cada variável declarada como sendo um cliente tem o seu próprio conjunto de propriedades, mas a mesma estrutura básica, ficou claro?

Assim, se você declarar cliente1, cliente2, etc; cada um terá o seu nome, sua idade, etc. Independente um do outro, mas com o mesmo “esqueleto”.

Mas e os comportamentos?

Funções de classe em Javascript

Toda classe é composta de propriedades e funções. Essas funções, por uma questão de organização, devem ser sempre relativas à responsabilidade da classe em si, e geralmente manipulam ou utilizam as propriedades do objeto em questão. Assim, uma classe cliente terá funções que usam ou manipulam as propriedades do objeto cliente.

Declarar uma função de classe (chamada de método em outras linguagens OO) é feita dentro do escopo da mesma (abre e fecha chaves mais externas). Não há necessidade da palavra function tradicionalmente usada, mas o restante segue a mesma lógica de functions tradicionais.

Como sabemos se estamos manipulando uma propriedade do objeto ou uma variável comum JS? Através do uso da palavra this novamente!

No exemplo acima descrevo que os objetos do tipo/classe Cliente possuem duas funções: isAdult que retorna true/false com base na idade do objeto e outra chamada getFirstName que baseada no nome do objeto/cliente, retorna a primeira parte do mesmo.

Para chamar estas funções você primeiro deve instanciar objetos do tipo Cliente e suas execuções devem produzir retornos conforme propriedades de cada objeto em particular.

No código acima, eu instancio dois clientes com dados diferentes e depois chamo a função isAdult pra ver quais deles são adultos ou não, com base na idade informada na sua criação.

O resultado você pode ver no seu console, mas basicamente o cliente de nome Luiz é adulto, enquanto que o cliente Pedro não é.

Se você reparar bem no meu código anterior, notará que instanciei o segundo objeto sem e-mail (terceiro argumento do construtor). Isso é totalmente permitido no JavaScript, embora possa causar alguma confusão. Por isso, crie alguma lógica interna para obrigar os campos obrigatórios a serem passados, caso eles existam.

Outra sugestão, oriunda de meus tempos como “Javeiro” é que crie funções get e set para cada uma das propriedades internas das suas classes, para evitar que as mesmas sejam acessadas e manipuladas sem qualquer tipo de encapsulamento, uma vez que em JavaScript não temos modificadores de acesso como private, protected, etc. Tecnicamente chamamos isso de métodos acessores.

E por fim, note que você pode ter propriedades internas que, por sua vez, são do tipo de outra classe. Elas podem ser instanciadas com new dentro do próprio construtor, em uma variável antes ou até depois, conforme sua lógica necessitar.

Para este exemplo, considere a classe endereço abaixo:

Note que criei uma função toString() que monta uma String baseada nas propriedades do endereço. Na verdade já existe uma função toString automaticamente em todas classes JavaScript, mas estamos sobrescrevendo seu comportamento padrão através de declaração de outra função de mesmo nome. Isso é chamado de sobrescrita na orientação à objetos, uma das formas conhecidas de sobrecarga de método/função.

Agora altere a nossa classe Cliente para que eles possuam uma propriedade endereço também:

Para testar o uso de objetos Endereco como propriedade do Cliente, meu index.js vai ficar assim:

O resultado é uma frase como “Luiz mora em Tupis 125…”. Isso porque a função toString de cada objeto endereço é automaticamente chamada quando usamos eles na concatenação de Strings. Acaba acontecendo a mesma coisa que se eu chamasse dessa forma:

O uso de objetos dentro de outros objetos é o que chamamos de associação, sendo que podemos ter associações de agregação ou de composição. Bacana, não?

Propriedades e funções estáticas em JavaScript

E para encerrar este artigo, vamos falar de mais uma característica de Orientação à Objetos implementada no JavaScript: as propriedades e funções estáticas.

Um componente estático (seja ele uma função ou propriedade) é compartilhado entre todos objetos da mesma classe e não necessita que a mesma seja instanciada para que o mesmo exista e possa ser usado/manipulado. Não confundir com as constantes, pois você pode mudar uma característica estática, mas se fizer isso, vai mudar para TODOS objetos que a possuem.

Para declarar uma propriedade estática, basta declará-la com a palavra-reservada static (ao invés de var, let ou const) dentro do escopo da classe. Abaixo, dei apenas um exemplo (uso questionável nesse caso, apenas me faltou criatividade para outro exemplo, hehe).

O que muda neste caso ao invés de usar um const? O ponto principal é que essa variável é da CLASSE e não do OBJETO, para usá-la você deve chamar “classe.propriedade”. Outro ponto é que você pode mudar o valor desta propriedade em tempo de execução ao contrário de const e o último ponto é que se mudá-la, muda para a CLASSE, independente dos OBJETOS, como no exemplo abaixo.

Note que independe de existir ou não algum objeto, a propriedade idadeAdulto existe para a CLASSE e, diferente de const, PODE ser modificada. Ao usar static, você torna esta propriedade global para a classe.

E o mesmo pode ser feito com funções, como mostra a função static abaixo da classe Endereco que retorna um array de UFs existentes no Brasil.

Para usar, você também não instancia um objeto endereço e apenas chama direto classe.funcao.

Eu poderia ter usado o array resultante da função static, mas resolvi apenas imprimi-lo, acredito que você tenha entendido a ideia.

Existem muitos outros conceitos relacionados à orientação à objetos presentes na linguagem JavaScript moderna e consequentemente no Node.js, mas por ora, os conceitos mais importantes ligados a classes em JS eu apresentei neste artigo.

Espero que tenham gostado!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu curso sobre programação web com Node.js!

Curso Node.js e MongoDB

Programação assíncrona em Node.js – Callbacks e Promises

Computadores são assíncronos por padrão. Isto significa que coisas podem acontecer de maneira independente do fluxo do programa principal. Nos computadores atuais, cada programa roda por um tempo específico e então pára sua execução para permitir que outro programa execute um pouco. Isto acontece tão rápido que é quase impossível perceber e temos geralmente a falsa impressão de que o computador está de fato fazendo várias coisas ao MESMO tempo, quando na verdade ele está fazendo UMA coisa de cada vez, um pouco de cada.

Claro, isso tendo uma CPU em mente. A realidade para UMA CPU é essa.

Programas internamente usam interrupções, um sinal que é emitido para o processador quando o programa quer a atenção do sistema. Logo, é normal para os programas serem assíncronos: eles começam, são deixados processando sozinhos e quando algo novo requer a atenção do sistema, eles emitem esse aviso.

No entanto, normalmente as linguagens de programação são síncronas (executam o código em série, uma linha depois da outra) e algumas fornecem mecanismos de assincronicidade através de bibliotecas. C, Java, C#, PHP, Go, Ruby, Swift, Python e JavaScript. Todas elas são síncronas por padrão e várias delas permitem assincronicidade usando threads, criando novos fluxos do processo.

Em Node.js, por outro lado, as coisas são diferentes, graças ao Event Loop. Resumidamente, dependendo do tipo de processamento necessário, a atividade sai da thread principal e é enviada para um pool de threads em background automaticamente e só retorna ao event loop quando terminar.

Como o Node se organiza para isso? Inicialmente através dos callbacks!

Callbacks em Node.js

Um callback é um listener, uma função que será disparada QUANDO e SE um evento acontecer. Isso já era usado desde os primórdios do JavaScript, quando ele foi inventado para rodar no front-end dos sites. Criávamos um código para ser disparado no clique de um botão, ou seja, no FUTURO. Não era na sequência linear do programa, era pra ser assíncrono (fora da sincronia normal), entende?

Como no JavaScript mesmo as funções são objetos, podemos armazená-las para serem disparadas mais tarde e esse princípio é a base dos callbacks. Um callback, resumidamente é uma função para ser “chamada de volta” (“to be called back” em Inglês) quando outra função terminar e você encontra isso desde o JS “raiz” (vanilla) como no exemplo abaixo.

Um dos desafios desse modelo é como fazer a gestão de erros. E uma solução encontrada é o que é feito no Node.js: o primeiro parâmetro de qualquer callback é o objeto de erro e isso é um padrão em Node.js, é o jeito default de se trabalhar com a tecnologia.

Essa simplicidade e praticidade é excelente para cenários simples. No entanto, outro problema com os callbacks, esse jamais sanado é o aninhamento de callbacks, o famoso callback hell (inferno de callbacks)!

Afinal, como lidar quando um callback também possui um callback? E se esse callback do callback possuir outro callback? O exemplo abaixo em JavaScript puro ilustra um pouco isso, em apenas 4 níveis:

Em uma tecnologia assíncrona como Node.js, isso não é algo incomum e o uso de callbacks em cenários complexos acaba se tornando um pesadelo rapidamente.

A partir da versão 6 do ECMAScript (ES6, de 2015), foi introduzido Promises, uma nova e mais elegante maneira de lidar com o encadeamento de funções assíncronas (async function chaining) e vai ser alvo de estudo neste artigo.

Promises em Node.js

Uma promise (promessa, em Inglês) é comumente definida como um proxy para um valor que eventualmente estará disponível. Embora este recurso seja mais antigo que 2015, foi neste ano que ele se tornou padrão na implementação ECMAScript e mesmo que em 2017 tenha sido implementado async/await na linguagem, Promises ainda é a base e deve ser entendida.

Basicamente, uma vez que uma promise é chamada, ela inicia em pending state (pendente). Isto significa que a função caller (que chamou a promise) continua sua execução enquanto espera pela promise terminar seu próprio processamento e retornar ao caller com algum feedback.

Quando esse retorno acontece, a promise é retornada em resolved state ou rejected state.

Criando uma Promise em Node.js

Para criar uma promise é bem simples, usa-se o construtor da própria classe e passa-se uma function como único argumento, que possui funções de resolve e reject na sua assinatura. Dentro dessa function, você deve chamar resolve quando sua execução for bem sucedida, ou reject, caso contrário.

Abaixo um pequeno exemplo:

Como pode ver, criei uma função que só aceita pares (ignore o fato dela ser beeeem simples, foque nos conceitos). Essa função retorna uma promise para permitir que seu conteúdo rode de maneira assíncrona. A promise que será retornada avalia o argumento ‘numero’ e se ele for par, retorna uma promise resolved, mas caso contrário, retorna uma promise rejected. Ambas funções de retorno (resolve e reject) permitem a passagem de um objeto por parâmetro que vai ser lido por quem estiver aguardando um feedback dessa promise.

Consumindo uma promise em Node.js

Usar uma promise é ainda mais fácil que criar uma e vou usar como base o código anterior. Quando você for chamar a function soAceitaPares, você vai chamá-la normalmente e depois captura o seu retorno positivo (resolved) com a função then e o retorno negativo (rejected) com a função catch, como abaixo.

Ao executar esse código, você verá que ele vai imprimir primeiro a linha teste, depois o conteúdo do then e NÃO vai chegar nem perto do conteúdo do catch. Agora se trocar o argumento de soAceitaPares para um número ímpar, verá que ele continua imprimindo o teste, executa o conteúdo do catch e não do then.

Por que ele imprime o teste primeiro? Porque visualmente parece síncrono, mas na verdade a função soAceitaPares é chamada e o código continua executando, imprime o teste e só quando a função terminar é que vai executar o then ou o catch.

Quando usamos bibliotecas de terceiros que implementam promises (a maioria das modernas), é assim que tratamos seu retorno.

Mas e quando o resultado de uma promise é uma função que retorna outra promise? Será que não caímos no mesmo problema do callback hell?

Encadeando promises em Node.js (chaining)

Uma promise pode ser retornada por outra promise, criando uma cadeia ou corrente de promises (chain) e é pra isso que justamente ela foi criada, afinal, para cenários simples os callbacks são excelentes.

Vamos criar outra função de exemplo, que também retorna uma promise:

Nesta função, usei outra abordagem de criar promises. Se o número for ímpar, a promise é criada já com o tipo rejeitada retornando um erro. Agora se for par, ela é criada já com o tipo resolvida e retorna a metade do número original. Na prática, ela funciona de maneira exatamente igual à instanciação de promise que fiz anteriormente, escolha a que for mais conveniente ao seu código (eu intercalo conforme vai ficar melhor no código).

Sabemos que cada uma destas duas funções são assíncronas e que se queremos ir para a segunda (dividePelaMetade) só depois de verificar o retorno da primeira (soAceitaPares), não podemos simplesmente usar um if como abaixo, que não funciona.

Para garantir que dividePelaMetade só seja executada depois que soAceitaPares retornar dizendo se o número é par ou não, temos de encadear o consumo das promises, como abaixo:

Assim, a função soAceitaPares é chamada e depois o código continua executando (imprime teste). Quando soAceitaPares terminar, se for sucesso, executará o primeiro then (dividePelaMetade), caso contrário, vai pro catch. Note que ignorei o retorno (result) da função, por que ele é inútil neste caso (mas em casos reais geralmente ele é útil).

Quando dividePelaMetade terminar, se for sucesso, executará o segundo then, cuja variável result2 é o retorno da promise (resolve), ou seja, o número dividido. Caso contrário, ele vai pro catch com o erro.

Em uma situação que a segunda função também retornasse uma promise, teríamos um terceiro then e assim por diante. Já o catch é apenas um, sempre no final, a menos que seu catch dispare um erro, que pode ser capturado por um segundo catch, mas não recomendo.

Mas e quando você tem várias promises que não são exatamente executadas uma depois da outra, mas que todas precisam ter sido finalizada para se tomar uma decisão?

Sincronizando promises em Node.js

Imagine a situação da imagem abaixo.

Fork e Join

Esse é um problema clássico da computação: um processo é “forkado” em vários subprocessos (threads, mas imagine aqui Promises), cada um executando de maneira independente. Porém, em dado momento posterior, o resultado de todos deve ser computado de uma única maneira.

Como sincronizamos (join) promises em Node.js?

Através da função Promise.all()

Como cada Promise também é um objeto em JavaScript, podemos armazenar todas as promises em um array e depois usando Promise.all() processamos todas elas e o array de resultados é passado em um único then, ou no catch, caso alguma dê erro.

No exemplo acima, declarei um array de números e um array de promises. O primeiro é usado em um foreach porque quero dividir todos eles pela metade. Essa função de divisão é assíncrona e retorna uma promise, que eu facilmente adiciono no array de promises.

Como não sabemos quando todas as divisões vão terminar, eu uso o Promise.all na linha seguinte sobre o array de promises e com isso, o then somente será executado quando TODAS promises retornarem. Se todas forem bem resolvidas, um array de results será passado ao then. Caso contrário, se apenas UMA já der erro, o catch será disparado.

Mantive a impressão de teste no final para você ver que as promises são todas assíncronas, ou seja, o teste (síncrono) vai ser impresso antes de todas as promises terminarem!

Experimente colocar um número ímpar no array e verá que somente o catch do Promise.all será executado.

Esse é um recurso muito útil, principalmente nestes cenários de uso de foreach com promises, mas também em diversos outros de junção (join) de threads.

E por hoje é isso, espero que este artigo tenha te ajudado a entender como as promises funcionam!

O código completo deste artigo pode ser visto abaixo:

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu curso sobre programação web com Node.js!

Curso Node.js e MongoDB

Backlog DEEP, Histórias INVEST e Tarefas SMART

Atualizado em 29/12/2019 com os slides e vídeo!

Quem trabalha comigo há algum tempo sabe o quanto eu repito a frase “agile não é bagunça” e quando o assunto é o backlog do time, isso não pode ser menos verdade. Existe uma má interpretação do Manifesto Ágil e da própria cultura Lean-Agile em geral que entende que não se precisa ter documentação em projetos rodando sobre frameworks ágeis.

Pura mentira!

A grande mudança de mindset nos métodos ágeis é criar a documentação estritamente necessária e somente quando for necessária, ao invés de gastar meses escrevendo documentos de Visão do Produto, Especificações Funcionais e Não-Funcionais e por aí vai. Isso não quer dizer, no entanto, que jamais vamos ter este tipo de documentação ou algo próximo à elas, mas que vamos distribuir a sua produção ao longo de várias sprints conforme nosso entendimento for aumentando e a necessidade se torne emergente.

E por que eu falo que agile não é bagunça?

Porque justamente em um ambiente auto-gerenciável e sem metodologias prescritivas é que precisamos do mais alto grau de organização e isso começa com nosso requisitos ágeis. No artigo de hoje falarei sobre três acrônimos bem populares para ajudar na organização dos requisitos ágeis:

É importante ressaltar que estes acrônimos são norteadores para a criação e organização destes artefatos e de que não devem se tornar uma “religião” nem criar fanáticos que impeçam os projetos de prosseguirem apenas porque alguma demanda não atende 100% a todos requisitos do acrônimo. Use-os como guias e não como grilhões.

No final deste artigo você encontra este material em forma de slides e nesse link você vê o vídeo da minha palestra sobre este assunto.

Vamos começar?

Backlog DEEP

DEEP é um acrônimo para Detalhado, Estimado, Emergente e Priorizado e são as características que o Product Owner deve buscar sempre em seu backlog.

Detalhado quer dizer que o backlog deve ter os detalhes suficientes para garantir clareza que propicie execução, mas não detalhado em demasia que gere desperdício. Entenda que a necessidade de profundidade no detalhamento é proporcional à emergência de uso do backlog, ou seja, os itens que vão entrar na próxima sprints (mais emergentes) devem estar melhor detalhados, enquanto que os itens que vão entrar daqui a várias sprints, podem estar bem menos detalhados.

Detalhamento de Backlog
Detalhamento de Backlog

Estimado quer dizer que todo item quando chega a um nível de detalhamento suficiente deve ter uma estimativa de esforço associada a ele, uma vez que o backlog não é apenas uma ferramenta de trabalho, mas e planejamento. Essa estimativa pode ser em horas, em pontos, em t-shirt size, em E/EE/EEE ou o que você possa imaginar e que faça sentido para seu time. Essa estimativa inclusive lhe auxilia a entender se o nível de detalhes está bom o suficiente, pois via de regra itens de backlog com uma estimativa muito alta deveriam ser quebrados em itens menores (a técnica de Planning Poker ou de Nivelamento de Features ajuda nisso).

 

T-Shirt Size
T-Shirt Size

Emergente quer dizer que o backlog evolui conforme o Product Owner vai aprendendo sobre o produto e o mercado vai lhe dando feedback sobre seus lançamentos (seu MVP, por exemplo), ele não é estático. O constante refinamento do backlog permite que o produto reaja bem a mudanças de estratégia e de escopo, ao invés de se apegar a um planejamento waterfall. O segredo aqui é rever todo o backlog a cada ciclo construir-medir-aprender do Lean Startup.

Ciclo do Lean Startup
Ciclo do Lean Startup

Priorizado quer dizer que a ordem em que os elementos estão dispostos no backlog importa. Os itens mais prioritários devem estar ao topo, enquanto que os menos prioritários devem estar mais embaixo. Os critérios e técnicas utilizados para priorização variam muito de Product Owner para Product Owner, de produto para produto e de mercado para mercado. Para o Scrum, não importa qual é a sua técnica, mas sim que o resultado deve gerar valor para os clientes e para a empresa.

WSJF - Técnica de Priorização do SAFe
WSJF – Técnica de Priorização do SAFe

Histórias INVEST

O acrônimo INVEST é um trocadilho para dizer que devemos “investir” tempo na escrita de boas histórias de usuário, tema de outro artigo aqui do blog. INVEST significa Independente, Negociável, Valiosa, Estimável, Small (Pequena) e Testável.

Independente significa que uma boa história de usuário não depende de outras e que outras não dependem dela. Que qualquer história deveria poder ser desenvolvida em qualquer ordem, conforme a prioridade que o Product Owner determinar. Sabemos que isso não é lá muito fácil de se alcançar, mas é sempre bom chegar o mais próximo possível disso.

Negociável significa que toda boa história é uma co-criação, uma negociação entre profissionais de negócio e profissionais técnicos. Uma história deve capturar a essência de um requisito, mas não os seus detalhes, que devem ser criados de forma colaborativa e emergente. Ao longo do tempo, mais detalhes serão adicionados a cada história conforme o produto evolui e os usuários demonstram novos comportamentos. Aqui entram os 3C’s necessários em todas histórias: Card, Conversation e Confirmation.

Valiosa quer dizer que toda história deve agregar valor ao produto e principalmente, ao usuário-chave da mesma. Uma historia jamais deveria agregar valor apenas ao desenvolvedor ou apenas ao negócio, e sim sempre centrada no cliente. O Product Owner sempre se deve perguntar se ele tornará melhor a vida do cliente ao colocar aquela historia em produção. A técnica de Nivelamento de Features pode ajudar a determinar o quão valiosa é cada história.

Estimável é semelhante ao que já falamos no DEEP sobre o backlog. Para entregar um backlog estimado que me permita planejar meu roadmap de produto, é necessário ter histórias estimadas primeiro. E para conseguir estimar as histórias elas precisam ser pequenas e claras, com detalhes suficientes e bom nível de entendimento entre negócio e técnicos. Esta estimativa não precisa ser precisa, mas deve dar noções de grandeza que ajudem na tomada de decisão, priorização, etc.

Small (Pequena) para que seja possível entregá-la em no máximo uma sprint, caso contrário ela será um épico. O grande desafio obviamente é o equilíbrio entre histórias pequenas o suficiente para que seja possível estimar, mas não tão pequenas que não entreguem valor algum para o cliente. Na verdade esse é o desafio de todo o INVEST, conciliar todas as características desejáveis em uma história. Via de regra, uma história não deveria levar mais do que alguns dias de um par de programadores trabalhando nela, se está levando mais de uma semana é uma dica de que deveríamos pensar em quebrá-la.

Testável indica que toda história deve poder ser coberta de testes para validar se seus requisitos funcionais e não-funcionais foram atendidos, geralmente abordados nos critérios de aceitação da história. Uma história que não podemos testar para garantir seu funcionamento não é uma boa história, pois ou não está Independente ou é pequena demais e não entrega valor algum.

Livro para Agile Coaches
Livro para Agile Coaches

Tarefas SMART

E para terminar, o acrônimo SMART é o mais famoso dentro e fora da computação uma vez que é usado para definir muito mais do que tarefas de qualidade, mas boas métricas, bons objetivos e por aí vai. Isso porque SMART é um acrônimo para Specific (Específica), Mensurável, Alcançável, Relevante e Temporal.

Specific (Específica) indica que toda tarefa deve ter uma responsabilidade única, ser coesa com seu objetivo e entregar apenas uma parte de uma história de usuário. Para quem é programador, é o mesmo que aplicar o princípio SRP (Single Responsibility Principle) do SOLID para tarefas. Ser específica ajuda a história ser Alcançável, Mensurável e Temporal, além de garantir o entendimento de todos no time.

Mensurável é uma característica desejável para apoiar o planejamento da sprint. Se eu não tenho como medir uma tarefa, como sei se entregaremos ela a tempo? Aqui não importa a unidade de medida, embora o ágil encoraje muito mais o uso de pontos do que de horas, desde que a unidade faça sentido para todos no time e gere informação valiosa para o time e para a empresa.

Alcançável é um requisito básico para toda tarefa. O time tem que ter as competências e recursos necessários para conseguir entregar a tarefa. Simplesmente não adianta definir como prioridade uma tarefa que ninguém sabe fazer ou que vai levar tanto tempo que ela por si só deveria ser uma história ou épico. Definir tarefas inalcançáveis para o time é o primeiro passo para perder o time, até mesmo por isso que geralmente a atividade de quebra de histórias em tarefas e feitas pelo Dev Team e não pelo PO.

Relevante tem a ver com o valor que esta tarefa agrega ao todo da história. Se a tarefa não agrega em nada, ou seja, é irrelevante para a história, ela não deveria ser feita. Tarefas são quebradas para ajudar os desenvolvedores a organizarem seu trabalho, mas cada uma deveria poder ser explicada em como contribui para a história principal.

Temporal quer dizer que toda tarefa tem um tempo para ser realizada, ela tem de estar delimitada a uma timebox específica, previamente combinada entre os membros do time e utilizada para ajudar na priorização e planejamento da sprint.

Claro, nenhum desses acrônimos é bala de prata e usar todos eles juntos, todos os dias, é um desafio imenso. Mas não desista, é justamente esta busca pela perfeição no trabalho com backlog, histórias e tarefas que cria maturidade em times ágeis.

E você, conhece algum outro acrônimo que eu não citei aqui? Deixe nos comentários!

* OBS: curtiu o post? Então dá uma olhada no meu livro de Scrum e Métodos Ágeis e/ou no meu curso sobre o mesmo assunto!

Curso de Scrum e Métodos Ágeis
Curso de Scrum e Métodos Ágeis