JavaRush /Blogue Java /Random-PT /Regras para escrever código: desde a criação de um sistem...

Regras para escrever código: desde a criação de um sistema até trabalhar com objetos

Publicado no grupo Random-PT
Boa tarde a todos: hoje gostaria de falar com vocês sobre como escrever código corretamente. Quando comecei a programar, não estava claramente escrito em nenhum lugar que você pudesse escrever assim, e se você escrever assim, eu vou te encontrar e…. Como resultado, muitas dúvidas surgiram na minha cabeça: como escrever corretamente, quais princípios devem ser seguidos nesta ou naquela seção do programa, etc. Regras para escrever código: da criação de um sistema ao trabalho com objetos - 1Bem, nem todo mundo quer mergulhar imediatamente em livros como Clean Code, já que muito está escrito neles, mas a princípio pouco fica claro. E quando terminar de ler, você poderá desencorajar todo desejo de codificar. Portanto, com base no que foi dito acima, hoje quero fornecer a vocês um pequeno guia (um conjunto de pequenas recomendações) para escrever código de nível superior. Neste artigo abordaremos as regras e conceitos básicos relacionados à criação de um sistema e ao trabalho com interfaces, classes e objetos. A leitura deste material não levará muito tempo e, espero, não o deixará entediado. Irei de cima para baixo, ou seja, da estrutura geral da aplicação até detalhes mais específicos. Regras para escrever código: da criação de um sistema ao trabalho com objetos - 2

Sistema

As características gerais desejáveis ​​do sistema são:
  • complexidade mínima – projetos excessivamente complicados devem ser evitados. O principal é simplicidade e clareza (melhor = simples);
  • facilidade de manutenção - ao criar uma aplicação, você deve lembrar que ela precisará de suporte (mesmo que não seja você), portanto o código deve ser claro e óbvio;
  • acoplamento fraco é o número mínimo de conexões entre diferentes partes do programa (uso máximo dos princípios OOP);
  • reutilização – projetar um sistema com a capacidade de reutilizar seus fragmentos em outras aplicações;
  • portabilidade – o sistema deve ser facilmente adaptado a outro ambiente;
  • estilo único - projetar um sistema em um estilo único em seus diferentes fragmentos;
  • extensibilidade (escalabilidade) - melhorar o sistema sem perturbar sua estrutura básica (se você adicionar ou alterar um fragmento, isso não deverá afetar o resto).
É virtualmente impossível construir uma aplicação que não requeira modificações, sem adicionar funcionalidades. Precisaremos constantemente introduzir novos elementos para que nossa ideia possa acompanhar os tempos. E é aqui que entra a escalabilidade . Escalabilidade é essencialmente expandir a aplicação, agregar novas funcionalidades, trabalhar com mais recursos (ou, em outras palavras, com mais carga). Ou seja, devemos aderir a algumas regras, como reduzir o acoplamento do sistema aumentando a modularidade, para que seja mais fácil adicionar novas lógicas.

Estágios de design do sistema

  1. Sistema de software - projetar um aplicativo de forma geral.
  2. Separação em subsistemas/pacotes - definindo partes logicamente separáveis ​​e definindo as regras de interação entre elas.
  3. Divisão de subsistemas em classes - divisão de partes do sistema em classes e interfaces específicas, bem como definição da interação entre elas.
  4. Dividir classes em métodos é uma definição completa dos métodos necessários para uma classe, com base na tarefa desta classe. Design de métodos - definição detalhada da funcionalidade de métodos individuais.
Normalmente, os desenvolvedores comuns são responsáveis ​​pelo design e o arquiteto do aplicativo é responsável pelos itens descritos acima.

Princípios e conceitos básicos de design de sistema

Idioma de inicialização lenta Um aplicativo não perde tempo criando um objeto até que ele seja usado, o que acelera o processo de inicialização e reduz a carga do coletor de lixo. Mas você não deve ir muito longe com isso, pois isso pode levar a uma violação da modularidade. Pode valer a pena mover todas as etapas do projeto para uma peça específica, por exemplo, principal, ou para uma classe que funcione como uma fábrica . Um dos aspectos de um bom código é a ausência de código padrão repetido com frequência. Via de regra, esse código é colocado em uma classe separada para que possa ser chamado no momento certo. AOP Gostaria de mencionar separadamente a programação orientada a aspectos . Trata-se de programação através da introdução de lógica ponta a ponta, ou seja, o código repetido é colocado em classes - aspectos, e chamado quando determinadas condições são atingidas. Por exemplo, ao acessar um método com um determinado nome ou acessar uma variável de um determinado tipo. Às vezes, os aspectos podem ser confusos, pois não fica imediatamente claro de onde o código é chamado, mas, mesmo assim, esta é uma funcionalidade muito útil. Em particular, ao armazenar em cache ou registrar: adicionamos essa funcionalidade sem adicionar lógica adicional às classes regulares. Você pode ler mais sobre OAP aqui . 4 regras para projetar arquitetura simples de acordo com Kent Beck
  1. Expressividade - a necessidade de um propósito claramente expresso da aula, é alcançada por meio de nomenclatura correta, tamanho reduzido e adesão ao princípio da responsabilidade única (veremos isso com mais detalhes a seguir).
  2. Um mínimo de classes e métodos - em seu desejo de dividir as classes em classes tão pequenas e unidirecionais quanto possível, você pode ir longe demais (antipadrão - espingarda). Esse princípio exige manter o sistema compacto e não ir muito longe, criando uma classe para cada espirro.
  3. Falta de duplicação - código extra que confunde é um sinal de mau design do sistema e é movido para um local separado.
  4. Execução de todos os testes - um sistema que passou em todos os testes é controlado, pois qualquer alteração pode levar à falha dos testes, o que pode nos mostrar que uma mudança na lógica interna do método também levou a uma mudança no comportamento esperado .
SOLID Ao projetar um sistema, vale a pena levar em consideração os conhecidos princípios do SOLID: S - responsabilidade única - o princípio da responsabilidade única; O - aberto-fechado - princípio de abertura/proximidade; L - Substituição de Liskov - Princípio de substituição de Barbara Liskov; I - segregação de interfaces - princípio da separação de interfaces; D – inversão de dependência – princípio da inversão de dependência; Não nos deteremos especificamente em cada princípio (isso está um pouco além do escopo deste artigo, mas você pode descobrir mais aqui) .

Interface

Talvez uma das etapas mais importantes na criação de uma classe adequada seja a criação de uma interface adequada que represente uma boa abstração que esconda os detalhes de implementação da classe e, ao mesmo tempo, represente um grupo de métodos que são claramente consistentes entre si . Vamos dar uma olhada em um dos princípios do SOLID - segregação de interface : os clientes (classes) não devem implementar métodos desnecessários que não usarão. Ou seja, se estamos falando em construir interfaces com um número mínimo de métodos que visam realizar a única tarefa dessa interface (já para mim é muito parecido com responsabilidade única ), é melhor criar alguns menores uns em vez de uma interface inchada. Felizmente, uma classe pode implementar mais de uma interface, como é o caso da herança. Você também precisa se lembrar da nomenclatura correta das interfaces: o nome deve refletir sua tarefa com a maior precisão possível. E, claro, quanto mais curto for, menos confusão causará. É no nível da interface que geralmente são escritos comentários para a documentação , o que, por sua vez, nos ajuda a descrever detalhadamente o que o método deve fazer, quais argumentos ele leva e o que ele retornará.

Aula

Regras para escrever código: da criação de um sistema ao trabalho com objetos - 3Vejamos a organização interna das aulas. Ou melhor, algumas visões e regras que devem ser seguidas na construção de classes. Normalmente, uma classe deve começar com uma lista de variáveis, organizadas em uma ordem específica:
  1. constantes estáticas públicas;
  2. constantes estáticas privadas;
  3. variáveis ​​de instância privada.
A seguir estão vários construtores em ordem de menos para mais argumentos. Seguem-se métodos desde o acesso mais aberto até aos mais fechados: via de regra, os métodos privados que ocultam a implementação de alguma funcionalidade que queremos restringir ficam no fundo.

Tamanho da turma

Agora eu gostaria de falar sobre o tamanho das turmas. Regras para escrever código: da criação de um sistema ao trabalho com objetos - 4Vamos lembrar um dos princípios do SOLID - responsabilidade única . Responsabilidade única - o princípio da responsabilidade única. Afirma que cada objeto tem apenas um objetivo (responsabilidade), e a lógica de todos os seus métodos visa garanti-lo. Ou seja, com base nisso, devemos evitar classes grandes e inchadas (que por sua natureza são um antipadrão - “objeto divino”), e se tivermos muitos métodos de lógica diversa e heterogênea em uma classe, precisamos pensar sobre dividi-lo em algumas partes lógicas (classes). Isso, por sua vez, melhorará a legibilidade do código, já que não precisaremos de muito tempo para entender o propósito de um método se soubermos o propósito aproximado de uma determinada classe. Você também precisa ficar de olho no nome da classe : ele deve refletir a lógica que ela contém. Digamos que se tivermos uma classe cujo nome tenha mais de 20 palavras, precisamos pensar em refatorar. Toda classe que se preze não deveria ter um número tão grande de variáveis ​​internas. Na verdade, cada método trabalha com um deles ou vários, o que provoca um maior acoplamento dentro da classe (que é exatamente o que deveria ser, já que a classe deveria ser um todo único). Como resultado, aumentar a coerência de uma aula leva a uma diminuição dela como tal e, claro, nosso número de aulas aumenta. Para alguns, isso é irritante; eles precisam ir mais às aulas para ver como funciona uma tarefa específica e grande. Entre outras coisas, cada classe é um pequeno módulo que deve estar minimamente conectado aos demais. Esse isolamento reduz o número de alterações que precisamos fazer ao adicionar lógica adicional a uma classe.

Objetos

Regras para escrever código: da criação de um sistema ao trabalho com objetos - 5

Encapsulamento

Aqui falaremos primeiro sobre um dos princípios do encapsulamento OOP . Portanto, ocultar a implementação não se resume à criação de uma camada de método entre variáveis ​​(restringindo irrefletidamente o acesso por meio de métodos únicos, getters e setters, o que não é bom, pois todo o objetivo do encapsulamento é perdido). Ocultar o acesso visa formar abstrações, ou seja, a classe fornece métodos concretos comuns através dos quais trabalhamos com nossos dados. Mas o usuário não precisa saber exatamente como trabalhamos com esses dados – funciona, e tudo bem.

Lei de Deméter

Você também pode considerar a Lei de Demeter: é um pequeno conjunto de regras que ajuda a gerenciar a complexidade no nível de classe e método. Então, vamos supor que temos um objeto Care ele possui um método - move(Object arg1, Object arg2). De acordo com a Lei de Deméter, este método limita-se a chamar:
  • métodos do próprio objeto Car(em outras palavras, isto);
  • métodos de objetos criados em move;
  • métodos de objetos passados ​​como argumentos - arg1, arg2;
  • métodos de objetos internos Car(o mesmo this).
Em outras palavras, a lei de Deméter é algo como uma regra infantil - você pode conversar com amigos, mas não com estranhos .

Estrutura de dados

Uma estrutura de dados é uma coleção de elementos relacionados. Ao considerar um objeto como uma estrutura de dados, ele é um conjunto de elementos de dados que são processados ​​​​por métodos, cuja existência está implícita. Ou seja, é um objeto cuja finalidade é armazenar e operar (processar) os dados armazenados. A principal diferença de um objeto regular é que um objeto é um conjunto de métodos que operam em elementos de dados cuja existência está implícita. Você entende? Em um objeto regular, o aspecto principal são os métodos, e as variáveis ​​internas visam seu correto funcionamento, mas em uma estrutura de dados é o contrário: os métodos suportam e ajudam a trabalhar com os elementos armazenados, que são o principal aqui. Um tipo de estrutura de dados é o Data Transfer Object (DTO) . Esta é uma classe com variáveis ​​públicas e sem métodos (ou apenas métodos de leitura/gravação) que passam dados ao trabalhar com bancos de dados, trabalham com análise de mensagens de soquetes, etc. convertido quase imediatamente na entidade com a qual nossa aplicação trabalha. Uma entidade, por sua vez, também é uma estrutura de dados, mas sua finalidade é participar da lógica de negócio em diferentes níveis da aplicação, enquanto o DTO é transportar dados de/para a aplicação. Exemplo de DTO:
@Setter
@Getter
@NoArgsConstructor
public class UserDto {
    private long id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
}
Tudo parece claro, mas aqui aprendemos sobre a existência de híbridos. Híbridos são objetos que contêm métodos para lidar com lógica importante e armazenar elementos internos e métodos de acesso (get/set) a eles. Esses objetos são confusos e dificultam a adição de novos métodos. Você não deve usá-los, pois não está claro para que servem - armazenar elementos ou executar algum tipo de lógica. Você pode ler sobre os possíveis tipos de objetos aqui .

Princípios de criação de variáveis

Regras para escrever código: da criação de um sistema ao trabalho com objetos - 6Vamos pensar um pouco nas variáveis, ou melhor, pensar quais seriam os princípios para criá-las:
  1. Idealmente, você deve declarar e inicializar uma variável imediatamente antes de usá-la (em vez de criá-la e esquecê-la).
  2. Sempre que possível, declare as variáveis ​​como finais para evitar que seu valor mude após a inicialização.
  3. Não se esqueça das variáveis ​​​​contadoras (normalmente as utilizamos em algum tipo de loop for, ou seja, não devemos esquecer de zerá-las, caso contrário pode quebrar toda a nossa lógica).
  4. Você deve tentar inicializar variáveis ​​no construtor.
  5. Se houver a opção de usar um objeto com ou sem referência ( new SomeObject()), escolha sem ( ), pois esse objeto, uma vez utilizado, será deletado na próxima coleta de lixo e não desperdiçará recursos.
  6. Faça com que o tempo de vida das variáveis ​​seja o mais curto possível (a distância entre a criação de uma variável e o último acesso).
  7. Inicialize variáveis ​​usadas em um loop imediatamente antes do loop, em vez de no início do método que contém o loop.
  8. Sempre comece com o escopo mais limitado e expanda-o apenas se necessário (você deve tentar tornar a variável o mais local possível).
  9. Use cada variável para apenas uma finalidade.
  10. Evite variáveis ​​com significados ocultos (a variável está dividida entre duas tarefas, o que significa que seu tipo não é adequado para resolver uma delas).
Regras para escrever código: da criação de um sistema ao trabalho com objetos - 7

Métodos

Regras para escrever código: da criação de um sistema ao trabalho com objetos - 8Passemos diretamente à implementação da nossa lógica, ou seja, aos métodos.
  1. A primeira regra é a compactação. Idealmente, um método não deve exceder 20 linhas; portanto, se, digamos, um método público “inchar” significativamente, você precisa pensar em mover a lógica separada para métodos privados.

  2. A segunda regra é que os blocos nos comandos if, elsee whileassim por diante não devem ser altamente aninhados: isso reduz significativamente a legibilidade do código. Idealmente, o aninhamento não deve ultrapassar dois blocos {}.

    Também é aconselhável tornar o código desses blocos compacto e simples.

  3. A terceira regra é que um método deve realizar apenas uma operação. Ou seja, se um método executa uma lógica complexa e variada, dividimos ele em submétodos. Como resultado, o próprio método será uma fachada, cujo objetivo é chamar todas as outras operações na ordem correta.

    Mas e se a operação parecer simples demais para criar um método separado? Sim, às vezes pode parecer como atirar em pardais com um canhão, mas pequenos métodos oferecem vários benefícios:

    • leitura de código mais fácil;
    • os métodos tendem a se tornar mais complexos ao longo do desenvolvimento, e se o método era inicialmente simples, complicar sua funcionalidade será um pouco mais fácil;
    • ocultar detalhes de implementação;
    • facilitando a reutilização de código;
    • maior confiabilidade do código.
  4. A regra descendente é que o código deve ser lido de cima para baixo: quanto mais baixo, maior a profundidade da lógica, e vice-versa, quanto mais alto, mais abstratos são os métodos. Por exemplo, os comandos switch são pouco compactos e indesejáveis, mas se você não puder fazer isso sem usar um switch, tente movê-lo o mais baixo possível, para os métodos de nível mais baixo.

  5. Argumentos do método – quantos são ideais? Idealmente, não há nenhum)) Mas isso realmente acontece? No entanto, você deve tentar ter o menor número possível, porque quanto menos houver, mais fácil será usar esse método e mais fácil será testá-lo. Em caso de dúvida, tente adivinhar todos os cenários para usar um método com um grande número de argumentos de entrada.

  6. Separadamente, gostaria de destacar métodos que possuem um sinalizador booleano como argumento de entrada , pois isso naturalmente implica que este método implementa mais de uma operação (se verdadeiro então uma, falso - outra). Como escrevi acima, isso não é bom e deve ser evitado, se possível.

  7. Se um método tiver um grande número de argumentos recebidos (o valor extremo é 7, mas você deve pensar nisso depois de 2-3), será necessário agrupar alguns argumentos em um objeto separado.

  8. Se houver vários métodos semelhantes (sobrecarregados) , então parâmetros semelhantes deverão ser passados ​​na mesma ordem: isso aumenta a legibilidade e a usabilidade.

  9. Ao passar parâmetros para um método, você deve ter certeza de que todos eles serão usados, caso contrário, para que serve o argumento? Corte-o da interface e pronto.

  10. try/catchNão parece muito bom por natureza, então uma boa jogada seria movê-lo para um método intermediário separado (método para lidar com exceções):

    public void exceptionHandling(SomeObject obj) {
        try {
            someMethod(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Falei sobre a repetição de código acima, mas vou adicioná-lo aqui: Se tivermos alguns métodos com partes repetidas do código, precisaremos movê-lo para um método separado, o que aumentará a compactação tanto do método quanto do aula. E não se esqueça dos nomes corretos. Contarei os detalhes da nomenclatura correta de classes, interfaces, métodos e variáveis ​​na próxima parte do artigo. E isso é tudo que tenho por hoje. Regras para escrever código: da criação de um sistema ao trabalho com objetos - 9Regras do código: o poder da nomenclatura adequada, dos comentários bons e ruins
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION