JavaRush /Blogue Java /Random-PT /Pausa para café #64. Como escrever código limpo. Por que ...

Pausa para café #64. Como escrever código limpo. Por que Java é melhor que C++ para sistemas de baixa latência

Publicado no grupo Random-PT

Como escrever código limpo

Fonte: Dev.to Escrever código limpo é como escrever poesia. Esta é uma poesia que deve ser concisa, compreensível e acessível a mudanças. Código limpo implica uma organização escalável. Isso significa que fazer mudanças não causa caos. A capacidade de escrever esse código é uma das principais qualidades de um desenvolvedor experiente. Depois que várias pessoas me recomendaram a leitura do livro Código Limpo, finalmente criei coragem para lê-lo. Acontece que este é um daqueles livros em que a capa faz jus ao hype em torno dele. As recomendações do livro são claras, específicas, práticas e até apresentadas com humor. Hoje quero compartilhar com vocês as principais conclusões deste livro.Pausa para café #64.  Como escrever código limpo.  Por que Java é melhor que C++ para sistemas de baixa latência - 1

1. O código não deve apenas funcionar, mas também ser legível

A maior parte do custo do software está vinculada ao suporte de longo prazo. Portanto, o código que você escreve deve expressar claramente suas intenções. Deve ser tal que os novos desenvolvedores que ingressam na equipe possam entender facilmente o que exatamente está acontecendo no código e por quê. Quanto mais compreensível for o código que o autor escreve, menos tempo levará para outros desenvolvedores entendê-lo. Isso reduz defeitos e custos de manutenção. Como conseguir isso? Boa nomenclatura + classes e funções com responsabilidade única + testes de redação.

2. Mais tarde significa nunca

Sejamos honestos: todos nós às vezes prometemos a nós mesmos que voltaremos e limparemos o código mais tarde, mas acabamos esquecendo disso. Não deixe pedaços de código inúteis que não são mais necessários. Eles confundem outros desenvolvedores e não têm valor. Portanto, ao fazer alterações na funcionalidade, sempre remova o código antigo. Se algo quebrar em algum lugar, os testes ainda mostrarão isso imediatamente. Como conseguir isso? Excluir código pode ser assustador, especialmente em grandes arquiteturas. Portanto, o teste é fundamental aqui. Eles permitem que você remova código com confiança.

3. Os recursos devem ser pequenos

A primeira regra para escrever funções é que elas devem ser pequenas, até cerca de 20 linhas . Quanto menor a função e mais focada em uma tarefa, mais fácil será encontrar um bom nome para ela. Quanto aos argumentos de funções, o seu número ideal é 0. A seguir vem 1, 2, mas você deve tentar ter no máximo 3 argumentos . As funções devem ser escritas de acordo com os princípios de responsabilidade única e abertas/fechadas.

4. A duplicação de código é ruim

A duplicação é inimiga de um sistema bem organizado. É trabalho extra, risco extra e complexidade extra desnecessária. O que fazer sobre isso? Certifique-se de que seu código seja escrito de acordo com o princípio DRY, isolado e modular.

5. O único comentário bom é aquele que você encontrou um jeito de não escrever.

“Não há nada mais útil do que um bom comentário no lugar certo. Mas os comentários, mesmo na melhor das hipóteses, são um mal necessário.” Os comentários têm como objetivo compensar nossa incapacidade de expressar nossos pensamentos em código. Ou seja, inicialmente é uma admissão de derrota. Sim, temos que usá-los porque nem sempre podemos deixar claras as nossas intenções com o código, mas isso não é motivo para comemorar. O problema é que os comentários muitas vezes mentem. Nem sempre e nem de propósito, mas com muita frequência. Quanto mais antigo for o comentário e quanto mais distante estiver do código que descreve, maior será a probabilidade de estar incorreto. A razão para isso é simples: os programadores não conseguem manter muito bem o código e todos os comentários. Portanto, muitas vezes os comentários são separados do código ao qual se referem e tornam-se anotações órfãs com precisão mínima. O que fazer sobre isso? Métodos de nomenclatura descritivos devem ser usados. Ao ler o nome de uma variável, você deve entender imediatamente o que ela é. Testes também são necessários para que outros desenvolvedores entendam qual funcionalidade é mais importante.

6. O objeto revela comportamento, mas não dados.

Um módulo não deve saber sobre o interior dos objetos que manipula. Os objetos ocultam seus dados e revelam suas operações. Isso significa que um objeto não deve expor sua estrutura interna através de métodos acessadores. Não é necessário que todos vejam você nu. O que fazer sobre isso? O escopo das variáveis ​​deve ser o mais local possível para não expor mais do que o necessário.

7. Teste

O código de teste é tão importante quanto o que entra em produção. Portanto, deve mudar e crescer à medida que o projeto se desenvolve. Os testes mantêm seu código flexível, sustentável e reutilizável. Sem eles, qualquer alteração pode resultar em bugs. Os testes permitem que você limpe seu código sem medo de que algo quebre. Portanto, manter a pureza dos testes é de grande importância. A limpeza dos testes garante sua legibilidade. Os testes são uma oportunidade de explicar a outros desenvolvedores em linguagem simples as intenções do autor do código. Portanto, testamos apenas um conceito em cada função de teste. Isso torna o teste descritivo, mais fácil de ler e, se falhar, é mais fácil rastrear o motivo. Como conseguir isso? Deve-se seguir os princípios dos FIRST testes limpos . Os testes devem ser:
  • Rápido. Os testes devem ser executados rapidamente. Se você tiver que esperar muito para que um teste seja executado, é menos provável que você o execute com mais frequência.
  • Independente/isolado (Independente). Os testes devem ser tão isolados e independentes quanto possível.
  • Repetivel. Os testes devem ser repetíveis em qualquer ambiente – desenvolvimento, preparação e produção.
  • Autovalidação. O resultado do teste deve ser um valor booleano. O teste deve passar ou falhar.
  • Minucioso. Devemos nos esforçar para cobrir todos os casos extremos, todos os problemas de segurança, todos os casos de uso (caso de uso) e caminho feliz (o cenário mais favorável para o código) com testes.

8. Tratamento de erros e exceções

Cada exceção lançada deve fornecer contexto suficiente para determinar a origem e o local do erro. Normalmente você tem um rastreamento de pilha de qualquer exceção, mas um rastreamento de pilha não informa o propósito da operação que falhou. Se possível, evite passar null no seu código. Se você estiver tentado a retornar nulo de um método, considere lançar uma exceção. Faça do tratamento de erros uma tarefa separada que pode ser visualizada independentemente da lógica principal. Como conseguir isso? Crie mensagens de erro informativas e repasse-as junto com suas exceções. Especifique a operação que falhou e o tipo de erro.

9. Aulas

As turmas devem ser pequenas. Mas não são as linhas de código que precisam ser contadas, mas sim a responsabilidade. Os nomes das classes são essenciais para descrever suas responsabilidades. Nossos sistemas devem consistir em muitas classes pequenas, e não em algumas classes enormes. Cada classe pequena deve encapsular uma única responsabilidade. Deve haver apenas uma razão específica para a existência de cada classe, e cada classe deve “cooperar” com várias outras classes para alcançar o comportamento desejado do sistema. Raramente há um bom motivo para criar uma variável pública. Enfraquecer o encapsulamento é sempre o último recurso. Além disso, deve haver poucas variáveis ​​de instância. Um bom design de software permite que alterações sejam feitas sem grandes investimentos ou retrabalho. Restringir o leque de variáveis ​​torna esta tarefa mais fácil. Como conseguir isso? A separação de interesses é uma das técnicas de design mais antigas e importantes. As aulas devem ser abertas para extensão, mas fechadas para modificação. Em um sistema ideal, habilitamos novos recursos estendendo o sistema em vez de fazer alterações no código existente.

10. Formatação

Cada linha vazia é uma dica visual para ajudar a identificar que um conceito novo e separado começou. As variáveis ​​locais devem aparecer no topo da função. Variáveis ​​de instância devem ser declaradas no topo da classe. Linhas curtas são melhores que linhas longas. Normalmente o limite é de 100 a 120 caracteres; você não deve aumentar. Como conseguir isso? A maioria dos parâmetros pode ser passada para um linter em seu CI ou editor de texto. Use essas ferramentas para tornar seu código o mais limpo possível.

Princípios de Desenvolvimento de Programas

Use as seguintes técnicas e seu código estará sempre limpo: Nomeando variáveis. A escolha de nomes apropriados (boa nomenclatura) é fundamental para tornar o código legível e, portanto, sustentável. “Você deve escolher um nome para uma variável com a mesma responsabilidade que escolheria para seu primogênito.” Escolher bons nomes costuma ser um desafio para os desenvolvedores. Isto requer boas habilidades descritivas e uma formação cultural compartilhada. Código limpo é aquele que é lido e aprimorado por desenvolvedores completamente diferentes. O nome de uma variável, função ou classe deve responder a todas as questões básicas: por que esta entidade existe, o que e como ela é usada. Se um nome requer comentários, significa que não revela suficientemente a essência daquilo que descreve. Nomes mais longos são mais importantes que os mais curtos, e qualquer nome pesquisável é melhor que uma constante. Nomes de uma letra só podem ser usados ​​como variáveis ​​locais dentro de métodos curtos: o comprimento do nome deve corresponder ao escopo. Os nomes dos métodos devem ser verbos ou frases verbais; o nome da classe não deve ser um verbo. As dependências devem ser reduzidas ao mínimo. É melhor confiar naquilo que você controla do que naquilo que você não pode controlar. Caso contrário, essas coisas irão controlar você. Precisão. Cada pedaço de código deve estar em um local onde o leitor espera encontrá-lo. A navegação pela base de código deve ser intuitiva e as intenções do desenvolvedor devem ser claras. Limpeza. Não deixe código inútil na base de código (antigo e não mais usado ou criado "por precaução"). Reduza a duplicação e crie abstrações simples desde o início. Estandardização. Ao escrever código, você deve seguir o estilo e as práticas estabelecidas para o repositório. Autodisciplina. À medida que as tecnologias usadas se desenvolvem e novas aparecem, os desenvolvedores geralmente desejam mudar e melhorar algo no código existente. Não ceda ao hype muito rapidamente: estude novas pilhas minuciosamente e apenas para um propósito específico. Manter sua base de código limpa envolve mais do que ser educado com seus colegas atuais e futuros. É essencial para a sobrevivência do programa a longo prazo. Quanto mais limpo for o seu código, mais felizes serão os desenvolvedores, melhor será o produto e mais tempo ele durará.

Por que Java é melhor que C++ para sistemas de baixa latência

Fonte: StackOverflow Como desenvolvedores, todos nós sabemos que existem duas maneiras de fazer as coisas: manualmente, de forma lenta e irritante, ou automaticamente, de maneira difícil e rápida. Eu poderia usar inteligência artificial para escrever este artigo para mim. Isso poderia me poupar muito tempo - a IA pode gerar milhares de artigos por segundo, mas meu editor provavelmente não ficaria feliz em saber que levaria dois anos para gerar o primeiro artigo. Pausa para café #64.  Como escrever código limpo.  Por que Java é melhor que C++ para sistemas de baixa latência - 2Uma situação semelhante surge no desenvolvimento de sistemas de software com baixa latência. A sabedoria convencional é que seria uma loucura usar qualquer coisa diferente de C++ porque todo o resto tem muita latência. Mas estou aqui para convencê-lo da noção oposta, contra-intuitiva e quase herética: quando se trata de alcançar baixa latência em sistemas de software, Java é melhor. Neste artigo, quero dar um exemplo específico de software que valoriza a baixa latência: sistemas de negociação. No entanto, os argumentos aqui apresentados podem ser aplicados a quase todas as circunstâncias em que a baixa latência é necessária ou desejada. Só é mais fácil discutir em relação à área de desenvolvimento em que tenho experiência. E a verdade é que a latência é difícil de medir. Tudo se resume ao que você entende por baixa latência. Vamos descobrir isso agora.

Sabedoria Adquirida

Como o C++ está muito mais próximo do hardware, a maioria dos desenvolvedores dirá que a codificação nesta linguagem oferece uma vantagem de velocidade. Em situações de baixa latência, como negociações de alta velocidade, onde milissegundos podem fazer a diferença entre um software viável e um desperdício legado de espaço em disco, o C++ é considerado o padrão ouro. Pelo menos era assim que costumava ser. Mas a realidade é que muitos grandes bancos e corretoras agora usam sistemas escritos em Java. E quero dizer escrito nativamente em Java, não escrito em Java e depois interpretado em C++ para reduzir a latência. Estes sistemas estão a tornar-se padrão mesmo para bancos de investimento de nível 1, apesar de serem (supostamente) mais lentos. Então o que está acontecendo? Sim, C++ pode ter “baixa latência” quando se trata de execução de código, mas definitivamente não é baixa latência quando se trata de implantar novos recursos ou até mesmo encontrar desenvolvedores que possam escrevê-lo.

Diferenças (reais) entre Java e C++

A questão do tempo de desenvolvimento é apenas o começo quando se trata das diferenças entre Java e C++ em sistemas do mundo real. Para entender o verdadeiro valor de cada linguagem neste contexto, vamos nos aprofundar um pouco mais. Primeiro, é importante lembrar o verdadeiro motivo pelo qual C++ é mais rápido que Java na maioria das situações: um ponteiro C++ é o endereço de uma variável na memória. Isso significa que o software pode acessar variáveis ​​individuais diretamente e não precisa rastrear tabelas computacionalmente intensivas para procurá-las. Ou pelo menos pode ser resolvido especificando onde eles estão, porque com C++ muitas vezes você precisa gerenciar explicitamente o tempo de vida e a propriedade dos objetos. Como resultado, a menos que você seja realmente bom em escrever código (uma habilidade que pode levar décadas para ser dominada), o C++ exigirá horas (ou semanas) de depuração. E como qualquer pessoa que já tentou depurar um mecanismo Monte Carlo ou uma ferramenta de teste PDE lhe dirá, tentar depurar o acesso à memória em um nível fundamental pode consumir muito tempo. Apenas um ponteiro defeituoso pode facilmente derrubar um sistema inteiro, portanto, lançar uma nova versão escrita em C++ pode ser realmente assustador. Claro, isso não é tudo. Pessoas que gostam de programar em C++ apontarão que o coletor de lixo do Java sofre picos de latência não lineares. Isso é especialmente verdadeiro ao trabalhar com sistemas legados, portanto, enviar atualizações para o código Java sem interromper os sistemas clientes pode torná-los tão lentos que ficam inutilizáveis. Em resposta, gostaria de salientar que muito trabalho foi feito na última década para reduzir a latência criada pelo Java GC. Disruptor LMAX, por exemplo, é uma plataforma de negociação de baixa latência escrita em Java, também construída como um framework que possui “interação mecânica” com o hardware em que é executada e não requer bloqueio. Os problemas podem ser ainda mais atenuados se você construir um sistema que use um processo de integração e entrega contínua (CI/CD), uma vez que CI/CD permite a implantação automatizada de alterações de código testadas. Isso ocorre porque o CI/CD fornece uma abordagem iterativa para reduzir a latência da coleta de lixo, onde o Java pode melhorar e se adaptar gradativamente a ambientes de hardware específicos sem o processo intensivo de recursos de preparação do código para diferentes especificações de hardware antes de enviá-lo. Como o suporte Java do IDE é muito mais amplo que C++, a maioria dos frameworks (Eclipse, IntelliJ IDEA) permite refatorar Java. Isso significa que os IDEs podem otimizar o código para desempenho de baixa latência, embora essa capacidade ainda seja limitada ao trabalhar com C++. Mesmo que o código Java não corresponda à velocidade do C++, a maioria dos desenvolvedores ainda acha mais fácil obter um desempenho aceitável em Java do que em C++.

O que queremos dizer com “mais rápido”?

Na verdade, há boas razões para duvidar que C++ seja realmente “mais rápido” ou até mesmo tenha “latência menor” que Java. Percebo que estou entrando em águas bastante turvas e que muitos desenvolvedores começarão a questionar minha sanidade. Mas me escute. Vamos imaginar esta situação: você tem dois desenvolvedores - um escreve em C++ e o outro em Java, e você pede que eles escrevam uma plataforma de negociação de alta velocidade do zero. Como resultado, um sistema escrito em Java levará mais tempo para concluir transações comerciais do que um sistema escrito em C++. No entanto, Java tem muito menos instâncias de comportamento indefinido do que C++. Para dar apenas um exemplo, a indexação fora de um array é um bug tanto em Java quanto em C++. Se você acidentalmente fizer isso em C++, poderá obter um segfault ou (mais frequentemente) acabará com algum número aleatório. Em Java, sair dos limites sempre gera um erro ArrayIndexOutOfBoundsException . Isso significa que a depuração em Java é muito mais fácil porque os erros geralmente são identificados imediatamente e a localização do erro é mais fácil de rastrear. Além disso, pelo menos na minha experiência, Java é melhor em reconhecer quais partes de código não precisam ser executadas e quais são críticas para a operação do seu software. Você pode, é claro, passar dias ajustando seu código C++ para que ele não contenha absolutamente nenhum código estranho, mas no mundo real, todo software contém algum inchaço, e Java é melhor em reconhecê-lo automaticamente. Isso significa que, no mundo real, Java costuma ser mais rápido que C++, mesmo pelas métricas de latência padrão. E mesmo quando este não é o caso, a diferença na latência entre os idiomas é muitas vezes superada por outros fatores que não são grandes o suficiente para serem importantes, mesmo em negociações de alta velocidade.

Benefícios do Java para sistemas de baixa latência

Todos esses fatores, na minha opinião, constituem um argumento bastante convincente para usar Java para escrever plataformas de negociação de alta velocidade (e sistemas de baixa latência em geral, falaremos mais sobre isso em instantes). No entanto, para convencer um pouco os entusiastas de C++, vejamos alguns motivos adicionais para usar Java:
  • Primeiro, qualquer excesso de latência que o Java introduz em seu software provavelmente será muito menor do que outros fatores que afetam a latência, como problemas de Internet. Isso significa que qualquer código Java (bem escrito) pode funcionar tão bem quanto C++ na maioria das situações de negociação.

  • O tempo de desenvolvimento mais curto do Java também significa que, no mundo real, o software escrito em Java pode se adaptar às mudanças de hardware (ou mesmo às novas estratégias de negociação) mais rapidamente do que o C++.

  • Se você se aprofundar nisso, verá que até mesmo a otimização do software Java pode ser mais rápida (quando considerada em todo o software) do que uma tarefa semelhante em C++.

Em outras palavras, você pode muito bem escrever código Java para reduzir a latência. Você só precisa escrevê-lo como C++, tendo em mente o gerenciamento de memória em cada estágio de desenvolvimento. A vantagem de não escrever em C++ é que a depuração, o desenvolvimento ágil e a adaptação a vários ambientes são mais fáceis e rápidos em Java.

conclusões

A menos que você esteja desenvolvendo sistemas de negociação de baixa latência, provavelmente está se perguntando se alguma das opções acima se aplica a você. A resposta, com muito poucas exceções, é sim. O debate sobre como alcançar baixa latência não é novo nem exclusivo do mundo das finanças. Por esta razão, lições valiosas podem ser aprendidas com ele para outras situações. Em particular, o argumento acima de que Java é “melhor” porque é mais flexível, mais tolerante a falhas e, em última análise, mais rápido de desenvolver e manter pode ser aplicado a muitas áreas de desenvolvimento de software. Os motivos pelos quais (pessoalmente) prefiro escrever sistemas de baixa latência em Java são os mesmos que tornaram a linguagem tão bem-sucedida nos últimos 25 anos. Java é fácil de escrever, compilar, depurar e aprender. Isso significa que você pode gastar menos tempo escrevendo código e mais tempo otimizando-o. Na prática, isto leva a sistemas de negociação mais confiáveis ​​e mais rápidos. E isso é tudo que importa para negociações de alta velocidade.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION