Software com Sabedoria: 7 Princípios para Escrever Código 100% Livre de Bugs
Este post apresenta uma reflexão sobre a prática consciente da engenharia de software: um contive à excelência na disciplina, sete princípios que desafiam a norma, incentivando desenvolvedores a pensar!.
1. Rejeite tudo que é "moderno"
Frequentemente, o apelo do "moderno" carrega uma autoridade inquestionável, quem, em sã consciência, optaria por regressar ao uso de cavalos para transporte ou cartas para comunicação? No entanto, a chamada modernidade no desenvolvimento de software, revela verdadeiramente uma ausência notável de inovações. São apenas repaginações de conceitos antigos, que existem desde os primórdios da computação. Não seja ingênuo, toda a computação é moderna, não existe outra opção. A tendência recente de promover a renderização no lado do servidor e "vender" frameworks como Next.js ou Nuxt.js e seu suporte 'nativo' para SSR como inovação tecnológica, quando apenas uma origem às origens, ilustra bem a falácia do "moderno". Microserviços nada mais são que a boa e velha computação distribuída; serverless e a tão aclamada nuvem; infraestruturas gerenciadas, empresas ganham bilhões com isto desde sempre; a dockerização e kubernetização das aplicações, nada mais é do que a velha virtualização em doses cavalares.
A única novidade realmente impressionante é que agora você pode dar o deploy de 420 servidores em 90 segundos, com apenas um cartão de crédito, mas ninguém ainda descobriu um caso de uso para isso.. Este princípio não é um chamada ao retrocesso, mas sim um convite ao pensamento crítico: estamos adotando estas novas abordagens por elas serem efetivamente superiores e adequadas aos nossos problemas, ou simplesmente porque são "modernas"?
Leitura:
https://world.hey.com/dhh/why-we-re-leaving-the-cloud-654b47e0
https://mcfunley.com/choose-boring-technology
2. O verdadeiro mestre é o passado
Nos primórdios da computação, programadores lidavam com restrições severas de hardware e largura de banda, uma realidade que exigia uma abordagem disciplinada e meticulosa ao desenvolvimento de software. Programas tinham que ser simples e eficientes; cada byte e ciclo de processamento era imporatantes. Por uma necessidade imposta pelas circunstâncias, os programadores daquela época possuíam um entendimento profundo de como os processadores, compiladores, sistemas operacionais funcionavam. Esse conhecimento íntimo dos sistemas permitia-lhes otimizar seus códigos de maneiras que muitas vezes parecem perdidas na modernidade, onde camadas e mais camadas de abstração escondem os detalhes de implementação.
Este princípio não é uma rejeição ao progresso, mas um lembrete: assim como os programadores conheciam cada abstração com os quais trabalhavam, os desenvolvedores de software "modernos" também se beneficiam enormemente de um entendimento profundo de como a máquina (e todas as outras abstraçõoes empilhadas na sua stack) operam em um nível fudamental. Mesmo em uma era de abundância de poder computacional e recursos virtualmente infinitos, há valor inestimável na economia de recursos e a simplicidade do design que outrara foram limitações impostas.
Leitura:
https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html
https://danluu.com/in-house/
3. Não Empilhe Abstrações Desnessarias
O exemplo dos frameworks pesados, como Rails (Ruby) e Django (Python), ilustra ainda mais esse ponto. Ambos são incrivelmente poderosos e oferecem muitas conveniências para o desenvolvimento rápido de aplicações web. No entanto, a realidade de executá-los por trás de servidores web como o Nginx destaca uma grande incongruência: usamos um servidor web altamente eficiente para gerenciar roteamento, apenas para passar essas solicitações para um framework que irá, replicar esse mesmo processamento, em uma aplicação 100x mais lenta. Isso não apenas introduz redundância, mas também complica a arquitetura da aplicação sem nenhuma necessidade.
Cada camada de abstração, embora possa oferecer benefícios imediatos de produtividade ou conveniência, vem com um custo computacional e conceitual. Abstrações ocultam detalhes de implementação, o que pode é ótimo - até certo ponto. Quando acumuladas, essas abstrações tornam cada vez mais díficil entender e controlar o que está acontecendo em níveis mais baixos (e fundamentais), chegando a um ponto em que pode se tornar impossível diagnosticar e resolver problemas eficientemente.
Leitura:
https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/
https://www.scattered-thoughts.net/writing/complexity-budgets/
4. Desempenho é uma Característica Fundamental
Toda aplicação deve ser projetada com requisitos máximos de consumo de recursos, seja em tempo de CPU, largura de banda de I/O, consumo de memória, rotações de disco, etc. Se algo exceder esses limites, é considerado um bug crítico, que torna sua aplicação inútil! Portanto, ao projetar e desenvolver software, é fundamental considerar o desempenho como uma característica essencial.
Assim como os sistemas críticos de tempo real, que são projetados para atender estes tipos de requisitos, as aplicações "modernas", mesmo que não classificadas como "em tempo real", não estão distantes dessa realidade. Ao criar sistemas interativos, como aplicativos web, jogos ou qualquer coisa que interaja diretamente com um ser humano, estamos efetivamente criando sistemas de tempo real. A expectativa do usuário quanto à resposta e fluidez do sistema é uma parte crítica da experiência do usuário.
Leitura:
https://blog.codinghorror.com/performance-is-a-feature/
https://brooker.co.za/blog/2021/04/19/latency.html
5. Mais Hardware Não é a Solução: A eficiência é a chave
No desenvolvimento de software "moderno", frequentemente nos deparamos com a tentação de resolver problemas de desempenho simplesmente adicionando mais recursos de hardware. No entanto, essa abordagem não só é economicamente insustentável como também tem um impacto ambiental considerável. Estamos consumindo recursos computacionais a uma taxa insustentável, e, assim como ocorreu com os recursos naturais, precisamos aprender a ser mais eficientes. Assim como a eficiência de combustível se tornou um fator crítico na indústria automobilística, a eficiência de recursos computacionais será um diferencial competitivo chave no software, muito em breve. Estamos chegando a um ponto de inflexão onde a capacidade de desenvolver software que realiza mais com menos não será apenas desejável, mas essencial.
Ainda mais importante, Simplificar o design de um sistema não apenas o torna mais eficiente em termos de recursos computacionais, mas também mais compreensível e, consequentemente, mais confiável. A eficiência leva à necessidade de fazer mais com menos o que exige uma abordagem minuciosa no design e na implementação de soluções de software, levando naturalmente à simplicidade. A simplicidade não é apenas uma questão de consumo recursos ou de preferência pessoal; é um requisito para a criação de sistemas sustentáveis e robustos.
Leitura:
https://joearms.github.io/published/2014-06-25-minimal-viable-program.html
https://unixsheikh.com/articles/advice-to-business-owners-and-managers-dont-use-the-modern-way-of-web-development.html
6. Solucione o Problema em Questão
A chave para o desenvolvimento de software verdadeiramente eficaz é a capacidade de focar exclusivamente no problema principal, evitando desvios para subproblemas que possam surgir durante o processo. Em outras palavras, o objetivo é evitar gastar tempo resolvendo problemas que, na realidade, nunca deveriam ter surgido.
Este princípio sublinha a importância de uma análise cuidadosa e uma compreensão profunda do problema central antes de qualquer tentativa de solucioná-lo. Ao entender completamente o desafio à frente, é possível tomar decisões informadas sobre as ferramentas e abordagens mais apropriadas, garantindo que cada escolha contribua diretamente para uma solução eficiente e elegante. Antes de escolher as ferramentas e tecnologias a serem utilizadas, é crucial dedicar tempo e esforço para entender completamente o problema.
Leitura:
https://www.joelonsoftware.com/2009/09/23/the-duct-tape-programmer/
https://ericsink.com/articles/Requirements.html
7. Construa suas Abstrações
Ao desenvolver soluções de software, é fundamental construir abstrações que resolvam especificamente o problema em questão, minimizando a dependência de códigos "genéricos" de terceiros. Sempre que pertinente priorize o uso da biblioteca padrão da linguagem de programação e as funcionalidades oferecidas pelo sistema operacional, ou mesmo do runtime escolhido. Compreender profundamente cada componente introduzido no seu sistema é o básico.
A ideia de inspecionar cada linha de código de bibliotecas de terceiros pode parecer completa loucura em uma era onde a instalação de ~369 pacotes desenvolver site é norma. No entanto, se o objetivo é construir software verdadeiramente robusto, capaz de resistir a um apocalipse, essa cautela é indispensável. É importante reconhecer que nem todo uso de software de terceiros deve ser encarado com total desconfiança. Softwares livres com um histórico comprovado de segurança e eficácia, podem e devem ser utilizados, mas com parcimônia. A chave é a evidência concreta da necessidade de tais ferramentas dentro do projeto e uma comprensão sólida de seu funcionamento interno.
Quando o código é suficientemente simples e direto, os erros se tornam evidentes. Alcançar um nível de maturidade no software, que se possa ter evidências concretas e robustas da ausência de quaisquer bugs deveria ser o padrão em todas as aplicações, não apenas dos programas embarcados nas aeronaves e reatores nucleares.
Leitura:
https://blog.regehr.org/archives/1289
https://tonsky.me/blog/tech-sucks/
Os princípios aqui discutidos servem como uma luz, para orientar a fazer escolhas informadas e intencionais que valorizam a simplicidade e a eficiência, acima da conveniência e praticidade das soluções "modernas". Mas, de maneira alguma, são regras absolutas; esse é justamente o ponto crucial: evitar o dogmatismo ao construir software. Softwares não existem para resolver problemas abstratos e genéricos, mas sim um caso concreto e específico.
TL;DR: Não resolva um problema que você não tem. Não crie obstáculos para o seu eu do futuro. Você não pode prever o futuro, mas deve tornar o presente tão simples quanto possível.