Aprenda Programação: Bibliotecas
Créditos para a imagem: Imagem criada pelo autor usando o programa Spectacle.
Pré-Requisitos
Na introdução sobre ambientes de desenvolvimento, indiquei Python, Lua e JavaScript como boas escolhas de linguagens de programação para iniciantes. Posteriormente, comentei sobre GDScript como opção para pessoas que tenham interesse em programar jogos digitais ou simulações. Para as atividades de introdução a programação, você precisará de, no mínimo, um ambiente de desenvolvimento configurado em uma das linguagens anteriores.
Caso queira experimentar programação sem configurar um ambiente, você pode usar um dos editores online que criei:
Contudo, eles não possuem todos os recursos dos interpretadores para as linguagens. Assim, cedo ou tarde, você precisará configurar um ambiente de desenvolvimento. Caso precise configurar um, confira os recursos a seguir.
Assim, se você tem um Ambiente Integrado de Desenvolvimento (em inglês, Integrated Development Environment ou IDE) ou a combinação de editor de texto com interpretador, você está pronto para começar. Os exemplos assumem que você saiba executar código em sua linguagem escolhida, como apresentado nas páginas de configuração.
Caso queira usar outra linguagem, a introdução provê links para configuração de ambientes para as linguagens C, C++, Java, LISP, Prolog e SQL (com SQLite). Em muitas linguagens, basta seguir os modelos da seção de experimentação para adaptar sintaxe, comandos e funções dos trechos de código. C e C++ são exceções, por requerem o uso de ponteiros para acesso à memória.
Sobre Rodas de Gigantes
O tópico sobre Arquivos e Serialização (Marshalling) marcou o final dos principais conceitos básicos fundamentais para programação, que podem ser aplicados para qualquer (ou quase todo) paradigma. Deste tópico em diante, pode-se começar a explorar recursos complementares de programação. Por sinal, recursos complementares muitas vezes estendem ou provêm funcionalidades adicionais para os conceitos fundamentais.
Por exemplo, quando se cria subrotinas (como funções ou procedimentos) ou registros (ou classes), é possível criar código reusável. Pode-se chamar uma subrotina diversas vezes em um programa; similarmente, pode-se declarar várias instâncias de variáveis do tipo de um registro.
Logo, quando se cria código reusável, por que não aproveitá-lo em múltiplos projetos? De fato, é possível copiar definições de subrotinas e registros em arquivos de código-fonte para novos programas. Inclusive, alguns exemplos adotaram essa abordagem ao longo de tópicos anteriores (por exemplo, para reusar subrotinas em Lua ou para codificação de arquivos binários).
Contudo, copiar e colar trechos de código não é uma boa abordagem para reuso de software. Como mencionado em subrotinas e registros (e em vários outros tópicos), duplicar código normalmente não é uma boa idéia. Por exemplo, caso se identifique um erro de implementação no código duplicado, é preciso corrigir todos os arquivos em que se usou o código duplicado.
Uma abordagem melhor é definir código reusável em arquivos de código-fonte próprios. Assim, quando se quiser usar o código, basta duplicar o arquivo todo. Quando o código do arquivo mudar, basta atualizar o(s) arquivo(s) em projetos em que for(em) chamado(s). Ainda melhor, é possível manter os arquivos com código reusável em um diretório comum (ou hierarquia de diretórios) para referenciá-los em todos os projetos futuros. Sem duplicações, toda correção e melhoria beneficiará todos os projetos que usem a funcionalidade.
Assim como bibliotecas reúnem artefatos (como livros, revistas e periódicos) que contém conhecimento pronto para ser consultado, bibliotecas de programação (doravante bibliotecas) fornecem código ponto para uso.
Bibiliotecas, Arcabouços (Frameworks) e Motores (Engines)
Em alguns tópicos anteriores, comentou-se brevemente sobre criação e uso de bibliotecas para algumas linguagens de programação. Por exemplo, a configuração de ambiente para a linguagem C (e C++) abordou rapidamente bibliotecas, ligação (link) e ligador (linker), cabeçalhos e diretórios de cabeçalhos, sistemas de construção, e cross-compilation (compilação feita em uma plataforma para a geração de código outra plataforma diferente). Convém, agora, expandir o conceito de bibliotecas.
Um arquivo auto-contido com código-fonte reusável define uma biblioteca. De forma bastante simplificada, uma biblioteca é um arquivo com definições de constantes, subrotinas, registros (classes e tipos de dados) e variáveis globais (ou em um escopo próprio). Definições podem incluir o código-fonte necessário para a implementação da biblioteca ou apenas assinaturas, por meio de uma Interface de Programação de Aplicação (do inglês, Application Programming Interface ou API).
Um módulo (module) é uma forma de distribuir vários arquivos ou bibliotecas como um único pacote. Tecnicamente, a definição, a complexidade e os recursos podem variar. Na forma mais simples, um módulo é uma biblioteca de bibliotecas (ou uma única biblioteca). Em abordagens mais sofisticadas, módulos fornecem recursos e informações adicionais, como versões, isolamento de código externo e espaços de nomes (do inglês, namespaces) para evitar conflitos de variáveis, subrotinas ou registros com o mesmo nome em escopo global. Além disso, pode-se combinar bibliotecas e ferramentas relacionadas em um pacote chamado de kit de desenvolvimento de software (do inglês, Software Development Kit ou SDK ou devkit).
Em particular, bibliotecas não definem um ponto de entrada (por exemplo, uma função main()
).
Uma biblioteca que define ponto de entrada é, normalmente, chamada de arcabouço (framework) ou de motor (engine), dependendo da abordagem escolhida.
Em um framework ou engine, é comum (embora não seja obrigatório) que o ponto de entrada seja pré-definido. O framework ou engine define uma subrotina (ou métodos ou objeto) que deve ser definida com o código específico do programa. Isso é conhecido como inversão de controle (do inglês, inversion of control ou IoC), também chamado de princípio de Hollywood. A comparação ocorre devido ao ditado de que, em Hollywood, uma atriz ou um ator não entram em contato com estúdio; o estúdio é que entra em contato com a pessoa.
Em programação, a inversão de controle pode ser implementada, por exemplo, usando programação orientada a eventos (do inglês, event-driven programming). Um recurso comum em programação orientada a eventos são subrotinas chamadas de callbacks. Um callback é implementado pela programadora ou programadora da aplicação, com o código desejado para execução em seu programa. A subrotina é chamada pelo framework ou engine em momento apropriado (ao invés de pela programadora ou programador), quando ela deva ser executada.
Por exemplo, em JavaScript, usou-se um callback para a leitura de arquivos.
Para a leitura de um arquivo texto inteiro, definia-se uma subrotina a ser passada para onload
.
A subrotina definida em onload
era chamada em momento apropriado por readAsText()
, após o carregamento de dados.
let leitor_arquivos = new FileReader()
leitor_arquivos.onload = function(evento) {
// Código do callback onload().
let conteudo = evento.target.result
console.log(conteudo)
// ...
}
leitor_arquivos.readAsText(arquivo_texto)
Programação orientada a eventos é um paradigma de programação comum e versátil, que pode levar a criação de programação com qualidades desejáveis (como alta coesão e baixo acoplamento).
Em um motor ou framework, a estrutura normalmente define nomes específicos para callbacks importantes.
Por exemplo, em framework para processamento de arquivos poderia existir uma convenção de que o callback que receba uma subrotina para processamento de um arquivo de texto lido deve chamar on_file_loaded()
(algo como "quando o arquivo for carregado").
Assim, um exemplo de uso para criar uma aplicação usando o framework proposto poderia ser:
// Código da aplicação usando o framework...
function on_file_loaded(conteudo) {
console.log(conteudo)
// ...
}
O framework ou engine implementaria o restante do código e chamaria a subrotina on_file_loaded()
no momento apropriado.
Ele assume que a definição exista, para chamá-la (quando necessário) com os valores calculados.
// Código do framework...
class ProcessadorArquivos {
constructor(on_file_loaded) {
this.on_file_loaded = on_file_loaded
// ...
}
le_arquivo() {
// Abre arquivo, acessa dados, salva em conteudo...
let conteudo = "Olá, meu nome é Franco!"
this.on_file_loaded(conteudo)
// ...
}
}
// Implementação alternativa, com callback como subrotina global ou em namespace adequado:
class ProcessadorArquivos {
le_arquivo() {
// Abre arquivo, acessa dados, salva em conteudo...
let conteudo = "Olá, meu nome é Franco!"
if (on_file_loaded) {
on_file_loaded(conteudo)
}
// ...
}
}
A programadora ou o programador da aplicação não precisa saber como o framework está implementado.
Ela ou ele apenas precisam saber que deve implementar uma subrotina para o callback on_file_loaded()
.
De fato, isso é o que ocorre, por exemplo, em código GDScript.
O código definido em subrotinas como _ready()
e _init()
são callbacks chamados pelo motor Godot Engine para a execução do código-fonte implementado em momento apropriado.
Assim, o conteúdo desta seção introduz os nomes para alguns recursos e conceitos que já foram utilizados em tópicos anteriores (embora, tecnicamente, trata-se de métodos virtuais e polimofismo em GDScript, como se pode aprender pela leitura do cabeçalho de código-fonte C++ para a classe Node
).
Por sinal, esta é uma grande vantagem de código-aberto e software livre: pode-se aprender com a leitura do código-fonte de programas ou bibliotecas que você use.
Reinventar a Roda ou Estar Sobre Ombros de Gigantes?
Além da biblioteca padrão de linguagens de programação, pode-se usar bibliotecas externas (também chamadas de dependências ou biblioteca desenvolvida por terceiros -- do inglês third-party). Por exemplo, a versão em Lua para serialização usando aquivos JSON usou uma biblioteca externa para converter cadeias de caracteres para o formato JSON. Isso foi necessário porque Lua não fornecia o recurso na biblioteca padrão. Embora fosse possível implementar uma solução própria, o uso da biblioteca permitiu focar no problema em questão (ilustrar o uso de arquivos) ao invés de implementar o código para conversão de texto para o formato textual de JSON. Por outro lado, como o recurso não está na biblioteca padrão ou do código de programa, o projeto não funcionará até que se obtenha o código da biblioteca externa (por isso o nome dependência).
Em programação, a expressão "reinventar a roda" é comum, especialmente na negação "não se deve reinventar a roda". O contrário de "não reinventar a roda" é "não inventado aqui" ("not invented here" ou NIH).
As expressões remetem a idéia de que, normalmente, é mais vantajoso usar código existente que implementar uma solução para um problema, caso tal solução já exista. Em outras palavras, normalmente é preferível usar uma biblioteca (ou framework, motor, ou qualquer variação) a criar uma nova que desempenhe as mesmas operações. Da mesma forma, costuma ser preferível usar uma solução existente a criar uma solução própria apenas porque a solução existente "não foi inventada aqui". Evidentemente, existem exceções e critérios para escolha de boas bibliotecas, conforme será discutido.
Antes, para explorar uma terceira frase, pode-se citar a expressão "sobre ombros de gigantes", popularizada por Isaac Newton como "se eu vi mais longe, foi por estar sobre ombros de gigantes". Usar bibliotecas de qualidade permite desenvolver código mais rapidamente por estar sobre ombros de gigantes; logo, por que não parafrasear como sobre usar rodas de gigantes?
De fato, rodas de gigantes tendem a ser preferíveis; porém, existem situações em que é preferível criar as próprias bibliotecas. Uma delas é aprendizado. Criar uma solução própria para fins de aprendizado sempre é algo válido. Por outro lado, usar a solução criada quando existem outras melhores é algo discutível, requerendo razões técnicas e justificativas apropriadas. Também é discutível usar bibliotecas de utilidade questionável ou de implementação trivial apenas para usar uma biblioteca. Portanto, convém considerar alguns aspectos para se decidir de forma embasada pela adoção (ou não) de uma biblioteca. Afinal, programação é parte arte, parte ciência; decisões técnicas requerem análises pela parte da científica.
Licenças
Aviso. Este conteúdo não é aconselhamento legal. Para uso profissional ou comercial de bibliotecas ou software, você deve procurar conselhos de advogados ou advogadas especializados em software.
A existência pública de uma biblioteca ou de código que resolva um problema não resulta, necessariamente, em permissão para usá-lo. Código-fonte é normalmente protegido por leis de proteção de direito autoral e de propriedade intelectual (em alguns países, programas também podem ser patenteáveis). Assim, é necessário ter permissão das autores e dos autores para usar o código deles em seus programas. Portanto, algo essencial para se considerar a adoção de uma biblioteca é a licença do código-fonte.
Algumas licenças são restritivas. Elas podem requerer o pagamento de taxas de licenciamento para uso ou impor uma determinada licença em seu programa. Licenças que exigem pagamento, ou condições para uso ou redistribuição são ditas proprietárias (regidas por copyright). Licenças que não impõe condições de distribuição são regidas por copyleft (embora elas ainda possam impor outras restrições).
Copyleft não costuma impor barreiras financeiras para uso e distribuição de código-fonte, mas pode ter outras exigências. Por exemplo, copyleft pode ser forte, quando impõe que todo trabalho derivativo seja licenciado usando a mesma licença do código em questão (ou uma compatível). Isso é conhecido como licença viral e é o caso de licenças como GNU General Public License (GPL) e Affero General Public License (AGPL). As licenças anteriores garantem (legalmente, não necessariamente eticamente) que todo o código que use as bibliotecas continuem com a mesma licença -- ou seja, serão de código aberto. Caso você não queira compartilhar seu código-fonte, ou se você não pretende exigir que pessoas que usem seu código tenham que tornar público os projetos derivativos criados por elas, isso pode não ser desejável.
Outras licenças possuem copyleft fraco. Para copyleft fraco, algumas licenças como GNU Lesser General Public License (LGPL, caso usada com link dinâmico; caso contrário, ela é equivalente à licença GPL) e Mozilla Public License (MPL) exigem que a solução derivativa utilize a mesma licença. Outras, também chamadas de licenças permissivas, podem ser usadas mediante atribuição (citação e inclusão da licença, de seus termos e dos autores) para quaisquer fins, desde que não se altere a licença original do código delas. Exemplos de licenças assim são MIT (ou X11), Berkeley Software Distribution (BSD) e Apache.
Existem diversas outras licenças para as categorias anteriores. Também existem projetos licenciados com múltiplas licenças (como dual-licensing ou algo como licença dupla). Por exemplo, a biblioteca pode ser gratuita para uso pessoal, mas paga para uso comercial (ou paga a partir de uma determinada receita). Outros projetos usam licenças como Unlicense, com o objetivo de tornar o trabalho domínio público (ao invés de impor uma copyright ou copyleft).
Assim, o uso de uma biblioteca externa pode impor restrições para o licenciamento de seu próprio programa. Além disso, deve-se verificar se as licenças de diferentes bibliotecas são compatíveis entre si e com os objetivos para seu programa.
Em suma, por vezes existe uma excelente biblioteca disponível, mas cuja licença inviabilize o uso para seus objetivos. Sobretudo caso se deseje fazer uso comercial de uma biblioteca ou de um programa desenvolvido usando uma biblioteca, é necessário, pois, atenção à escolha para evitar problemas legais no futuro.
Qualidade e Maturidade
Via de regra (embora existam exceções), a biblioteca padrão de uma linguagem de programação costuma ser de alta qualidade. Todavia, a qualidade de uma biblioteca externa é variável.
Algumas bibliotecas são novas, instáveis e/ou experimentais. Elas podem não ser recomendáveis para projetos que requeiram estabilidade. Em particular, APIs de bibliotecas novas tendem a mudar, exigindo modificações no código-fonte que as utilizarem. Algumas bibliotecas foram boas no passado, mas estão desatualizadas ou sem mantenedores. Isso torna a adoção arriscada; elas podem resolver o problema hoje, mas não há garantias de que continuarão a funcionar no futuro. Outras bibliotecas são robustas, estáveis, testadas (preferencialmente com uma suíte de testes automáticos), mantidas, e usadas por milhares de projetos. Em particular, a existência de documentação de qualidade é desejável, pois é importante para uso efetivo de bibliotecas.
Logo, a maturidade e o prospecto futuro de uma biblioteca são critérios relevantes para motivar (ou impedir) sua adoção. Antes de adotar uma biblioteca, convém analisar sua viabilidade a médio e longo prazo. Isso é particularmente importante caso seu projeto seja longo e será mantido por anos. Caso se use uma biblioteca que deixe de ser atualizada ou cesse de existir, você possivelmente terá problemas no futuro.
Neste sentido, bibliotecas de código aberto (open source) possuem a vantagem inerente da disponibilidade do código-fonte. Na pior das hipóteses, é possível ramificar o projeto original (fork) e mantê-lo. Além disso, caso você encontre problemas, você pode eventualmente usar suas habilidades de desenvolvimento de software para corrigi-los e enviar suas correções para o projeto original. Assim, você e outras pessoas poderão usufruir de correções e melhorias de forma colaborativa. De fato, uma comunidade participativa e ativa de colaboradores é um bom sinal quando se considera a adoção de uma biblioteca externa.
Por outro lado, bibliotecas de código aberto não possuem garantias. Caso garantias sejam importantes para um projeto, pode ser necessário optar por soluções proprietárias que as ofereçam.
Segurança
Em última análise, só é possível confiar em código que você leu e entendeu completamente, ou que foram auditados (e cuja cópia foi obtida sem modificações desde a inspeção). Para maior transparência e honestidade, por vezes não é possível sequer confiar completamente em si mesma ou si mesmo. Poucas organizações seguem metodologias formais que garantam a corretude de uma especificação ou de programa. Em geral, software é implementado e (espera-se) testado. A garantia são os testes; ou seja, pode-se apontar que o sistema possui falhas, não é possível afirmar que esteja correto.
Praticamente todo software minimamente complexo possui problemas. Eles podem não ser conhecidos, mas, possivelmente, existem. Isso é válido tanto para seu próprio código quanto para todas as bibliotecas usadas por ele (padrão da linguagem e externas).
Neste sentido, o uso de uma biblioteca externa provê vantagens e desvantagens. Uma vantagem importante é que bibliotecas populares são usadas e testadas por milhares de projetos. Assim, elas tendem a funcionar bem para casos de uso comuns e freqüentes.
Reciprocamente, casos de uso incomuns tendem a ser menos testados e, portanto, podem apresentar problemas. Em particular, problemas de segurança.
Logo, uma biblioteca externa popular e de qualidade tende a ser mais segura que uma biblioteca própria devido a freqüencia de uso.
Contudo, todo uso assume boa-fé da equipe de desenvolvimento da biblioteca, assim como da origem usada para a obtenção dos arquivos.
Mesmo que o código da biblioteca seja relativamente seguro e correto, os arquivos obtidos podem estar comprometida.
Isso pode ser particularmente agravado em sistemas Web que utilizem bibliotecas hospedadas remotamente, fora de seu controle.
Por exemplo, o gerenciador de pacotes npm
para JavaScript apresentou alguns problemas e foi alvo de ataques ao longo dos anos.
Em especial, em janeiro de 2022, o mantenedor de uma biblioteca popular para JavaScript chamada colors
alterou a implementação adicionando um laço infinito, comprometendo o funcionamento de diversos websites que utilizam a versão mais recente da biblioteca.
Evidentemente, equipes de desenvolvimento que usaram a última versão da biblioteca sem verificações e testes partilham da responsabilidade. O ponto, entretanto, é que desenvolvimento de software comumente assume boa-fé das partes envolvidas. Em outras palavras, confiança.
Programação exige cuidados e responsabilidade. Como habitualmente, minha recomendação para desenvolvimento de software é suspeitar de tudo e de todos (inclusive de você e de mim mesmo). Sempre que se usar código externo, é prudente inspecionar o código para verificar se ele realmente faz o que afirma fazer. Exceto caso se tenha acesso ao código-fonte da biblioteca e inspecione-o a cada modificação, não há garantias sobre a segurança da biblioteca. Para se possuir controle total sobre uma solução, pode ser necessário evitar o uso de código externo.
Tamanho e Dependências Necessárias pela Biblioteca
Por vezes, existem bibliotecas de alta qualidade que resolvem um problema. A licença é adequada, o projeto é viável e maduro, o código é estável e (pelo que se sabe) seguro. A escolha parece perfeita.
Infelizmente, existe um fato adicional que pode ser relevante para a adoção de uma biblioteca: os recursos (do sistema) que ela usa. Dependendo de requisitos de hardware para o seu projeto, o tamanho da biblioteca pode ser relevante. Existem bibliotecas com dezenas de milhões de linhas de código; seu projeto pode ter poucas centenas. Em outras palavras, seu programa que, potencialmente, poderia ser leve, pode tornar-se pesado devido à biblioteca usada (algo conhecido como bloat). Isso pode ocorrer tanto para o tamanho do projeto (que poderia ter poucos kilobytes, mas pode chegar a centenas de megabytes devido à biblioteca), quanto para o uso de recursos computacionais durante o uso. Ambas as situações podem ser comuns em linguagens como JavaScript.
Além disso, assim como seu projeto pode ter dependências externas, uma biblioteca também pode ter. Em outras palavras, o projeto pode depender de uma biblioteca que depende de outras bibliotecas (que dependem de outras bibliotecas...). Em particular, isso pode causar conflitos caso uma dependência precise de uma versão específica de uma biblioteca, mas outras dependência precise de outra versão dela. Ou seja, nem toda biblioteca é compatível com todas as outras bibliotecas.
Portanto, optar por bibliotecas com poucas dependências (ou, idealmente, nenhuma dependência exceto a biblioteca padrão) pode ser uma bom critério para escolha.
Sobre Ombros de Gigantes, com Pára-quedas para Saída de Emergência com Segurança
As subseções anteriores podem motivar a escolha por "not invented here". Toda escolha em computação possui contrapartidas (trade-offs). Como sempre, convém tomar decisões criteriosas e racionais para a escolha.
Entretanto, o desenvolvimento moderno de software requer estar sobre ombros de gigantes. Com boas práticas de programação, é possível abstrair o uso de uma biblioteca externa ou própria criando-se uma interface de programação (ou seja, uma API) própria. Assim, é possível iniciar um projeto usando uma biblioteca externa e alterá-la no futuro com modificações mínimas no código do projeto. Isso abre possibilidades como, por exemplo, usar uma biblioteca externa diferente ou criar uma implementação própria no futuro. Alternativamente, pode-se começar com uma implementação própria e trocá-la por uma biblioteca externa se/quando necessário.
Em outras palavras, convém estar sobre ombros de gigantes, assim como é prudente criar proteções para não cair em casos de instabilidades ou quando o gigante tornar-se (ou mostrar-se) inadequado para a tarefa em questão. Pode-se pensar o desenvolvimento como composto por blocos, conhecidos como componentes. As interfaces criadas definem os encaixes; os componentes são as peças ou blocos. Criando-se o encaixe, pode-se adicionar a peça à obra; ou seja, uma solução com baixo acoplamento e alta coesão. Subrotinas e registros são duas estratégias que podem ser exploradas para isso. Em linguagens de programação funcionais, é possível abstrair o uso com funções de ordem alta (high-order functions) como callbacks. Em linguagens de programação orientadas a objetos, pode-se usar polimorfismo. As técnicas serão apresentadas próximo ao final deste tópico. Em linguagens sem nenhum dos recursos anteriores, pode-se usar estruturas condicionais como alternativa (mais limitada).
Suas Próprias Bibliotecas
Exceto em exemplos para JavaScript (que por vezes também arquivos HTML), a maioria dos projetos de tópicos anteriores definiam o código-fonte em um único arquivo. Deste tópico em diante, bibliotecas permitirão dividir um projeto em múltiplos arquivos. Além de facilitar o reuso de código-fonte entre diferentes projetos, isso permite agrupar código de acordo com funcionalidades, possivelmente melhorando a manutenibilidade do projeto.
Para ilustrar a criação e uso de bibliotecas, o primeiro exemplo de cada linguagem implementará apenas um procedimento. Em seguida, o segundo exemplo implementará um registro e algumas operações. O registro será um ponto bidimensional (2D) da geometria.
Um ponto possui dois valores (coordenadas): , chamada de abscissa, e , chamada de ordenada, que são números (assumidos reais nos exemplos). Algumas operações com pontos e incluem:
Soma:
Subtração:
Distância:
Logo, operações entre pontos são feitas entre as respectivas coordenadas de cada ponto. As operações anteriores são comuns em computação gráfica (computer graphics ou CG) e simulações (por exemplo, para Matemática, Física e jogos digitais). Para antecipar tópicos futuros (mais interativos e multimídia) e uma primeira simulação deste tópico, convém defini-las aqui -- na forma reusável de uma biblioteca.
Criação e Uso de Bibliotecas em JavaScript
Existem duas formas principais de usar bibliotecas em JavaScript, dependendo do alvo:
- JavaScript para interpretadores de navegadores;
- JavaScript para interpretadores de linha de comando, como SpiderMonkey ou Node.js.
Todos os exemplos de tópicos anteriores assumem o uso de navegadores. Embora este tópico abordará Node.js, convém iniciar em navegadores. Para JavaScript em navegador, precisa-se de três arquivos:
- O arquivo com a definição da biblioteca (por exemplo,
biblioteca.js
); - O arquivo que usará a biblioteca (por exemplo,
projeto.js
); - Uma página para o projeto (por exemplo,
pagina.html
).
export function ola() {
console.log("Olá, meu nome é Franco!")
}
import { ola } from "./biblioteca.js"
ola()
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
</head>
<body>
<script src="./projeto.js" type="module"></script>
</body>
</html>
O arquivo projeto.js
ilustra o uso da biblioteca biblioteca.js
.
Também é possível carregar todo o código da biblioteca em uma variável, usando-a como referência para acessar subrotinas, classes e variáveis definidas na biblioteca.
import * as biblioteca from "./biblioteca.js"
biblioteca.ola()
Em ambos os casos, o carregamento da biblioteca utiliza import
(documentação).
Bibliotecas criadas como na forma anterior são chamadas de módulos (documentação), que correspondem à forma moderna de definir bibliotecas em JavaScript.
Caso se tenha interesse nos efeitos colaterais gerados por um módulo, pode-se usar import "/caminho/para/modulo"
.
Alguns guias de estilo sugerem usar a extensão .mjs
ao invés de .js
, para explicitar que se trata de um módulo.
Isso pode ser útil porque existe uma segunda forma de criar bibliotecas na linguagem, que é mais tradicional: pode-se fazer declarações globais e interpretar o arquivo com a biblioteca antes do arquivo com o código do projeto.
De qualquer forma, é preciso atentar que o código anterior não funcionará diretamente em um navegador. Por questões de segurança, uma página em um navegador não pode acessar arquivos da máquina sem uma ação iniciada pela usuária ou usuária (assim como ocorreu anteriormente durante a leitura de arquivos). Para usar o código, deve-se iniciar um servidor Web (Web server), que servirá (razão do nome) o conteúdo requisitado para um navegador usando um protocolo de Internet como Hypertext Transfer Protocol (HTTP) em uma porta (port) específica.
Caso o interpretador Python esteja instalado em sua máquina, uma forma simples de iniciar um servidor Web local consiste em navegar até o diretório dos arquivos, abrir um interpretador de comandos e usar o seguinte comando:
python -m http.server 8080 --bind 127.0.0.1 --directory ./
O valor 8080
define a porta usada para comunicação.
O comando iniciará o servidor HTTP http.server
do módulo Python com o mesmo nome (documentação).
Ele é um servidor básico que pode ser usado para fins de desenvolvimento (ou seja, não como uma opção para um servidor de produção).
Em seguida, a página poderá ser acessada em um navegador usando o endereço: http://localhost:8080/pagina.html
.
É importante especificar a porta 8080
.
Caso omitida, o navegador utiliza 443
para o protocolo HTTPS ou 80
para o protocolo HTTP.
Por exemplo, https://www.francogarcia.com:443/
e https://www.francogarcia.com/
são endereços equivalentes, assim como
http://www.francogarcia.com:80/
e http://www.francogarcia.com/
.
O valor de localhost
tipicamente é o endereço de Protocolo da Internet (Internet Protocol (IP) address) versão 4 (IPv4) 127.0.0.1
ou versão 6 (IPv6) ::1
.
Na Internet, todo nome de domínio é traduzido para um endereço IP por um Sistema de Nomes de Domínio (Domain Name System ou DNS).
Quando se usa um servidor Web, é possível referenciar a raiz do servidor usando uma barra (/
).
Assim, poder-se-ia escrever o arquivo HTML como:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
</head>
<body>
<script src="/projeto.js" type="module"></script>
</body>
</html>
No código original, o ponto (.
) referencia o diretório atual no interpretador de comandos.
Como o servidor Web foi iniciado com o diretório atual (./
) como raiz do servidor, os caminhos são equivalentes.
Caso você esteja confusa ou confuso sobre caminhos de arquivos, convém consultar Sistemas de Arquivos: Arquivos, Diretórios (Pastas) e Caminhos.
No mais, é importante atentar que a tag script
no arquivo HTML defina type="module"
, para especificar que o script pode usar módulos JavaScript.
Caso se omita o valor, o uso de import
falhará.
Se, por algum motivo, não se possa usar módulos, é possível carregar código JavaScript como valores globais e usá-los.
Para isso, pode-se usar a abordagem tradicional de bibliotecas em JavaScript como uma alternativa secundária (fallback).
O exemplo a seguir ilustra tal uso, embutindo código JavaScript em uma tag <script>
em HTML:
<script type="module">
// Código JavaScript usando módulos.
// Executado por navegadores com suporte a módulos,
// ignorado por navegadores sem suporte.
import { ola } from "./biblioteca.js"
// ...
</script>
<script nomodule>
// Código JavaScript sem módulos.
// Ignorado por navegadores com suporte a módulos,
// executado por navegadores sem suporte.
function ola() {
console.log("Olá, meu nome é Franco!")
}
// ...
</script>
Os próximos exemplos assumem o uso de um navegador moderno e atualizado; portanto, não será definido nomodule
como fallback.
Para o exemplo do ponto, pode-se criar três arquivos:
- O arquivo com a definição da biblioteca (no caso,
ponto.js
); - O arquivo que usará a biblioteca (por exemplo,
main.js
); - Uma página para usar os arquivos anteriores (desta vez
index.html
).
class Ponto {
constructor(x, y) {
this.x = x
this.y = y
}
}
const ORIGEM = new Ponto(0, 0)
function some(ponto1, ponto2) {
let resultado = new Ponto(ponto1.x + ponto2.x,
ponto1.y + ponto2.y)
return resultado
}
function subtraia(ponto1, ponto2) {
let resultado = new Ponto(ponto1.x - ponto2.x,
ponto1.y - ponto2.y)
return resultado
}
function distancia(ponto1, ponto2) {
let dx = ponto1.x - ponto2.x
let dy = ponto1.y - ponto2.y
let resultado = Math.sqrt(dx * dx + dy * dy)
return resultado
}
function escreva(ponto) {
// console.log("(" + ponto.x + ", " + ponto.y + ")")
console.log(`(${ponto.x}, ${ponto.y})`)
}
export {
Ponto,
ORIGEM,
some,
subtraia,
distancia,
escreva
}
// Para uso com Node.js:
// let ponto = require("./ponto")
// Para uso como módulo:
import * as ponto from "./ponto.js"
let ponto1 = new ponto.Ponto(1, 2)
ponto.escreva(ponto1)
let ponto2 = new ponto.Ponto(4, 6)
ponto.escreva(ponto2)
console.log(ponto.distancia(ponto1, ponto2))
let somados = ponto.some(ponto1, ponto2)
console.log(ponto.distancia(somados, ponto.ORIGEM))
let subtraidos = ponto.subtraia(ponto1, ponto2)
console.log(ponto.distancia(subtraidos, ponto.ORIGEM))
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Bibliotecas em JavaScript</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main>
<script src="./main.js" type="module"></script>
</main>
</body>
</html>
Para usar o projeto:
python -m http.server 8080 --bind 127.0.0.1 --directory ./
Em seguida, a página poderá ser acessada em um navegador usando o endereço: http://localhost:8080/index.html
.
Como a página chama-se index.html
, também é possível usar http://localhost:8080/
.
A implementação do registro e de subrotinas não deve apresentar surpresas.
Em escreva()
, a sintaxe `${x}`
permite escrever o valor de uma variável como cadeia de caracteres.
No caso, ela equivale a escrever "(" + ponto.x + ", " + ponto.y + ")"
.
O arquivo da biblioteca ponto.js
define registros, constantes e subrotinas, mas nenhum código executado automaticamente.
As partes que serão fornecidas pela biblioteca são exportadas usando export
.
Também é possível definir a exportação diretamente caso se prefixe a declaração com export
.
Similarmente, caso não se deseje usar um prefixo, pode-se usar desestruturação para carregar apenas os recursos desejados da biblioteca.
import { Ponto, distancia, some, subtraia, escreva, ORIGEM } from "./ponto.js"
let ponto1 = new Ponto(1, 2)
escreva(ponto1)
let ponto2 = new Ponto(4, 6)
escreva(ponto2)
console.log(distancia(ponto1, ponto2))
let somados = some(ponto1, ponto2)
console.log(distancia(somados, ORIGEM))
let subtraidos = subtraia(ponto1, ponto2)
console.log(distancia(subtraidos, ORIGEM))
No arquivo main.js
modificado, cada variável receberá o valor com o mesmo nome declarado em ponto.js
.
Para evitar conflitos de nomes em espaços de nomes (namespaces), é possível declarar um "apelido" (alias) para o recurso importado.
import * as p from "./ponto.js"
let ponto1 = new p.Ponto(1, 2)
import { escreva as e } from "./ponto.js"
e(ponto1)
Os nomes p
e e
podem ser modificados de acordo com a necessidade.
Por fim, pode-se definir um valor como padrão para a exportação usando-se export default
.
// ponto.js
// ...
export {
Ponto,
ORIGEM,
some,
subtraia,
distancia,
escreva
}
export default {
Ponto,
ORIGEM,
some,
subtraia,
distancia,
escreva
}
A alteração permite a importação como a seguir.
import RecebeDefault, { escreva } from "./ponto.js"
let ponto1 = new RecebeDefault.Ponto(1, 2)
RecebeDefault.escreva(ponto1)
ponto1.x = 100
escreva(ponto1)
No caso, RecebeDefault
recebe o valor de export default
, enquanto escreva
funciona como anteriormente.
Por fim, é uma prática comum minificar (minify) bibliotecas JavaScript antes de usá-la online. O intuito é reduzir o tamanho do arquivo servido pelo servidor Web, para acelerar o carregamento de uma página em navegadores (arquivos menores significam menos dados para download; conseqüentemente, menor tempo para obter o arquivo). Opções populares de ferramentas chamadas minifiers incluem UglifyJS, Closure Compiler e Esmangle.
Em suma, agora você pode dividir seus projetos em múltiplos arquivos. O uso de bibliotecas locais em JavaScript é mais complicado devido à necessidade de um servidor Web; as demais linguagens precisão apenas dos arquivos de código-fonte.
Criação e Uso de Bibliotecas em Python
Criar bibliotecas em Python é similar a JavaScript, embora mais simples, pois não é preciso de um servidor Web. Todos os arquivos usados podem ser locais, armazenados no sistema de arquivos da máquina.
O primeiro exemplo utiliza dois arquivos: biblioteca.py
, com o código da biblioteca, e projeto.py
, que utiliza o código implementado para a biblioteca.
def ola():
print("Olá, meu nome é Franco!")
from biblioteca import ola
ola()
É interessante notar que, como se usou módulos da biblioteca padrão em tópicos anteriores com import
(documentação), usar as próprias bibliotecas é similar.
Para carregar o código, usa-se import nome_arquivo_biblioteca
.
Caso se queira carregar uma classe, subrotina ou variável em particular, usa-se from nome_arquivo_biblioteca import nome_recurso
.
Caso se importe toda a biblioteca, basta prefixar cada chamada com nome_arquivo_biblioteca
.
import biblioteca
biblioteca.ola()
Para executar o projeto, utiliza-se python projeto.py
, sendo projeto.py
o arquivo que define o ponto de entrada do programa, comumente chamado de main
.
Caso se use um IDE como Thonny, pode-se executar o arquivo principal (no caso, projeto.py
).
Em alguns IDEs, contudo, pode ser necessário escolher um arquivo como o arquivo principal ou definir uma subrotina main()
como a seguir.
import biblioteca
def main():
biblioteca.ola()
if (__name__ == "__main__"):
main()
A implementação do ponto é similar.
import math
from typing import Final
class Ponto:
def __init__(self, x, y):
self.x = x
self.y = y
ORIGEM: Final = Ponto(0, 0)
def some(ponto1, ponto2):
resultado = Ponto(ponto1.x + ponto2.x,
ponto1.y + ponto2.y)
return resultado
def subtraia(ponto1, ponto2):
resultado = Ponto(ponto1.x - ponto2.x,
ponto1.y - ponto2.y)
return resultado
def distancia(ponto1, ponto2):
dx = ponto1.x - ponto2.x
dy = ponto1.y - ponto2.y
resultado = math.sqrt(dx * dx + dy * dy)
return resultado
def escreva(ponto):
# print("(" + str(ponto.x) + ", " + str(ponto.y) + ")")
print(f"({ponto.x}, {ponto.y})")
import ponto
ponto1 = ponto.Ponto(1, 2)
ponto.escreva(ponto1)
ponto2 = ponto.Ponto(4, 6)
ponto.escreva(ponto2)
print(ponto.distancia(ponto1, ponto2))
somados = ponto.some(ponto1, ponto2)
print(ponto.distancia(somados, ponto.ORIGEM))
subtraidos = ponto.subtraia(ponto1, ponto2)
print(ponto.distancia(subtraidos, ponto.ORIGEM))
Para evitar conflitos de nomes de módulos ou subrotinas, pode-se importar uma biblioteca com um nome diferente.
Para isso, usa-se as
como parte do comando de importação.
import ponto as p
ponto1 = p.Ponto(1, 2)
p.escreva(ponto1)
# Para um identificador em particular.
from ponto import escreva as e
e(ponto1)
Os nomes p
e e
podem ser alterados conforme necessário.
Isso permite evitar conflitos de nomes em um mesmo namespace.
Criação e Uso de Bibliotecas em Lua
Existem duas formas principais de criar bibliotecas em Lua.
A primeira forma consiste em declarar todas as variáveis e subrotinas como locais, usando local
.
No final do arquivo da biblioteca, retorna-se uma tabela com as definições que se deseja exportar.
local function ola()
print("Olá, meu nome é Franco!")
end
return {
ola = ola
}
-- Outra forma de escrever:
-- local biblioteca = {}
-- function biblioteca.ola()
-- print("Olá, meu nome é Franco!")
-- end
-- return biblioteca
local biblioteca = require("biblioteca")
-- Também é comum ver código como:
-- local biblioteca = require "biblioteca"
-- Isso é válido porque, em alguns casos, Lua permite definir chamadas
-- de subrotinas sem parênteses.
biblioteca.ola()
-- Caso se queira omitir a variável, pode-se armazenar uma referência para a
-- função em uma variável.
local ola = biblioteca.ola
ola()
Para carregar uma biblioteca usando-se a abordagem, usa-se require()
(documentação).
Para executar o código, usa-se lua projeto.lua
em linha de comando, ou executa-se o arquivo projeto.lua
em um IDE como ZeroBrane Studio.
A segunda abordagem é uma variação da primeira, usando namespace global.
local biblioteca = {}
_G.biblioteca = biblioteca
function biblioteca.ola()
print("Olá, meu nome é Franco!")
end
require("biblioteca")
-- ou dofile("biblioteca.lua")
biblioteca.ola()
No caso, adicionar-se a biblioteca
criada em _G
(documentação).
G
é a tabela de variáveis globais usadas por padrão em Lua.
A terceira forma consiste em omitir o uso de local
das definições na biblioteca, criando-se variáveis globais.
Em seguida, carrega-se o código com dofile()
(documentação), que executa código de um arquivo.
function ola()
print("Olá, meu nome é Franco!")
end
dofile("biblioteca.lua")
ola()
A primeira abordagem é preferível para bibliotecas, pois evita problemas de sobrescrita de nomes no namespace global. Como é possível armazenar funções em variáveis, basta atribuir uma função a uma variável caso se queira criar um alias. Ainda assim, a segunda e a terceira abordagens podem ser úteis em projetos que utilizam Lua como linguagem embutida (embedded) em outra, com a intenção de alterar as variáveis globais em scripts. Também existem outras abordagens, embora as três anteriores sejam tradicionais.
Como este é um tópico sobre bibliotecas, a implementação do ponto utiliza a primeira abordagem. Para uma variação, usa-se a forma alternativa mencionada no comentário.
local ponto = {}
ponto.Ponto = {
x = 0,
y = 0
}
function ponto.Ponto:new(objeto, x, y)
objeto = objeto or {}
setmetatable(objeto, self)
self.__index = self
objeto.x = x or 0
objeto.y = y or 0
return objeto
end
ponto.ORIGEM = ponto.Ponto:new(nil, 0, 0)
function ponto.some(ponto1, ponto2)
local resultado = ponto.Ponto:new(nil,
ponto1.x + ponto2.x,
ponto1.y + ponto2.y)
return resultado
end
function ponto.subtraia(ponto1, ponto2)
local resultado = ponto.Ponto:new(nil,
ponto1.x - ponto2.x,
ponto1.y - ponto2.y)
return resultado
end
function ponto.distancia(ponto1, ponto2)
local dx = ponto1.x - ponto2.x
local dy = ponto1.y - ponto2.y
local resultado = math.sqrt(dx * dx + dy * dy)
return resultado
end
function ponto.escreva(ponto)
print("(" .. ponto.x .. ", " .. ponto.y .. ")")
end
return ponto
local ponto = require "ponto"
local ponto1 = ponto.Ponto:new(nil, 1, 2)
ponto.escreva(ponto1)
local ponto2 = ponto.Ponto:new(nil, 4, 6)
ponto.escreva(ponto2)
print(ponto.distancia(ponto1, ponto2))
local somados = ponto.some(ponto1, ponto2)
print(ponto.distancia(somados, ponto.ORIGEM))
local subtraidos = ponto.subtraia(ponto1, ponto2)
print(ponto.distancia(subtraidos, ponto.ORIGEM))
O exemplo utiliza uma das abordagens descritas em registros para a criação de objetos em Lua.
Seria válido usar qualquer outra abordagem descrita no tópico (como, por exemplo, definir uma função crie_ponto()
).
Criação e Uso de Bibliotecas em Godot GDScript
A criação de bibliotecas em GDScript é similar a Python e Lua. Existem duas abordagens principais: a primeira requer instanciação de um objeto; a segunda utiliza métodos estáticos para subrotinas.
Na primeira abordagem, um arquivo define a biblioteca (por exemplo, biblioteca.gd
), outro arquivo define o programa (por exemplo, projeto.gd
).
extends Node
func ola():
print("Olá, meu nome é Franco!")
extends Node
var biblioteca = preload("res://biblioteca.gd").new()
func _ready():
biblioteca.ola()
Para carregar a biblioteca, pode-se usar preload()
(documentação) ou load()
(documentação).
Caso se conheça o arquivo desejado em tempo de implementação, preload()
é preferível, por otimizar o carregamento durante a interpretação (parsing) do arquivo, ao invés de em tempo de uso.
O prefixo res://
refere-se a raiz do projeto do Godot.
O diretório base é o que contém o arquivo project.godot
(ou engine.cfg
, em versões mais antigas).
Todo caminho deve ser absoluto em relação ao diretório base.
Uma forma simples de obter o caminho de um arquivo é usar o painel de sistemas de arquivos do motor (o painel escrito FileSystem, não um gerenciador de arquivos externo).
Quando se clica com o botão direito em um arquivo no painel, uma das opções fornecidas pelo menu de contexto é para copiar o caminho do arquivo (atalho: Ctrl Shift C
).
Também é possível clicar em um arquivo no painel e arrastá-lo no editor de texto embutido; o valor acrescentado ao documento será o caminho correto para o arquivo.
A segunda abordagem é similar, com pequenas diferenças.
extends Node
class_name Biblioteca
static func ola():
print("Olá, meu nome é Franco!")
extends Node
func _ready():
Biblioteca.ola()
Desta vez, o arquivo da biblioteca define um nome para a biblioteca utilizando class_name
.
O nome funciona como uma referência global para a classe criada no arquivo.
Caso se defina subrotinas como métodos estáticos, pode-se usá-las como método de classe ao invés de método de objeto.
Para o exemplo, isso é feito como Biblioteca.ola()
.
Em outras palavras, o nome escolhido (Biblioteca
, no caso) funciona como uma variável global para representar a classe definida no arquivo.
Assim, o nome escolhido precisa ser único no projeto.
A implementação do ponto é similar; ela pode usar qualquer uma das duas abordagens.
Caso se escolha a segunda, deve-se notar que não é permitido definir class_name
como Ponto
, pois o nome coincidiria com o nome da classe interna Ponto
.
extends Node
class Ponto:
var x
var y
func _init(x, y):
self.x = x
self.y = y
# <https://github.com/godotengine/godot/issues/33531>
# const ORIGEM = Ponto.new(0, 0)
static func ORIGEM():
return Ponto.new(0, 0)
static func some(ponto1, ponto2):
var resultado = Ponto.new(ponto1.x + ponto2.x,
ponto1.y + ponto2.y)
return resultado
static func subtraia(ponto1, ponto2):
var resultado = Ponto.new(ponto1.x - ponto2.x,
ponto1.y - ponto2.y)
return resultado
static func distancia(ponto1, ponto2):
var dx = ponto1.x - ponto2.x
var dy = ponto1.y - ponto2.y
var resultado = sqrt(dx * dx + dy * dy)
return resultado
static func escreva(ponto):
print("(" + str(ponto.x) + ", " + str(ponto.y) + ")")
extends Node
var ponto = preload("res://ponto.gd").new()
func _ready():
var ponto1 = ponto.Ponto.new(1, 2)
ponto.escreva(ponto1)
var ponto2 = ponto.Ponto.new(4, 6)
ponto.escreva(ponto2)
print(ponto.distancia(ponto1, ponto2))
var somados = ponto.some(ponto1, ponto2)
# print(ponto.distancia(somados, ponto.ORIGEM))
print(ponto.distancia(somados, ponto.ORIGEM()))
var subtraidos = ponto.subtraia(ponto1, ponto2)
# print(ponto.distancia(subtraidos, ponto.ORIGEM))
print(ponto.distancia(subtraidos, ponto.ORIGEM()))
Quando se escreveu este exemplo, exista uma limitação para a criação de constantes como instâncias de uma classe (referência).
Como alternativa, definiu-se um método estático ORIGEM()
para gerar o valor esperado para a constante ORIGEM
.
A solução não é ideal e apresenta desempenho inferior ao de uma constante estática, mas serve ao propósito.
Compartilhamento de Bibiliotecas
Após criar uma biblioteca, é possível usá-la em vários projetos. A forma mais simples de fazer isso consiste copiar o(s) arquivo(s) gerado(s) para a biblioteca para o diretório de um novo projeto e importá-lo(s). O termo foi usado no plural porque uma biblioteca também pode ter vários arquivos de código-fonte (algo particularmente útil para bibliotecas grandes e complexas).
Entretanto, duplicar o arquivo da biblioteca não é a melhor opção para usá-la. Afinal, caso se altere o código do biblioteca, é necessário atualizar todos os arquivos duplicados em outros projetos.
Uma opção melhor é definir um diretório para a biblioteca e importar os mesmos arquivos em todos os projetos. Em particular, definir uma variável de ambiente é melhor que usar um caminho absoluto. Variáveis de ambiente foram mencionadas rapidamente em Instalação de Programas.
Além disso, caso você crie uma biblioteca, ela pode ser útil para outras pessoas além de você. Assim, você pode decidir por compartilhá-la. Uma forma de compartilhá-la com outras pessoas consiste em hospedar o código em um repositório Web. Neste caso, utiliza-se um sistema de gerenciamento para controle de versões de código-fonte (source-control management ou SCM), como git, Mercurial (hg) ou Subversion (svn) e um serviço para hospedagem do código. Opções populares incluem:
Até o momento da escrita deste tópico, o GitHub é a opções mais popular. O SourceForge é a opção mais antiga das quatro. Embora não seja mais tão popular quanto antigamente, ele continua uma opção válida. As quatro opções são populares e convém conhecê-las (mesmo que apenas pelo nome).
Pessoalmente, eu prefiro GitLab e BitBucket. A maioria dos meus repositórios são privados ao invés de públicos; antigamente, tanto o GitLab quanto o BitBucket possuíam planos grátis melhores para repositórios privados que o GitHub. Em comparação, o GitHub permitia apenas o uso gratuito de repositórios públicos. Embora isso tenha mudado, eu ainda uso o GitLab como serviço primário para hospedam de código-fonte.
Alternativamente, você pode hospedar o código em um servidor ou uma página que seja sua. Qualquer que seja o caso, lembre-se de escolher uma licença de sua preferência para sua biblioteca. Um código sem licença significa que todos os direitos são reservados ao autor ou à autora; ou seja, outras pessoas não poderão (legalmente) usá-los.
Dependências: Uso de Bibliotecas Externas
Da mesma forma que você pode compartilhar suas bibliotecas com outras pessoas, você pode usar bibliotecas criadas por outras pessoas. De fato, usar bibliotecas externas é similar a usar bibliotecas próprias. Para usar qualquer biblioteca, deve-se obter o arquivo com o código e importá-lo em um (ou mais) arquivo(s) de um projeto. Em linguagens compiladas, também pode ser possível obter um cabeçalho com a API da biblioteca e um arquivo binário com o código compilado, que deve ser ligado ao seu programa usando um linker. Isso foi comentado anteriormente para configuração de ambiente para a linguagens C e C++.
Em tempos atuais, a principal fonte de arquivos para bibliotecas é a Internet. Como resultado, desta vez será mais fácil usar bibliotecas externas em JavaScript para navegadores que em outras linguagens (ou mesmo em JavaScript para linha de comando). Isso ocorre simplesmente porque o navegador media o acesso à Internet. Logo, ele torna trivial a obtenção de arquivos remotos para uso em HTML, JavaScript e CSS.
Em outras linguagens (e JavaScript para linha de comando), o processo pode ser manual ou automatizado por ferramentas adicionais. O processo manual é relativamente padronizado na maioria das linguagens de programação. Para usar uma biblioteca externa, deve-se realizar os seguintes passos:
Obter o código-fonte da biblioteca.
Para isso, normalmente acessa-se o website oficial da biblioteca, procura-se a seção de downloads e baixa-se o(s) arquivo(s) necessário.
Em distribuições Linux, também existem pacotes de desenvolvimento para a instalação de bibliotecas populares para diversas linguagens de programação. Isso permite instalar uma biblioteca uma única vez e usá-la em todos os programas que dependerem dela.
Em linguagens de programação compiladas, também é comum obter os cabeçalhos para a biblioteca e um arquivo com o código compilado, pronto para uso. Quando possível, isso normalmente recomendável.
Copia-se o arquivo obtido em um diretório (pasta) ou subdiretório (subpasta) do (seu) projeto.
É uma boa prática criar subdiretórios para dependências e referenciá-las usando caminhos relativos. A divisão possui a vantagem imediata de separar o código do projeto do código de dependências. Outros benefícios incluem a maior facilidade para adicionar, remover ou atualizar dependências, pois os arquivos não estarão misturados.
Por exemplo, pode-se definir um diretório chamado
lib
(de libraries ou bibliotecas),third-party
oudependencies
. Cada biblioteca externa pode estar em um subdiretório. Por exemplo, a biblioteca "Biblioteca A" pode ficar emlib/biblioteca_a/
; o framework B emlib/framework_b
e assim por diante.Importa-se o(s) arquivo(s) da biblioteca desejados no projeto. Em linguagens de programação interpretadas, isso costuma ser suficiente.
Em linguagens de programação compiladas, pode ser necessário juntar o código das bibliotecas com o do projeto para usá-la. Em ligação estática (static link), é possível compilar a biblioteca junto ao projeto. Em link dinâmico, é necessário pré-compilar a biblioteca antes de usá-la. Pré-compilar a biblioteca sempre é interessante, para reduzir o tempo total de compilação. Afinal, se o código da biblioteca não mudar, não é necessário compilá-la para gerar o mesmo código.
Para entender mais como funciona o processo em linguagens compiladas, pode-se acessar a página sobre configuração de ambiente para a linguagem C. O processo é o mesmo para C++, e similar (porém mais simples) em linguagens como Java.
Caso a biblioteca dependa de outras bibliotecas, é necessário repetir os passos (1) e (2) e acertar diretórios de importação (e link, em linguagens compiladas).
Assim, quase sempre é mais simples usar bibliotecas com nenhuma (idealmente) ou poucas dependências. Por isso a escolha de uma biblioteca com um único arquivo para uso de JSON em Lua. Outra opção é escolher bibliotecas que incluam as próprias dependência. Isso ocorre, por exemplo, em motores ou frameworks. A desvantagem é o menor controle sobre versões da biblioteca incluída.
Se o processo parece repetitivo, ele realmente é. Assim, várias linguagens possuem recursos para automatizá-lo, usando-se programas chamados gerenciadores de pacotes. Antes de descrever alguns, pode-se usar JavaScript em navegador como exemplo para uso de bibliotecas externas usando o processo clássico.
Uso de Bibliotecas Externas em JavaScript para Navegador
Para simplificar o exemplo, o código JavaScript está embutido no código HTML, dentro da tag <script>
.
A solução funcionaria da mesma forma com múltiplos arquivos, usando o atributo src
de <script>
.
A primeira versão importa a biblioteca em escopo global.
O arquivo hello-franco-global.js
define dois procedimentos: ola_franco()
e hello_franco()
, que escrevem mensagens já tradicionais neste material.
Você pode usar o endereço no código para obter o arquivo em um navegador, caso deseje.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Bibliotecas Externas em JavaScript</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Para servidor local: -->
<!-- <script src="/hello-franco-global.js"></script> -->
<script src="https://www.francogarcia.com/js/hello-franco-global.js"></script>
</head>
<body>
<main>
<!-- Código JavaScript embutido no arquivo HTML. -->
<script>
ola_franco()
</script>
</main>
</body>
</html>
A primeira versão apresenta a forma tradicional de trabalhar com bibliotecas em JavaScript para navegador.
Existe uma segunda forma, mais recente e que usa módulos.
A segunda versão requer navegadores modernos e atualizados para funcionar.
Além disso, o módulo escolhido não pode importar outros arquivos usando require()
(específico de Node.js).
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Bibliotecas Externas em JavaScript</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main>
<!-- Código JavaScript embutido no arquivo HTML. -->
<script type="module">
// Para servidor local:
// import { ola } from "/hello-franco-module.js"
import { ola } from "https://www.francogarcia.com/js/hello-franco-module.js"
ola()
</script>
</main>
</body>
</html>
A biblioteca hello-franco-module.js
possui dois procedimentos: ola()
e hello()
, que são equivalentes a ola_franco()
e hello_franco()
, respectivamente.
Caso se use um servidor local para testar os arquivos acima no diretório em que estão salvos, deve-se acessar o endereço http://localhost:8000/nome_arquivo.html
.
nome_arquivo.html
é o nome escolhido para o arquivo criado.
Isso pode ser necessário caso o navegador acuse erro de diferenças de protocolos para acesso ao arquivo da biblioteca.
O arquivo original utiliza protocolo HTTPS (https://
); localhost
utiliza HTTP (http://
); arquivos locais utilizam file
(file://
).
Exemplos Adicionais: jQuery, Lodash e Cowsay
Uma das formas mais simples de usar bibliotecas em HTML, JavaScript e CSS consiste no uso de uma rede de fornecimento, entrega e distribuição de conteúdo (do inglês, Content Delivery Network ou CDN).
Para um exemplo usando CDN, pode-se considerar a biblioteca jQuery (licença MIT), popular para desenvolvimento front-end para a Web. jQuery não usa módulos, fornecendo outro exemplo de carregamento de bibliotecas com definições globais.
A biblioteca jQuery provê instruções de como usá-la com um CDN. Para a distribuição oficial, pode-se acessar este link. Também é comum usar as versões hospedadas por Google ou Microsoft, para maximizar as chances de o arquivo já existir no cache do navegador (potencialmente evitando-se a necessidade de novo download).
$(document).ready(function() {
$("a").hover(function(event) {
// Aumenta a fonte e troca a cor para vermelho quando o link recebe foco.
$(this).css("font-size", "50px")
$(this).css("color", "red")
$(this).text("Clique ou toque para acessar!")
}, function(event) {
// Diminui um pouco a fonte e troca a cor para laranja quando o link perde foco.
$(this).css("font-size", "30px")
$(this).css("color", "orange")
$(this).text("Ei, volte aqui!")
})
$("a").click(function(event) {
alert("Muito obrigado!\nAcessando a página...")
})
})
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI="
crossorigin="anonymous"></script>
</head>
<body>
<a href="https://www.francogarcia.com/">Acesse o <i>website</i> do autor</a>
<script src="./script.js"></script>
</body>
</html>
A sintaxe diferente para o código JavaScript é provida por jQuery.
jQuery é comumente importada como o caractere $
(cifrão).
Pode-se notar que jQuery utiliza callbacks.
A implementação no código JavaScript troca a cor, o tamanho e a mensagem do link quando ele recebe (ou perde) foco.
Além disso, quando se clica no link, exibe-se uma mensagem agradecendo o acesso.
Para este tópico sobre bibliotecas, o intuito foi demostrar o uso de uma biblioteca externa para JavaScript em navegadores. Caso você tenha interesse em aprender mais sobre jQuery, consulte a documentação (em inglês).
Para ilustrar o uso de um módulo, pode-se escolher a biblioteca Lodash (licença MIT). A versão em módulo chama-se lodash-es.
import * as _ from "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.min.js"
// Exemplos da página inicial da biblioteca:
// <https://lodash.com/>
console.log(_.defaults({ 'a': 1 }, { 'a': 3, 'b': 2 }))
console.log(_.partition([1, 2, 3, 4], n => n % 2))
// <https://lodash.com/docs/4.17.15#cloneDeep>
let objects = [{ 'a': 1 }, { 'b': 2 }]
let deep = _.cloneDeep(objects)
console.log(deep[0] === objects[0])
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Bibliotecas Externas em JavaScript</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="conteudo"></div>
<script src="./script.js" type="module"></script>
</body>
</html>
Lodash é comumente importada como o caractere _
(underscore).
A biblioteca possui recursos úteis para manipulação de cadeias de caracteres e coleções (como vetores e conjuntos).
Ela foi mencionada previamente em Vetores (arrays), cadeias de caracteres (strings), coleções (collections) e estruturas de dados devido ao método cloneDeep()
fornecido.
cloneDeep()
pode fazer cópias profundas de vetores, dicionários e outras estruturas de dados.
Novamente, o objetivo não é introduzir Lodash, mas demonstrar a importação de um módulo. A importação de módulos em navegadores modernos é simples, porém ainda restrita. Por exemplo:
- Ela falhará caso a implementação da biblioteca possua código específico para ambientes de linha de comando (como para Node.js).
- Por vezes, será necessário carregar a biblioteca como um efeito colateral;
- Pode ser necessário converter o código original para um formato compatível com navegador. Isso pode ser feito, por exemplo, usando ferramentas como webpack ou Browserify.
Para um exemplo adicional, pode-se considerar a biblioteca Cowsay (licença MIT).
cowsay
é, originalmente, um programa de linha de comando que desenha arte textual de uma vaca dizendo uma mensagem.
O código original está disponível neste repositório (licença GPL-3.0).
A página da Wikipedia fornece alguns exemplos de saída do programa original.
// A rigor, o módulo deveria ser importado como:
// import * as cowsay from "https://cdn.jsdelivr.net/npm/cowsay@1.5.0/build/cowsay.es.min.js"
// Ou:
// let {say, TUX} = await import("https://cdn.jsdelivr.net/npm/cowsay@1.5.0/build/cowsay.es.min.js")
// Contudo, existe um problema de caminhos nas importações internas:
// <https://github.com/piuccio/cowsay/issues/48>
// Assim, ele precisou ser carregado como efeito colateral:
import "https://cdn.jsdelivr.net/npm/cowsay@1.5.0/build/cowsay.umd.min.js"
// Ou:
// await import("https://cdn.jsdelivr.net/npm/cowsay@1.5.0/build/cowsay.umd.min.js")
let mensagem = prompt("Escreva uma mensagem", "Olá, meu nome é Franco!")
let resultado = cowsay.say({
text: mensagem,
// cow: cowsay.TUX,
eyes: "oO",
tongue: "U "
})
console.log(resultado)
// <https://www.francogarcia.com/pt-br/blog/ambientes-de-desenvolvimento-javascript/>
function adicione_elemento(valor, nome_elemento = "p") {
const pai = document.getElementById("conteudo")
const novo_elemento = document.createElement(nome_elemento)
novo_elemento.innerHTML = valor
pai.appendChild(novo_elemento)
}
adicione_elemento(resultado.replace(/\\n/g, "<br/>"), "pre")
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Bibliotecas Externas em JavaScript</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="conteudo"></div>
<script src="./script.js" type="module"></script>
</body>
</html>
Possivelmente será necessário usar um servidor Web para usar o exemplo.
python -m http.server 8080 --bind 127.0.0.1 --directory ./
Com um servidor em execução, pode-se usar o exemplo acessando-se http://localhost:8080/cowsay.html
.
Desta vez, a biblioteca é carregada de um CDN chamado jsDelivr.
jsDelivr e UNPKG são opções de CDNs que provêem bibliotecas hospedadas por npm
.
A implementação alternativa descrita em um comentário utiliza await
no import
para aguardar o carregamento do módulo.
Para mostrar a imagem em ASCII Art gerada como saída, utiliza-se adicione_elemento()
e uma expressão regular para trocar quebras de linha em cadeias de caracteres (\n
) por quebras de linha em HTML (<br/>
).
Além disso, escolhe-se "pre"
como tag para escrever o resultado em fonte monoespaçada.
Isso é necessário porque o desenho assume que todos os caracteres possuam a mesma largura.
O bloco a seguir apresenta uma possível saída para o programa. Caso a leitura do conteúdo seja feita via leitor de tela, o texto não fará muito sentido.
_________________________
< Olá, meu nome é Franco! >
-------------------------
\ ^__^
\ (oO)\_______
(__)\ )\/\
U ||----w |
|| ||
O bloco contém o desenho de uma vaca feito com caracteres (conhecido como ASCII Art).
A vaca diz Olá, meu nome é Franco!
em um balão desenhado com hífens, underscores e sinais de menor e maior.
Existem outras implementações para cowsay
para JavaScript.
Uma delas chama-se cowsayjs
, mas ela importa código interno usando require()
.
Caso contrário, o uso seria mais simples:
import * as cowsay from "https://cdn.jsdelivr.net/npm/cowsayjs@1.0.5/lib/index.min.js"
let resultado = cowsay(mensagem)
console.log(resultado)
Ao invés de usá-la aqui, ela será explorada usada após a introdução de gerenciadores de pacotes.
Gerenciadores de Pacotes
Embora o uso de bibliotecas externas em JavaScript para navegadores seja mais simples que em outras linguagens (e para JavaScript para console ou terminal), linguagens de programação modernas costumam possuir programas chamados gerenciadores de pacotes.
O termo gerenciador de pacotes é conhecido de pessoas que usam sistemas operacionais Linux.
Muitas distribuições fornecem gerenciadores de pacotes para a instalação e atualização de programas (e do próprio sistema operacional) na máquina.
Os comandos pacman
em Arch Linux, apt
em Debian e Ubuntu, dnf
ou yum
em Fedora, e emerge
em Gentoo são exemplos de gerenciadores em algumas distribuições tradicionais.
JavaScript, Python e Lua fornecem gerenciadores de pacote para uso via linha de comando; alguns IDEs para estas linguagens fornecem interfaces gráficas para o uso dos gerenciadores. O motor Godot fornece um gerenciador de pacotes com interface gráfica embutido.
Uso de Bibliotecas Externas em JavaScript (Node.js)
JavaScript com o interpretador de linha de comando Node.js possui duas boas opções de gerenciadores de pacotes: npm e Yarn.
Caso o nome npm
pareça familiar, é porque ele foi mencionado no início desta página, quando se comentava sobre segurança em bibliotecas.
Para usar qualquer um dos gerenciadores de pacotes, é preciso instalá-lo primeiro.
As páginas oficiais fornecem instruções em seções como Getting Started (algo como "começando a usar").
Algumas pessoas preferem npm
; outras pessoas preferem yarn
.
No geral, versões recentes de ambos são similares.
Adotando-se npm
como exemplo, este link fornece instruções para a instalação de Node.js e npm.
Para instalar um pacote com uma biblioteca (ou programa) usando npm
, usa-se o comando npm install
em um interpretador de linha de comando.
Por exemplo, para instalar cowsayjs (licença MIT), usa-se:
npm install cowsayjs
Também é possível npm install -g nome_pacote
, caso se queira instalá-lo no sistema de forma global.
Pessoalmente, o autor não recomenda o uso de instalações globais; instalações locais permitem personalizar versões de mesmos pacotes para diferentes projetos com maior facilidade.
Isso ajuda a criar projetos que sejam mais auto-contidos (por exemplo, com todos os arquivos necessários em um único diretório pai).
As instruções para instalação, configuração e uso de pacotes possuem suas próprias páginas no catálogo do npm
.
Por exemplo, esta é a entrada para Cowsay.
Uma vantagem adicional de gerenciadores de pacotes é a possibilidade de atualizar todos os pacotes instalados usando-se npm update
.
Ao invés de substituir biblioteca por biblioteca, basta usar o comando para atualizar todas as que forem possíveis.
Atualizações que possam gerar conflitos de versões são ignoradas pelo processo automático, pois requerem intervenção manual.
Embora isso seja inconveniente, é algo positivo: é melhor conhecer os conflitos para poder resolvê-los da melhor forma possível em um projeto.
npm
provê um comando adicional chamado npx
, que permite executar programas instalados usando npm
em linha de comando.
Por exemplo, após instalar cowsayjs
, pode-se usar o comando a seguir para escrever uma vaca dizendo Olá, meu nome é Franco!
em um terminal:
npx cowsayjs Olá, meu nome é Franco!
Para um segundo exemplo, pode-se considerar a criação de um projeto usando React. Como sempre, a documentação descreve os passos. Inicie um interpretador de comandos em um diretório de seu escolha (preferencialmente crie uma pasta vazia). Em seguida:
npx create-react-app nome-projeto
Caso create-react-app
(página oficial e documentação) não esteja instalado, npx
perguntará se você deseja instalá-lo.
Confirme.
A instalação e a preparação do projeto provavelmente levarão alguns minutos.
Quando terminar, acesse o diretório criado (no exemplo, ele chama-se nome-projeto
).
Para executar o projeto em um navegador, usa-se npm start
.
npm start
Em seguida, acessa-se o endereço http://localhost:3000/
em um navegador.
Em React, um componente deve retornar o código HTML que a ser apresentado na página. Ao invés de escrever código HTML em um arquivo HTML, o código HTML é definido em JavaScript. A sintaxe especial com tags dentro do código JavaScript é provida por React. Ela chama-se JSX (documentação).
O código-fonte para a página está em src/App.js
.
Você pode modificá-lo para alterar a página apresentada no navegador.
Ao salvar o arquivo, a página aberta no navegador será atualizada automaticamente.
function App() {
return (
<div>
<p>Código HTML (escrito em JSX) aqui.</p>
<h1>Olá, meu nome é Franco!</h1>
<p>Esta é uma página com HTML escrito dentro de código JavaScript usando React.</p>
</div>
)
}
export default App
Para usar bibliotecas, primeiro deve-se instalá-las usando npm
.
npm install cowsayjs
npm start
Em seguida, basta importá-las e usá-las no código-fonte do programa.
Para incluir o valor de uma variável na página, React fornece a sintaxe {nome_variavel}
no código JSX.
import { cowsay } from "cowsayjs"
function App() {
let mensagem = "Olá, meu nome é Franco!"
let resultado = cowsay(mensagem)
console.log(resultado)
resultado = resultado.replace(/\\n/g, "<br/>")
return (
<div>
<p>Código HTML (escrito em JSX) aqui.</p>
<h1>Olá, meu nome é Franco!</h1>
<p>Esta é uma página com HTML escrito dentro de código JavaScript usando React.</p>
<pre>{resultado}</pre>
</div>
)
}
export default App
Nesta pequena introdução, você tornou-se um iniciante em programação Web usando React (com um Olá, mundo!
dito por uma vaca).
Além do gerenciador de pacotes, pessoas interessadas em desenvolvimento Web com Node.js devem conhecer ferramentas como webpack ou Browserify.
Essas ferramentas permitem converter módulos para uso navegadores de Internet.
Embora um dos exemplos fornecidos anteriormente ilustre como carregar um módulo diretamente, o recurso ainda é novo, com suporte variável.
Assim, para atividades profissionais, o uso de webpack ou Browserify continua importante.
Uso de Bibliotecas Externas em Python
O uso de gerenciadores de pacotes costuma ser similar independentemente da linguagem escolhida. Assim, os conceitos para JavaScript aplicam-se também para Python.
Por outro lado, o IDE Thonny permite a instalação de bibliotecas usando uma interface gráfica. Assim, caso você não queira usar a linha de comando neste momento, Thonny pode ser uma alternativa para usar as primeiras bibliotecas externas em Python.
Linha de Comando com pip e venv
Python possui uma lista de recomendações oficiais para ferramentas.
O gerenciador de pacotes recomendado chama-se pip.
Para complementar pip
, existe venv.
venv
permite instalar pacotes localmente em um ambiente isolado, chamado de virtual environment (ambiente virtual).
Assim como no caso de npm
, o autor prefere ambientes locais a ambientes globais para dependências.
Para terminar a lista, existem recomendações para a criação de pacotes e uma lista de pacotes chamada de Python Package Index (PyPI).
PyPI é um bom recurso para procurar por pacotes.
Para esta seção, pip
é obrigatório, mas venv
é opcional.
Caso não se use venv
, os pacotes serão instalados de forma global.
Portanto, caso se queira instalar pacotes localmente, não se deve usar os próximos comandos antes da criação do ambiente virtual usando venv
.
Para instalar um programa usando-se Python, usa-se pip install nome_pacote
em um interpretador de linha de comando.
Como exemplo, pode-se assumir a instalação das bibliotecas pandas (licença BSD-3-Clause) e NumPy (também com licença BSD-3-Clause).
pip install pandas
pip install numpy
Caso o comando pip
não esteja disponível, pode-se usar python -m pip
.
python -m pip install pandas
python -m pip install numpy
Para testar, pode-se criar um arquivo script.py
como a seguir:
import pandas as pd
import numpy as np
print("Versão de Pandas:", pd.__version__)
print("Versão de NumPy:", np.__version__)
Em Python, é importante atentar que o nome de arquivo que inclua um módulo não possa ter o mesmo nome de um módulo importado.
Isso significa que, no caso anterior, o arquivo com o código-fonte não pode chamar pandas.py
nem numpy.py
, pois eles são nomes de bibliotecas utilizadas no programa.
Caso se crie o arquivo com um dos dois nomes, a execução resultará em erro.
Isso ocorre porque o interpretador tentará carregar o arquivo criado como se fosse uma das bibliotecas, resultando em uma dependência circular (um laço infinito para carregamento de arquivos).
Isso é válido para todas as bibliotecas (ou seja, não é uma particularidade de pandas
ou numpy
).
Para mais detalhes, pode-se consultar a documentação.
A biblioteca pandas
é comumente importada como pd
, assim como numpy
é importada como np
.
Caso se leia um código Python aleatório que utilize um dos dois prefixos (por exemplo, pd.DataFrame
), é bastante provável que o prefixo refira-se à biblioteca.
numpy
e pandas
são usadas, dentre outros, para Ciência de Dados, processamento de imagens, Inteligência Artificial, Biologia, Bioinformática e Química.
venv
é usado como um bloco no início e no final das instruções.
O primeiro passo é criar um ambiente virtual.
python -m venv virtual_environment
O comando anterior cria um ambiente chamado virtual_environment
.
Muitas pessoas preferem nomear o ambiente como venv
ou .venv
.
Caso se escolha outro nome para o ambiente, deve-se trocar virtual_environment
pelo nome escolhido nos próximos exemplos.
Quando se quiser usar o ambiente, usa-se source
no início da sessão e deactivate
ao final.
Os comandos entre esses dois que instalem, atualizem ou removam pacotes afetarão apenas o ambiente virtual ativo.
Da mesma forma, o interpretador python
poderá usar qualquer um desses pacotes, além de suas próprias bibliotecas, a biblioteca padrão, e pacotes que tenham sido instalados globalmente.
source virtual_environment/bin/activate
# No Windows, o seguinte comando pode ser necessário ao invés do anterior:
# .\virtual_environment/Scripts/activate.bat
# Usos de pip...
# pip install pandas numpy
# Usos de python...
# python script.py
deactivate
Assim, para instalar pandas
e numpy
em um ambiente virtual, usar-se-ia:
# Caso ainda não se criou o ambiente:
python -m venv virtual_environment
# Caso contrário, começar aqui.
source virtual_environment/bin/activate
# No Windows, o seguinte comando pode ser necessário ao invés do anterior:
# .\virtual_environment/Scripts/activate.bat
pip install pandas numpy
# O interpretador pode utilizar ambas as versões instaladas no ambiente virtual.
# python script.py
deactivate
Para testar o ambiente, pode-se usar o mesmo arquivo script.py
apresentado anteriormente.
Agora, o arquivo será executado com sucesso apenas se o ambiente estiver ativo.
Em outras palavras, é necessário usar source
antes do comando python
.
source virtual_environment/bin/activate
# No Windows, o seguinte comando pode ser necessário ao invés do anterior:
# .\virtual_environment/Scripts/activate.bat
python script.py
deactivate
Naturalmente, não é preciso usar source
e deactivate
a cada uso.
Eles podem ser usados uma única vez por sessão de uso: source
no início, deactivate
no final.
Interface Gráfica com Thonny
Para usar a interface gráfica para instalar programas usando Thonny, deve-se acessar:
- Ferramentas (Tools), depois Gerenciar pacotes (Manage packages);
- Na nova interface, deve-se escolher INSTALAR (INSTALL);
- Em seguida, escreva o nome de do pacote no campo do topo da janela. Aperte Pesquisar no PyPI (Search on PyPI);
- Clique no resultado apropriado após a busca;
- Clique em Instalar (Install). Caso queira alterar a versão, clique em ....
Para um exemplo, pode-se procurar pelo pacote pygame
, que instala a biblioteca Pygame para programação de jogos em Python (licença LGPL).
Após clicar em instalar, aguarde o término da instalação.
O resultado será equivalente ao comando:
pip install pygame
Com a instalação concluída, pode-se usar o módulo pygame
.
Para criar sua primeira simulação com gráficos, você pode usar o código a seguir em um arquivo chamado main.py
.
Você pode escolher qualquer nome, exceto pygame.py
ou typing.py
.
import pygame
from typing import Final
LARGURA_JANELA: Final = 320
ALTURA_JANELA: Final = 240
COR_FUNDO_JANELA: Final = (0, 0, 0)
cor_bola = (255, 0, 0)
raio_bola = 10
posicao_bola_x = 0
posicao_bola_y = 0
velocidade_bola_x = 100
velocidade_bola_y = 100
pygame.init()
janela = pygame.display.set_mode((LARGURA_JANELA, ALTURA_JANELA))
ultimo_tempo_ms = pygame.time.get_ticks()
fim = False
while (not fim):
for event in pygame.event.get():
if (event.type == pygame.QUIT):
fim = True
elif (event.type == pygame.KEYDOWN):
if (event.key == pygame.K_ESCAPE):
fim = True
if (posicao_bola_x < 0):
posicao_bola_x = 0
velocidade_bola_x = -velocidade_bola_x
elif (posicao_bola_x > LARGURA_JANELA):
posicao_bola_x = LARGURA_JANELA
velocidade_bola_x = -velocidade_bola_x
if (posicao_bola_y < 0):
posicao_bola_y = 0
velocidade_bola_y = -velocidade_bola_y
elif (posicao_bola_y > ALTURA_JANELA):
posicao_bola_y = ALTURA_JANELA
velocidade_bola_y = -velocidade_bola_y
tempo_atual_ms = pygame.time.get_ticks()
intervalo_tempo_ms = (tempo_atual_ms - ultimo_tempo_ms) / 1000.0
ultimo_tempo_ms = tempo_atual_ms
posicao_bola_x += velocidade_bola_x * intervalo_tempo_ms
posicao_bola_y += velocidade_bola_y * intervalo_tempo_ms
janela.fill(COR_FUNDO_JANELA)
pygame.draw.circle(janela, cor_bola, (posicao_bola_x, posicao_bola_y), raio_bola)
pygame.display.update()
pygame.quit()
Uma nota sobre o código é que o uso de parênteses em (0, 0, 0)
, (255, 0, 0)
e (posicao_bola_x, posicao_bola_y)
é intencional.
Ao invés de agrupamento, trata-se de uma tupla (documentação).
Uma tupla é uma seqüência de valores em que a ordem importa.
Embora seja possível pensá-las como vetores (e usá-las como tal), valores em tuplas são imutáveis (enquanto valores em vetores são mutáveis).
Ou seja, não é possível alterar um valor em uma tupla.
Para se alterar um valor, é necessário criar uma nova tupla que copie os demais valores e altere o valor desejado.
No código, valores com posições X e Y poderiam usar o registro Ponto
criado anteriormente.
Eles servem o mesmo propósito: armazenar o ponto em que o desenho começará no plano (ou o centro, no da bola).
O código cria uma simulação simples de uma bola (um círculo) ricocheteando na janela. Uma implementação melhor consideraria o tamanho da bola na verificação de limites de posições (pense em como fazê-lo). Contudo, como nos outros casos, o interesse atual é em demonstrar o uso de uma biblioteca.
Thonny também permite instalar programas usando a linha de comando. Para isso:
Ferramentas (Tools), depois em Abrir shell do sistema (Open system shell...);
Na janela com o terminal que abrir, digite o comando para
pip.
Por exemplo:pip install pygame
Após a instalação, feche o terminal;
Feche e abra o IDE Thonny. Alternativamente, clique em Executar (Run), depois em Parar/reiniciar backend (Stop/Restart backend).
Para mais informações, pode-se consultar a documentação.
Uso de Bibliotecas Externas em Lua
Após JavaScript com npm
(ou yarn
), Python com pip
, a próxima linguagem é Lua.
Lua possui dois gerenciadores de pacotes: LuaRocks e LuaDist.
Dos dois, LuaRocks é o mais atualizado e recomendado.
De fato, LuaDist recomenda a utilização de LuaRocks.
Para instalar um programa com luarocks
, usa-se luarocks install nome_pacote
em um interpretador de linhad de comando.
Por exemplo, para instalar a biblioteca json.lua
(licença MIT) usada em Arquivos e Serialização (Marshalling), pode-se usar o comando:
luarocks install rxi-json-lua
Nota.
Como o pacote rxi-json-lua
não foi marcado com suporte para Lua 5.4, o comando anterior deve falhar caso esta seja a versão utilizada para o interpretador.
A instalação e configuração para versões específicas será comentada em breve.
A instalação padrão é global.
Assumindo instalação com sucesso, pode-se usar require()
para carregar a biblioteca.
Para instalações globais de pacotes e sem conflitos de versões, pode-se usar a biblioteca escolhida.
local json = require "rxi-json-lua"
local dados = {
nome = "Franco",
saudacao = "Olá!",
despedida = "Tchau!",
}
print(json.encode(dados))
O nome para a biblioteca e para o pacote podem ser pesquisados no website de LuaRocks.
Por exemplo, esta é a página para json.lua
.
Por outro lado, agora é possível escolher opções com outras dependências, como, por exemplo, lua-cjson, que era a biblioteca mais baixada da semana quando da escrita deste tópico.
Em outras palavras, agora é possível escolher implementações mais complexas e com mais dependências. O gerenciador de pacotes encarregar-se-á de obter os pré-requisitos necessários para a instalação da biblioteca escolhida.
Assim como mencionado para JavaScript e Lua, o autor prefere instalação de pacotes no diretório do projeto, de forma local. Para uma instalação local, pode-se usar:
luarocks install --tree ./dependencias/ rxi-json-lua
O exemplo anterior instala os arquivos da biblioteca no diretório dependencias/
.
Caso se queira especificar uma versão de Lua, pode-se passá-la como parâmetro.
Por exemplo, a definição da biblioteca json.lua
não inclui suporte para Lua 5.4 (embora ela seja compatível).
Para especificar a versão da biblioteca para a versão 5.3, pode-se usar:
luarocks install --tree ./dependencias/ --lua-version 5.3 rxi-json-lua
Em seguida, é necessário ajustar os diretórios de bibliotecas usados por Lua para require()
.
Isso pode ser feito modificando-se o valor de duas variáveis: package.path
(documentação) e package.cpath
(documentação).
Para realizar os ajustes, basta concatenar os novos diretórios às variáveis anteriores, seguindo o formato esperado.
local LIB = "dependencias/"
for _, version in ipairs({"5.3", "5.4"}) do
package.path = LIB .. "/share/lua/" .. version .. "/?.lua;" .. package.path
package.cpath = LIB .. "/lib/lua/" .. version .. "/?.so;" .. package.cpath
end
print("Path:", package.path)
print("CPath:", package.cpath)
A tabela define todas as versões desejadas de Lua.
Por exemplo, caso se desejasse usar apenas valores para a versão 5.1, bastaria alterar o valor para {"5.1"}
.
O código anterior deve ser executado uma vez, antes do primeiro uso de require()
que precise de uma biblioteca instalada localmente por luarocks
.
-- Ajuste de caminhos para dependências instaladas por LuaRocks no diretório
-- escolhido como base.
local LIB = "dependencias/"
for _, version in ipairs({"5.3", "5.4"}) do
package.path = LIB .. "/share/lua/" .. version .. "/?.lua;" .. package.path
package.cpath = LIB .. "/lib/lua/" .. version .. "/?.so;" .. package.cpath
end
-- Início do código do projeto.
local json = require "rxi-json-lua"
local dados = {
nome = "Franco",
saudacao = "Olá!",
despedida = "Tchau!",
}
print(json.encode(dados))
Alternativamente, é possível configurar variáveis de ambiente e usar um carregador especial fornecido por luarocks
.
Os detalhes para configuração estão disponíveis na documentação.
Com a configuração de diretórios para package
, pode-se instalar e usar pacotes de luarocks
com versões mais recentes de Lua, caso eles sejam compatíveis, mas o pacote não explicite a compatibilidade em suas propriedades.
Uso de Bibliotecas Externas em GDScript
Diferentemente de JavaScript, Python e GDScript, o gerenciador de pacotes fornecido pelo motor Godot possui uma interface gráfica. O conteúdo é disponível no Godot Asset Library. Na tradução literal, asset significa ativo; entretanto, game asset é um termo em inglês que pode ser melhor entendido como recurso de jogo. Em geral, assets referem-se a imagens, sons, textos e outros recursos que fazem parte do conteúdo de jogo. Algumas definições também incluem código (scripts) como assets.
Assim, é comum que conteúdos usando-se a Asset Library contenham arquivos que não sejam exclusivamente de código-fonte. De fato, muitos assets fornecem scripts e cenas combinadas, para edição ou configuração usando o editor gráfico (ao invés de um editor de texto). Alguns assets podem ser extensões (plug-ins) para o editor do motor (ao invés de bibliotecas de código-fonte).
Para instalar uma biblioteca (ou plug-in, cena, ou qualquer outro recurso) do Asset Library, deve-se:
Clicar em AssetLib. A aba fica ao lado de 2D, 3D e Script, na parte superior central do editor.
Procurar um recurso no campo de busca. Por exemplo, pode-se procurar por math e escolher Extra Math for GDScript (licença Unlicense);
Para instalar, clique sobre o nome ou ícone do resultado. Em seguida, escolha Baixar (Download);
Quando perguntado, confirme a instalação usando Instalar (Install).
O painel de confirmação descreve o diretório em que os arquivos serão salvos. Por padrão, será um diretório local ao projeto, na forma
res://addons/nome_asset/
.
A forma de usar o conteúdo dependerá do que for instalado. No caso de Extra Math for GDScript, trata-se, de fato, de uma biblioteca de código-fonte.
extends Node
func _ready():
print(ExtraMath.EULER)
print(ExtraMath.LN2)
print(ExtraMath.get_vectors_rotation(Vector3(1, 2, 3), Vector3(2, 3, 4)))
Outros recursos que se assemelham com bibliotecas são frameworks para testes. Para identificar alguns, pode-se procurar por test. Um possível exemplo é Godot Unit Test (GUT; licença MIT), para testes unitários, comentados anteriormente em Subrotinas (Funções e Procedimentos).
GUT introduz um painel de testes ao editor. Para usá-lo, é necessário ativá-lo (conforme instruções da documentação). Para isso:
- Acesse o menu Projeto (Project);
- Escolha Configurações do Projeto (Project Settings);
- Escolha a aba Plugins;
- Ative GUT, marcando a caixa de estado (status) Habilitar (Enable).
Após habilitado, aparecerá uma nova opção no painel inferior. Ao lado de Saída (Output), Depurador (Debugger), Áudio (Audio) e Animação (Animation), aparecerá uma nova opção chamada GUT.
Agora é possível definir testes unitários usando GUT.
Crie um diretório de testes para o projeto.
Ele pode chamar-se, por exemplo, testes/
.
Na parte direita do painel, configure diretórios de inclusão.
Por exemplo, em Directory 0, escreva res://testes/
.
Também é possível escolher um prefixo para o nome dos arquivos de teste.
O padrão é test_
; ele poderia chamar teste_
em Português.
Agora é possível criar testes.
No diretório definido para testes, crie um arquivo chamado teste_1
(res://testes/teste_1
).
Para um exemplo real, pode-se criar testes para a biblioteca ponto definida anteriormente em ponto.gd
.
extends "res://addons/gut/test.gd"
var ponto = preload("res://ponto.gd").new()
# Todo procedimento para teste deve começar com o prefixo test_.
func test_aritmetica():
var ponto1 = ponto.Ponto.new(1.0, 2.0)
var ponto2 = ponto.Ponto.new(4.0, 6.0)
# assert_eq() verifica se os dois primeiros parâmetros possuem o mesmo valor.
# Operação, resultado esperado, descrição.
assert_eq(ponto.distancia(ponto1, ponto2), 5.0, "Distância")
assert_eq(ponto.distancia(ponto1, ponto1), 0.0, "Distância")
var somados = ponto.some(ponto1, ponto2)
assert_eq(somados.x, 5.0, "Soma: X")
assert_eq(somados.y, 8.0, "Soma: Y")
var subtraidos = ponto.subtraia(ponto1, ponto2)
assert_eq(subtraidos.x, -3.0, "Soma: X")
assert_eq(subtraidos.y, -4.0, "Soma: Y")
O exemplo utiliza assert_eq()
.
Contudo, GUT fornece outros tipos de comparações; portanto, convém consultar a documentação.
Para executar os testes, use Run all no painel GUT. Os arquivos dos diretórios serão executados; os resultados obtidos serão comparados aos esperados. Testes que não resultarem nos valores esperados serão indicados. Nesse caso, existem duas possibilidades:
- A implementação testada está errada e deve ser corrigida;
- O teste está errado e deve ser corrigido.
Qualquer que seja o caso, o procedimento é o mesmo: verificar, corrigir e re-executar os testes.
Para mais informações sobre a Asset Library, pode-se consultar a documentação.
Técnicas para Uso de Bibliotecas e Dependências
Esta seção apresenta algumas técnicas mais avançadas para sofisticar e melhorar o uso de bibliotecas.
Abstração por Interfaces (APIs) e Programação Por Contrato (Programming By Contract)
Definir interfaces para abstrair detalhes de abstração de subrotinas e tipos de dados é uma boa prática de programação. Nesse contexto, interfaces de subrotinas são assinaturas genéricas de subrotinas; interfaces de tipos de dados são as assinaturas de métodos de classes (ou subrotinas que manipulem registros). O intuito é que o uso de uma subrotina ou de uma biblioteca dependa apenas do conhecimento da assinatura, dispensando-se conhecer os detalhes de implementação. Um benefício imediato da prática é a possibilidade de alterar a implementação da subrotina sem comprometer o funcionamento de código que faça chamadas a ela.
Quando se trabalha com bibliotecas (próprias ou externas), isso é ainda mais útil. Embora seja possível usar a assinatura fornecida pela API da biblioteca (ou variáveis globais, métodos, etc.), pode-se definir uma API interna para uso no programa. Em outras palavras, ao invés de chamar diretamente uma subrotina, cria-se uma subrotina própria para chamar a subrotina da biblioteca. A biblioteca usada torna-se um detalhe; pode substituí-la com facilidade. Retomando o título de uma das subseções deste tópico: sobre ombros de gigantes, com pára-quedas para saída de emergência com segurança.
A técnica é importante e deve fazer parte do repertório de toda boa programadora e todo bom programador. Logo, convém entender as origens e com aplicá-la. O objetivo é poder alterar implementações que realizem um determinado processamento por outras que forneçam um resultado (ou efeito colateral) equivalente.
Para começar, pode-se considerar o seguinte exemplo em Python, que apresenta três funções que retornam o valor absoluto (módulo) de um número.
import math
print(abs(-1.23), abs(4.56), abs(0.0))
print(math.fabs(-1.23), math.fabs(4.56), math.fabs(0.0))
# Função definida em uma biblioteca externa hipotética.
def calcule_valor_absoluto(x):
if (x >= 0.0):
return x
return -x
print(calcule_valor_absoluto(-1.23), calcule_valor_absoluto(4.56), calcule_valor_absoluto(0.0))
As três funções possuem o mesmo propósito e fornecem um resultado intercambiável.
A rigor, math.fabs()
sempre retorna um valor em ponto flutuante (como 0.0
).
Como as três funções apresentam os mesmos resultados para números reais, pode-se considerar que o parâmetro seja um número real para se focar no conceito.
Alternativamente, pode-se converter o argumento recebido para um número de ponto flutuante para que o resultado sempre tenha esse tipo.
O problema é que, caso se chame diretamente abs()
, math.fabs()
ou calcule_valor_absoluto()
, cria-se uma dependência do programa em relação a função.
Para que o programa funcione, uma função com este nome deve existir.
Se a função é definida por uma biblioteca ou módulo, ele deve estar instalado e pronto para uso.
A solução consiste em abstrair a implementação.
Ao invés de usar qualquer uma das implementações, pode-se criar uma assinatura própria, como parte da API interna do programa.
Por exemplo: valor_absoluto(x)
, que retorna o valor absoluto de x
(número real) como número real.
Assim: float valor_absoluto(float x)
.
Como a função é implementada não é relevante; basta saber o(s) efeito(s) e o(s) resultado(s) dado o(s) parâmetro(s).
Com a interface proposta, é possível definir três implementações para valor_absoluto()
usando o trecho de código original:
def valor_absoluto(x):
# Força resultado real.
return 1.0 * abs(x)
import math
def valor_absoluto(x):
return math.fabs(x)
def valor_absoluto(x):
if (x >= 0.0):
return 1.0 * x
return -1.0 * x
# Assumindo que calcule_valor_absoluto() fosse definida em uma biblioteca:
# def valor_absoluto(x):
# return calcule_valor_absoluto(x)
Cada implementação anterior define um padrão de projeto chamado de Adaptador (Adapter, Wrapper ou Translator).
Como o adaptador criado garante os mesmos resultados para as três implementações, elas podem ser usadas intercambiavelmente, isto é, pode-se chamar qualquer uma delas para se obter o mesmo resultado.
Conseqüentemente, também é possível usar qualquer uma das três implementações de biblioteca sem modificar o código de um programa que faça uma chamada a valor_absoluto()
.
A única diferença seria a biblioteca adaptada importada.
from a import valor_absoluto
print(valor_absoluto(-1.23), valor_absoluto(4.56), valor_absoluto(0.0))
from b import valor_absoluto
print(valor_absoluto(-1.23), valor_absoluto(4.56), valor_absoluto(0.0))
from c import valor_absoluto
print(valor_absoluto(-1.23), valor_absoluto(4.56), valor_absoluto(0.0))
De fato, como as três implementações são intercambiáveis, é possível definir uma implementação que pudesse escolher uma dentre as três possíveis bibliotecas.
# Seleção de biblioteca.
biblioteca_desejada = "a"
if (biblioteca_desejada == "a"):
from a import valor_absoluto
elif (biblioteca_desejada == "b"):
from b import valor_absoluto
elif (biblioteca_desejada == "c"):
from c import valor_absoluto
else:
import sys
# Erro.
sys.exit()
# Programa.
print(valor_absoluto(-1.23), valor_absoluto(4.56), valor_absoluto(0.0))
Como a interface definida para valor_absoluto()
é a mesma, os programas funcionarão da mesma forma (assumindo que as implementações de cada biblioteca estejam corretas).
for biblioteca_desejada in ["a", "b", "c"]:
# Seleção de biblioteca.
if (biblioteca_desejada == "a"):
from a import valor_absoluto
elif (biblioteca_desejada == "b"):
from b import valor_absoluto
elif (biblioteca_desejada == "c"):
from c import valor_absoluto
else:
import sys
sys.exit()
# Programa.
print(valor_absoluto(-1.23), valor_absoluto(4.56), valor_absoluto(0.0))
O programa não precisa conhecer a biblioteca que efetivamente implementa o código; basta que a implementação siga a interface esperada. Isso permite, dentre outros:
- Trocar bibliotecas sem comprometer o funcionamento de um programa. É possível escrever uma biblioteca própria ou usar dependências externas. Basta mapear os nomes de subrotinas de cada biblioteca para os da interface, assim como converter o(s) parâmetro(s) da interface para os tipos de dados esperados pela biblioteca em questão. Caso mais ajustes sejam necessários, eles podem ser adaptados;
- Escrever código específico para diferentes plataformas. Por exemplo, uma implementação para Linux, outra para Windows, outra para macOS, outra para Android, outra para iOS, outra para Raspberry Pi, outra para um videogame. No momento de compilação ou execução, seleciona-se a implementação compatível com a plataforma que executará o código.
- Alterar entre versões de teste (para desenvolvedores) e versões para produção (uso por usuários finais) de uma biblioteca.
Este tipo de técnica relaciona-se com uma prática chamada de Programação por Contrato (Programming by Contract) ou Design por Contrato (Design by Contract). O uso de interfaces em geral é uma boa prática de programação e um dos pilares de um acrônimo conhecido como SOLID:
- Single-responsibility principle ou princípio de única responsabilidade;
- Open-closed principle ou princípio de aberto/fechado;
- Liskov substitution principle ou princípio da substituição de Liskov;
- Interface segregation principle ou princípio da segregação de Interface;
- Dependency inversion principle ou princípio da inversão de dependência.
Os termos e conceitos estão relacionados à Programação Orientada a Objetos (Object-Oriented Programming ou OOP), mas as idéias aplicam-se a qualquer paradigma. Em particular, linguagens como JavaScript, Python e Lua (e GDScript, a partir da versão 4 de Godot) permitem usá-los com facilidade, já que é possível alterar a definição de uma subrotina como qualquer outra variável.
Em OOP, a implementação depende de classes, interfaces (de classes), herança e polimorfismo.
class Matematica:
def __init__(self):
pass
def valor_absoluto(self, x):
raise NotImplementedError("Classe base")
import matematica
class A(matematica.Matematica):
def valor_absoluto(self, x):
# Força resultado real.
return 1.0 * abs(x)
import matematica
import math
class B(matematica.Matematica):
def valor_absoluto(self, x):
return math.fabs(x)
import matematica
class C(matematica.Matematica):
def valor_absoluto(self, x):
if (x >= 0.0):
return 1.0 * x
return -1.0 * x
# Seleção de biblioteca.
biblioteca_desejada = "a"
if (biblioteca_desejada == "a"):
import a
matematica = a.A()
elif (biblioteca_desejada == "b"):
import b
matematica = b.B()
elif (biblioteca_desejada == "c"):
import c
matematica = c.C()
else:
import sys
sys.exit()
# Programa.
print(matematica.valor_absoluto(-1.23), matematica.valor_absoluto(4.56), matematica.valor_absoluto(0.0))
Nos exemplos fornecidos, A
, B
e C
são classes derivadas (subclasses) de Matematica
(classe pai ou superclasse).
A classe pai define a interface comum para uso das subrotinas.
As classes filhas definem (isto é, redefinem) o método abstrato valor_absoluto()
com a implementação específica; por isso o uso de NotImplementedError
, que é uma exceção lançada via raise
(documentação).
Lê-se construções como A(matematica.Matematica)
como: a classe A
herda (herança) a classe pai matematica.Matematica
(o primeiro matematica
, com m
minúsculo, é o nome do módulo; o segundo Matematica
, com M
maiúsculo, é o nome da classe).
Na hora de usar o programa, deve-se instanciar uma das classes derivadas e usar a interface definida na classe pai. A classe pai não contém uma implementação; ela define o contrato do que se espera que as classes filhas implementem.
Assim, a versão em OOP é equivalente à primeira versão com funções. Algumas linguagens permitem usar apenas uma das abordagens; outras permitem usar ambas; algumas não permitem usar nenhuma das duas. Cada ferramenta com suas particularidades.
Live Coding, Live Reloading, Hot Swap e Run-Time Compilation
Em configuração de ambiente para a linguagem C, mencionou-se rapidamente técnicas como hot swap e run-time compilation. A técnica é bastante usada na criação de jogos digitais e para o desenvolvimento de motores para jogos digitais (game engines).
O princípio é manter um programa em execução, sem fechá-lo, e atualizá-lo quando ocorre mudança externa no código-fonte do programa. Em outras palavras, transmitir as modificações no código-fonte (assim que o código for salvo) no processo (programa em execução). Isso gera um processo de desenvolvimento interativo e iterativo, pois reduz o número de tarefas (e o tempo necessário) entre modificar o código-fonte do projeto e observar os resultados no programa.
Em linguagens de programação compiladas, hot swap requer o uso de bibliotecas dinâmicas ao invés de bibliotecas estáticas.
Bibliotecas dinâmicas possuem extensões como .dll
no Windows, .so
em Linux e .dylib
em macOS.
Por outro lado, hot swap é particularmente simples de implementar em linguagens de programação interpretadas, pois basta modificar e reinterpretar um trecho (ou arquivo) de código. Em especial, o arquivo em questão pode ser uma biblioteca.
Hot Swap em Lua
O código a seguir utiliza a linguagem Lua como exemplo.
local biblioteca = {}
function biblioteca.ola()
print("Olá, meu nome é Franco!")
end
function biblioteca.operacao(valor)
local resultado = valor + 1
return resultado
end
function biblioteca.fim()
return false
end
return biblioteca
-- <https://www.francogarcia.com/pt-br/blog/aprenda-programacao-registros/>
function sleep(tempo_segundos)
local fim = tonumber(os.clock() + tempo_segundos);
while (os.clock() < fim) do
-- Espera o tempo passar, desperdiçando ciclos do processador.
end
end
local fim = false
local numero = 0
while (not fim) do
local biblioteca = dofile("biblioteca.lua")
biblioteca.ola()
numero = biblioteca.operacao(numero)
print(numero)
sleep(0.5)
fim = biblioteca.fim()
if (fim) then
print("Preparando-se para terminar o programa...")
end
end
print("Fim do programa")
Deve-se observar que, neste momento, main.lua
possui um laço (loop) infinito, pois fim
nunca será true
.
Isso é intencional.
O próximo passo é executar o programa: lua main.lua
(ou usando um IDE).
O programa escreverá Olá, meu nome é Franco!
e o valor de numero
a cada 500 milissegundos.
É importante não fechar o programa; ele deve continuar executando o laço infinito.
O próximo passo é modificar o arquivo biblioteca.lua
.
Por exemplo:
local biblioteca = {}
function biblioteca.ola()
print("Olá, meu nome (ainda) é Franco!!!")
end
function biblioteca.operacao(valor)
local resultado = valor + 100
return resultado
end
function biblioteca.fim()
return false
end
return biblioteca
Deve-se salvar o arquivo e observar os resultados do programa em execução.
No momento em que o novo código da biblioteca for reexecutado por dofile()
, o código em execução para biblioteca.ola()
e biblioteca.operacao()
mudarão.
A soma agora será feita de 100
em 100
e a mensagem será Olá, meu nome (ainda) é Franco!!!
.
Ou seja, é possível manter o programa em execução e melhorá-lo continuamente, conforme necessário.
Para terminar o programa, basta modificar novamente a biblioteca.
A única alteração necessária é retornar true
em biblioteca.fim()
.
local biblioteca = {}
function biblioteca.ola()
print("Tchau!")
end
function biblioteca.operacao(valor)
local resultado = 0
return resultado
end
function biblioteca.fim()
return true
end
return biblioteca
Ao salvar o arquivo, o programa terminará assim que o código for reavaliado por dofile()
.
Para melhorar a solução, o ideal seria recarregar a biblioteca apenas quando o arquivo biblioteca.lua
mudasse.
Para isso, poder-se-ia checar a última modificação do arquivo usando-se uma biblioteca de sistema de arquivos.
Manter-se-ia o valor da última alteração e comparar-se-ia com o novo.
Caso o novo valor seja mais recente que o antigo, ele é atualizado e a biblioteca é recarregada.
Após a introdução de operações de entrada não-bloqueante, outra opção seria designar uma tecla de atalho para recarregar o código (por exemplo, F5).
Por fim, pode-se alterar a solução para usar require()
ao invés de dofile()
.
Para isso, é preciso modificar a entrada para a biblioteca na tabela package.loaded
(documentação).
Isso é necessário porque require()
otimiza o carregamento de bibliotecas: bibliotecas carregadas anteriormente na execução de um programa não são recarregadas, para melhorar o desempenho.
Neste caso, isso não é desejável.
local biblioteca = require("biblioteca")
-- <https://www.francogarcia.com/pt-br/blog/aprenda-programacao-registros/>
function sleep(tempo_segundos)
local fim = tonumber(os.clock() + tempo_segundos);
while (os.clock() < fim) do
-- Espera o tempo passar, desperdiçando ciclos do processador.
end
end
local fim = false
local numero = 0
while (not fim) do
package.loaded.biblioteca = nil
biblioteca = require("biblioteca")
biblioteca.ola()
numero = biblioteca.operacao(numero)
print(numero)
sleep(0.5)
fim = biblioteca.fim()
if (fim) then
print("Preparando-se para terminar o programa...")
end
end
print("Fim do programa")
Embora o exemplo use Lua, ele também pode ser implementado em GDScript, Python e JavaScript.
Hot Swap em GDScript usando Godot Engine
O motor Godot fornece a funcionalidade de hot swap pré-definida, basta habilitá-la:
- Para projetos, pode-se habilitar a sincronização de mudanças em cenas e scripts (documentação). Elas estão no menu Depuração (Debug) como Sincronizar Mudanças de Cena (Synchronize Scene Changes) e Sincronizar Mudanças de Script (Synchronize Script Changes);
- Para o editor, pode-se usar o modo
tool
em um script (documentação).
Para usar a versão em GDScript, basta habilitar as sincronizações de mudanças em scripts.
O script será um pouco diferente, pois Godot é um motor (engine) e impõe convenções e assume pressupostos de uso.
Para o motor Godot, código que deve ser repetido ao longo do tempo é definido em métodos especiais: _process()
(documentação) e _physics_process()
(documentação).
Assim, ao invés de definir-se uma estrutura de repetição, pode-se implementar o código a ser repetido em um dos dois métodos.
O código implementado será repetido a cada passo do laço de jogo (game loop).
extends Node
static func ola():
print("Olá, meu nome é Franco!")
static func operacao(valor):
var resultado = valor + 1
return resultado
static func fim():
# Modificar para true e salvar o arquivo para terminar o programa.
return false
extends Node
var biblioteca = preload("res://biblioteca.gd").new()
var fim = false
var numero = 0
var tempo_acumulado = 0.0
# _process() é repetido pelo motor a cada iteração do laço de jogo.
func _process(delta):
tempo_acumulado += delta
if (tempo_acumulado < 0.5):
return
tempo_acumulado = 0.0
biblioteca.ola()
numero = biblioteca.operacao(numero)
print(numero)
fim = biblioteca.fim()
if (fim):
print("Preparando-se para terminar o programa...")
print("Fim do programa")
get_tree().quit()
_process()
fornece como parâmetro o intervalo de tempo (em segundos) desde a última atualização.
Na implementação, acumula-se cada intervalo delta
em tempo_acumulado
para simular uma chamada de sleep()
(como espera ocupada).
Isso permite atualizar e escrever os resultados a cada meio segundo.
Para usar o programa, basta alterar o código de biblioteca.gd
com o programa em execução.
Com a opção de sincronização de script habilitada, o programa deve refletir as alterações (quando possível).
Em particular, pode ser necessário usar preload()
ao invés de class_name
, pois o motor pode não permitir mudanças no código-fonte com o projeto em execução para class_name
.
Hot Swap em Python
Em Python, pode-se usar o método reload()
do módulo importlib
(documentação).
def ola():
print("Olá, meu nome é Franco!")
def operacao(valor):
resultado = valor + 1
return resultado
def fim():
# Modificar para True e salvar o arquivo para terminar o programa.
return False
import biblioteca
import time
import importlib
fim = False
numero = 0
while (not fim):
importlib.reload(biblioteca)
biblioteca.ola()
numero = biblioteca.operacao(numero)
print(numero)
time.sleep(0.5)
fim = biblioteca.fim()
if (fim):
print("Preparando-se para terminar o programa...")
print("Fim do programa")
importlib
requer versões recentes de Python 3.
Em versões anteriores, existem recursos equivalentes, mas eles possuem outros nomes.
Hot Swap em JavaScript
Em JavaScript para navegador, o processo é um pouco mais complicado, pois não é possível acessar o sistema de arquivos da máquina. Contudo, ainda é possível usar a técnica. Inclusive, algo similar foi explorado no exemplo usando React, que utiliza uma implementação de hot swap usando um servidor Web local.
As próximas subseções apresentam implementações simples de como fazê-lo.
Hot Swap em JavaScript com Subrotinas Globais
Para um primeiro exemplo de implementação usando JavaScript, pode-se considerar que a biblioteca carrega funções globais.
O exemplo também seria possível caso contrário (bastaria retornar um dicionário em carregar_script()
e usar o valor obtido).
function ola() {
console.log("Olá, meu nome é Franco!")
}
function operacao(valor) {
let resultado = valor + 1
return resultado
}
function fim() {
// Modificar para true e salvar o arquivo para terminar o programa.
return false
}
carregar_script("./biblioteca.js", "biblioteca")
async function main() {
let terminou = false
let numero = 0
while (!terminou) {
ola()
numero = operacao(numero)
console.log(numero)
await new Promise(resolve => setTimeout(resolve, 500))
terminou = fim()
if (terminou) {
console.log("Preparando-se para terminar o programa...")
} else {
carregar_script("./biblioteca.js", "biblioteca")
}
}
console.log("Fim do programa")
}
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Bibliotecas em JavaScript</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
function carregar_script(nome_arquivo, div_id) {
let pai = document.getElementById(div_id)
console.assert(pai)
let novo_script = document.createElement("script")
novo_script.id = nome_arquivo
novo_script.type = "text/javascript"
novo_script.src = nome_arquivo + "?timestamp=" + new Date().getTime()
novo_script.setAttribute("async", "")
pai.replaceChildren(novo_script)
}
</script>
</head>
<body onload="main()">
<div id="biblioteca"></div>
<script src="./main.js"></script>
</body>
</html>
Para usar a implementação, deve-se iniciar um servidor Web local e acessar algo como: http://localhost:8080/
(assumindo porta 8080
e arquivo HTML chamado index.html
).
A parte importante do código está em index.html
.
No script embutido em <head>
, cria-se um script com o código da biblioteca.
Em particular, obtém-se o tempo atual com getTime()
(documentação), cujo valor é passado como parâmetro para o endereço do arquivo para evitar problemas de cache.
Como o intuito é modificar o conteúdo do arquivo, não se deseja que o navegador forneça a versão anterior.
O script criado é adicionado na <div>
que armazena o código da biblioteca.
A segunda novidade é o uso de onload
em <body>
(documentação).
A implementação define o código do programa principal em um procedimento chamado main()
, que é chamado em onload
quando o documento carrega.
Isso garante que o script da biblioteca esteja carregado antes do primeiro uso.
Em main.js
, carregar_script()
está no final do laço while
como recordação de que a alteração da biblioteca ocorrerá na próxima iteração.
O restante da implementação é similar às demais.
O valor lógico fim
foi renomeado para terminou
para evitar conflitos de nome com a função global fim()
definida em biblioteca.js
.
Hot Swap em JavaScript com Módulos
Para uma segunda implementação, pode-se considerar o uso de módulos.
function ola() {
console.log("Olá, meu nome é Franco!")
}
function operacao(valor) {
let resultado = valor + 1
return resultado
}
function fim() {
// Modificar para true e salvar o arquivo para terminar o programa.
return false
}
export {
ola,
operacao,
fim
}
let fim = false
let numero = 0
while (!fim) {
let biblioteca = await import("./biblioteca.js?" + new Date().getTime())
biblioteca.ola()
numero = biblioteca.operacao(numero)
console.log(numero)
await new Promise(resolve => setTimeout(resolve, 500))
fim = biblioteca.fim()
if (fim) {
console.log("Preparando-se para terminar o programa...")
}
}
console.log("Fim do programa")
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<title>Bibliotecas em JavaScript</title>
<meta name="author" content="Franco Eusébio Garcia">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<script src="./main.js" type="module"></script>
</body>
</html>
A implementação com módulos torna-se mais próxima das feitas para outras linguagens, pela possibilidade de usar import()
dinamicamente.
Simples e concisa, mas requer um navegador moderno (como versões recentes do Firefox).
Novos Itens para Seu Inventário
Ferramentas:
- Gerenciadores de pacotes;
- Rede de fornecimento, entrega e distribuição de conteúdo (Content Delivery Network ou CDN);
- Frameworks para teste unitário;
- Servidores Web (Web server);
- Hot swap.
Habilidades:
- Criação de bibliotecas;
- Uso de bibliotecas;
- Uso de gerenciadores de pacotes;
- Uso de servidor Web (Web server) local.
Conceitos:
- Bibliotecas;
- Módulos;
- Frameworks;
- Motores (engines);
- Dependências;
- Espaços de nomes (namespaces);
- Kit de desenvolvimento de software (Software Development Kit, SDK ou devkit);
- Inversão de controle;
- Licenças;
- Rede de fornecimento, entrega e distribuição de conteúdo (Content Delivery Network ou CDN);
- Servir (serve) conteúdo para Internet, usando servidores (servers);
- Adaptador (Adapter, Wrapper ou Translator);
- Programação por Contrato (Programming by Contract);
- SOLID.
Recursos de programação:
- Uso e criação de bibliotecas;
- Callbacks;
- Interfaces.
Pratique
Existem três formas de praticar o conteúdo deste tópico:
- Criar suas próprias bibliotecas;
- Usar as bibliotecas que você criou;
- Usar bibliotecas externas (dependências).
Uma boa atividade em potencial é revisar os tópicos anteriores. Você pode reunir definições de subrotinas e tipos de dados em bibliotecas para JavaScript, Python, Lua e GDScript. Assim, você poderá reusar o conhecimento adquirido em seus projetos futuros.
Além disso, você pode explorar algumas das bibliotecas apresentadas ao longo deste tópico. Elas foram variadas, como forma de ilustrar novas possibilidades e expandir seu repertório.
Próximos Passos
Em programação, ninguém sabe tudo. O autor possui doutorado em Ciência da Computação e aprende algo novo a cada dia, mesmo resolvendo problemas simples. O conhecimento e a experiência acumulam-se; porém, em programação, todos são eternos aprendizes.
É essencial perceber o quanto ainda não se sabe. Honestidade e auto-avaliação são importantes para continuar a melhorar. Afinal, a identificação de áreas de dificuldade ou de conhecimento fraco permitem focar em aperfeiçoamentos.
Da mesma forma, é importante reconhecer o que se sabe. Cada pessoa sabe algumas coisas, mas não sabe muitas outras. Isso é algo interessante, porque significa que o conhecimento e as habilidades de uma pessoa podem complementar os de outra. Comunidades que se ajudam possuem melhores chances a prosperar.
Isso é o que ocorre em programação. O desenvolvimento de software é uma atividade colaborativa, mesmo para programadoras ou programadores solo. Praticamente todo desenvolvedor e toda desenvolvedora usa ferramentas e bibliotecas criadas por outras pessoas. Da mesma forma, cada pessoa pode melhorar ferramentas e bibliotecas existentes.
A participação não requer, apenas, a criação de novas bibliotecas. Pelo contrário, é possível:
- Melhorar bibliotecas existentes;
- Relatar problemas e bugs encontrados durante o uso;
- Prover sugestões de melhorias.
Colaborações como as anteriores permitem melhorar continuamente processos, ferramentas e recursos de programação. Elas são alguns dos motivos do sucesso de soluções de código aberto (open source) e software livre (free software).
Além disso, o uso de bibliotecas permite usufruir do conhecimento e experiência de outras pessoas como forma de suprir lacunas no próprio conhecimento. Em outras palavras, pode-se resolver problemas sabendo-se aplicar ferramentas existentes. Nem sempre é preciso criar uma nova -- especialmente se uma alternativa adequada já existe.
Não se deve reinventar a roda sem motivos. Pelo contrário: deve-se usar o que está à disposição para se alavancar o sucesso mais rapidamente. Linguagens de programação são ferramentas; bibliotecas são ferramentas; programas de desenvolvimento são ferramentas. Logo, convém usar rodas de gigantes sempre que possível, tomando-se os devidos cuidados para não se cair.
Sem bibliotecas, os recursos para programação de uma pessoa restringem-se aos próprios conhecimentos da pessoa. Com bibliotecas, pode-se usufruir do conhecimento e de esforços de comunidades de pessoas contribuindo para a criação de ferramentas e sistemas melhores. Não apenas para programação e computação: bibliotecas existem para diversas áreas de conhecimento.
Em particular a este tópico, as bibliotecas externas escolhidas para cada linguagem foram diferentes. Isso é intencional: existem várias formas de continuar a aprender e expandir seu conhecimento. Seu conhecimento atual é composto por fundamentos. Você criou o alicerce; este é o momento de começar as fundações de seus próprios projetos.
Em próximos tópicos, o autor pretende expandir o material com simulações e uso de conteúdo multimídia. Antes, contudo, ainda é pertinente desbravar a linha de comando. Desta vez, você manipulará argumentos passados por interpretadores de linha de comando para seus próprios programas.
- Introdução;
- Ponto de entrada e estrutura de programa;
- Saída (para console ou terminal);
- Tipos de dados;
- Variáveis e constantes;
- Entrada (para console ou terminal);
- Aritmética e Matemática básica;
- Operações relacionais e comparações;
- Operações lógicas e Álgebra Booleana;
- Estruturas de condição (ou condicionais ou de seleção);
- Subrotinas: funções e procedimentos;
- Estruturas de repetição (ou laços ou loops);
- Vetores (arrays), cadeias de caracteres (strings), coleções (collections) e estruturas de dados;
- Registros (structs ou records);
- Arquivos e serialização (serialization ou marshalling);
- Bibliotecas;
- Entrada em linha de comando;
- Operações bit-a-bit (bitwise operations);
- Testes e depuração.