- A incident.io decidiu usar o tempo de build em Go em vez de percepção subjetiva para avaliar se deveria trocar os notebooks dos desenvolvedores por modelos M3, coletando dados reais do loop de feedback do desenvolvimento local
- Como era difícil obter os dados necessários com os hot reloaders Go existentes, a empresa criou sua própria ferramenta e passou a enviar ao data warehouse eventos de build como plataforma, memória, estado de energia, etapa do build, arquivo que disparou o build e tempo total
- Após filtrar falhas, cancelamentos e builds em bateria de cerca de 25 mil builds, analisou 12.525 builds bem-sucedidos e confirmou uma diferença estatisticamente significativa de desempenho em favor dos builds com alimentação AC
- No fim, usuários de M1 frequentemente esperavam quase 2 minutos até o fim do build, o M2 mostrou uma grande melhora em relação ao M1, e o M3 trouxe uma melhora gradual em relação ao M2
- Embora a diferença de memória não tenha sido tão clara no tempo total de build, o tempo do linker favoreceu máquinas com 32~36GB, levando a incident.io a substituir máquinas M1 por M3 Pro base com 36GB
O critério de upgrade é o loop de feedback do desenvolvimento
- Todos os desenvolvedores da incident.io usam MacBook para desenvolvimento
- Depois que a Apple anunciou o MacBook Pro M3 em outubro de 2023, o CTO Pete disse que faria a troca se o valor do upgrade fosse comprovado por dados
- Para decidir sobre o upgrade para M3, a equipe preparou três coisas
- Um hot reloader Go customizado
- Coleta de telemetria de build dos notebooks dos desenvolvedores
- Análise de dados com os modelos mais recentes da OpenAI e o Code Interpreter
- Embora seja difícil quantificar diretamente a produtividade do desenvolvedor, a incident.io considera um loop de feedback rápido essencial para a eficiência da engenharia
- Os loops de feedback repetidos com frequência no desenvolvimento local são os seguintes
- Compilação do monólito em Go
- Geração de código como clientes de API e interfaces
- Hot reload de frontend e app mobile
- Os desenvolvedores da incident.io executam localmente todo o ambiente da incident.io no notebook e mantêm um loop de feedback abaixo de 30 segundos entre alterar código e executar
- Como o app em Go está chegando perto de 1 milhão de linhas de código, a compilação em Go, que é frequente e custosa, foi escolhida como métrica para comparar o desempenho dos MacBooks
Como a telemetria de build foi coletada
- A incident.io usa codegangsta/gin como hot reloader Go desde a criação inicial do repositório no GitHub
- Outras alternativas de hot reload também foram avaliadas, mas a empresa não encontrou uma ferramenta que fornecesse a telemetria necessária para analisar o tempo de build
- Os dados que queriam coletar em cada build eram os seguintes
- Nível de sistema: plataforma M1/M2/M3, memória total etc.
- Métricas de runtime: OS, uso de memória, fonte de energia, nível de bateria etc.
- Telemetria de build: tempo total, etapas do build em Go, arquivos que dispararam o build etc.
- Como não havia alternativa pronta, a empresa criou sua própria ferramenta a partir de
main.go, executando e fazendo parse da saída de vários binários do Mac para extrair os valores necessáriosmemory_pressuredockersysctlpmset
- O código relacionado foi publicado em um Gist
- Depois de criar os coletores de sistema e runtime, a equipe encapsulou o comando de build Go para coletar tempos por etapa, como linker e compilação, além do arquivo que acionou o build
- O hot reloader final passou a ser executado no alvo
make runjá existente, sendo uma mudança invisível para o time de engenharia - Ao fim de cada build, um evento de telemetria era enviado para um endpoint HTTP e carregado no data warehouse por meio de um webhook receiver da Fivetran
Fluxo de análise com o OpenAI Assistant
- Após acumular um dataset suficiente por algumas semanas, a equipe exportou para CSV o resultado de
select * except(payload) from developer__build_eventsno BigQuery - Forneceu aos OpenAI Assistants um prompt explicando o objetivo e o arquivo CSV
- Usou o modelo experimental
gpt-4-1106-previewcom Code Interpreter habilitado para a análise de dados - O tempo de build varia muito até no mesmo sistema, e o cache do compilador Go também influencia bastante, então comparar apenas a média por plataforma não seria justo
- Um M3 Max sem cache poderia ser mais lento que um Intel MacBook antigo com cache
- Em vez de uma simples comparação de médias, a análise foi feita organizando as condições de build e separando plataforma, memória e estado de energia
Limpeza dos dados e condições para uma comparação justa
- O dataset completo tinha cerca de 25 mil builds e foi coletado em diferentes horários do dia, notebooks e condições
- Para comparar plataformas de forma justa, os seguintes builds foram excluídos
- Builds com falha ou cancelados: como não foram concluídos, não servem para comparar velocidade de build
- Builds em bateria: o OS X pode limitar desempenho para preservar a bateria
- Depois de excluir os builds com falha, foram contabilizados 12.525 builds bem-sucedidos
- A diferença de desempenho entre builds em energia AC e em bateria foi comparada principalmente entre M1 Pro e M2 Max
- No teste estatístico, o tempo médio dos builds em energia AC foi menor, com p-value de cerca de 0,0014
- A análise seguinte usou apenas builds bem-sucedidos em energia AC
Por que o tempo de build em Go oscila
- O monólito em Go da incident.io é um alvo de observação contínua do desempenho de build, e remover ou ajustar o próprio processo de build é tão importante quanto comprar hardware
- Projetos Go são compostos por vários pacotes, e o compilador Go usa cache para recompilar apenas os pacotes que considera alterados
- O app da incident.io foi projetado com um grafo de dependências amplo e poucos módulos de base, para que a maioria das mudanças não leve à recompilação de todo o grafo
- Os tipos de build se dividem aproximadamente em quatro categorias
- Conclusão imediata, menos de 3 segundos: mudanças sem relação com o compilador Go, permitindo usar binário em cache
- Build rápido, menos de 30 segundos: mudança em um único pacote com poucas dependências, reutilizando a maior parte do cache, com tempo gasto principalmente em linkedição
- Build intermediário, 30 segundos a 1 minuto: alteração em um pacote funcional com algumas dependências inferiores, mas com grande parte ainda reaproveitável
- Build lento, mais de 1 minuto: adição de tipos ao pacote base
domain, exigindo recompilar todos os pacotes do app
- A comparação entre plataformas precisa considerar essas diferenças na natureza dos builds; misturar tudo seria comparar coisas incomparáveis
Resultado da comparação entre M1, M2 e M3
- Primeiro foi feita a comparação entre M1 Pro e M2 Max considerando apenas builds bem-sucedidos em energia AC
- O M2 Max foi claramente mais rápido que o M1 Pro em build, mas os dois equipamentos diferiam não só no chipset, como também na configuração de memória
- A distribuição dos eventos de build bem-sucedidos por plataforma e memória foi a seguinte
- Apple M1 Pro 16GB: 5.235
- Apple M2 Pro 16GB: 1.927
- Apple M2 Max 32GB: 3.842
- Apple M3 Pro 18GB: 321
- Apple M3 Pro 36GB: 899
- Apple M3 Max 36GB: 301
- A comparação entre M1 Pro 16GB e M2 Max 32GB não era totalmente justa por causa da diferença de memória
- Ao comparar M2 Pro 16GB e M2 Max 32GB, o efeito de 32GB de memória no tempo total de build pareceu pequeno
- M2 Pro e M2 Max são, em geral, o mesmo chip, e o Max tem mais 2 núcleos de eficiência energética
- Como esses núcleos equivalem a cerca de 1/5 de um núcleo de desempenho, concluiu-se que a contribuição deles para compilar programas Go é pequena
- Para avaliar o M3, a equipe comprou os três modelos a seguir
- M3 Pro 12-core, 6 núcleos de desempenho + 6 núcleos de eficiência energética, 18GB
- M3 Pro 12-core, 6 núcleos de desempenho + 6 núcleos de eficiência energética, 36GB
- M3 Max 14-core, 10 núcleos de desempenho + 4 núcleos de eficiência energética, 36GB
- Os gráficos de tempo de build do M3 Pro 18GB e 36GB foram parecidos, mas havia menos dados de M3 que das demais plataformas
- Excluindo os builds muito rápidos de menos de 3 segundos, a comparação entre M3 Pro e M3 Max mostrou que o M3 Max não trouxe uma melhora suficientemente marcante para justificar o preço 60% mais alto em relação ao M3 Pro base
- A conclusão geral foi a seguinte
- Usuários de notebooks M1 frequentemente esperam quase 2 minutos até o fim do build
- O M2 é um grande upgrade em relação ao M1
- O M3 é uma melhora gradual em relação ao M2
- Usuários de M1 devem fazer upgrade para o M3 Pro base
- Usuários de M2 não precisam fazer upgrade
A memória aparece com mais clareza no tempo do linker
- Na comparação do tempo total de build, aumentar de 16~18GB para 32~36GB não mostrou uma melhora muito significativa
- Como, ao contrário do esperado, o efeito da memória não apareceu com força nos gráficos, a equipe analisou separadamente o tempo do linker entre as etapas do build
- Os eventos de telemetria incluíam tempos das etapas de link e compilação, e a equipe criou a coluna
linker_timea partir debuild_stages.link.duration_secondspara análise - Ao comparar
linker_timepor plataforma e configuração de memória, surgiu um padrão diferente- Máquinas M1, M2 e M3 com 32~36GB de memória quase sempre concluíam a linkedição em menos de 20 segundos
- Equipamentos com 18GB ou menos frequentemente ultrapassavam 20 segundos na linkedição
- Mesmo que a memória extra seja menos evidente no tempo total de build, ela se mostrou útil na etapa de linker
- A empresa também está avaliando remover o Docker das máquinas de desenvolvimento, interpretando que dispositivos com menos memória poderiam melhorar o tempo de linker ao aumentar a memória disponível do sistema sem Docker
- No desenvolvimento de app mobile, simuladores usam muita memória do sistema, então aumentar a memória também foi considerado um custo válido de preparação para o futuro
Decisão final e efeitos colaterais positivos
- A incident.io decidiu fazer upgrade das máquinas M1 para o M3 Pro base com 36GB de memória
- As máquinas M2 já parecem ter desempenho suficientemente bom e, por enquanto, não serão atualizadas
- Além da decisão de compra dos notebooks, a empresa passou a entender melhor seu ambiente e suas ferramentas de desenvolvimento
- Os resultados obtidos pela equipe foram os seguintes
- Encontrou no tempo de build em Go um bom benchmark para medir o desempenho das máquinas dos desenvolvedores
- Criou um hot reloader Go próprio para rastrear as métricas necessárias e ainda ganhou outras melhorias de usabilidade
- Passou a entender melhor os fatores que aceleram ou desaceleram os builds em Go
- Confirmou que os OpenAI Assistants podem ser usados para problemas semelhantes de análise de dados
- Quantificou, do ponto de vista de desenvolvedores Go, as melhorias entre as diferentes linhas de chips da Apple
- A memória é importante, mas aparece de forma mais clara no tempo do linker do que no tempo total de build
1 comentários
Opiniões no Hacker News
É um ótimo texto, e gostei da variedade de formas como os dados foram coletados e analisados, mas acho que teria sido muito mais fácil e preciso colocar cada notebook lado a lado e rodar builds cronometrados no mesmo cenário
Daria para comparar alguns casos, como build completo, build incremental das alterações recentes e build incremental que exige recompilar um módulo específico, ou criar em um dia um script que aplicasse os últimos 100 commits do Git em sequência e medisse o tempo dos builds incrementais
Ao reunir estatísticas da empresa inteira, o viés pode ficar grande. Por exemplo, é provável que pessoas recém-contratadas usem M3 e funcionários antigos usem M1; os recém-contratados fazem mais alterações pequenas, enquanto os mais experientes mexem em partes profundas ou áreas complexas do código, o que pode aumentar o tempo de build
Por isso, a análise em si é legal, mas, considerando os vieses inerentes à amostra, acho que o certo seria começar por um método simples de benchmark dos commits recentes em cada notebook antes de criar uma arquitetura de coleta de dados para a empresa inteira
O motivo de coletar esses dados não era só comparar máquinas, mas também acumular dados históricos sobre o tempo de build dos desenvolvedores e medir continuamente o desempenho dos builds para detectar regressões
Quando vemos o tempo de build aumentar, ajustamos com frequência a estrutura da base de código para deixar os builds mais rápidos
O M3 pode fazer builds 30% mais rápidos que o M1, mas o build em rede é 15 vezes mais rápido. Dá para avaliar se, em vez de dar M3 aos desenvolvedores, teria sido melhor investir em builds em rede
Foi feito um teste t em dados que não foram amostrados de forma independente: vários pontos de dados vieram de pessoas diferentes, e cada pessoa trabalha em tarefas distintas, que podem exigir quantidades diferentes de computação, gerando fatores de confusão. Isso viola pressupostos básicos do teste t, mas o interpretador de código não apontou
Em vez disso, poderia ter sido usado um modelo linear de efeitos mistos, colocando fatores como dono do notebook e tempo de casa como efeitos aleatórios
Ainda assim, os dados em si são interessantes, especialmente a parte de RAM. Cache é poderoso, e ter mais RAM traz um ganho maior do que muita gente imagina. Normalmente, em um MacBook com mais RAM do que o necessário, a maior parte da RAM sobrando fica preenchida por cache
Coletando isso por mais ou menos uma semana, daria para obter um recorte da carga de trabalho real, repetir esses builds em cada categoria de hardware e reutilizá-los mais tarde em novos hardwares
Como cientista, acho interessante a forma como programadores lidam com dados
Eles fizeram gráficos bonitos, automatizaram a análise muito rapidamente com o ChatGPT, e o ChatGPT produziu um teste t bastante plausível
Mas havia variação por quantidade de memória e tipo de chip, e eles não pensaram em regressão linear; além disso, fizeram histogramas difíceis de comparar. Poderiam complementar com médias simples e barras de erro, ou usar uma função de distribuição acumulada (CDF), que facilita ver sobreposição ou deslocamento
Em geral, as pessoas lidam com os dados do jeito que as ferramentas mostram, o que tem bastante relação com os conjuntos de produtos de análise, análise de desempenho e observabilidade
Esperar que um engenheiro de software médio conheça CDF é parecido com esperar que ele conheça quatérnios em gráficos 3D ou o básico de escrever shaders
Outra opção seria regressão quantílica. Se a hipótese é M3 > M2 > M1, o famoso teste de Jonckheere–Terpstra, usado para medianas ordenadas, talvez fosse perfeito para esse tipo de pseudoanálise
Para um exemplo, veja o último gráfico desta página: https://ggplot2.tidyverse.org/reference/stat_ecdf.html
É uma análise sólida, mas, pela minha experiência pessoal, quero deixar um alerta
Em uma empresa de software de porte médio, com 2 mil funcionários, tentamos aumentar a produtividade de desenvolvimento e exploramos a possibilidade de mover a stack de desenvolvimento para instâncias AWS em vez de comprar notebooks novos
No fim, virou um projeto de vários anos com cerca de 4 desenvolvedores dedicados em tempo integral e, olhando para trás, não trouxe benefício em relação ao custo. Reproduzir na nuvem uma experiência de desenvolvimento totalmente local ainda é difícil demais
Então acho melhor fazer upgrade dos notebooks
O código fica no notebook, mas é sincronizado em tempo real com serviços remotos, sem builds Docker nem deploys K8s, então parece realmente local
Em especial, dá para executar imediatamente testes de integração ou superiores enquanto se codifica, evitando o ciclo commit-push-pray
Para isso usamos o Garden(https://docs.garden.io). Usando Garden ou não, com as ferramentas adequadas, aproveitar o poder da nuvem no loop interno de desenvolvimento pode ser muito bom
Um texto com mais detalhes sobre a experiência: https://thenewstack.io/one-year-of-remote-kubernetes-develop...
Também se tornaram possíveis várias coisas que não eram na versão apenas local. Por exemplo, ao alternar entre várias branches, trocar de máquina em vez de mudar os arquivos locais reduz muito a latência de troca de contexto
Quando é preciso subir mais de 200 componentes para fazer algo, fica difícil trabalhar até em uma única parte que interage só com alguns deles
Numa era em que temos servidores com 128+ cores e 256+ threads, estou cada vez mais inclinado a pensar que, para a maior parte dos softwares, monólitos voltam a ser uma opção melhor
É puro desperdício de dinheiro e tempo. Dá para perceber que interceptar todas as chamadas de sistema com software inútil de fornecedor é ruim para toolchains no estilo Unix que executam inúmeros subprocessos
Em grandes empresas de tecnologia como Google e Meta, o ambiente de desenvolvimento fica na nuvem para a maioria dos engenheiros de software
É uma experiência de desenvolvimento muito melhor que a local
Considerando também o custo para desenvolvimento iOS, a conclusão da minha pesquisa pessoal foi esta
O M2 Pro é bom, mas a melhoria em relação ao M1 Pro de 10 núcleos não é tão grande assim. No XcodeBenchmark, são 136 segundos contra 120 segundos: https://github.com/devMEremenko/XcodeBenchmark
O M3 Pro parece ter sido enfraquecido para diferenciá-lo do M3 Max e, como tem apenas 6 núcleos de desempenho, na prática é parecido com o M2 Pro
No fim, comprei um M1 Pro de 10 núcleos levemente usado e estou muito satisfeito. Por menos da metade do preço do M3 Pro básico, consegui 85% do desempenho, levando em conta também que, em geral, a CPU precisa ficar pelo menos 33% a 50% mais rápida para a diferença ser perceptível
Ele é muito mais eficiente que o M2 Pro e tem desempenho um pouco melhor. Em um notebook, é isso que eu quero; largura de banda de memória não é algo que eu use de fato
Não vi uma diferença tão grande quanto a mostrada neste texto. Pode depender da linguagem ou do projeto, mas, ao benchmarkar lado a lado o mesmo comando de compilação, não vi uma diferença tão grande
Como a toolchain de compilação é diferente, não é surpreendente; e na toolchain do Go também dá para ver especificações específicas atuando de forma diferente em cada etapa do build, como memória adicional ajudando o desempenho do linker
Também vi várias reações dizendo que o desempenho do M3 foi estranhamente limitado; espero que isso não continue nos modelos a partir do M4
120 Hz também seria bom, mas acho que não vai chegar aos notebooks Air
Fui um dos principais contribuidores do Chromium e do Node.js, e hoje sou um dos principais contribuidores do gRPC Core/C++; tempo de build nunca foi algo que me incomodou muito
Existem os “builds interativos”, que são builds incrementais para rodar novamente os testes unitários relacionados durante o trabalho, e os builds não interativos, que você deixa rodando enquanto vai tomar café ou ler e-mails. Nunca vi uma troca de hardware transformar um build não interativo em interativo
Meu equipamento pessoal é um Intel i7 com mais de 5 anos e 16 GB de memória; quando percebi que precisava de mais memória para linkar o Node.js no WSL, acrescentei mais 16 GB
Meu notebook de trabalho é um MacBook Pro Intel com Touch Bar, e não acho que isso tenha grande impacto na produtividade. O importante é o tamanho e a qualidade da tela, e a velocidade do armazenamento. Mais do que a evolução da CPU, o sistema de build — como a velocidade de builds incrementais e o suporte a builds distribuídos — tem impacto maior. Em projetos pessoais uso Bazel
Compilação e linkedição deveriam, na prática, terminar instantaneamente, rápido a ponto de você nem perceber que existe uma etapa de compilação
Builds de release com técnicas como otimização do programa inteiro podem demorar, mas o loop comum de compilar/depurar/testar pode ser imediato. A compilação de linguagens de sistema é incrivelmente lenta por motivos legados, mas não precisa necessariamente ser assim
Passei tanto tempo mexendo em arquivos BUILD que fiquei em dúvida se aquilo tinha mais valor do que um Makefile comum. Isso foi há 3 anos, então talvez o ecossistema público esteja melhor agora
Sobre a velocidade de armazenamento, não sei bem, e nossos builds rodam todos em máquinas remotas de desenvolvimento potentes
Para pessoas que querem fazer análise de dados com IA, como no texto, acho muito mais fácil colocar os dados no R, Stata etc. e consultá-los diretamente
Os comandos são mais curtos e precisos e, acima de tudo, muito mais reproduzíveis
A parte mais difícil da análise de dados é entender os dados e o mecanismo que os gerou. Para isso é preciso um modelo causal do domínio do problema
Se a IA não tiver sido treinada antes com outros dados daquele domínio, não sei se ela consegue criar um modelo causal útil. Sem esse modelo, é impossível interpretar os dados de forma razoável, e também fico curioso se os modelos atuais de IA conseguem detectar confundimento, influência excessiva de outliers e variáveis interessantes de moderação de efeitos
Quando fiz, foi com Python e pandas, e você pode pedir para ver o código usado na análise
A diferença é entre colocar os dados no R/Python e pesquisar “como faço xyzzzy” para escrever o código você mesmo, ou usar o ChatGPT
A parte “todos os desenvolvedores rodam localmente um ambiente incident.io completo no notebook e obtêm um loop de feedback de menos de 30 segundos entre mudança de código e execução” parece ser o maior feito
Fora uma vez em que ajudei uma startup por pouco tempo, nunca trabalhei em uma empresa onde fosse possível rodar a instância de desenvolvimento/local da empresa inteira em uma única máquina
Sempre havia alguma coisa inacessível, sempre havia uma armadilha
Não entendo por que as pessoas não ficam mais indignadas com uma experiência do desenvolvedor tão horrível. Parece que os recém-saídos da faculdade hoje nem sabem o que estão perdendo
Quem nunca viveu nesse mundo não entende o quanto é melhor e racionaliza de todo jeito
Só que no ano passado adicionamos o Snowflake; ele de fato resolve problemas reais, mas desenvolver iterativamente essa parte é doloroso
É linear em relação ao número de serviços que precisam ser suportados e também linear em relação ao número de engenheiros que precisam ser suportados. Além disso surgem vários casos de uso diferentes que não se encaixam bem e, quando você percebe, a equipe de infraestrutura virou gargalo para lançar funcionalidades, e as pessoas começam a usar abordagens próprias
Depois que essa caixa de Pandora é aberta, é praticamente impossível voltar atrás. Ainda assim, reduzir o ciclo de desenvolvimento de horas ou dias para minutos é muito mais importante do que reduzir alguns minutos em 25%
Até agora está indo bem
Como autor do texto, obrigado por postar
Ele cobre várias coisas, como profiling da compilação em Go, criação de um hot reloader e análise de um dataset de builds com IA
Em resumo, para nós valeu a pena atualizar do M1 para o M3 Pro, e nos testes o Max não fez grande diferença. O M2 ficou bem próximo do M3, então para nós não valia a pena atualizar
Se houver perguntas, posso responder
Também me pergunto como esse custo afeta o período de retorno
Ou talvez porque a tarefa terminava tão rápido que, no uso real, a diferença não aparecia
A ideia é interessante, mas a qualidade da análise dos dados parece bem baixa, e não tenho certeza se eles estão realmente aprendendo aquilo que acham que estão
Em especial, é difícil entender por que os builds abaixo de 20 segundos aumentam de forma tão dramática ao passar do M1 Pro para o M2 Pro. Em tarefas de compilação de código, a diferença real de desempenho entre os dois é de aproximadamente 20% a 25%
Também não faz muito sentido que as máquinas M3 tenham menos builds abaixo de 20 segundos que as máquinas M2, ou que o M3 Pro, com metade dos núcleos, tenha mais builds abaixo de 20 segundos que o M3 Max
É bem provável que diferenças de comportamento dos desenvolvedores, como o fato de pessoas com tipos diferentes de notebook normalmente fazerem trabalhos diferentes, tenham produzido essas diferenças
Algumas observações de uma leitura rápida: o compilador Go parece não aproveitar muito os núcleos adicionais; a forma de combinar os dados é fundamentalmente incômoda; e a forma de comparação também carece de consistência, misturando histogramas com gráficos de densidade por faixas e usando intervalos diferentes no eixo y
O Mac não reduz o desempenho da CPU só por estar na bateria. Se os builds realmente ficam mais lentos na bateria, é difícil ter certeza apenas pelo gráfico, mas provavelmente é porque a configuração de “baixo consumo” está ativada
Fugindo um pouco do assunto, fico curioso para saber como outras empresas equilibram gerenciamento de endpoints e software de segurança com a produtividade dos desenvolvedores
Na nossa empresa, os notebooks de desenvolvedores rodam mais de 5 serviços em segundo plano, tanto no Mac quanto no Windows. Isso inclui gerenciamento de endpoints, interceptação de elevação de privilégios, interceptação e inspeção de TLS, proteção contra malware e cliente VPN
Essa combinação tem um grande impacto no desempenho. Seja o que for que você faça na máquina, esses serviços consomem CPU e desempenho de I/O, e os desenvolvedores vêm reclamando de travamentos e engasgos aleatórios
Entendo que segurança é necessária, considerando o aumento de ransomware e roubo de propriedade intelectual, mas fico me perguntando se há empresas que encontraram formas melhores de fornecer segurança com menos impacto na produtividade dos desenvolvedores