JavaRush /Blogue Java /Random-PT /Guia para estilo geral de programação
pandaFromMinsk
Nível 39
Минск

Guia para estilo geral de programação

Publicado no grupo Random-PT
Este artigo faz parte do curso acadêmico "Java Avançado." Este curso foi desenvolvido para ajudá-lo a aprender como usar os recursos Java de maneira eficaz. O material cobre tópicos “avançados”, como criação de objetos, competição, serialização, reflexão, etc. O curso ensinará como dominar com eficácia as técnicas Java. Detalhes aqui .
Contente
1. Introdução 2. Escopo da variável 3. Campos de classe e variáveis ​​locais 4. Argumentos de método e variáveis ​​locais 5. Boxing e Unboxing 6. Interfaces 7. Strings 8. Convenções de nomenclatura 9. Bibliotecas padrão 10. Imutabilidade 11. Teste 12. Próximo. .. 13. Baixe o código-fonte
1. Introdução
Nesta parte do tutorial continuaremos nossa discussão sobre os princípios gerais de um bom estilo de programação e design responsivo em Java. Já vimos alguns desses princípios nos capítulos anteriores do guia, mas muitas dicas práticas serão dadas com o objetivo de aprimorar as habilidades de um desenvolvedor Java.
2. Escopo variável
Na Parte Três ("Como Projetar Classes e Interfaces") discutimos como a visibilidade e a acessibilidade podem ser aplicadas a membros de classes e interfaces, dadas as restrições de escopo. Entretanto, ainda não discutimos variáveis ​​locais que são usadas em implementações de métodos. Na linguagem Java, toda variável local, uma vez declarada, possui um escopo. Esta variável torna-se visível desde o local onde é declarada até o ponto onde a execução do método (ou bloco de código) é concluída. Geralmente, a única regra a seguir é declarar uma variável local o mais próximo possível do local onde ela será utilizada. Vejamos um exemplo típico: for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } em ambos os fragmentos de código, o escopo das variáveis ​​é limitado aos blocos de execução onde essas variáveis ​​são declaradas. Quando o bloco é concluído, o escopo termina e a variável fica invisível. Isso parece mais claro, mas com o lançamento do Java 8 e a introdução de lambdas, muitos dos idiomas conhecidos da linguagem que usam variáveis ​​locais estão se tornando obsoletos. Deixe-me dar um exemplo do exemplo anterior usando lambdas em vez de um loop: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); Você pode ver que a variável local se tornou um argumento para a função, que por sua vez é passada como um argumento para o método forEach .
3. Campos de classe e variáveis ​​locais
Cada método em Java pertence a uma classe específica (ou, no caso de Java8, a uma interface onde o método é declarado como método padrão). Entre variáveis ​​locais que são campos de uma classe ou métodos utilizados na implementação, existe a possibilidade de conflito de nomes. O compilador Java sabe como selecionar a variável correta dentre as disponíveis, mesmo que mais de um desenvolvedor pretenda usar essa variável. Os IDEs Java modernos fazem um ótimo trabalho informando ao desenvolvedor quando tais conflitos estão prestes a ocorrer, por meio de avisos do compilador e destaque de variáveis. Mas ainda é melhor pensar nessas coisas enquanto escreve o código. Sugiro ver um exemplo: public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } O exemplo parece bem fácil, mas é uma armadilha. O método calculaValue introduz um valor de variável local e, operando sobre ele, oculta o campo da classe com o mesmo nome. A linha value += value; deveria ser a soma do valor do campo de classe e da variável local, mas em vez disso, algo mais está sendo feito. Uma implementação adequada seria semelhante a esta (usando a palavra-chave this): public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } Embora este exemplo seja ingênuo em alguns aspectos, ele demonstra um ponto importante que, em alguns casos, pode levar horas para depurar e corrigir.
4. Argumentos de método e variáveis ​​locais
Outra armadilha que os desenvolvedores Java inexperientes costumam cair é usar argumentos de método como variáveis ​​locais. Java permite reatribuir valores a argumentos não constantes (no entanto, isso não afeta o valor original): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } O trecho de código acima não é elegante, mas faz um bom trabalho ao descobrir o problema: o argumento str é atribuído um valor diferente (e é basicamente usado como uma variável local). Em todos os casos (sem exceção), você pode e deve prescindir deste exemplo (por exemplo, declarando os argumentos como constantes). Por exemplo: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } Seguindo esta regra simples, é mais fácil rastrear o código fornecido e encontrar a origem do problema, mesmo ao introduzir variáveis ​​locais.
5. Embalagem e desembalagem
Boxing e unboxing é o nome de uma técnica usada em Java para converter tipos primitivos ( int, long, double, etc. ) em wrappers de tipo correspondentes ( Integer, Long, Double , etc.). Na Parte 4 do tutorial Como e quando usar genéricos, você já viu isso em ação quando falei sobre agrupar tipos primitivos como parâmetros de tipo de genéricos. Embora o compilador Java tente ao máximo ocultar essas conversões executando autoboxing, às vezes isso é menor do que o esperado e produz resultados inesperados. Vejamos um exemplo: public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); O trecho de código acima compila perfeitamente. No entanto, lançará um NullPointerException na linha // блок onde está convertendo entre Long e long . O conselho para tal caso é que é aconselhável utilizar tipos primitivos (porém, já sabemos que nem sempre isso é possível).
6. Interfaces
Na Parte 3 do tutorial, "Como projetar classes e interfaces", discutimos interfaces e programação de contratos, enfatizando que as interfaces devem ser preferidas às classes concretas sempre que possível. O objetivo desta seção é encorajá-lo a considerar primeiro as interfaces, demonstrando isso com exemplos da vida real. As interfaces não estão vinculadas a uma implementação específica (exceto para métodos padrão). São apenas contratos e, por exemplo, proporcionam muita liberdade e flexibilidade na forma como os contratos podem ser executados. Esta flexibilidade torna-se mais importante quando a implementação envolve sistemas ou serviços externos. Vejamos um exemplo de interface simples e sua possível implementação: public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; } public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } } O trecho de código acima mostra um padrão de interface típico e sua implementação. Esta implementação usa um serviço HTTP externo ( http://api.geonames.org/ ) para recuperar o fuso horário de um local específico. No entanto, porque o contrato depende da interface, é muito fácil introduzir outra implementação da interface, utilizando, por exemplo, um banco de dados ou mesmo um arquivo simples normal. Com eles, as interfaces são muito úteis no projeto de código testável. Por exemplo, nem sempre é prático chamar serviços externos em todos os testes, por isso faz sentido implementar uma implementação alternativa e mais simples (como um stub): public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } esta implementação pode ser usada em qualquer lugar onde a interface TimezoneService seja necessária, isolando o script de teste da dependência de componentes externos. Muitos exemplos excelentes de uso eficaz de tais interfaces estão encapsulados na biblioteca padrão Java. Coleções, listas, conjuntos - essas interfaces têm múltiplas implementações que podem ser facilmente trocadas e podem ser trocadas quando os contratos aproveitam. Por exemplo: public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } } print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) );
7. Cordas
Strings são um dos tipos mais usados ​​em Java e em outras linguagens de programação. A linguagem Java simplifica muitas manipulações rotineiras de strings, oferecendo suporte imediato a operações de concatenação e comparação. Além disso, a biblioteca padrão contém muitas classes que tornam as operações com strings eficientes. Isso é exatamente o que discutiremos nesta seção. Em Java, strings são objetos imutáveis ​​representados na codificação UTF-16. Cada vez que você concatena strings (ou executa qualquer operação que modifique a string original), uma nova instância da classe String é criada . Por conta disso, a operação de concatenação pode se tornar muito ineficiente, fazendo com que muitas instâncias intermediárias da classe String sejam criadas (gerando lixo, em geral). Mas a biblioteca padrão Java contém duas classes muito úteis cujo propósito é tornar conveniente a manipulação de strings. Estes são StringBuilder e StringBuffer (a única diferença entre eles é que StringBuffer é thread-safe enquanto StringBuilder é o oposto). Vejamos alguns exemplos de uma dessas classes sendo usadas: final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" ); Embora usar StringBuilder/StringBuffer seja a maneira recomendada de manipular strings, pode parecer um exagero no cenário mais simples de concatenar duas ou três strings, de modo que o operador de adição normal ( ("+"), por exemplo: String userId = "user:" + new Random().nextInt( 100 ); Muitas vezes, a melhor alternativa para simplificar a concatenação é usar a formatação de string, bem como a Biblioteca Padrão Java para ajudar a fornecer um método auxiliar String.format estático . Isso oferece suporte a um rico conjunto de especificadores de formato, incluindo números, símbolos, data/hora, etc. (Consulte a documentação de referência para obter detalhes completos) O String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12% método String.format fornece uma abordagem limpa e leve para gerar strings de vários tipos de dados. É importante notar que IDEs Java modernos podem analisar a especificação do formato a partir dos argumentos passados ​​para o método String.format e avisar os desenvolvedores se alguma incompatibilidade for detectada.
8. Convenções de nomenclatura
Java é uma linguagem que não obriga os desenvolvedores a seguir estritamente qualquer convenção de nomenclatura, mas a comunidade desenvolveu um conjunto de regras simples que fazem o código Java parecer consistente tanto na biblioteca padrão quanto em qualquer outro projeto Java:
  • os nomes dos pacotes estão em letras minúsculas: org.junit, com.fasterxml.jackson, javax.json
  • nomes de classes, enumerações, interfaces, anotações são escritos com letra maiúscula: StringBuilder, Runnable, @Override
  • nomes de campos ou métodos (exceto static final ) são especificados em notação camel: isEmpty, format, addAll
  • os nomes dos campos finais estáticos ou das constantes de enumeração estão em letras maiúsculas, separados por sublinhados ("_"): LOG, MIN_RADIX, INSTANCE.
  • variáveis ​​locais ou argumentos de método são digitados em notação camel: str, newLength, minimizeCapacity
  • nomes de tipos de parâmetros para genéricos são representados por uma única letra maiúscula: T, U, E
Seguindo essas convenções simples, o código que você escreve parecerá conciso e indistinguível em estilo de outra biblioteca ou estrutura, e parecerá que foi escrito pela mesma pessoa (um daqueles raros momentos em que as convenções realmente funcionam).
9. Bibliotecas padrão
Não importa em que tipo de projeto Java você esteja trabalhando, as bibliotecas padrão Java são suas melhores amigas. Sim, é difícil discordar que eles têm algumas arestas e decisões de design estranhas; no entanto, 99% das vezes é código de alta qualidade escrito por especialistas. Vale a pena explorar. Cada versão Java traz muitos recursos novos para bibliotecas existentes (com alguns possíveis problemas com recursos antigos) e também adiciona muitas bibliotecas novas. Java 5 trouxe uma nova biblioteca de simultaneidade como parte do pacote java.util.concurrent . O Java 6 forneceu suporte (menos conhecido) para scripts ( o pacote javax.script ) e uma API do compilador Java (como parte do pacote javax.tools ). Java 7 trouxe muitas melhorias para java.util.concurrent , introduzindo uma nova biblioteca de E/S no pacote java.nio.file e suporte para linguagens dinâmicas em java.lang.invoke . E, finalmente, o Java 8 adicionou a tão esperada data/hora no pacote java.time . Java como plataforma está evoluindo e é muito importante que progrida junto com as mudanças acima. Sempre que você considerar incluir uma biblioteca ou estrutura de terceiros em seu projeto, certifique-se de que a funcionalidade necessária ainda não esteja contida nas bibliotecas Java padrão (é claro, existem muitas implementações especializadas e de alto desempenho de algoritmos que estão à frente do algoritmos nas bibliotecas padrão, mas na maioria dos casos eles realmente não são necessários).
10. Imutabilidade
A imutabilidade ao longo do guia e nesta parte permanece como um lembrete: leve isso a sério. Se uma classe que você projeta ou um método que você implementa pode fornecer uma garantia de imutabilidade, ele pode ser usado na maioria dos casos em qualquer lugar, sem medo de ser modificado ao mesmo tempo. Isso tornará sua vida como desenvolvedor (e, esperançosamente, a vida dos membros de sua equipe) mais fácil.
11. Teste
A prática de desenvolvimento orientado a testes (TDD) é extremamente popular na comunidade Java, elevando o padrão de qualidade do código. Com todos os benefícios que o TDD oferece, é triste ver que a biblioteca padrão Java hoje não inclui nenhuma estrutura de teste ou ferramentas de suporte. No entanto, o teste tornou-se uma parte necessária do desenvolvimento Java moderno e nesta seção veremos algumas técnicas básicas usando a estrutura JUnit . Em JUnit, essencialmente, cada teste é um conjunto de declarações sobre o estado ou comportamento esperado de um objeto. O segredo para escrever ótimos testes é mantê-los simples e curtos, testando uma coisa de cada vez. Como exercício, vamos escrever um conjunto de testes para verificar se String.format é uma função da seção string que retorna o resultado desejado. package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } } Ambos os testes parecem muito legíveis e sua execução é instanciada. Hoje, o projeto Java médio contém centenas de casos de teste, dando ao desenvolvedor feedback rápido durante o processo de desenvolvimento sobre regressões ou recursos.
12. Próximo
Esta parte do guia completa uma série de discussões relacionadas à prática de programação em Java e manuais desta linguagem de programação. Na próxima vez retornaremos aos recursos da linguagem, explorando o mundo do Java em relação às exceções, seus tipos, como e quando usá-las.
13. Baixe o código-fonte
Esta foi uma lição sobre princípios gerais de desenvolvimento do curso Java Avançado. O código fonte da lição pode ser baixado aqui .
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION