Quando estamos codificando um sistema, nem sempre nos preocupamos com o trabalho feito por trás daquela programação. No entanto, saber qual método de tradução será usado pode ajudar bastante na hora de distribuir um projeto, especialmente quando se trata de portabilidade ou desempenho. É nessas horas que um conhecimento prévio sobre qual técnica de implementação pode ser usada para a linguagem escolhida pode ajudar. Linguagens compiladas por um compilador e interpretadas têm muito por trás do que apenas converter o código para linguagem de máquina.
Lembrando que não existe uma “linguagem compilada” ou “linguagem interpretada” literalmente. Salvo alguma especificação, todas podem passar por esses dois processos. No entanto, você pode usar esses termos, já que é subentendido por todos da área, só #ficaadica.
Recado dado, podemos seguir para o artigo! A pergunta de hoje é: qual utilizar na minha aplicação?
O que é compilador
Antes de começar, vamos entender o que é um compilador. De modo geral, um compilador é um sistema capaz de traduzir outro sistema em linguagem de alto nível para um de linguagem simbólica, mantendo uma semântica semelhante.
Para ser capaz de cumprir suas tarefas, o compilador deve conseguir desempenhar duas etapas de atividades fundamentais: análise e síntese.
A etapa de análise consiste em analisar o código fonte, reconhecendo a estrutura e o significado da linguagem de alto nível a ser traduzida. Aqui também são verificados possíveis erros de digitação do código, tipagem incorreta e até a sintaxe.
Após estar tudo ok na análise, a etapa de síntese começa. Ela consiste em sintetizar a linguagem, ou seja, de fato “traduzir” o programa que anteriormente estava na linguagem complexa, que foi analisada, para assembly ou código binário.
Esse processo, no fim, gera um novo arquivo com todo o código compilado, em linguagem de máquina, pronto para ser executado pelo seu computador, como se fosse um compactado .zip.
Veja esse vídeo caso tenha ficado alguma dúvida no conceito geral de um compilador. No geral, é algo simples de ser entendido, assim como o exemplo que ele usa.
Exemplos de compiladores
Para exemplificar, vou trazer alguns exemplos de compiladores para você saber mais ou até mesmo fazer o download.
O primeiro é o GCC. Ele é um pacote de programas responsáveis por fazer a compilação do seu código em C, C++, Objective-C, Fortran, Ada, Go, and D.
O NetBeans é uma IDE mas também compilador de códigos em diversas linguagens, como C, C++ e PHP.
Uma alternativa online e que também vai servir é o Ideone. Suporta mais de 60 linguagens.
Em resumo, a maioria das IDEs possuem compilador na sua estrutura de ferramentas, provavelmente a que vocÊ usa também!
O que é interpretador de código
Agora vamos para o interpretador. Diferente do compilador, ao utilizarmos um interpretador para transformar nosso código em linguagem de máquina, não “traduzimos” o programa inteiro, gerando outro arquivo.
O interpretador funciona, de certa forma, em tempo real. Ao executar o código através de um interpretador, você estará convertendo seu código fonte em código alvo (ou linguagem de máquina) linha por linha.
Já em seguida, a sua máquina irá executar o que foi convertido e não será criado nenhum tipo de arquivo posterior.
Isso ocorre através de um software interpretador, que controla o fluxo de trabalho e garante que tudo sairá como planejado.
Dessa forma, é possível detectar erros no momento em que eles passam pelo interpretador. Você saberá onde está esse problema e poderá debugar o código mais facilmente.
Por outro lado, como é trabalhado linha por linha, você deve imaginar que a performance não é a mesma que executar um código que já está 100% compilado, certo? Certo.
Interpretador vs compilador
Basicamente, um compilador traduz todas as suas linhas de código para outra linguagem – normalmente, uma de alto nível para outra de baixo nível (Assembly ou linguagem de máquina). Delphi, Rust, C++ e Swift figuram na lista de compiladas.
O interpretador faz esse trabalho de conversão aos poucos, sempre que uma declaração ou função é executada, por exemplo. MATLAB, Lisp, Perl e PHP são apontadas como interpretadas.
Embora isso dê a sugestão de que essa interpretação leve muito tempo para ficar pronta, o compilador também caminha bastante para ser convertido.
Alguns dos passos são: Análise léxica e semântica, pré-processamento, análise e otimização de código e, ao fim, geração do produto final.
Porém, uma vez compilado, ele não precisará fazer mais essa tradução – a menos que o código precise ser alterado.
Beleza, entendi os conceitos e sei o que cada um faz. Qual eu devo escolher?
Qual deles eu devo usar
Interpretador ou compilador, então? Pode parecer descuidada ou sem preocupação com as peculiaridades específicas de um sistema, mas a resposta básica é assustadoramente simples e completa: tanto faz. Quê?
Isso mesmo, tanto faz. Na verdade, qualquer linguagem pode ser compilada ou interpretada, pois esses conceitos não são próprios de uma linguagem em si, ou seja, não é um atributo ou característica intrinsecamente ligada a ela.
Ambos são técnicas de implementação para executar (ou, melhor ainda, traduzir) o seu código fonte e podem ser aplicados para qualquer linguagem, desde que se tenha o tradutor necessário para tal.
Uma atitude comum a alguns programadores que reforça essa ideia é construir seu código em um editor de texto comum, tipo o bloco de notas. Depois de construído, o arquivo era usado em um programa para executado (quem nunca montou um HTML dessa forma?).
Que tal: As melhores linguagens de programações pro dev iniciante aprender!
Compilada e Interpretada: vantagens e desvantagens
Uma das grandes vantagens dos compiladores é sua velocidade de execução, muito em função do que já falamos sobre traduzir todo o código de uma vez. Não precisar fazer a conversão toda vez que o sistema é executado dá uma eficiência muito maior do que um interpretador.
Uma compilação costuma dar resultados mais confiáveis graças às suas diversas etapas de validação e otimização. Uma checagem de tipos estáticos, por exemplo, é comum em compiladores, e identifica diversos erros de programação antes do executável ser gerado.
Por sua vez, enquanto uma linguagem compilada precisa fazer essa tradução para cada plataforma destinada (como versões específicas do Adobe Photoshop para Windows, Linux e Mac), a interpretação, por poder rodar em tempo de execução, é independente.
Não importa se é Linux ou Mac; basta ter o Python instalado na máquina que ela vai rodar seu código em Python.
Além disso, a capacidade de execução em runtime permite utilizar reflexão (ou seja, examinar e modificar sua própria estrutura em tempo de execução) e tipagem dinâmica (capacidade de escolher dinamicamente o tipo de uma variável, não exigindo uma declaração), uma característica básica do PHP, por exemplo.
Verificar e modificar o código de uma linguagem interpretada também é mais fácil, já que basta abrir o arquivo e ver o que tem escrito. Para fazer o mesmo com uma biblioteca compilada, é preciso utilizar um descompilador (o Java Decompiler já salvou minha vida quando precisei abrir uma biblioteca de um projeto herdado).
Isso, no entanto, também pode ser visto como uma desvantagem, pois qualquer pessoa com um mínimo de conhecimento pode ver a implementação de um JavaScript embutido numa página web ou até mesmo realizar uma injeção de código.
Por isso mesmo, a segurança é sempre uma preocupação em momentos assim.
Bitbucket vs GitHub: quem vence a batalha?
E quanto aos scripts?
Essa preocupação com a segurança e com a flexibilidade das aplicações, entre outros motivos, foram responsáveis a dar vida para aqueles pequenos trechos de código (ou scripts) baseados em Java, transformando-os em uma linguagem propriamente dita (linguagem de script) como o JavaScript.
Por outro lado, sua principal característica ainda é a interpretação do código pelo lado do cliente, através do navegador (sim, uma das funções básicas de um browser é interpretação), sem que precise ser executado no servidor.
Mesmo assim, isso não impede que um script seja compilado. Na verdade, os games fazem uso disso com certa frequência, seja para adicionar novas funcionalidades ou corrigir problemas com a aplicação de atualizações.
Lua também é uma linguagem de script extremamente poderosa, bem famosa pela construção de macros e add-ons compilados por jogadores de World of Warcraft. Porém, como ela gera bytecodes como resultado, além de ter tipagem dinâmica e gerenciamento automático de memória, é mais comum sua associação com interpretadores.
Git, SVN e CVS — comparação dos principais VCS
Just in Time – JIT
Por terem características diferentes (e, de certa forma, complementares), algumas linguagens fazem uso de compliadores e de interpretadores, sendo Java o maior exemplo disso.
Implementações do Java costumam compilar o código a fim de gerar um bytecode, ao invés de instruções em linguagem de máquina. A partir dessa conversão, o bytecode pode ser interpretado por uma JVM (Java Virtual Machine), que pode ser instalada em qualquer máquina. Assim, o Java combina a confiabilidade e a otimização da compilação com a flexibilidade da interpretação.
Uma evolução desse modelo está no conceito de compilação just-in-time (JIT). O JIT nada mais é que uma compilação feita em tempo de execução, ao invés de antes de rodar a aplicação (ahead-of-time). Ela pode ser feita por arquivo, função ou até fragmentos de código, traduzindo dinamicamente essas partes e executando diretamente na memória.
Ou seja, o JIT mescla os dois tipos de tradutores. Ele compila apenas parte do código que será usado na execução e interpreta essa fração. Os ganhos de performance são notáveis.
Mas essa técnica ainda apresenta algumas desvantagens como o atraso na inicialização (“startup delay”), pois ainda é necessário carregar os primeiros blocos do código para serem compilados.
Assim, quanto mais o JIT for otimizado, melhor o código gerado, mas também esse atraso fica maior.
Uma boa dica para conhecer mais sobre o assunto está no artigo “Compilação Just-In-Time: Histórico, Arquitetura, Princípios e Sistemas“, escrito pelos então estudantes George Souza Oliveira e Anderson Faustino da Silva, da Universidade Federal do Maringá.
Quando usar cada uma
Como dito acima, as linguagens podem ser tanto interpretadas quanto compiladas, então vai da necessidade da aplicação o método de execução, independentemente da linguagem escolhida.
Sistemas web frequentemente usam linguagens interpretadas para serem feitos pela facilidade de manutenção e pela necessidade de portabilidade.
Um problema que até recentemente afetava bastante essas páginas era a falta de padronização do mercado: um site que era bem visualizado no Google Chrome podia ficar com um layout esquisito no Internet Explorer.
Muitas vezes, os programadores tinham que recorrer a scripts, alterações de layout ou de código diversas vezes, então imagina só ter que lidar com diversas distribuições de compilação.
Ao contrário, realizar as modificações necessárias e vê-las aplicadas pouco tempo depois é bem mais interessante para desenvolvedores e usuários.
Em contrapartida, aplicações compiladas são bem mais seguras e rápidas, e são recomendadas para quem exige um desempenho considerável. Mas também traz alguns incômodos, como ter que reinstalar o sistema quando uma versão mais atualizada fica disponível.
Além disso, várias das linguagens mais aceitas atualmente utilizam podem gerar bytecode como formato intermediário – como Java, C#, Python e Ruby – sem problemas. Assim, é possível ficar em um meio-termo, garantindo as melhores vantagens de cada um para seu sistema.
Entendeu o que é e o que faz cada um dos métodos de tradução? Se ficou alguma dúvida, pode comentar abaixo que iremos responder!