Playbook de Desenvolvimento de Produto IoT de Ponta a Ponta - Etapa 4: Desenvolvimento de Firmware e Software Cloud
A Etapa 4 move o produto para o lado direito do V: implementação e testes. No clássico V-model de desenvolvimento de produto, o lado esquerdo é sobre design (que abordámos nas etapas anteriores); aqui desenvolvemos o firmware (o software que corre no seu dispositivo IoT) e o software cloud (servidores, bases de dados, APIs, interfaces de utilizador) que, juntos, dão vida ao seu produto. Ao contrário do desenvolvimento de hardware, que é relativamente inflexível uma vez impresso numa placa de circuito, o desenvolvimento de software é altamente iterativo e ágil. Aliás, pode já ter escrito partes de firmware na Etapa 3 enquanto testava o seu design de hardware. Agora é tempo de acelerar. O software é como barro – fácil de moldar e mudar a qualquer momento – por isso podemos iterar rapidamente e refinar continuamente. Esta etapa correrá em paralelo com qualquer trabalho de hardware restante, cada um informando o outro conforme necessário.
A Etapa 4 pode ser pensada em duas partes principais: primeiro planear a arquitetura e o design de software (para estabelecer uma fundação forte), e depois um ciclo contínuo de sprints de desenvolvimento onde construímos, testamos e repetimos até o software estar sólido. Vamos decompô-lo.
4.1 Arquitetura e Design de Software
Antes de alguém saltar para o código, é sensato esboçar um plano de jogo para a arquitetura de software – tanto para o firmware no dispositivo como para os componentes cloud. Pense nisto como planear uma viagem de carro. Não começaria simplesmente a conduzir sem um mapa; da mesma forma, esboçamos os componentes principais e como interagem antes de escrevermos milhares de linhas de código. Isto é análogo à arquitetura de sistema que fizemos na Etapa 2, mas agora ao nível do código. Um bom planeamento de arquitetura de software ajuda-o a evitar “código esparguete” mais tarde. Eis os pontos-chave a estabelecer nesta fase:
Arquitetura de Firmware (Abordagem em Camadas e Considerações de RTOS)
Defina a estrutura do seu código embebido dividindo-o em camadas. Uma abordagem comum em firmware é uma arquitetura em camadas, que ajuda a organizar o código logicamente e o torna mais fácil de manter. Por exemplo, no nível mais baixo pode ter um Board Support Package (BSP) ou camada de abstração de hardware – contém drivers de baixo nível específicos do seu microcontrolador e periféricos de hardware. Acima disso, pode ter drivers de dispositivo ou módulos para os seus sensores e atuadores (tratando de coisas como ler um sensor de temperatura ou acionar um LED). Acima dos drivers, pode ter uma camada de serviços – pode implementar protocolos de comunicação, algoritmos de processamento de dados ou outra lógica que processa os dados em bruto. Finalmente, no topo, tem a camada de aplicação, que é o cérebro do seu dispositivo: liga tudo para cumprir o propósito do seu produto (por exemplo, ler valores de sensores, tomar decisões, enviar dados para a cloud e reagir a comandos). Considere desenhar um diagrama simples (diagrama de componentes UML ou apenas caixas e setas num quadro branco) para visualizar estas camadas. Isto ajuda todos na equipa a compreender a separação de responsabilidades – como construir uma casa com fundação, estrutura e telhado, cada camada tem o seu trabalho.
Outra grande decisão arquitetónica no lado do firmware é se deve usar um RTOS (Real-Time Operating System) ou não. Um RTOS é como ter um pequeno sistema operativo no seu dispositivo que consegue correr múltiplas tarefas aparentemente ao mesmo tempo (via scheduling). Se o seu dispositivo só faz uma coisa de cada vez, pode não precisar de um RTOS. Mas a maioria dos gadgets IoT tem muita coisa a acontecer em simultâneo – por exemplo, ler sensores periodicamente, escutar mensagens recebidas (da cloud ou de input do utilizador), atualizar um ecrã ou LED e tratar da comunicação de rede. Com um RTOS, pode desenhar o seu firmware como um conjunto de tasks ou threads. Por exemplo, pode ter uma tarefa dedicada à amostragem de sensores, outra dedicada a tratar das comunicações (enviar dados ou receber comandos) e outra para housekeeping ou atualizações da interface de utilizador. Como parte do design da sua arquitetura, esboce estas tarefas e como vão comunicar entre si – mecanismos comuns incluem message queues, event flags ou dados partilhados protegidos por mutexes (para evitar conflitos). Decidir isto cedo ajuda a garantir que o seu firmware não se torna um emaranhado de código interligado onde tudo depende de tudo (vulgo código esparguete). Em vez disso, terá interfaces claras: talvez a tarefa de sensores coloque novas leituras numa queue, e a tarefa de comunicação as recolha para enviar para a cloud. Se desenhar estas interfaces com clareza agora, a codificação será muito mais suave e vai agradecer-se mais tarde quando for altura de resolver problemas ou estender o firmware.
Porquê tanto alarido arquitetónico para o firmware? Porque lhe facilita a vida no caminho. Com um bom design em camadas e estrutura de tarefas, pode modificar uma parte (digamos, trocar um driver de sensor ou ajustar um algoritmo) sem partir todo o resto. Também facilita que vários programadores trabalhem juntos sem pisar os calos uns dos outros – uma pessoa pode desenvolver o driver do sensor enquanto outra trabalha no código de comunicação cloud, desde que tenham acordado como essas partes fazem interface (por exemplo, o formato de dados de uma mensagem de leitura de sensor). Em resumo, um pouco de planeamento aqui ajuda a evitar muita dor mais tarde.
Arquitetura Cloud (Estrutura de Servidor, Base de Dados e Aplicação)
Agora consideremos o lado da cloud. O dispositivo IoT não vive isolado – normalmente está a falar com algum serviço cloud ou servidor. Um pensamento arquitetónico semelhante é necessário aqui: esboce os componentes principais do seu software cloud. Por exemplo, pode ter um servidor de API que recebe dados dos dispositivos e também serve pedidos de aplicações de utilizador. Haverá provavelmente uma base de dados onde armazena os dados dos dispositivos (leituras de sensores, estados dos dispositivos, informação de utilizadores, etc.). Podem existir serviços ou microserviços adicionais a fazer processamento ou analítica de dados – por exemplo, analisar os dados à procura de anomalias, disparar alertas ou ações quando certas condições são cumpridas (por exemplo, enviar um email se uma leitura de temperatura for demasiado alta), ou fazer agregações para relatórios. Por cima disto, considere as aplicações cliente: talvez um dashboard web ou uma aplicação móvel com que os utilizadores interagem. Esses clientes comunicarão com o servidor (frequentemente via API) para mostrar dados e enviar comandos do utilizador (como um utilizador a premir um botão para ligar a sua lâmpada IoT a partir do telefone).
É útil desenhar um diagrama deste ecossistema – o modelo C4 (Client-Container-Component-Code) é uma forma popular de diagramar arquitetura de software. Desenhe pelo menos o nível Container: mostre o dispositivo, o servidor, a base de dados e a aplicação cliente, e como os dados fluem entre eles. Por exemplo, o seu diagrama pode ter uma seta do Dispositivo para a API Cloud (“envia dados do sensor via HTTP/MQTT”), uma seta da Cloud para a Base de Dados (“armazena dados”), e uma seta da Aplicação Cliente para a Cloud (“utilizador pede dados ou envia comando”), e assim por diante. Este mapa visual garante que todos (programadores de firmware, engenheiros de cloud, programadores de frontend, etc.) têm um modelo mental claro de como as coisas se ligam. É muito mais fácil discutir e apanhar problemas de design cedo com um diagrama destes na mão. Talvez descubra nesta fase que precisa de um componente adicional – por exemplo, um message broker ou serviço de plataforma IoT – para lidar com a escala de muitos dispositivos. Melhor pensar nisso agora do que quando estiver mergulhado no código.
Mais um aspeto: pense em escalabilidade e fiabilidade no seu design cloud. Se espera eventualmente 100.000 dispositivos a ligar-se, a base de dados e o design de servidor que escolheu aguentam? Se um servidor cair, o que acontece (tem redundância)? Esses detalhes podem ser aprofundados numa etapa posterior, mas o design de alto nível deve ter em conta preocupações maiores como essas desde o início. Por exemplo, pode decidir usar uma plataforma IoT cloud (AWS IoT, Azure IoT Hub, etc.) em vez de construir tudo de raiz, o que passaria a fazer parte da sua decisão de arquitetura.
Protocolos de Comunicação e Formatos de Dados (Comunicação Dispositivo-Cloud)
Um produto IoT envolve inerentemente comunicação entre o dispositivo e a cloud. Parte do design de software é decidir como falam um com o outro e em que língua (protocolos e formatos de dados). Há algumas escolhas de protocolo comuns em IoT: por exemplo, o seu dispositivo usará MQTT, HTTP/REST, CoAP, ou talvez algo como WebSockets? Cada um tem os seus prós e contras. O MQTT é um protocolo publish/subscribe leve, ótimo para IoT porque é eficiente e funciona bem sobre redes pouco fiáveis – muitos sistemas IoT usam-no para mensagens dispositivo-para-cloud. O HTTP/REST é muito comum e fácil de testar (já que o pode atingir com ferramentas web standard), mas pode ter um pouco mais de overhead por mensagem. O CoAP é um protocolo IoT especializado semelhante a um HTTP leve, frequentemente usado em redes limitadas. A chave é escolher um que se ajuste às capacidades do seu dispositivo e à sua infraestrutura cloud. Por exemplo, num dispositivo muito limitado (microcontrolador de 8 bits a bateria), MQTT ou CoAP são frequentemente melhor escolha do que HTTP. Se o seu dispositivo é mais capaz (a correr Linux ou semelhante), HTTP/REST serve bem e é mais direto.
Uma vez escolhido o protocolo, desenhe o formato de dados das mensagens. Muitos projetos usam JSON porque é legível por humanos e fácil de trabalhar (a maioria das linguagens tem parsers de JSON). Por exemplo, uma mensagem de leitura de temperatura em JSON pode ter este aspeto:
{ "device_id": "ABC123", "timestamp": 1692124800, "temperature_c": 22.5 }
Isto expõe claramente os campos. O JSON é um pouco verboso, no entanto, por isso se o seu dispositivo precisa de enviar dados de forma muito eficiente (pense em milhares de mensagens ou largura de banda muito baixa), pode usar um formato binário (como Protocol Buffers, CBOR ou um esquema binário personalizado) para poupar bytes. A escolha de design aqui afetará tanto o firmware como a cloud: ambos os lados têm de concordar no protocolo e no formato. É um pouco como acordar numa língua antes de começar uma conversa – se o dispositivo “fala” MQTT+JSON e o servidor espera outra coisa, não se vão entender.
Uma ferramenta útil nesta fase é desenhar um diagrama de sequência ou simplesmente escrever um exemplo de sequência de comunicação. Por exemplo: “O dispositivo faz uma leitura do sensor, formata um payload JSON, envia-o via MQTT para o tópico X; o broker cloud recebe-o, o serviço cloud processa-o e confirma-o, depois armazena-o na base de dados; mais tarde, um utilizador pede dados via aplicação, a API cloud vai buscá-los à base de dados e devolve JSON à aplicação.” Escrever este fluxo garante que todos (o programador de firmware e o de cloud, especialmente) estão literalmente na mesma página sobre como os dados se movem. Pode prevenir mal-entendidos como “Ah, pensei que estávamos a usar timestamp UNIX em segundos, não milissegundos” ou “Estávamos a comprimir os dados ou não?”. Apanhe isso agora no design, poupará tempo de depuração mais tarde.
Padrões de Codificação e Ferramentas de Equipa (Qualidade Desde o Início)
Agora, falemos de como vamos escrever código e colaborar, porque ter toda a equipa a seguir práticas comuns é essencial. Acorde em padrões de codificação para cada parte do sistema. Para desenvolvimento em C embebido, muitas equipas adotam as diretrizes MISRA-C – um conjunto de regras para a linguagem C destinadas a evitar construções inseguras ou propensas a bugs (originárias da indústria automóvel, mas amplamente úteis em qualquer firmware crítico). Se está a usar C++ no firmware, pode decidir que funcionalidades do C++ moderno são permitidas ou banidas (para manter as coisas seguras para sistemas embebidos – por exemplo, talvez evitar alocação dinâmica em runtime, etc.). Para linguagens de nível mais alto no lado da cloud como Python, provavelmente seguiria o guia de estilo PEP 8, e para JavaScript/TypeScript talvez usar um linter com o estilo Airbnb ou o que for standard. O objetivo não é ser picuinhas, é garantir consistência e apanhar armadilhas comuns. Quando todos escrevem num estilo consistente, a base de código parece um único produto em vez de uma colcha de retalhos, e é mais fácil ler e manter código escrito por outra pessoa da equipa.
Para além do estilo de código, escolha as ferramentas e infraestrutura que a equipa vai usar. Isto inclui controlo de versões e configuração do repositório (quase de certeza Git – se ainda não o está a usar, faça-o definitivamente, pois é a espinha dorsal da codificação colaborativa). Decida a estratégia de branching: vai usar um desenvolvimento simples baseado em trunk, ou GitFlow com feature branches, etc.? Configure também um sistema de tracking de issues se ainda não o fez (como Jira, Trello, Asana ou GitHub Issues) para gerir as tarefas e user stories dos seus sprints. É também uma boa altura para configurar o pipeline de Continuous Integration (CI): escolha um serviço de CI (GitHub Actions, GitLab CI, Jenkins, CircleCI, etc.) e configure-o para compilar e testar automaticamente o seu código sempre que forem feitos pushes. Falaremos mais sobre CI na próxima secção, mas a etapa de arquitetura é onde garante que todos os programadores têm acesso a estas ferramentas e sabem como as usar.
Outra parte da “cultura de código” é decidir coisas como práticas de code review – por exemplo, cada pull request exigirá a aprovação de pelo menos outro programador? Definir estas expectativas cedo é saudável. Pode parecer formal, mas poupa-o de cenários em que o código não revisto de alguém introduz um bug desagradável ou um buraco de segurança que outros só apanham muito mais tarde. Ao concordar que “fazemos sempre code reviews, seguimos o guia de estilo e corremos os testes antes de fazer merge”, instila uma mentalidade de qualidade desde o primeiro dia. O desenvolvimento moderno é tanto sobre colaboração e disciplina de equipa como sobre génio de codificação.
Finalmente, lembre-se de que este planeamento da Etapa 4.1 não é sobre desenhar diagramas bonitos que ninguém olha – é sobre dar à equipa uma compreensão partilhada e um roadmap técnico. É como planear a rota de uma viagem de carro e levar um mapa: sabe onde estão as grandes curvas e como vai navegar, mas ainda pode ser flexível com paragens ou desvios. Gaste um tempo razoável neste design de arquitetura, mas não aponte à perfeição nem detalhe cada última função. Quer design suficiente para que as pessoas possam começar a programar de forma coordenada. Com esta fundação lançada, está pronto para saltar para o desenvolvimento com confiança.
4.2 Sprints de Desenvolvimento – Iterar e Conquistar
Com a planta da arquitetura em mãos, a construção começa – escrever código, testá-lo e ver o seu produto IoT realmente fazer coisas. Abordamos isto em iterações, tipicamente usando sprints como na metodologia Agile. Um sprint é um ciclo curto e de duração fixa (geralmente 2 semanas, mas pode ser 1 semana ou até 4 semanas dependendo da preferência da sua equipa) em que a equipa se compromete a entregar um conjunto de funcionalidades ou melhorias até ao fim. A ideia é dividir o trabalho massivo de “desenvolver todo o software” em pedaços do tamanho de uma dentada que podemos atacar de forma constante e com progresso tangível.
Esta abordagem iterativa tem benefícios claros: força a integração contínua de componentes (para que firmware e software cloud sejam testados juntos frequentemente), e dá-lhe muitas oportunidades de obter feedback e ajustar o rumo se necessário. É muito melhor do que programar durante 6 meses e só então tentar ver se tudo funciona (o que é uma receita para surpresas desagradáveis). Vejamos como um ciclo de sprint típico pode parecer para o nosso desenvolvimento de produto IoT:
Sprint Planning e User Stories
Cada sprint começa com uma reunião de Sprint Planning. A equipa olha para o product backlog – a lista mestra de todas as funcionalidades, correções e tarefas a fazer, que foi inicialmente derivada dos requisitos que recolhemos na Etapa 1 (e aumentada à medida que aprendemos mais nas etapas seguintes). No planeamento, a equipa seleciona um conjunto de user stories ou tarefas do backlog para atacar no sprint, com base na prioridade e na capacidade da equipa.
Uma user story é uma descrição do tamanho de uma dentada de uma funcionalidade da perspetiva do utilizador final, frequentemente seguindo o modelo “Como <tipo de utilizador>, quero <alguma capacidade> para que <benefício>.” Por exemplo, uma story pode ser: “Como utilizador, quero que o dispositivo IoT envie dados de temperatura para a cloud a cada 10 minutos, para poder monitorizar a temperatura do meu quarto remotamente.” A partir desta story, a equipa descobrirá o que precisa de ser feito tanto no lado do dispositivo como no da cloud: talvez no firmware do dispositivo precisemos de ler o sensor de temperatura e enviar uma mensagem MQTT a cada 600 segundos; na cloud, precisamos de uma API ou message handler para receber esses dados e de um sítio para os armazenar, e talvez uma interface simples para os mostrar.
Durante o sprint planning, a equipa discute cada story escolhida para fixar os critérios de aceitação – basicamente, como sabemos quando esta story está “done” e a funcionar? Para o exemplo acima, os critérios podem ser: “Dado um dispositivo com um sensor de temperatura, quando funciona durante 10 minutos, pelo menos uma leitura de temperatura deve estar visível na base de dados cloud e no dashboard do utilizador.” Ser específico ajuda a equipa a compreender exatamente o que construir e como verificar. Esta é a altura de fazer perguntas e clarificar detalhes. É muito melhor resolver ambiguidades agora do que descobrir mais tarde que o pessoal do firmware e o da cloud tinham pressupostos diferentes (como um programador achar que era a cada 10 minutos quando ativo, outro achar que era sempre, etc.). Uma vez o planeamento feito, todos devem saber de que pedaço de trabalho são responsáveis no sprint que vem.
Implementação e Continuous Integration (CI)
Agora passamos à implementação – escrever o código para dar vida a essas user stories. Os programadores começarão a codificar as funcionalidades de firmware e cloud identificadas para o sprint. Uma boa prática fundamental aqui é escrever também testes automatizados para cada nova funcionalidade à medida que é desenvolvida. Para firmware, isto pode incluir escrever testes unitários para lógica que pode correr numa máquina host (por exemplo, se tem uma função que faz parse de uma leitura de sensor ou uma máquina de estados para o dispositivo, consegue frequentemente escrever um teste baseado em PC para ela). Para software cloud, é normalmente mais fácil: pode escrever testes unitários para as suas funções e testes de integração para as suas APIs.
À medida que os programadores escrevem código, fazem check-in no controlo de versões (Git). É aqui que a nossa configuração de Continuous Integration (CI) brilha. Com CI, cada vez que código é enviado ou um pull request é aberto, um build automatizado arranca num servidor de CI. O CI compila o código de firmware (talvez em múltiplas configurações se necessário), corre quaisquer testes unitários que tenha, e talvez até implemente o firmware num simulador ou dispositivo de teste. Também compila o software cloud, corre todos os seus testes, verifica o estilo de código (linters), corre ferramentas de análise estática e até mede a cobertura de código (quanto do código é exercido pelos testes). O objetivo é apanhar quaisquer problemas imediatamente. Se um programador introduziu acidentalmente um bug que faz um teste falhar, o sistema de CI sinaliza-o de imediato – minutos depois de o código ser escrito – para que a equipa o possa corrigir antes de se tornar um problema maior. Este ciclo de feedback rápido é crucial. É muito mais fácil corrigir um bug que introduziu há uma hora (ainda se lembra do que estava a fazer) do que um bug do mês passado (que entretanto se enredou em muitas outras alterações).
Por exemplo, imagine que um programador muda o código de leitura do sensor e introduz acidentalmente um bug de overflow para valores muito altos. Se tiver um teste unitário que empurra um valor de sensor ao extremo, o CI apanha essa falha e alerta a equipa. O programador pode então resolvê-la imediatamente. Sem esse teste, o bug podia esconder-se até uma onda de calor chegar, os sensores reais reportarem temperaturas altas e o dispositivo crashar no terreno. CI e testes existem para apanhar exatamente estes momentos – automaticamente, e minutos depois de o código ser escrito.
A Continuous Integration não é só sobre testes; as equipas integram frequentemente ferramentas de análise estática no CI também. Ferramentas de análise estática examinam o seu código-fonte à procura de bugs potenciais, vulnerabilidades de segurança ou violações de estilo sem o executar. Por exemplo, conseguem apanhar coisas como “possível desreferenciação de null pointer” ou “variáveis não usadas” ou outros code smells. Muitas equipas de embebidos usam ferramentas como PC-Lint ou Cppcheck ou clang-tidy para C/C++, e equipas de cloud usam linters (ESLint para JavaScript, Pylint para Python, etc.) e talvez scanners de segurança. O CI pode ser configurado para falhar o build se estas ferramentas encontrarem problemas sérios. Isto estabelece uma fasquia de qualidade – por exemplo, “o código de firmware tem de ter 0 novos avisos e todos os testes a passar para um merge ser permitido.” Pode soar rigoroso, mas mantém a base de código saudável.
Adicionalmente, o CI pode acompanhar a cobertura de código – que percentagem do seu código é exercida por testes automatizados. Embora 100% de cobertura seja frequentemente irrealista (e nem todo o código é igualmente fácil de testar), muitas equipas definem uma meta (digamos, pelo menos 80% de cobertura) e acompanham-na. Se uma nova entrega de código derruba subitamente a cobertura de 80% para 50%, isso é uma bandeira vermelha – talvez testes tenham sido saltados ou muito código não testado tenha sido adicionado. Ao monitorizar estas métricas sprint a sprint, mantém alta qualidade de código ao longo do desenvolvimento, em vez de tentar “aparafusá-la” mais tarde.
Em resumo, a implementação num contexto iterativo não é apenas “escrever um monte de código.” É escrever código, integrar continuamente esse código numa base partilhada, testá-lo continuamente e corrigir problemas à medida que avança. O resultado é um sistema de software que está sempre num estado semi-funcional, a ficar um pouco melhor a cada dia.
Builds Incrementais e Demos de Fim de Sprint
No final de cada sprint, aponta a ter um build incremental de todo o sistema que é uma melhoria tangível sobre o anterior. Idealmente, é algo que consegue realmente correr num dispositivo e num ambiente cloud de teste para ver as novas funcionalidades em ação. Um bom mantra é “esteja sempre pronto para demonstrar” no final do sprint. Para o nosso exemplo de produto IoT, talvez no final do Sprint 1 tenhamos um fluxo básico de ponta a ponta a funcionar: o dispositivo lê um valor de sensor fictício (talvez um número fixo ou um valor de teste aleatório) e envia-o para a cloud, a cloud recebe-o e armazena-o, e conseguimos verificar manualmente que os dados passaram (mesmo que seja só olhar diretamente para a base de dados ou logs). Já é um marco significativo – prova que toda a canalização do dispositivo à cloud funciona!
No Sprint 2, podemos substituir o valor fictício por uma leitura real do sensor do hardware, e configurar uma página web simples que mostra a última leitura. Agora conseguimos realmente ver dados ao vivo. No Sprint 3, talvez adicionemos uma funcionalidade de alerta: se a temperatura ultrapassar um limiar, o dispositivo ou a cloud dispara um alerta (talvez um LED pisca ou um email é enviado). E assim por diante. A cada sprint, adicionamos camadas de funcionalidade: talvez um sprint adicione suporte para controlo remoto (enviar comandos da cloud para o dispositivo), outro adicione um belo gráfico de UI com dados históricos, etc. Após vários sprints, terá um sistema completo em funcionalidades. Mas crucialmente, em cada etapa, tinha um sistema funcional ao qual estava a adicionar. Isto significa que se algo correr mal, sabe que provavelmente tem a ver com as adições mais recentes, o que facilita a depuração. Também significa que os stakeholders (como o seu chefe, ou o gestor de produto, ou até um utilizador de teste) conseguem ver o progresso e dar feedback ao longo do caminho.
Demos regulares são uma ótima prática. No final de cada sprint (ou até mais frequentemente, como um show-and-tell rápido semanal), reúna a equipa e quaisquer stakeholders interessados e demonstre o que há de novo. Pode ser tão simples como “O dispositivo está agora a enviar dados reais do sensor – vejam enquanto aqueço o sensor com a mão, e vejam o número neste dashboard subir em tempo real!” Estas demos criam uma sensação de realização e momento. Também forçam disciplina de integração: não se pode demonstrar algo que não funciona realmente em conjunto, por isso empurram a equipa a resolver problemas de integração à medida que avança, em vez de adiar a integração para o fim. Ver o produto ganhar vida aos poucos, sprint após sprint, é muito mais motivador do que trabalhar no vazio durante meses.
Testes e Critérios de Definition of Done
Falámos de testes automatizados durante o desenvolvimento, mas vale a pena enfatizar como os testes estão entretecidos na própria definição de progresso em cada sprint. Uma funcionalidade ou user story é considerada “done” apenas quando cumpre certos critérios para além de “o código foi escrito.” As equipas têm frequentemente uma checklist de Definition of Done (DoD) que pode incluir itens como:
- Código Completo: O código da funcionalidade foi escrito e compila sem erros nem avisos.
- Testes Unitários: Testes unitários automatizados que cobrem o novo código foram escritos e todos esses testes passam. (Por exemplo, se escreveu uma função para calcular uma condição de alerta, deve haver testes para casos normais e limite dessa função).
- Testado em Integração: Se a funcionalidade interage com outros componentes (e a maioria interage), foi testada num contexto mais amplo – por exemplo, o dispositivo realmente envia uma mensagem ao serviço cloud real e verificámos que flui de ponta a ponta. Isto pode envolver testes manuais ou testes de integração automatizados.
- Sem Regressões: Os testes existentes (de funcionalidades anteriores) ainda passam, o que significa que não partiu nada que funcionava antes.
- Análise Estática Limpa: As ferramentas de análise estática não mostram novos avisos críticos no código. Por outras palavras, não introduziu um potencial memory leak ou uma construção arriscada.
- Revisto por Pares: O código foi revisto por pelo menos outro programador da equipa, e qualquer feedback foi endereçado. Isto garante que um segundo par de olhos procurou erros de lógica, legibilidade e conformidade com os padrões.
- Documentação Atualizada: Se esta funcionalidade exige alterações na documentação ou diagramas, essas foram feitas. Por exemplo, se adiciona um novo formato de mensagem de dados, atualiza a documentação da API ou a especificação do protocolo de comunicação em conformidade. Ou se decidiu adicionar um novo microserviço na cloud, atualiza o diagrama de arquitetura.
- Aceite pelos Stakeholders: Idealmente, demonstrou a funcionalidade a funcionar ao product owner ou stakeholder relevante e eles concordam que cumpre os requisitos (os critérios de aceitação que definimos antes). Esta é a confirmação final de que a story está genuinamente done.
É uma lista longa, e à primeira vista pode parecer muitas caixas para assinalar. Mas estas práticas poupam-lhe muitos problemas mais tarde. É muito melhor apanhar e corrigir um bug ou uma falha de design agora, durante o sprint atual, do que deixá-lo escapar para os testes finais ou para a produção. A Definition of Done significa basicamente “esta funcionalidade não está a meio gás; está verdadeiramente integrada, testada e pronta.” Quando cada parte do seu sistema cumpre estes critérios à medida que avança, a fase final de integração e testes (Etapa 5) reserva poucas surpresas – esteve a verificar o tempo todo.
Documentação Contínua (Mantenha os Mapas Atualizados)
Provavelmente criou alguns diagramas e documentos na fase de arquitetura (Etapa 4.1). Agora, à medida que o desenvolvimento progride, é importante manter esses documentos de design atualizados. O software tem uma forma engraçada de evoluir – talvez descubra a necessidade de uma nova tarefa no RTOS para uma função de watchdog, ou a arquitetura cloud mude para adicionar uma camada de cache por desempenho. Se não atualizar os documentos de arquitetura, eles vão afastar-se da realidade e eventualmente ninguém confia neles. Para evitar o problema clássico da documentação desatualizada, trate os documentos como uma parte viva do projeto. Algumas equipas até escrevem os seus diagramas como código usando ferramentas como PlantUML ou Mermaid. Desta forma, a “fonte” do diagrama vive no repositório ao lado do código. Quando é feita uma alteração que afeta a arquitetura, a equipa atualiza o código do diagrama e regenera a imagem.
Uma opção prática (se a sua equipa estiver disposta) é integrar a documentação também no processo de CI. Por exemplo, pode ter uma verificação de que, se certos ficheiros centrais mudaram (como um esquema de mensagem ou uma interface de componente), e o ficheiro de documentação correspondente não foi atualizado, o CI lembra-o ou falha o build. Dá um empurrãozinho aos programadores: “Ei, mudaste a forma como o sistema funciona – atualiza o diagrama para que todos saibam.” Pode soar exagerado, mas manter os documentos em sincronia compensa quando um novo programador se junta ou quando volta ao projeto daqui a seis meses e tenta lembrar-se de como as coisas estão organizadas. Mesmo que não o automatize, pelo menos agende uma revisão rápida dos diagramas a cada poucos sprints para garantir que ainda refletem a realidade.
O essencial: documentação atual vale o esforço. Significa que qualquer pessoa na equipa (ou fora dela, como um auditor de conformidade ou um parceiro) pode olhar para os documentos e confiar neles. Evita situações como “Não leias a wiki, está velha – o Joe tem a informação real na cabeça (ou em alguns comentários de código).” Em vez disso, o conhecimento é capturado e atualizado à medida que avança, o que é uma marca de uma equipa madura e focada na qualidade.
Demos Regulares e Ciclos de Feedback
Tocámos em demos no contexto dos sprints, mas enfatizemos o valor de estabelecer uma cadência regular de demonstrar progresso. Em Agile, isto é frequentemente a reunião de Sprint Review: no final do sprint, a equipa mostra o que construiu aos stakeholders (gestores de produto, outras equipas, por vezes até utilizadores reais ou executivos). Para um projeto IoT, uma demo pode ser literal – mostrar o dispositivo e o sistema a trabalhar em conjunto. Por exemplo, pode projetar o dashboard web num ecrã e ter o dispositivo físico na mesa. Depois faça algo como disparar o dispositivo (premir um botão ou criar uma condição) e mostrar que a cloud atualiza em tempo real. Estes momentos são genuinamente validadores para a equipa e valiosos para os stakeholders. Tornam o projeto real e mantêm todos envolvidos. É também uma oportunidade de obter feedback: talvez um stakeholder diga: “Está giro, mas podíamos também mostrar dados de humidade na próxima versão?” ou “O alerta demorou um bocado a aparecer, conseguimos acelerar isso?”. Feedback precoce como este é especialmente valioso, porque pode ser alimentado aos sprints seguintes, muito antes de o produto ser final, quando as mudanças são muito mais fáceis de fazer.
Não se preocupe se nas primeiras demos a funcionalidade for muito básica – é expectável. A chave é que está a melhorar iterativamente. A primeira demo pode ser apenas provar que um dispositivo consegue falar com um servidor. A próxima demo mostra uma UI simples. Demos posteriores mostram polimento e funcionalidades adicionais. Se algo não funcionou como esperado numa demo, também não faz mal, porque descobriu um problema de integração à frente de todos e agora pode priorizar a correção. (Aliás, algumas equipas têm um ditado: “Demo cedo, demo frequentemente – falha frequentemente, corrige mais depressa!”) A demo também impõe um pouco de disciplina: coloca pressão positiva na equipa para terminar o trabalho num estado entregável a cada sprint, em vez de procrastinar a integração para mais tarde. É muito melhor descobrir agora que o JSON do dispositivo não está a fazer parse corretamente no servidor, do que descobrir isso durante uma grande fase final de testes. Por isso, trate as demos não apenas como um espetáculo, mas como parte do próprio processo de desenvolvimento.
Finalmente, celebre essas demos. Construir um produto IoT é trabalho duro, envolvendo muitas peças, e cada peça funcional – mesmo uma pequena funcionalidade – merece um momento de reconhecimento. Isto mantém a moral alta e constrói a confiança da equipa: a energia mantém-se mais alta quando todos veem regularmente os resultados tangíveis do seu trabalho.
Para resumir a Etapa 4: O desenvolvimento de firmware e software cloud é tudo sobre progresso iterativo e melhoria contínua. Ao investir tempo na arquitetura de software antecipadamente, dá à sua equipa uma planta sólida (e evita programar-se para um canto). Ao usar sprints de desenvolvimento, integração contínua e uma forte definition of done, garante que a qualidade é incorporada em cada passo – os bugs são apanhados cedo, e o software está sempre a um passo de um produto funcional. No final da Etapa 4 (que provavelmente abrange vários ciclos de sprint), deve ter um firmware completo em funcionalidades e testado em hardware real ou representativo, e um sistema cloud completo em funcionalidades e testado num ambiente realista. Por outras palavras, todo o lado de software do seu produto está pronto a rolar.
Graças a esta abordagem iterativa, conseguiu ajustar e melhorar continuamente, em vez de apostar tudo num único big bang no fim. Isso reduz o risco e torna o processo de desenvolvimento mais previsível. É também simplesmente uma forma melhor de trabalhar: em vez de um longo período de incerteza, viu o seu produto IoT ganhar vida passo a passo, de um LED a piscar a um dispositivo inteligente totalmente ligado e integrado na cloud. O trabalho pesado no lado do software está feito, e foi feito com rigor e agilidade. Para a próxima etapa, onde pegamos neste hardware e software bem construídos e os preparamos para os lançar no mundo.