JavaRush /Java Blog /Random EN /General programming style guide
pandaFromMinsk
Level 39
Минск

General programming style guide

Published in the Random EN group
This article is part of the "Advanced Java" academic course. This course is designed to help you learn how to use Java features effectively. The material covers "advanced" topics, such as object creation, competition, serialization, reflection, etc. The course will teach you how to effectively master Java techniques. Details here .
Content
1. Introduction 2. Variable scope 3. Class fields and local variables 4. Method arguments and local variables 5. Boxing and unboxing 6. Interfaces 7. Strings 8. Naming conventions 9. Standard libraries 10. Immutability 11. Testing 12. Next... 13. Downloading the source code
1. Introduction
In this part of the tutorial, we will continue our discussion of the general principles of good programming style and agile design in Java. We have already seen some of these principles in the previous chapters of the guide, but a lot of practical advice will be given in order to improve the skills of a Java developer.
2. Scope of variables
In Part 3 ("How to Design Classes and Interfaces"), we discussed how visibility and accessibility can be applied to members of classes and interfaces, given scope constraints. However, we have not yet discussed local variables that are used in method implementations. In the Java language, every local variable, once declared, has a scope. This variable becomes visible from the place where it is declared to the point where the execution of the method (or block of code) ends. As a general rule, the only rule to follow is to declare a local variable as close as possible to the place where it will be used. Let's take a look at a typical example: for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } In both code snippets, the scope of variables is limited to the execution blocks where these variables are declared. At the end of the block, the scope ends and the variable becomes invisible. It looks more understandable, but with the release of Java 8 and the introduction of lambdas, many well-known language idioms using local variables are becoming obsolete. Here's an example from the previous example using a lambda instead of a loop: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); You can see that the local variable has become an argument to a function passed in turn as an argument to the forEach method .
3. Class fields and local variables
Every method in Java belongs to a particular class (or, in the case of Java8, an interface where the given method is declared as the default method). Between local variables that are fields of a class or used in the implementation of methods, there is, as such, the possibility of a name conflict. The Java compiler knows how to choose the correct variable from a set of available ones, even though more than one developer intends to use that variable. Modern Java IDEs do a great job of telling the developer when such conflicts are about to occur, through compiler warnings, variable highlighting. But it's still better to think about things like this while writing the code. Let's take a look at an example: public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } The example looks quite easy, but it's a trap. calculateValue Method introduces a local variable value and, operating on it, hides the class field with the same name. The line value += value; should be the sum of the value of the class field and the local variable, but instead, something else is done. The correct implementation would look like this (using the this keyword): public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } This example is naive in some ways, but nevertheless it demonstrates an important point where, in some cases, it can take hours to debug and fix.
4. Method Arguments and Local Variables
Another trap that inexperienced Java developers often fall into is using method arguments as local variables. Java allows non-constant arguments to be reassigned (however, this has no effect on the original value): The public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } code snippet above is not elegant, but it's good enough to expose the problem: the str argument has been assigned a different value (and is mostly used as a local variable) . In all cases (without any exception) this example can and should be dispensed with (for example, by declaring the arguments as constants). For example: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } By following this simple rule, this code is easier to trace and find the source of the problem in it, even when introducing local variables.
5. Packing and unpacking
Boxing and unboxing is the name of a technique used in Java to convert primitive types ( int , long , double , etc. ) to their corresponding type wrappers ( Integer , Long , Double , etc. ). In part 4 of the guide How and When to Use Generics, you already saw this in action when I talked about wrapping primitive types as generic type parameters. Even though the Java compiler tries its best to hide such conversions by performing autoboxing, sometimes it doesn't work as well as expected and produces unexpected results. Let's look at an example: public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); The code snippet above will compile just fine. However, it will throw a NullPointerException on the line // блок where the conversion between Long and long . The advice for this case is to use primitive types (however, we already know that this is not always possible).
6. Interfaces
In Part 3 of How to Design Classes and Interfaces, we discussed interfaces and programming by contract, emphasizing that interfaces should be preferred to concrete classes where possible. The purpose of this section is to convince you again to consider interfaces first by demonstrating this with real examples. Interfaces are not tied to a specific implementation (with the exception of default methods). They are just contracts and, as an example, they provide a lot of freedom and flexibility in the way contracts could be executed. This flexibility becomes more important when the implementation entails external systems or services. Let's look at an example of a simple interface and its possible implementation: 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(); } } The code snippet above shows a typical interface pattern and its implementation. This implementation uses an external HTTP service ( http://api.geonames.org/ ) to retrieve the time zone of a specific location. However, since the contract depends on the interface, it is very easy to introduce another implementation of the interface, using, for example, a database or even a regular flat file. With them, interfaces are very helpful in designing testable code. For example, it's not always practical to call external services on every test, so instead it makes sense to implement an alternative, simpler implementation (like a stub): public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } This implementation can be used anywhere where the TimezoneService interfacerequired, isolating the test script from dependency on external components. Many excellent examples of the efficient use of such interfaces are encapsulated within the Java standard library. Collections, lists, sets - these interfaces have multiple implementations that can be seamlessly swapped and can be interchanged when contracts take precedence. For example: 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. Strings
Strings are one of the most used types in both Java and other programming languages. The Java language simplifies many common string operations by supporting concatenation and comparison operations out of the box. In addition, the standard library contains many classes that make string operations efficient. This is exactly what we are going to discuss in this section. In Java, strings are immutable objects represented in UTF-16 encoding. Every time you concatenate strings (or perform any operation that changes the original string), a new instance of the String class is created . Because of this, the concatenation operation can become very inefficient, causing many intermediate instances of the String class to be created.(creates garbage, generally speaking). But the Java standard library contains two very useful classes whose purpose is to make string manipulation convenient. They are StringBuilder and StringBuffer (the only difference between them is that StringBuffer is thread safe while StringBuilder is the other way around). Let's take a look at a couple of examples of using one of these classes: 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(), "]" ); Although using StringBuilder / StringBuffer is the recommended way to manipulate strings, it may look redundant in the simplest scenario of concatenating two or three strings, so that the normal addition operator can be used instead ( "+"), for example: String userId = "user:" + new Random().nextInt( 100 ); Often the best alternative to simplify concatenation is to use string formatting and the Java standard library to help provide the static helper method String.format . This supports a rich set of format specifiers, including numbers, symbols, date/time, and more. (See the reference documentation for complete details) The 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% method provides a clean and easy approach to generating strings from various data types. It's worth noting that modern Java IDEs can parse the format specification from the arguments passed to the String.format method and warn developers if they find any mismatches.
8. Naming Conventions
Java is a language that does not force developers to strictly follow any naming convention, however the community has developed a set of simple rules that make Java code look the same in the standard library as in any other Java project:
  • package names are in lower case: org.junit, com.fasterxml.jackson, javax.json
  • the names of classes, enumerations, interfaces, annotations are capitalized: StringBuilder, Runnable, @Override
  • field or method names (with the exception of static final ) are in camel case: isEmpty, format, addAll
  • static final fields or names of enumeration constants are specified in uppercase, separated by an underscore ("_"): LOG, MIN_RADIX, INSTANCE.
  • local variables or method arguments are in camel case: str, newLength, minimumCapacity
  • parameter type names for generics are represented by a single uppercase letter: T, U, E
By following these simple conventions, the code you write will appear concise and indistinguishable in style from code in another library or framework, and will give the impression that it was developed by the same person (one of those rare times when conventions actually work). ).
9. Standard Libraries
No matter what kind of Java project you're working on, the Java standard libraries are your best friends. Yes, it's hard not to agree that they have some rough edges and strange design decisions, however, 99% of them are high-quality code written by experts. It's worth exploring. Each Java release brings many new features to the existing libraries (with some possibly contentious issues with the old features) and adds many new libraries as well. Java 5 brought a new concurrency library as part of the java.util.concurrent package . Java 6 provided (less well known) support for scripting ( the javax.script package) and the Java compiler API (as part of the javax.tools package).). Java 7 brought many improvements to java.util.concurrent , introduced a new I/O library in the java.nio.file package , and support for dynamic languages ​​in java.lang.invoke . Finally, Java 8 added the long awaited date/time in the java.time package. Java as a platform is evolving and it is very important for it to progress along with the above changes. Whenever you consider including a third party library or framework in your project, make sure that the required functionality is not already contained in the standard Java libraries (of course, there are many specialized and high performance implementations of algorithms that are ahead of the algorithms from the standard libraries, but in most cases they really are Not needed).
10. Immutability
Immutability throughout the manual and in this part remains as a reminder: please take it seriously. If the class you design or the method you implement can provide an immutability guarantee, it can be used in most cases everywhere without fear of being modified at the same time. This will make your life as a developer (and, hopefully, the lives of your teammates) easier.
11. Testing
The practice of test-driven development (TDD) is extremely popular in the Java community, raising the bar for code quality. With all the benefits that TDD provides, it's sad to see that the Java standard library doesn't include any test framework or support tools today. However, testing has become a necessary part of modern Java development, and in this section we'll take a look at a few basic tricks using the JUnit framework . In JUnit, essentially, each test is a set of assertions about the expected state or behavior of an object. The secret to writing great tests is to keep them short and simple, testing one thing at a time. As an exercise, let's write a test suite to check that String.format isis a function from the strings section that returns the desired result. 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" ) ); } } Both tests look very readable and their execution is instantiated. Today, the average Java project contains hundreds of test cases, giving the developer quick feedback on regressions or features during the development process.
12. Next
This part of the guide concludes a series of discussions related to the practice of programming in Java and guides to this programming language. Next time we'll return to the specifics of the language, exploring the Java world in terms of exceptions, their types, and how and when to use them.
13. Download source code
This was a lesson on general development principles from the Advanced Java course. The source code for the lesson can be downloaded here .
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION