JavaRush /Java Blog /Random-IT /Guida allo stile generale di programmazione
pandaFromMinsk
Livello 39
Минск

Guida allo stile generale di programmazione

Pubblicato nel gruppo Random-IT
Questo articolo fa parte del corso accademico "Advanced Java." Questo corso è progettato per aiutarti a imparare come utilizzare in modo efficace le funzionalità Java. Il materiale copre argomenti "avanzati" come la creazione di oggetti, la competizione, la serializzazione, la riflessione, ecc. Il corso ti insegnerà come padroneggiare efficacemente le tecniche Java. Dettagli qui .
Contenuto
1. Introduzione 2. Ambito della variabile 3. Campi di classe e variabili locali 4. Argomenti di metodo e variabili locali 5. Boxing e Unboxing 6. Interfacce 7. Stringhe 8. Convenzioni di denominazione 9. Librerie standard 10. Immutabilità 11. Test 12. Successivo. .. 13. Scarica il codice sorgente
1. Introduzione
In questa parte del tutorial continueremo la nostra discussione sui principi generali di un buon stile di programmazione e del responsive design in Java. Alcuni di questi principi li abbiamo già visti nei capitoli precedenti della guida, ma verranno dati tanti consigli pratici con l'obiettivo di migliorare le competenze di uno sviluppatore Java.
2. Ambito variabile
Nella terza parte ("Come progettare classi e interfacce") abbiamo discusso come la visibilità e l'accessibilità possono essere applicate ai membri di classi e interfacce, dati i vincoli di ambito. Tuttavia, non abbiamo ancora discusso le variabili locali utilizzate nelle implementazioni dei metodi. Nel linguaggio Java ogni variabile locale, una volta dichiarata, ha uno scope. Questa variabile diventa visibile dal punto in cui è dichiarata fino al punto in cui viene completata l'esecuzione del metodo (o del blocco di codice). Generalmente l'unica regola da seguire è dichiarare una variabile locale il più vicino possibile al luogo in cui verrà utilizzata. Consideriamo un esempio tipico: for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } in entrambi i frammenti di codice, l'ambito delle variabili è limitato ai blocchi di esecuzione in cui vengono dichiarate queste variabili. Una volta completato il blocco, l'ambito termina e la variabile diventa invisibile. Questo sembra più chiaro, ma con il rilascio di Java 8 e l'introduzione delle lambda, molti degli idiomi ben noti del linguaggio che utilizzano variabili locali stanno diventando obsoleti. Vorrei fare un esempio dall'esempio precedente utilizzando lambda invece di un ciclo: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); si può vedere che la variabile locale è diventata un argomento della funzione, che a sua volta viene passata come argomento al metodo forEach .
3. Campi di classe e variabili locali
Ogni metodo in Java appartiene a una classe specifica (o, nel caso di Java8, a un'interfaccia in cui il metodo è dichiarato come metodo predefinito). Tra le variabili locali che sono campi di una classe o metodi utilizzati nell'implementazione esiste la possibilità di conflitto di nomi. Il compilatore Java sa selezionare la variabile corretta tra quelle disponibili, anche se più di uno sviluppatore intende utilizzare quella variabile. I moderni IDE Java fanno un ottimo lavoro nel comunicare allo sviluppatore quando stanno per verificarsi tali conflitti, attraverso avvisi del compilatore ed evidenziazione delle variabili. Ma è comunque meglio pensare a queste cose mentre si scrive il codice. Suggerisco di guardare un esempio: public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } l'esempio sembra abbastanza semplice, ma è una trappola. Il metodo calcolaValue introduce un valore di variabile locale e, operando su di esso, nasconde il campo della classe con lo stesso nome. La linea value += value; dovrebbe essere la somma del valore del campo classe e della variabile locale, ma invece viene fatto qualcos'altro. Un'implementazione corretta sarebbe simile a questa (usando la parola chiave this): public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } Sebbene questo esempio sia in qualche modo ingenuo, dimostra un punto importante che in alcuni casi possono essere necessarie ore per eseguire il debug e correggere.
4. Argomenti del metodo e variabili locali
Un'altra trappola in cui spesso cadono gli sviluppatori Java inesperti è l'utilizzo degli argomenti del metodo come variabili locali. Java consente di riassegnare valori ad argomenti non costanti (tuttavia, ciò non ha alcun effetto sul valore originale): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } lo snippet di codice sopra non è elegante, ma fa un buon lavoro nello scoprire il problema: l'argomento str è assegnato un valore diverso (ed è fondamentalmente utilizzato come variabile locale). In tutti i casi (senza alcuna eccezione) si può e si deve fare a meno di questo esempio (ad esempio dichiarando gli argomenti come costanti). Ad esempio: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } seguendo questa semplice regola è più semplice risalire al codice dato e trovare l'origine del problema, anche introducendo variabili locali.
5. Imballo e disimballo
Boxing e unboxing sono il nome di una tecnica utilizzata in Java per convertire i tipi primitivi ( int, long, double, ecc. ) nei corrispondenti wrapper di tipo ( Integer, Long, Double , ecc.). Nella parte 4 del tutorial Come e quando utilizzare i generici, lo avete già visto in azione quando ho parlato del confezionamento dei tipi primitivi come parametri di tipo dei generici. Sebbene il compilatore Java faccia del suo meglio per nascondere tali conversioni eseguendo l'autoboxing, a volte questo è inferiore al previsto e produce risultati inaspettati. Diamo un'occhiata ad un esempio: public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); lo snippet di codice sopra viene compilato correttamente. Tuttavia, lancerà una NullPointerException sulla riga // блок in cui sta effettuando la conversione tra Long e long . Il consiglio in questo caso è che è consigliabile utilizzare i tipi primitivi (tuttavia sappiamo già che ciò non è sempre possibile).
6. Interfacce
Nella parte 3 del tutorial, "Come progettare classi e interfacce", abbiamo discusso delle interfacce e della programmazione dei contratti, sottolineando che le interfacce dovrebbero essere preferite alle classi concrete quando possibile. Lo scopo di questa sezione è incoraggiarti a considerare prima le interfacce dimostrandolo con esempi di vita reale. Le interfacce non sono legate a un'implementazione specifica (ad eccezione dei metodi predefiniti). Sono solo contratti e, ad esempio, forniscono molta libertà e flessibilità nel modo in cui i contratti potrebbero essere eseguiti. Questa flessibilità diventa più importante quando l'implementazione coinvolge sistemi o servizi esterni. Diamo un'occhiata ad un esempio di un'interfaccia semplice e alla sua possibile implementazione: 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(); } } il frammento di codice sopra mostra un tipico modello di interfaccia e la sua implementazione. Questa implementazione utilizza un servizio HTTP esterno ( http://api.geonames.org/ ) per recuperare il fuso orario di una posizione specifica. Tuttavia, perché il contratto dipende dall'interfaccia, è molto semplice introdurre un'altra implementazione dell'interfaccia, utilizzando, ad esempio, un database o anche un normale file flat. Con essi, le interfacce sono molto utili nella progettazione di codice testabile. Ad esempio, non è sempre pratico chiamare servizi esterni ad ogni test, quindi ha senso implementare invece un'implementazione alternativa e più semplice (come uno stub): public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } questa implementazione può essere utilizzata ovunque sia richiesta l'interfaccia TimezoneService , isolando il test script dalla dipendenza da componenti esterni. Molti esempi eccellenti di utilizzo efficace di tali interfacce sono incapsulati nella libreria standard Java. Raccolte, elenchi, insiemi: queste interfacce hanno molteplici implementazioni che possono essere facilmente scambiate e scambiate quando i contratti ne traggono vantaggio. Per esempio: 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. Stringhe
Le stringhe sono uno dei tipi più utilizzati sia in Java che in altri linguaggi di programmazione. Il linguaggio Java semplifica molte manipolazioni di routine delle stringhe supportando immediatamente operazioni di concatenazione e confronto. Inoltre, la libreria standard contiene molte classi che rendono efficienti le operazioni sulle stringhe. Questo è esattamente ciò di cui parleremo in questa sezione. In Java, le stringhe sono oggetti immutabili rappresentati nella codifica UTF-16. Ogni volta che concateni stringhe (o esegui qualsiasi operazione che modifica la stringa originale), viene creata una nuova istanza della classe String . Per questo motivo, l'operazione di concatenazione può diventare molto inefficiente, causando la creazione di molte istanze intermedie della classe String (creando spazzatura, in generale). Ma la libreria standard Java contiene due classi molto utili il cui scopo è rendere comoda la manipolazione delle stringhe. Questi sono StringBuilder e StringBuffer (l'unica differenza tra loro è che StringBuffer è thread-safe mentre StringBuilder è l'opposto). Diamo un'occhiata ad un paio di esempi di utilizzo di una di queste classi: 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(), "]" ); Sebbene l'utilizzo di StringBuilder/StringBuffer sia il modo consigliato per manipolare le stringhe, potrebbe sembrare eccessivo nello scenario più semplice di concatenazione di due o tre stringhe, in modo che il normale operatore di addizione ( ("+"), ad esempio: String userId = "user:" + new Random().nextInt( 100 ); Spesso la migliore alternativa per semplificare la concatenazione è utilizzare la formattazione delle stringhe e la libreria standard Java per fornire un metodo di supporto String.format statico . Supporta un ricco set di identificatori di formato, inclusi numeri, simboli, data/ora, ecc. (Fare riferimento alla documentazione di riferimento per i dettagli completi) Il 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% metodo String.format fornisce un approccio pulito e leggero alla generazione di stringhe da vari tipi di dati. Vale la pena notare che i moderni IDE Java possono analizzare la specifica del formato dagli argomenti passati al metodo String.format e avvisare gli sviluppatori se vengono rilevate eventuali discrepanze.
8. Convenzioni di denominazione
Java è un linguaggio che non obbliga gli sviluppatori a seguire rigorosamente alcuna convenzione di denominazione, ma la comunità ha sviluppato una serie di semplici regole che rendono il codice Java coerente sia nella libreria standard che in qualsiasi altro progetto Java:
  • i nomi dei pacchetti sono in minuscolo: org.junit, com.fasterxml.jackson, javax.json
  • i nomi di classi, enumerazioni, interfacce, annotazioni sono scritti con la lettera maiuscola: StringBuilder, Runnable, @Override
  • i nomi dei campi o dei metodi (ad eccezione di static final ) sono specificati nella notazione camel: isEmpty, format, addAll
  • I nomi dei campi finali statici o delle costanti di enumerazione sono in maiuscolo, separati da caratteri di sottolineatura ("_"): LOG, MIN_RADIX, INSTANCE.
  • le variabili locali o gli argomenti del metodo vengono digitati nella notazione cammello: str, newLength, MinimumCapacity
  • i nomi dei tipi di parametri per i generici sono rappresentati da una singola lettera maiuscola: T, U, E
Seguendo queste semplici convenzioni, il codice che scrivi apparirà conciso e indistinguibile nello stile da un'altra libreria o framework e sembrerà come se fosse stato sviluppato dalla stessa persona (uno di quei rari casi in cui le convenzioni funzionano davvero).
9. Librerie standard
Non importa su quale tipo di progetto Java stai lavorando, le librerie standard Java sono le tue migliori amiche. Sì, è difficile non essere d'accordo sul fatto che abbiano alcuni aspetti irregolari e strane decisioni di progettazione, tuttavia, il 99% delle volte si tratta di codice di alta qualità scritto da esperti. Vale la pena esplorare. Ogni versione Java apporta molte nuove funzionalità alle librerie esistenti (con alcuni possibili problemi con le vecchie funzionalità) e aggiunge anche molte nuove librerie. Java 5 ha introdotto una nuova libreria di concorrenza come parte del pacchetto java.util.concurrent . Java 6 ha introdotto il supporto per gli script (meno conosciuto) ( pacchetto javax.script ) e un'API del compilatore Java (come parte del pacchetto javax.tools ). Java 7 ha apportato molti miglioramenti a java.util.concurrent , introducendo una nuova libreria I/O nel pacchetto java.nio.file e il supporto per i linguaggi dinamici in java.lang.invoke . Infine, Java 8 ha aggiunto la tanto attesa data/ora nel pacchetto java.time . Java come piattaforma si sta evolvendo ed è molto importante che progredisca insieme ai cambiamenti di cui sopra. Ogni volta che consideri di includere una libreria o un framework di terze parti nel tuo progetto, assicurati che la funzionalità richiesta non sia già contenuta nelle librerie Java standard (ovviamente, ci sono molte implementazioni specializzate e ad alte prestazioni di algoritmi che sono all'avanguardia rispetto a algoritmi nelle librerie standard, ma nella maggior parte dei casi in realtà non sono necessari).
10. Immutabilità
L'immutabilità in tutta la guida e in questa parte rimane un promemoria: prendila sul serio. Se una classe che progetti o un metodo che implementi può fornire una garanzia di immutabilità, può essere utilizzato nella maggior parte dei casi ovunque senza timore di essere modificato allo stesso tempo. Ciò renderà la tua vita di sviluppatore (e, si spera, quella dei membri del tuo team) più semplice.
11. Test
La pratica del test-driven development (TDD) è estremamente popolare nella comunità Java e innalza il livello della qualità del codice. Con tutti i vantaggi offerti da TDD, è triste vedere che la libreria standard Java oggi non include alcun framework di test o strumenti di supporto. Tuttavia, il testing è diventato una parte necessaria del moderno sviluppo Java e in questa sezione esamineremo alcune tecniche di base utilizzando il framework JUnit . In JUnit, essenzialmente, ogni test è un insieme di affermazioni sullo stato o sul comportamento atteso di un oggetto. Il segreto per scrivere ottimi test è mantenerli semplici e brevi, testando una cosa alla volta. Come esercizio, scriviamo una serie di test per verificare che String.format sia una funzione della sezione string che restituisce il risultato desiderato. 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" ) ); } } Entrambi i test sembrano molto leggibili e la loro esecuzione è istanze. Oggi, il progetto Java medio contiene centinaia di casi di test, fornendo allo sviluppatore un rapido feedback durante il processo di sviluppo su regressioni o funzionalità.
12. Avanti
Questa parte della guida completa una serie di discussioni relative alla pratica della programmazione in Java e ai manuali per questo linguaggio di programmazione. La prossima volta torneremo sulle caratteristiche del linguaggio, esplorando il mondo Java per quanto riguarda le eccezioni, le loro tipologie, come e quando utilizzarle.
13. Scarica il codice sorgente
Questa era una lezione sui principi generali di sviluppo del corso Java avanzato. Il codice sorgente della lezione può essere scaricato qui .
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION