Executando verificação de segurança...
23

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.

Carregando publicação patrocinada...
2

Todos os princípios são válidos, mas no meu entendimento, eles não se aplicam a todos os projetos.

Por exemplo, eu preciso perder tempo otimizando algo para encaixar num requisito de hardware, se esse projeto tem um orçamento alto, ou se é um projeto tão pequeno que mesmo mal otimizado ele irá funcionar bem até num raspberry pi?

"O verdadeiro mestre é o passado":
grandes empresas como bancos por exemplo vivem no passado, pela segurança, pela confiabilidade. Pra eles o que importa é que funcione bem, "foda-se" se o programador vai ter que aprender a programar em Cobol, viver numa codigo cheio de retalhos e ter uma pessima devx.

Você ta fazendo um código novo, quer que ele seja bonito, legível, moderno.

Enfim, acho que em varios pontos de aplica o bom e velho DEPENDE.

2

Eliaseas, você está absolutamente correto. Esses princípios foram pensados tendo em mente projetos muito específicos, cujo objetivo é escrever um código livre de bugs, que possa se provar que o software faz exatamente o que deveria fazer e nada além disso. O título, ao contrário do que foi sugerido por Wymored, não é apelativo, mas sim direto ao ponto. Veja que não mencionei otimização especificamente, o uso máximo de recursos pode ser quão generoso seja apropriado, desde que seja conhecido e controlado.

Quando você está desenvolvendo um código novo, naturalmente deseja que seja bonito e legível. Concordo plenamente com isso. No entanto, a questão de ser "moderno" é onde devemos ter muita cautela. Não existe uma qualidade intrínseca em ser moderno; muito pelo contrário, ser moderno implica ser novidade, ser pouco testado, ser instável, enfim... Veja o Next.js, considerado uma das das ferramentas "modernas" para desenvolver aplicações web: mais de 2000 issues abertas no GitHub. Isso mostra que novidade é praticamente sinônimo de bugs.

2

Esses principios vão de encontro com o que penso!
Amanhã com mais tempo irei ler cada um dos textos referenciados para
entender melhor todos os principios!

2

Embora eu tenha achado "100% livre de bugs" apelativo, concordo com os princípios apresentados e já os aplico no meu dia a dia. Realmente são coisas que aprendemos ao longo de mais de 20 anos de programação: os atalhos e o caminho fácil custam caro.
Ao invés de "Rejeite tudo o que é moderno", colocaria "Avalie com cuidado tudo que é apresentado como moderno".
Concordo também com o excesso de abstrações só confunde, nunca comprei a "necessidade" da orientação a objetos para cada detalhe de um projeto, e vemos muito "over-engineering", soluções complexas para problemas que poderiam ser resolvidos de forma bem mais simples.
A respeito de construa suas abstrações concordo apenas em parte. Isso se aplica quando se usa uma solução complexa para resolver um problema simples. Mas muitas vezes há bibliotecas pequenas e simples que já são validadas pela comunidade, então não é necessário reinventar a roda. Outro dia apresentei uma solução opensource para um cliente apenas para ele ter uma ideia do que poderia ser alcançado, mas no geral a solução era complexa demais para o projeto, e ainda por cima, não era desenhada desde o início para ser escalável, como os requisitos do projeto demandam. Usar essa solução como base e "remendá-la" exigiria muito mais esforço a longo prazo do que apenas entender os elementos essenciais e implementá-los desde o início para escalabilidade.
Querer usar docker para tudo, cloud para tudo, quando uma simples instalação em um vps ou servidor dedicado é mais que suficiente para 99% dos casos de uso, é um exemplo clássico.
Não percebem a armadilha do "vendor lock-in" aquela amostra grátis por x tempo vai te custar muito caro depois.
De forma geral, parabéns pelo texto!

2
1