Donald Knuth compara um programador a um ensaísta que trabalha com formas estéticas e literárias tradicionais, bem como com conceitos matemáticos, para comunicar a forma como um algoritmo funciona e para convencer um leitor de que os resultados estarão corretos [1]. Isto torna a programação também uma expressão criativa, enfatizando o papel do estilo, da estrutura e da comunicação na transmissão dos algoritmos e da sua correção. Tal como um ensaísta elabora argumentos e narrativas através da linguagem, um programador constrói soluções computacionais através do código. Isto envolve não apenas a precisão técnica, mas também um elemento de design e elegância para comunicar eficazmente a lógica.
Na sua perspectiva, "A ciência é o que entendemos bem o suficiente para explicar a um computador; a arte é todo o resto".[2] Isto posiciona a programação como uma ponte entre a ciência e a arte. As áreas que exigem maior exatidão podem ser formalizadas em instruções, enquanto os aspetos mais criativos exigem uma abordagem artística. A resolução de problemas em software pode envolver mais "arte" à medida que exploramos e experimentamos. Quando conhecemos mais profundamente, podemos transformar essas soluções artísticas em algoritmos mais exatos e determinísticos.
A partir do momento que um software será usado por outros humanos, e será feito por humanos. O Software não pode ser um simples emaranhado de números e palavras sem sentido. Deve ter a lógica e precisão da ciência e a sutileza, beleza e narrativa da arte.
O próprio código de alto e baixo nível também é feito para que os humanos entendam e executem, não somente com maior precisão e técnica, mas com um bom grau de liberdade e criatividade.
O código Assembly que é um linguagem de baixo nível (menos abstrata para humanos), por exemplo, é escrito por um programador, utilizando símbolos que representam as operações do processador.
O montador lê esse código Assembly.
O montador substitui cada instrução simbólica por sua representação binária correspondente no código de máquina.
O código de máquina resultante é então carregado e executado pelo processador.
Um exemplo de instrução Assembly poderia ser “MOV EAX, 10” (move o valor 10 para o registrador EAX). O montador traduziria essa instrução para um equivalente em código binário, que o processador entenderia:
Uma linguagem de baixo nível, por mais que seja mais próxima da linguagem humana, ainda está muito longe. Ao contrário das linguagens de alto nível, como python, Java e C#. As linguagens de alto nível, ou seja, que usam símbolos mais próximos aos nossos para dar instruções ao computador, precisam ser traduzidas para uma linguagem de baixo nível, como assembly ou diretamente código de máquina, para que o processador consiga entender e executar as instruções.
Só para um comparativo claro, para que entendamos a diferença entre a linguagem de baixo nível e alto nível, esse é um exemplo de um programa que mostrará ao usuário uma mensagem: “Hello World”, em uma linguagem de baixo nível (Assembly):
Já esse é um código que fará a mesma coisa, em uma linguagem de alto nível (Python):
print("Hello World")
As linguagens de alto nível facilitam nossa interação com o computador, pois os aproximam da nossa linguagem, dos nossos símbolos e convenções. Tornando a programação mais “humana”, o que pode dar espaço para que ela seja mais “artística”, livre e criativa.
Voltando ao que Knuth disse: podemos desenvolver softwares próximo como escrevemos um ensaio, mas dando comandos a máquina para que execute certas ações para nós. Isso torna a programação, não somente matemática, engenharia e estatística, mas também, em certa medida, arte.
A código acima é um programa simples que soma 2 números que foram digitados pelo usuário. Foi feito em portugol, uma linguagem de programação simples que usa comandos em português. Observe que é bem próximo do que seria o mesmo programa usando Python, por exemplo, com a maior diferença sendo que, Python usa a língua inglesa para basear seus símbolos e comandos.
Imaginemos o seguinte cenário:
Roteiro em português:
“Quero que para todo valor maior que 6, de uma determinada lista que contenha os números [6, 8, 10, 2, 22, 70, 48, 3, 1], seja impresso o valor na tela.”
Roteiro em Python:
lista = [6, 8, 10, 2, 22, 70, 48, 3, 1]
for valor in lista:
if valor > 6:
print(valor)
// resultado: 8, 10, 22, 70, 48
Veja que o “roteiro” do meu programa escrito em português, e o roteiro escrito em python, são exatamente a mesma coisa, o que muda é a sintaxe. Traduzindo a linguagem python do inglês para o português, ficaria exatamente assim a partir do “for”:
para valor na lista:
se valor > 6:
imprima(valor)
Ou seja, a única coisa que precisamos para falar o que um computador deve fazer, isto é, desenvolver um software, é falar a língua dele através de uma linguagem que ele entenda. Nesse exemplo usei o python, mas poderia ser uma outra linguagem qualquer. Partindo desses exemplos simples, poderíamos ir para programas muito mais complexos e detalhados, cheio de funcionalidades e com uma aparência agradável. Assim como, partindo de uma frase, podemos escrever um texto, um livro com ilustrações, histórias complexas, personagens cativantes e profundos
Pode-se ter inúmeras respostas para a pergunta de “como se tornar um bom programador” como conceitos técnicos de design patterns, clean code, arquitetura, etc. Até respostas mais subjetivas, como “deve-se amar o que faz”, “deve-se fazer coisas boas que ajudem as pessoas”, etc. Porém, irei me ater ao que sustenta o “ser bom em alguma coisa”, ou melhor, na capacidade de entender, expressar e resolver problemas necessários.
As técnicas e o conhecimento básico e avançado são bem importantes, porém, eles por si não fazem nada. Engenharia e arquitetura civil não importam se não terei uma casa para morar. Tanto a engenharia, como a arquitetura, servem alguns propósitos, resolvem alguns tipos de problemas, e atendem aos problemas das pessoas em suas especificidades.
Para o conhecimento profundo, não importa se sei falar “homem” em várias línguas, se não sei o que é um homem, ou o que é ser homem - “Parabéns, você decorou algumas palavras, isso é bom” - mas o que fará com ela? Como a entende? O que ela significa?
É muito comum pessoas que fazem sons com a boca, mas não entendem o significado dos sons, só repetem as palavras que ouviram de alguém, que decoraram. As palavras são símbolos que representam uma realidade, objetiva ou abstrata. Não importa o som que sai da boca, ou a palavra que é decorada, ou a linguagem de programação que usa, o importante é o que se predica dessas coisas. O entendimento das abstrações de determinada área de conhecimento é muitíssimo importante para ser “bom naquilo”.
Abstrações permitem focar nos aspectos mais importantes de um problema, ignorando detalhes desnecessários naquele momento. Por exemplo, em programação, você usa uma "lista" sem precisar saber como ela é implementada internamente. Quando se entende abstrações, consegue-se resolver problemas mais amplos, criar modelos mentais melhores e pensar de forma mais organizada, aumentando a fluidez de raciocínio.
Um bom entendimento de abstrações permite criar soluções reutilizáveis e aplicar conhecimentos de uma situação em outras, o que facilita o reaproveitamento e generalização, ou seja, um conhecimento específico quando abstraído, deve servir para vários contextos e situações. Se você aprender a lavar uma sala, certamente estará mais capacitado para lavar um quintal e um banheiro. Se aprendeu a ler livros de ficção, o conhecimento adquirido também será útil para ler artigos científicos.
“Camada de serviço”, “entidade”, “interface”, “paradigma”, são todas palavras abstratas muito usadas em programação. Assim como “sociedade”, “povo”, “mídia”, são palavras também abstratas usadas na política, sociologia e filosofia. Entender o significado das abstrações é muito importante para ser realmente bom em sua área do conhecimento.
Decomposição é basicamente, decompor um problema, quebrá-lo em partes menores, para resolver os problemas menores (mais fáceis), até que o problema maior seja resolvido. Há uma relação direta entre decomposição e algoritmos, quebrar um problema em partes menores já é um passo essencial na construção de um algoritmo.
Relação entre decomposição e algoritmos
Usamos a técnica de decomposição naturalmente, sem percebermos, em várias etapas do nosso dia. Como para tomar banho, por exemplo:
Ir ao banheiro
Entrar no banheiro
Fechar a porta
Colocar a toalha no lugar
Preparar o ambiente
Verificar se há sabonete, shampoo etc.
Regular a temperatura da água
Ligar o chuveiro
Molhar o corpo
Entrar no box
Molhar da cabeça aos pés
Aplicar produtos de higiene
Passar shampoo no cabelo
Enxaguar o cabelo
Passar sabonete no corpo
Enxaguar o corpo
Encerrar o banho
Fechar o chuveiro
Retirar o excesso de água do corpo
Secar-se
Pegar a toalha
Secar o corpo e cabelo
Finalizar
Sair do banheiro
Vestir-se
Estender a toalha (muitíssimo importante, cuidado homens).
Se, por acaso, tivéssemos que escrever um algoritmo para que uma máquina tenha que ir até o banheiro e tomar banho, começamos decompondo esse grande problema em etapas menores até que o problema seja resolvido, como fizemos.
Cada uma dessas etapas, poderia ser uma função:
Mesmo decompondo o problema principal e esboçando um algoritmo com 12 funções, ainda assim, nenhuma dessas decomposições nos leva a dar um comando específico para criar nosso algoritmo final. Pense, por exemplo, em como essa máquina irá até o banheiro (para esse exemplo, vamos ignorar a natureza dessa máquina, se terá rodas ou será um robô humanoide, etc), como ela localizará o banheiro, quais movimentos ela deverá fazer para ir até o banheiro, muito problemas devem ser resolvidos para que a máquina cumpra o objetivo determinado.
Sendo assim, podemos decompor ainda mais, função por função, até chegarmos em soluções mais específicas e comandos mais diretos:
Veja, podemos decompor dezenas, ou até centenas de vezes, até que nossa tarefa seja concluída e o algoritmo seja claro e funcional. Cada passo que damos, nosso problema que parece ser extremamente difícil vai se tornando mais possível e concreto. As abstrações vão se tornando comandos específicos, a junção dos comandos específicos em funções vão se tornando abstrações que poderão ser usadas, não só para que o robô vá “tomar banho”, mas para executar outros objetivos, como entrar em uma sala abrindo e fechando a porta, verificar se o shampoo acabou, etc.
Usamos decomposição e algoritmos de maneira inconsciente para quase tudo que executamos. Para fazer uma receita, para atravessar a rua, tocar um instrumento, ou até mesmo ler. Saber decompor os problemas, transformá-los em passos claros e objetivos, com nomenclaturas bem claras, que o máximo possível de pessoas entenda, é também uma habilidade fundamental para um bom programador.
É impossível ficarmos bons em algo sem praticar. Não sabe trocar um chuveiro? Troque uns 2 ou 3, e já fique razoavelmente bom naquilo. Não sabe fazer um bom churrasco? Tente alguns temperos, e faça tentativas, que depois de alguns fracassos, carnes super salgadas, duras ou com pouco tempero, você ficará melhor e mais confiante. A prática é muito boa para dar uma “musculatura” para seu conhecimento.
Não basta ficar na internet assistindo vídeos de pessoas malhando e treinando, você pode saber como fazer o movimento certo, da melhor maneira possível, isolando o bíceps, saber toda a teoria de como ficar com o bíceps super forte e definido. Mas, por mais incrível que pareça, você não ficará com o bíceps forte e definido se não aplicar seu conhecimento.
Muitos conhecimentos, só são conquistados com práticas, tentativas e erros exaustivos, não importa o tanto que você leia e entenda de teoria, após os estudos e as práticas, o conhecimento é como portas que vão se abrindo para cada desafio que concluímos.
refs:
[1] Selected Papers on Computer Science, Donald Ervin Knuth[2] Things a Computer Scientist Rarely Talks About (Volume 136) Donald Ervin KnuthCoding == Art