JavaRush /Java-Blog /Random-DE /Leitfaden zum allgemeinen Programmierstil
pandaFromMinsk
Level 39
Минск

Leitfaden zum allgemeinen Programmierstil

Veröffentlicht in der Gruppe Random-DE
Dieser Artikel ist Teil des akademischen Kurses „Advanced Java“. Dieser Kurs soll Ihnen dabei helfen, die effektive Nutzung von Java-Funktionen zu erlernen. Das Material behandelt „fortgeschrittene“ Themen wie Objekterstellung, Wettbewerb, Serialisierung, Reflexion usw. Der Kurs vermittelt Ihnen, wie Sie Java-Techniken effektiv beherrschen. Details hier .
Inhalt
1. Einführung 2. Variablenbereich 3. Klassenfelder und lokale Variablen 4. Methodenargumente und lokale Variablen 5. Boxing und Unboxing 6. Schnittstellen 7. Strings 8. Namenskonventionen 9. Standardbibliotheken 10. Unveränderlichkeit 11. Testen 12. Weiter. .. 13. Quellcode herunterladen
1. Einleitung
In diesem Teil des Tutorials werden wir unsere Diskussion der allgemeinen Prinzipien eines guten Programmierstils und eines responsiven Designs in Java fortsetzen. Einige dieser Prinzipien haben wir bereits in den vorherigen Kapiteln des Leitfadens gesehen, es werden jedoch viele praktische Tipps gegeben, mit dem Ziel, die Fähigkeiten eines Java-Entwicklers zu verbessern.
2. Variablenumfang
Im dritten Teil („So entwerfen Sie Klassen und Schnittstellen“) haben wir erörtert, wie Sichtbarkeit und Zugänglichkeit angesichts von Bereichsbeschränkungen auf Mitglieder von Klassen und Schnittstellen angewendet werden können. Lokale Variablen, die in Methodenimplementierungen verwendet werden, haben wir jedoch noch nicht besprochen. In der Java-Sprache hat jede lokale Variable, sobald sie deklariert wurde, einen Gültigkeitsbereich. Diese Variable wird von der Stelle, an der sie deklariert wird, bis zu dem Punkt sichtbar, an dem die Ausführung der Methode (oder des Codeblocks) abgeschlossen ist. Im Allgemeinen besteht die einzige zu befolgende Regel darin, eine lokale Variable so nah wie möglich an dem Ort zu deklarieren, an dem sie verwendet wird. Lassen Sie mich ein typisches Beispiel betrachten: for( final Locale locale: Locale.getAvailableLocales() ) { // блок Codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока Codeа } In beiden Codefragmenten ist der Umfang der Variablen auf die Ausführungsblöcke beschränkt, in denen diese Variablen deklariert sind. Wenn der Block abgeschlossen ist, endet der Gültigkeitsbereich und die Variable wird unsichtbar. Dies scheint klarer zu sein, aber mit der Veröffentlichung von Java 8 und der Einführung von Lambdas werden viele der bekannten Redewendungen der Sprache, die lokale Variablen verwenden, obsolet. Lassen Sie mich ein Beispiel aus dem vorherigen Beispiel geben, bei dem Lambdas anstelle einer Schleife verwendet werden: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок Codeа } ); Es ist ersichtlich, dass die lokale Variable zu einem Argument für die Funktion geworden ist, das wiederum als Argument an die Methode forEach übergeben wird .
3. Klassenfelder und lokale Variablen
Jede Methode in Java gehört zu einer bestimmten Klasse (oder, im Fall von Java8, einer Schnittstelle, in der die Methode als Standardmethode deklariert ist). Zwischen lokalen Variablen, die Felder einer Klasse oder Methoden sind, die in der Implementierung verwendet werden, besteht daher die Möglichkeit eines Namenskonflikts. Der Java-Compiler weiß, wie er die richtige Variable aus den verfügbaren Variablen auswählt, auch wenn mehr als ein Entwickler beabsichtigt, diese Variable zu verwenden. Moderne Java-IDEs leisten hervorragende Arbeit, indem sie dem Entwickler durch Compiler-Warnungen und Variablenhervorhebung mitteilen, wann solche Konflikte auftreten werden. Dennoch ist es besser, beim Schreiben von Code über solche Dinge nachzudenken. Ich schlage vor, sich ein Beispiel anzusehen: public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } Das Beispiel sieht ganz einfach aus, ist aber eine Falle. Die Methode „calculateValue“ führt einen lokalen Variablenwert ein und verbirgt bei der Bearbeitung das gleichnamige Klassenfeld. Die Zeile value += value; sollte die Summe des Werts des Klassenfelds und der lokalen Variablen sein, stattdessen wird jedoch etwas anderes getan. Eine ordnungsgemäße Implementierung würde so aussehen (unter Verwendung des Schlüsselworts this): public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } Auch wenn dieses Beispiel in mancher Hinsicht naiv ist, zeigt es doch einen wichtigen Punkt, dass das Debuggen und Beheben in manchen Fällen Stunden dauern kann.
4. Methodenargumente und lokale Variablen
Eine weitere Gefahr, in die unerfahrene Java-Entwickler oft tappen, ist die Verwendung von Methodenargumenten als lokale Variablen. In Java können Sie nicht-konstanten Argumenten Werte neu zuweisen (dies hat jedoch keine Auswirkung auf den ursprünglichen Wert): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } Der obige Codeausschnitt ist nicht elegant, aber er deckt das Problem gut auf: Das Argument str ist zugewiesen einen anderen Wert (und wird grundsätzlich als lokale Variable verwendet). In allen Fällen (ohne Ausnahme) können und sollten Sie auf dieses Beispiel verzichten (z. B. indem Sie die Argumente als Konstanten deklarieren). Beispiel: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } Wenn Sie diese einfache Regel befolgen, ist es einfacher, den gegebenen Code zu verfolgen und die Ursache des Problems zu finden, selbst wenn lokale Variablen eingeführt werden.
5. Ein- und Auspacken
Boxing und Unboxing ist der Name einer Technik, die in Java verwendet wird, um primitive Typen ( int, long, double usw. ) in entsprechende Typ-Wrapper ( Integer, Long, Double usw.) umzuwandeln. In Teil 4 des Tutorials „Wie und wann man Generics verwendet“ haben Sie dies bereits in Aktion gesehen, als ich darüber sprach, wie man primitive Typen als Typparameter von Generics umschließt. Obwohl der Java-Compiler sein Bestes versucht, solche Konvertierungen durch Autoboxing zu verbergen, gelingt dies manchmal nicht so gut wie erwartet und führt zu unerwarteten Ergebnissen. Schauen wir uns ein Beispiel an: public static void calculate( final long value ) { // блок Codeа } final Long value = null; calculate( value ); Der obige Codeausschnitt lässt sich gut kompilieren. Es wird jedoch eine NullPointerException in der Zeile ausgelöst, in der zwischen Long und long // блок konvertiert wird . Für einen solchen Fall empfiehlt es sich, primitive Typen zu verwenden (wir wissen jedoch bereits, dass dies nicht immer möglich ist).
6. Schnittstellen
In Teil 3 des Tutorials „So entwerfen Sie Klassen und Schnittstellen“ haben wir Schnittstellen und Vertragsprogrammierung besprochen und betont, dass Schnittstellen nach Möglichkeit konkreten Klassen vorgezogen werden sollten. Der Zweck dieses Abschnitts besteht darin, Sie dazu zu ermutigen, zunächst über Schnittstellen nachzudenken, indem dies anhand von Beispielen aus der Praxis demonstriert wird. Schnittstellen sind nicht an eine bestimmte Implementierung gebunden (mit Ausnahme von Standardmethoden). Es handelt sich lediglich um Verträge, die beispielsweise viel Freiheit und Flexibilität bei der Vertragsausführung bieten. Diese Flexibilität wird noch wichtiger, wenn die Implementierung externe Systeme oder Dienste umfasst. Schauen wir uns ein Beispiel für eine einfache Schnittstelle und ihre mögliche Implementierung an: 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(); } } Der obige Codeausschnitt zeigt ein typisches Schnittstellenmuster und seine Implementierung. Diese Implementierung verwendet einen externen HTTP-Dienst ( http://api.geonames.org/ ), um die Zeitzone eines bestimmten Standorts abzurufen. Allerdings, weil Da der Vertrag von der Schnittstelle abhängt, ist es sehr einfach, eine andere Implementierung der Schnittstelle einzuführen, beispielsweise mithilfe einer Datenbank oder sogar einer regulären Flatfile. Bei ihnen sind Schnittstellen sehr hilfreich bei der Gestaltung testbaren Codes. Beispielsweise ist es nicht immer praktisch, bei jedem Test externe Dienste aufzurufen, daher ist es sinnvoll, stattdessen eine alternative, einfachste Implementierung (z. B. einen Stub) zu implementieren: public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } Diese Implementierung kann überall dort verwendet werden, wo die TimezoneService- Schnittstelle erforderlich ist, wodurch die Schnittstelle isoliert wird Testskript aus Abhängigkeit von externen Komponenten. Viele hervorragende Beispiele für die effektive Nutzung solcher Schnittstellen sind in der Java-Standardbibliothek enthalten. Sammlungen, Listen, Sets – diese Schnittstellen verfügen über mehrere Implementierungen, die nahtlos ausgetauscht werden können und ausgetauscht werden können, wenn Verträge davon profitieren. Zum Beispiel: 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. Saiten
Strings sind einer der am häufigsten verwendeten Typen sowohl in Java als auch in anderen Programmiersprachen. Die Java-Sprache vereinfacht viele routinemäßige Zeichenfolgenmanipulationen, indem sie Verkettungs- und Vergleichsoperationen direkt nach dem Auspacken unterstützt. Darüber hinaus enthält die Standardbibliothek viele Klassen, die String-Operationen effizient machen. Genau das werden wir in diesem Abschnitt besprechen. In Java sind Strings unveränderliche Objekte, die in der UTF-16-Codierung dargestellt werden. Jedes Mal, wenn Sie Zeichenfolgen verketten (oder eine Operation ausführen, die die ursprüngliche Zeichenfolge ändert), wird eine neue Instanz der String- Klasse erstellt . Aus diesem Grund kann die Verkettungsoperation sehr ineffizient werden, was dazu führt, dass viele Zwischeninstanzen der String- Klasse erstellt werden (was im Allgemeinen zu Müll führt). Aber die Java-Standardbibliothek enthält zwei sehr nützliche Klassen, deren Zweck darin besteht, die String-Manipulation bequemer zu gestalten. Dies sind StringBuilder und StringBuffer (der einzige Unterschied zwischen ihnen besteht darin, dass StringBuffer threadsicher ist, während StringBuilder das Gegenteil ist). Schauen wir uns ein paar Beispiele für die Verwendung einer dieser Klassen an: 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(), "]" ); Während die Verwendung von StringBuilder/StringBuffer die empfohlene Methode zum Bearbeiten von Zeichenfolgen ist, kann sie im einfachsten Szenario der Verkettung von zwei oder drei Zeichenfolgen übertrieben wirken, sodass der normale Additionsoperator ( („+“), zum Beispiel: String userId = "user:" + new Random().nextInt( 100 ); Oft ist die beste Alternative zur Vereinfachung der Verkettung die Verwendung von Zeichenfolgenformatierung sowie der Java-Standardbibliothek, um eine statische String.format- Hilfsmethode bereitzustellen . Dies unterstützt eine Vielzahl von Formatspezifizierern, einschließlich Zahlen, Symbolen, Datum/Uhrzeit usw. (Ausführliche Informationen finden Sie in der Referenzdokumentation.) Die String.format- 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% Methode bietet einen sauberen und einfachen Ansatz zum Generieren von Zeichenfolgen aus verschiedenen Datentypen. Es ist erwähnenswert, dass moderne Java-IDEs die Formatspezifikation anhand der an die String.format- Methode übergebenen Argumente analysieren und Entwickler warnen können, wenn Abweichungen festgestellt werden.
8. Namenskonventionen
Java ist eine Sprache, die Entwickler nicht dazu zwingt, irgendwelche Namenskonventionen strikt einzuhalten, aber die Community hat eine Reihe einfacher Regeln entwickelt, die dafür sorgen, dass Java-Code sowohl in der Standardbibliothek als auch in allen anderen Java-Projekten konsistent aussieht:
  • Paketnamen werden in Kleinbuchstaben geschrieben: org.junit, com.fasterxml.jackson, javax.json
  • Namen von Klassen, Aufzählungen, Schnittstellen und Anmerkungen werden mit einem Großbuchstaben geschrieben: StringBuilder, Runnable, @Override
  • Namen von Feldern oder Methoden (außer static final ) werden in Camel-Notation angegeben: isEmpty, format, addAll
  • Statische Endfeld- oder Aufzählungskonstantennamen werden in Großbuchstaben geschrieben und durch Unterstriche („_“) getrennt: LOG, MIN_RADIX, INSTANCE.
  • Lokale Variablen oder Methodenargumente werden in Kamelnotation eingegeben: str, newLength, MinimumCapacity
  • Parametertypnamen für Generika werden durch einen einzelnen Buchstaben in Großbuchstaben dargestellt: T, U, E
Wenn Sie diese einfachen Konventionen befolgen, wird der Code, den Sie schreiben, prägnant aussehen und sich im Stil nicht von einer anderen Bibliothek oder einem anderen Framework unterscheiden, und es fühlt sich an, als wäre er von derselben Person entwickelt worden (eine dieser seltenen Situationen, in denen Konventionen tatsächlich funktionieren).
9. Standardbibliotheken
Ganz gleich, an welcher Art von Java-Projekt Sie arbeiten, die Java-Standardbibliotheken sind Ihre besten Freunde. Ja, man kann kaum widersprechen, dass sie einige Ecken und Kanten und seltsame Designentscheidungen haben, aber in 99 % der Fälle handelt es sich um qualitativ hochwertigen Code, der von Experten geschrieben wurde. Es lohnt sich, es zu erkunden. Jede Java-Version bringt viele neue Funktionen in bestehende Bibliotheken (mit einigen möglichen Problemen mit alten Funktionen) und fügt außerdem viele neue Bibliotheken hinzu. Java 5 brachte eine neue Parallelitätsbibliothek als Teil des Pakets java.util.concurrent mit . Mit Java 6 wurden (weniger bekannte) Skriptunterstützung ( Paket javax.script ) und eine Java-Compiler- API (als Teil des Pakets javax.tools ) eingeführt. Java 7 brachte viele Verbesserungen an java.util.concurrent , führte eine neue I/O-Bibliothek im Paket java.nio.file ein und unterstützte dynamische Sprachen in java.lang.invoke . Und schließlich fügte Java 8 dem Paket java.time das lang erwartete Datum/die Uhrzeit hinzu . Java als Plattform entwickelt sich weiter und es ist sehr wichtig, dass es sich mit den oben genannten Änderungen weiterentwickelt. Wenn Sie erwägen, eine Bibliothek oder ein Framework eines Drittanbieters in Ihr Projekt einzubinden, stellen Sie sicher, dass die erforderliche Funktionalität nicht bereits in den Standard-Java-Bibliotheken enthalten ist (natürlich gibt es viele spezialisierte und leistungsstarke Implementierungen von Algorithmen, die dem voraus sind). Algorithmen in den Standardbibliotheken, aber in den meisten Fällen werden sie wirklich nicht benötigt).
10. Unveränderlichkeit
Die Unveränderlichkeit im gesamten Leitfaden und in diesem Teil dient als Erinnerung: Bitte nehmen Sie es ernst. Wenn eine von Ihnen entworfene Klasse oder eine von Ihnen implementierte Methode eine Unveränderlichkeitsgarantie bieten kann, kann sie in den meisten Fällen überall verwendet werden, ohne befürchten zu müssen, gleichzeitig geändert zu werden. Dies wird Ihr Leben als Entwickler (und hoffentlich auch das Ihrer Teammitglieder) einfacher machen.
11. Testen
Die Praxis der testgetriebenen Entwicklung (TDD) erfreut sich in der Java-Community großer Beliebtheit und legt die Messlatte für die Codequalität höher. Bei all den Vorteilen, die TDD bietet, ist es traurig zu sehen, dass die Java-Standardbibliothek heute kein Test-Framework oder Support-Tools enthält. Allerdings ist das Testen zu einem notwendigen Bestandteil der modernen Java-Entwicklung geworden und in diesem Abschnitt werden wir uns einige grundlegende Techniken mit dem JUnit- Framework ansehen . In JUnit besteht jeder Test im Wesentlichen aus einer Reihe von Aussagen über den erwarteten Zustand oder das erwartete Verhalten eines Objekts. Das Geheimnis beim Schreiben großartiger Tests besteht darin, sie einfach und kurz zu halten und jeweils eine Sache zu testen. Als Übung schreiben wir eine Reihe von Tests, um zu überprüfen, ob String.format eine Funktion aus dem String-Abschnitt ist, die das gewünschte Ergebnis zurückgibt. 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" ) ); } } Beide Tests sehen sehr gut lesbar aus und ihre Ausführung ist Instanzen. Heutzutage enthält ein durchschnittliches Java-Projekt Hunderte von Testfällen, die dem Entwickler während des Entwicklungsprozesses schnelles Feedback zu Regressionen oder Features geben.
12. Weiter
Dieser Teil des Leitfadens schließt eine Reihe von Diskussionen ab, die sich auf die Programmierpraxis in Java und Handbücher für diese Programmiersprache beziehen. Beim nächsten Mal kehren wir zu den Funktionen der Sprache zurück und erkunden die Welt von Java im Hinblick auf Ausnahmen, ihre Typen und wie und wann sie verwendet werden.
13. Laden Sie den Quellcode herunter
Dies war eine Lektion über allgemeine Entwicklungsprinzipien aus dem Advanced Java-Kurs. Der Quellcode für die Lektion kann hier heruntergeladen werden .
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION