O Que é Lógica de Programação?

Antes de responder suas perguntas acho importante discutir o que é lógica de programação. Não existe uma definição formal do conceito. Comumente a área de lógica de programação está associado com o estudo do paradigma imperativo. Eu não concordo com essa abordagem.

Ela nos leva a equívocos como "Se você aprende lógica de programação você programa em qualquer linguagem, só a sintaxe muda". Isso não é verdade, e várias outras afirmações que decorrem dela também não.

  • "Mas toda linguagem tem if...else ou switch case";
  • "Mas toda linguagem tem for, while e outras estruturas de loop";
  • "Mas toda linguagem tem variáveis".

Nenhuma das afirmações acima é verdadeira. Assembly x86 não possui variáveis ou estruturas como if...else, for ou while. Haskell e outras linguagens funcionais também não possuem estruturas de loop e o if...else é uma expressão e não uma instrução.

Paradigmas de Programação

Paradigmas de programação são príncipios que fundamentam a elaboração de um programa. Como dito anteriormente, quando se menciona lógica de programação, tipicamente já pensamos no paradigma imperativo, ele é a base de praticamente todas as linguagens populares como C, C++, Rust, Golang, Java, C#, Python, JavaScript etc.

O paradigma imperativo é um paradigma de programação onde os programas são elaborados alterando o estado do programa e descrevendo instruções para uma máquina executar. O foco está mais no "como" ao invés de "o quê" fazer. Repare como conceito de variável está diretamente relacionado a ele? Você tem uma variável que representa um estado, e logo após algumas instruções ele pode mudar. O paradigma imperativo é a base para muitos outros como o estruturado, procedural e orientado a objetos.

Mas é possível escrever programas que não alterem estado? Ou que não descrevam como a máquina deve executar tal tarefa? E a resposta é sim. Daí vem o paradigma declarativo, onde o foco é mais no "o quê" ao invés do "como" fazer. O paradigma funcional tem base no paradigma declarativo, e modela os programas usando funções matemáticas que possuem transparência referencial (mesma entrada, mesma saída) e não dependem nem alteram o estado do programa. O fato do paradigma declarativo ser praticamente o oposto do paradigma imperativo, faz com que a elaboração dos programas seja radicalmente diferente nas duas abordagens.

Repare nesse código em Haskell, uma linguagem que segue o paradigma funcional declarativo.

dobro :: Integer -> Integer
dobro x = x * 2

O que ele faz? Imperativamente o sinal = significa atribuição, mas aqui significa declaração. Sabe as funções matemáticas como $f(x)$? é exatamente a mesma coisa. O que o código declara é uma função chamada dobro que recebe como parâmetro o valor inteiro $x$, onde o dobro de $x$ é igual a $x * 2$. A mesma entrada produz a mesma saída independente do estado do programa. Isso não descreve a alteração de estado de $x$, mas descreve a propriedade de que o dobro de um valor inteiro é sempre o valor multiplicado por dois, exatamente como na matemática.

Para calcular uma equação de segundo grau:

delta :: Double -> Double -> Double -> Double
delta a b c = b^2 - 4 * a * c

equacaoQuadratica :: Double -> Double -> Double -> (Double, Double)
equacaoQuadratica a b c = ((-b + sqrt (delta a b c)) / 2 * a, (-b - sqrt (delta a b c)) / 2 * a)

OBS. 1: Sei beeem pouco de Haskell, então esse código provavelmente é bem ruim. OBS. 2: Algumas linguagens de programação tipicamente imperativas também dão suporte em algum nível para o paradigma funcional, como Java, C#, Python etc.

Repare como isso muda totalmente a forma de pensar um programa? Sem variáveis que alteram seus valores, sem estados e nem estruturas que alteram o fluxo de execução. Se fossemos escrever um programa em C para realizar a mesma computação ele provavelmente seria totalmente diferente, não apenas em sintaxe mas em conceito, já que seria priorizado as características do paradigma imperativo ao invés do declarativo. O detalhe sútil é que o paradigma NÃO está na linguagem e sim no programa, a linguagem apenas o representa. É teoricamente possível explorar o design de uma linguagem para escrever programas em um determinado paradigma mesmo que essa linguagem não forneça recursos nativos que o represente. Por exemplo, mesmo que C não fornceça recursos para programação orientada a objetos, é perfeitamente possível escrever programas nessa linguagem que faça uso de tal paradigma. Existe uma biblioteca chamada GObject que auxilia na escrita de programas orientado a objetos na linguagem C.

Linguagens de Programação

Uma linguagem é um meio a qual um transmissor comunica uma ideia a um receptor. Linguagens de programação são um meio formal para descrever uma computação para pessoas ou máquinas. Nenhuma linguagem constroi ideias, mas apenas as representa. Um programa é primeiro pensado para depois ser codificado em uma linguagem. Se um paradigma de programação é um conjunto de princípios a qual um programa é elaborado e estruturado, uma linguagem de programação deve fornecer em seu corpo sintático e semântico recursos para representar tais paradigmas, daí então dizemos que tal linguagem é orientada a objetos, procedural, funcional etc.

Para mim, o estudo da lógica de programação é o estudo da aplicação de paradigmas de programação para solução de problemas computacionais. Isso envolve entender suas origens, quais problemas visam resolver e entender como eles podem ser representados em linguagens.

Respondendo as Perguntas

Como se treina isso?

Para treinar lógica de programação não há outro jeito se não programar. Como definimos, programas são construidos sobre paradigmas específicos. Para treinar sua lógica na solução de problemas em diversos paradigmas é preciso programar neles. A boa notícia é que mesmo existindo um monte, alguns paradigmas são baseado em outros. Por exemplo: o paradigma estruturado tem base no paradigma imperativo, estudar o primeiro te da boa base para entender o segundo; o paradigma orientado a objetos também tem base no paradigma imperativo e tipicamente (mas não necessariamente) usa procedimentos, conceito do paradigma procedural que também tem base no paradigma imperativo.

Como se cria um pensamento lógico sem depender de uma linguagem de programação específica?

É possível estudar lógica de programação de forma independente de linguagem? Na minha visão não. Mesmo que eu tenha separado o processo de idealização do de codificação da ideia em linguagem, o ser humano precisa de linguagem para pensamentos complexos. Tente pensar em política sem pensar na palavra política, tente pensar em uma equação matemática sem pensar em números. É impossível. Usar linguagem natural não nos salva disso. Pensar "Declare variavel inteira $x$ com valor 5" é o mesmo que pensar int x = 5, a primeira sentença é tão linguagem quanto a última.

Por isso, o estudo da lógica de programação terá que envolver o estudo de pelo menos uma linguagem. Mas aí vem o pulo do gato, toda vez que terminar de escrever um programa, pare e pense sobre ele. E isso não significa apenas pensar em refatoração ou otimização, estou dizendo pensar em qual foi o processo lógico que levou a sua criação. Em resumo, descubra qual paradigma influenciou seu pensamento na hora de programar.

"Aqui eu usei um for, mas o que isso significa exatamente e porque eu pensei em usa-lo nesse contexto? Que paradigma pode ser associado a ele? Há outras formas de modelar a solução desse problema?".

No exemplo citado, um for é uma instrução que representa uma estrutura de iteração, conceito vindo do paradigma estruturado que modela um programa a partir de três estruturas de controle: estrutura sequêncial, estrutura de seleção e estrutura de iteração. Tal paradigma se basea no paradigma imperativo, já que uma estrutura de iteração desvia o fluxo de execução de um programa até que uma determinado condição se torne falsa, ou seja, depende do estado do programa.

"Nesse trecho eu chamei uma função chamada print. Qual paradigma pode ser associado a isso?"

Funções nas linguagens imperativas, são apenas um outro nome para procedimentos, conceito vindo do paradigma procedural que também é baseado no paradigma imperativo. Um procedimento é um trecho de código que pode ser invocado, fazendo com que o fluxo de código seja desviado para executa-lo. Após o término da execução, ocorrerá outro desvio no fluxo para retomar a execução a partir da instrução seguinte a invocação do procedimento.

Podemos então concluir que a pessoa hipotética está usando o paradigma imperativo como base para a elaboração de seus programas. Ela está acostumada a dividir o problema em problemas menores usando procedimentos do paradigma procedural, que quando computados resolvem o problema por completo. Também modela suas soluções através de uma descrição detalhada do fluxo de execução através de estruturas de controle do paradigma estruturado.

Ela pode não pereceber, mas está pensando nesses paradigmas. O desconhecimento deles faz com que a pessoa seja dominado por paradigmas ao invés de domina-los, causando uma limitação em seu processo de elaboração lógica.

Mesmo que a programação esteja inerentemente ligada a linguagens, o mais próximo que você pode chegar de tornar o seu pensamento independente delas, é de entender quais paradigmas você está usando para elaborar seus programas, e como eles são a verdadeira matéria prima que influenciam seu processo de elaboração lógica.

Uma dica prática seria estudar uma linguagem que foque em um paradigma específico, enquanto paralelamente, olhar mesmo que de forma superficial como outras linguagens o representam. Por exemplo, para estudar orientação a objetos, você pode estudar C++ ao mesmo tempo que olha superficialmente como JavaScript representa o mesmo paradigma. JavaScript usa prototipagem para objetos ao invés de classificação como no C++.

É um bom exercício resolver um mesmo problema se utilizando de vários paradigmas. Por exemplo, criar um programa que calcule os últimos dois digitos de um CPF utilizando o paradigma imperativo em C e usando o paradigma declarativo em Haskell.

Também é interessante tentar criar um programa com um paradigma em uma linguagem que não foi "desenhada" para ele. Por exemplo, escrever um programa orientado a objetos em C, que como já dito, é possível.

Conseguem compartilhar links e/ou sites que sejam úteis para esse propósito? E principalmente, como VOCÊS aprenderam isso?

Eu não conheço sites específicos para isso. Geralmente estudo por livros que já possuem exercícios, especialmente aqueles de uma linguagem específica. Mas, se estabelecemos que o estudo da lógica de programação implica no estudo de paradigmas, pesquisar no Google exercícios de programação orientada a objetos, funcional, estruturado pode dar bons resultados.

Respondendo a segunda pergunta, eu não apendi kkkkk. Na verdade eu sou iniciante e programo a apenas dois anos. Sei que pediu dicas para devs experientes, mas achei que podia contribuir com algo. Quando descobri a programação funcional, fiquei curioso e logo percebi que programar funcionalmente é como aprender a programar pela primeira vez, praticamente nada da minha experiência com linguagens imperativas serve. Mas resolvi me desafiar e comecei a estudar Haskell ontem (por isso meu código tá uma merda kkkk). Também achei uma boa deixa para treinar minha escrita já que vou ter que fazer meu TCC ano que vem. Dito isso, é bom levar o que eu disse com certa ressalva, não sou especialista no assunto e apenas expressei minha opinião.

Uma pincelada: considerando que o paradigma imperativo é presente e o mais usado em todas as linguagens de programação com grande volume de projetos, nao é equivocado, em termos praticos, dizer que, ao saber lógica, você programará em qualquer linguagem.

A chance de um programador qualquer ter que lidar com linguagens como Haskell ou linguagens Assembly é minima, a menos que a pessoa seja uma entusiasta, e neste caso, ela saberá de antemão com o que está lidando.

O principal ponto da minha crítica é que não é deixado claro que estruturas de controle, procedimentos e orientação a objetos estão relacionados a uma forma específica de programar. Fui ensinado na faculdade que lógica de programação era basicamente o paradigma imperativo. Quando me deparei com outras formas de pensar ou fui para algo mais teórico perecebi que era mais complexo que isso. Como Análise e Desenvolvimento de Sistemas é um curso a nível tecnólogo, que em tese, serve para formar mão de obra e não necessariamente cientistas como na Ciência da Computação, é justificavel que se assuma um olhar mais pragmático e ensine apenas o paradigma imperativo, pois como você mesmo disse, é de longe o mais popular, mas, mesmo que pragmaticamente digamos que ao saber o paradigma imperativo se programa em qualquer linguagem, há pessoas que acreditam nisso de forma literal, e que teoricamente todas as linguagens se resumem a ele. Evitaria muita confusão e promoveria um olhar teórico muito mais sólido se fosse deixado claro que o que se está aprendendo é a lógica do paradigma imperativo. Percebo que em linguagens impertivas, mesmo que meus amigos consigam usar o mínimo de lambda, ainda acaba por ser um ponto de dúvida e a maioria não consegue produzir códigos mais elaborados justamente por não entender que é outra forma de pensar. Hoje em dia todo mundo se beneficiaria pelo menos um pouco do estudo da programação funcional.