Aprendizados da semana (meio atrasado)
A retrospectiva acaba reforçando um resumo de informações aprendidas e foi pensando nisso de revisão (o ponto em que mais peco) que vou começar a escrever semanalmente um resumão do que vi/aprendi essa semana.
Começando pela semana do dia 2 ao dia 8, os principais assuntos abordados foram:
- 3 formas de merge, suas diferenças, prós e contras
- abstraindo a camada do banco de dados com ‘Storage’
- um pouco de backend (processos, fork, threads…)
Formas de merge
Commit merge - o tradicional
O git joga as mudanças da branch A no final da main e realiza o commit da união. O contra é a linha da árvore com diversas bifurcações (efeito garfo), gerando uma poluição visual. O ponto forte é a segurança, pois ele não altera os commits antigos, ele apenas anexa no final da linha da árvore.
Squash and merge - o prensado
Você criou uma nova branch, fez 10 commits e abriu o PR. O squash vai transformar os 10 commits em um único commit e realizar o merge. O forte, ao meu ver, é o uso em branches de fix/hotfix (‘vai’, ‘agora vai’, ‘mais uma vez’, ‘arruma o erro’…), já que não polui o histórico. O fraco é que o seu eu do futuro pode se deparar com um problema parecido e queira ver o passo a passo que te levou à solução no passado, porém não vai conseguir encontrar devido a essa perda de histórico, o como você chegou até a resolução.
Rebase and merge - o pipe |
Um dos recursos do rebase é o merge, em que ele se propõe a resolver o problema das várias bifurcações na linha do tempo. Porém, o seu ponto negativo é que, se não prestar atenção em qual branch está e qual quer mergear, você pode perder trabalho realizado, principalmente em branches compartilhadas. A dica é realizar esse merge pelo botão do GitHub para mitigar o risco.
Camada de Storage
Na faculdade, tivemos o contato com a camada repository. Mas, quando vi um padrão diferente no trabalho, que se chama storage, resolvi implementar ele em meu projeto. Eis as duas principais características que diferencia um do outro:
Storage (armazenamento):
- Sabe como e onde os dados são salvos fisicamente.
- Comunica-se direto com o banco de dados ou o ORM.
Repositories (repositório):
- Pensa nos dados como coleções de objetos na memória.
- Esconde os detalhes do banco de dados do resto do sistema.
Um pouco de backend
Uma das metas de se realizar durante as férias (e que não se concretizou, estou começando meio que junto na volta as aulas) foi o de estudar me baseando em alguns vídeos do Fábio Akita, sendo o primeiro vídeo o “Backend Parte 1”.
Para isso, usei uma extensão do Chrome chamada “YouTube to NotebookLLM” para que eu consiga obter outras formas de explicação para dado conceito. E, de brinde, ganho a opção de gerar flashcard, mapa mental e questionários sobre o conteúdo.
História
Esse vídeo começa abordando o contexto histórico dos anos 80 e 90, tanto na gringa quanto no Brasil. Visto que a base do backend é a comunicação com o hardware, um dos principais assuntos a ser tratado é a explicação de como o compilador funciona.
Compilador
O compilador é responsável por gerar o binário final. Além disso, ele traduz o seu código para máquina e também realiza diversas otimizações. Nesse sentido, o dever do programador passa a ser o de projetar o seu código para que outro ser humano consiga ler e entender.
Biblioteca e linkagem
Naturalmente, o conceito de bibliotecas foi surgindo para que não fosse necessário sempre estar reinventando a roda. E com elas, ficou possível linkar com o binário de duas formas:
- Estática: gerando binários gigantes, mas independentes, como, por exemplo, é o Golang.
Possui o pró de não precisar se preocupar com as dependências.
Possui o contra de, se uma lib for comprometida e ela estiver em cinco projetos, vai ser preciso atualizar a versão e compilar novamente para cada projeto o binário inteiro.
- Dinâmica: binário menor, mas depende que a biblioteca XYZ na versão ABC estejam instaladas no sistema operacional.
Possui o pró de, no cenário de uma vulnerabilidade, basta ajustar a versão da biblioteca no header e compilar apenas aquele binário, ao invés do projeto inteiro.
Possui o contra de, precisa se preocupar se o sistema operacional possui aquela dependência instalada.
“Programação é como uma arte. Qual cor é a melhor? Azul? Vermelho? Não tem cor certa. Existe saber usar a cor certa no lugar certo.”
Gerenciador de pacote
Em Linux, quando não há o pacote, é feito um tarball, que é um arquivo .tar zipado. Enquanto que o Windows opta por deixar o “sistema mais gordo”, mantendo diversas versões de cada pacote, sendo vários, muito provavelmente, obsoletos. Mas a vantagem disso é que se pegar um binário feito para Windows 98, muito provavelmente vai rodar no Windows 10.
Interpretador
Foi, originalmente, criado para iniciar e encerrar rápido pelo terminal (CLI), tendo como função:
- ler código-fonte e
- traduzir em tempo real para a máquina.
Para essa interpretação, é necessário uma parte do compilador chamada PARSER, que, dada uma gramática específica, converte para AST (Abstract Syntax Tree¹) e, por fim, executa as instruções do programa.
Por conta dessa dinâmica, o interpretador possui uma forte dependência do sistema operacional, pois depende diretamente de como o sistema operacional gerencia recursos e execução.
¹ Olha só a estrutura de dados em ação.
Processo e memória
Toda vez que você inicia um programa, o sistema operacional se encarrega de:
- carregar;
- alocar memória;
- criar processo em execução.
E, uma vez que ele cria, fica meio implícito que o gerenciamento do processo também é responsabilidade do sistema operacional, podendo matá-lo, pausá-lo ou reiniciá-lo.
Cada processo (navegador, planilha, e-mail) é isolado, ele sente-se único na máquina. Mas já pensou como é feito para que um processo não ocupe memória do processo vizinho?
O sistema operacional se encarrega de criar uma memória virtual, onde os programas pensam que iniciam “no ponto 1” da memória, mas quem cuida de traduzir os endereços virtuais para reais, de maneira a impedir que um processo corrompa outro, é o sistema operacional.
Paralelismo: fork x threads
Com o advento dos servidores de aplicação web, foram utilizadas estratégias para tentar lidar com a alta demanda.
Fork
Fork é uma operação barata e eficiente devido ao copy-on-write. Em Linux/Unix, há um gasto menor de recursos, pois não é necessário copiar toda a memória. Contudo, em Windows, o custo para a criação de processos é elevado (overhead).

Threads
Uma alternativa muito mais leve são as threads. Todavia, não é mil maravilhas devido à ausência de isolamento. Em outras palavras, um “subprocesso” pode sobrepor um outro “subprocesso” e corromper.
Portanto, o programador precisa garantir o “thread safety²”, que usa lock para evitar que outros usem o recurso que está sendo usado/editado, para evitar o famoso deadlock (corrupção de memória).
² a thread avisa ao sistema operacional que vai usar o recurso; é usado o lock no recurso; o recurso é usado; por fim, o recurso é desbloqueado e fica acessível novamente
Esse foi o aprendizado da semana do dia 2 ao 8. Além de aprendizados por conta, pretendo escrever coisas interessantes que eu tiver o contato na faculdade.