Uma tarefa comum quando você está desenvolvendo um projeto é rodar um linter. Linters são ferramentas que analisam o código fonte do seu projeto e buscam detectar diferentes problemas no código, incluindo possíveis falhas na lógica e também questões relacionadas ao estilo do código. Porém, se tem uma coisa que incomoda MUITO é quando você está desenvolvendo, QUASE finalizando o software, e precisa ESPERAR para que ferramentas de verificação assim (que incluem tanto linters quanto testes, que são importantes para qualquer projeto) assim rodem.
Esse é o clássico problema que o XKCD retratou extremamente bem no post "Compiling":
E isso incomoda AINDA MAIS quando a ferramenta em questão é single-thread ou mal otimizada, pois o seu computador (que assumo ter um processador com múltiplos núcleos hoje em dia, certo?), fica mais ou menos assim:
Bom. Agora vamos ao tópico do post: Já que é bem difícil simplesmente paralelizar o trabalho de uma ferramenta de forma eficiente, vamos tentar tornar a sua atuação mais inteligente: Uma forma de deixar o trabalho de qualquer script - ou qualquer coisa, na verdade - mais eficiente, é fazer com que tal script (ou coisa) faça MENOS trabalho.
No caso de um linter, uma forma bem fácil de fazer MENOS trabalho é simplesmente... não ficar rechecando arquivos que não são necessários. Isso é MUITO mais eficiente do que simplesmente paralelizar o algoritmo, uma vez que mesmo o número de núcleos em um computador moderno é limitado.
Para exemplificar isso, considere um projeto que tenha, digamos, 5000 arquivos para serem analisados, cada um levando aí algo em torno de 0.04 segundos. Um linter rodando sequencialmente levaria, portanto, 200 segundos (ou 3 minutos e 20 segundos) para analisar tudo.
Se você tem um processador com 8 núcleos e 16 threads no seu computador, normalmente um algoritmo paralelo tentaria disparar 16 threads para processar cada arquivo. Digamos que essas 16 threads consigam acelerar em 16 vezes a velocidade de processamento (algo que nunca acontece pois há diversas limitações que impedem que algo assim aconteça). Em vez de demorar 200 segundos, ou 3 minutos e 20 segundos, tal algoritmo paralelo levaria cerca de 12 segundos para processar todos os 5000 arquivos.
Levar 12 segundos em vez de 3 minutos e 20 segundos é uma vitória tremenda, certo? Porém, considere que você NÃO vai modificar todos os 5000 arquivos a todo momento. Pelo contrário, você vai modificar ali 1, 3, 5 arquivos por vez, enquanto trabalha no seu projeto. E é aqui que mesmo a paralelização deixa de fazer sentido: Para que checar todos os arquivos, se apenas alguns poucos foram modificados? Se apenas 5 arquivos foram modificados, e o linter leva 0.04 segundos para processar um arquivo, apenas 0.04 * 5 = 0.2 segundos (ou MENOS de um segundo!) deveriam ser necessários para processar tudo, o que já é 60 vezes mais rápido que o algoritmo paralelo ou 1000 vezes mais rápido que o algoritmo sequencial.
E é aqui que entra o script que é o assunto desse post.
Esse é o script, em bash:
#!/bin/bash
set -e
SCRIPT_NAME=$(basename "$0");
find_script() {
script_name="$1"
current_dir=$(pwd)
parent_dir=$(dirname "$current_dir")
# Check if the path exists in the directory exists in the current directory
script_path="$current_dir/vendor/bin/$script_name"
if [ -x "$script_path" ]; then
echo "$script_path"
return
fi
# Traverse up the directory tree and check if the script exists in any parent directory
while [ "$parent_dir" != "$current_dir" ]; do
script_path="$parent_dir/vendor/bin/$script_name"
if [ -x "$script_path" ]; then
echo "$script_path"
return
fi
current_dir="$parent_dir"
parent_dir=$(dirname "$current_dir")
done
# Script not found
echo "Script $SCRIPT_NAME not found." > /dev/stderr
exit 1
}
SCRIPT_PATH=$(find_script "$SCRIPT_NAME");
MODIFIED_FILES=$(git status --porcelain=v2 . | awk '{print $NF}');
if [ -z "$MODIFIED_FILES" ]; then
echo "No modified files found in the current directory."
exit 0
fi
echo "Running $SCRIPT_PATH for the following list of files:";
echo "$MODIFIED_FILES" | xargs -i{} echo "- {}";
echo "$MODIFIED_FILES" | xargs "$SCRIPT_PATH";
O que o script faz? É bem simples, vou explicar:
- Primeiramente, rodamos
set -e
para fazer com que o bash imediatamente interrompa a execução caso qualquer comando retorne erro (com código de status de saída diferenet de0
) - Definimos uma variável chamada
SCRIPT_NAME
contendo o nome do arquivo atual, já explico mais abaixo o motivo disso - Definimos uma função
find_script
para procurar o script. Essa função busca encontrar o script de nomeSCRIPT_NAME
dentro da pastavendor/bin
, e busca tanto no diretório atual quanto em qualquer superior. Para exemplificar, se você rodar o arquivo dentro de uma pasta/home/usuario/projetos/MeuProjeto
, ele procurará um arquivo executável com o nome do script salvo emSCRIPT_NAME
(digamos que tal nome sejaphpcs
) nas seguintes pastas:/home/usuario/projetos/MeuProjeto/vendor/bin/phpcs
/home/usuario/projetos/vendor/bin/phpcs
/home/usuario/vendor/bin/phpcs
/home/vendor/bin/phpcs
/vendor/bin/phpcs
- Definimos uma variável
SCRIPT_PATH
contendo o caminho para o script encontrado pela funçãofind_script
- Computamos uma lista de arquivos modificados a partir do
git
. Apenas arquivos modificados cujas mudanças não foram comittados ainda aparecerão aqui - Verificamos se a lista de arquivos modificados possui, de fato, arquivos modificados. Se não houver arquivos modificados mostramos uma mensagem e encerramos o script
- Mostramos a lista de arquivos modificados, e passamos essa lista de arquivos modificados para o script em questão, como parâmetros.
Para usar esse script em uma computador Linux, basta você
- Criar uma pasta
bin
na sua$HOME
se você ainda não tiver, assim:mkdir -p $HOME/bin
- Adicionar essa pasta
bin
no seu environment, adicionando no arquivo de configuração do seu shell (digamos,.bashrc
) algo comoexport PATH=$PATH:$HOME/bin
- Adicionar esse script nessa pasta
bin
, com o nome da ferramenta que você quer otimizar. Você pode nomeá-lo comophpcs
ouphpcbf
, por exemplo - Em um projeto PHP com o
phpcbf
ouphpcs
instalado via Composer, executar o comando correspondente SEM usarvendor/bin
como prefixo - Pronto!
O comando só rodará de fato a ferramenta em questão SE houver arquivos modificados e não comittados ainda no repositório Git.
Também não executará se a ferramenta em questão não estiver instalada usando Composer, que já apresentei me outro post.
Finalmente, se você não estiver usando Git, o script não executará, pois ele não tem como rastrear quais arquivos modificados. Se você ainda não usa Git, verifque o meu post sobre, também. 😉
E você, tem alguma outra dica para agilizar tarefas demoradas - mas necessárias - do seu dia-a-dia? Compartilhe nos comentários! E se ficou alguma dúvida, pode madnar nos comentários que vou tentar fazer o meu melhor para ajudar, também. 😀
Valeu pela leitura!
Deixe um comentário