JavaRush /Java Blog /Random EN /Guide to General Programming Style
pandaFromMinsk
Level 39
Минск

Guide to General Programming Style

Published in the Random EN group
This article is part of the academic course "Advanced Java." This course is designed to help you learn how to effectively use Java features. 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. Download source code
1. Introduction
In this part of the tutorial we will continue our discussion of the general principles of good programming style and responsive design in Java. We have already seen some of these principles in previous chapters of the guide, but many practical tips will be given with the aim of improving the skills of a Java developer.
2. Variable scope
In Part Three ("How to Design Classes and Interfaces") we discussed how visibility and accessibility can be applied to members of classes and interfaces, given scope restrictions. 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 execution of the method (or block of code) completes. Generally, the only rule to follow is to declare a local variable as close as possible to the place where it will be used. Let me look at a typical example: for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } In both code fragments, the scope of variables is limited to the execution blocks where these variables are declared. When the block completes, the scope ends and the variable becomes invisible. This seems clearer, but with the release of Java 8 and the introduction of lambdas, many of the language's well-known idioms using local variables are becoming obsolete. Let me give an example from the previous example using lambdas instead of a loop: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); It can be seen that the local variable has become an argument to the function, which in turn is passed as an argument to the forEach method .
3. Class fields and local variables
Each method in Java belongs to a specific class (or, in the case of Java8, an interface where the method is declared as the default method). Between local variables that are fields of a class or methods used in the implementation, there is as such a possibility of name conflict. The Java compiler knows how to select the correct variable from among the 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 and variable highlighting. But it's still better to think about such things while writing code. I suggest looking 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 is a trap. The 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 being done. A proper 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; } } While this example is naive in some ways, it does demonstrate an important point that in some cases it can take hours to debug and fix.
4. Method arguments and local variables
Another pitfall that inexperienced Java developers often fall into is using method arguments as local variables. Java allows you to reassign values ​​to non-constant arguments (however, this has no effect on the original value): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } The code snippet above is not elegant, but it does a good job of uncovering the problem: the argument str is assigned a different value (and is basically used as a local variable) . In all cases (without any exception), you can and should do without this example (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, it is easier to trace the given code and find the source of the problem, 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 corresponding type wrappers ( Integer, Long, Double , etc.). In Part 4 of the How and When to Use Generics tutorial, you already saw this in action when I talked about wrapping primitive types as type parameters of generics. Although the Java compiler tries its best to hide such conversions by performing autoboxing, sometimes this is less than 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 compiles fine. However, it will throw a NullPointerException on the line // блок where it is converting between Long and long . Advice for such a case is that it is advisable to use primitive types (however, we already know that this is not always possible).
6. Interfaces
In Part 3 of the tutorial, "How to Design Classes and Interfaces," we discussed interfaces and contract programming, emphasizing that interfaces should be preferred over concrete classes whenever possible. The purpose of this section is to encourage you to consider interfaces first by demonstrating this with real-life examples. Interfaces are not tied to a specific implementation (except for default methods). They are only 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 involves 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, because 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 is not always practical to call external services on every test, so it makes sense to instead implement an alternative, simplest implementation (such as 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 interface is required, isolating the test script from dependency on external components . Many excellent examples of effective use of such interfaces are encapsulated within the Java standard library. Collections, lists, sets - these interfaces have multiple implementations that can be seamlessly swapped out and can be interchanged when contracts take advantage. 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 routine string manipulations by supporting concatenation and comparison operations right 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 modifies 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 (creating garbage, in general). But the Java standard library contains two very useful classes whose purpose is to make string manipulation convenient. These are StringBuilder and StringBuffer (the only difference between them is that StringBuffer is thread safe while StringBuilder is the opposite). Let's look at a couple of examples of one of these classes being used: 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(), "]" ); While using StringBuilder/StringBuffer is the recommended way to manipulate strings, it may look overkill in the simplest scenario of concatenating two or three strings, so that the normal addition operator ( ( "+"), for example: String userId = "user:" + new Random().nextInt( 100 ); Often the best alternative to simplify concatenation is to use string formatting as well as the Java Standard Library to help provide a static String.format helper method . This supports a rich set of format specifiers, including numbers, symbols, date/time, etc. (Refer to 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 lightweight approach to generating strings from various data types. It is worth noting that modern Java IDEs can parse the format specification from the arguments passed to the String.format method and warn developers if any mismatches are detected.
8. Naming conventions
Java is a language that does not force developers to strictly follow any naming convention, but the community has developed a set of simple rules that make Java code look consistent both in the standard library and in any other Java projects:
  • package names are in lowercase: org.junit, com.fasterxml.jackson, javax.json
  • names of classes, enumerations, interfaces, annotations are written with a capital letter: StringBuilder, Runnable, @Override
  • names of fields or methods (except for static final ) are specified in camel notation: isEmpty, format, addAll
  • static final field or enumeration constant names are in uppercase, separated by underscores ("_"): LOG, MIN_RADIX, INSTANCE.
  • local variables or method arguments are typed in camel notation: str, newLength, minimumCapacity
  • parameter type names for generics are represented by a single letter in upper case: T, U, E
By following these simple conventions, the code you write will look concise and indistinguishable in style from another library or framework, and will feel like 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 to disagree that they have some rough edges and strange design decisions, however, 99% of the time it’s high-quality code written by experts. It's worth exploring. Each Java release brings many new features to existing libraries (with some possible issues with old features), and also adds many new libraries. 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 a Java compiler API (as part of the javax.tools package ). Java 7 brought many improvements to java.util.concurrent , introducing a new I/O library in the java.nio.file package and support for dynamic languages ​​in java.lang.invoke . And 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 in the standard libraries, but in most cases they really are Not needed).
10. Immutability
The immutability throughout the guide and in this part remains as a reminder: please take it seriously. If a class you design or a 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 team members) 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 is sad to see that the Java standard library today does not include any test framework or support tools. However, testing has become a necessary part of modern Java development and in this section we will look at a few basic techniques using the JUnit framework . In JUnit, essentially, each test is a set of statements about the expected state or behavior of an object. The secret to writing great tests is to keep them simple and short, testing one thing at a time. As an exercise, let's write a set of tests to verify that String.format is a function from the string 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 instances. Today, the average Java project contains hundreds of test cases, giving the developer quick feedback during the development process on regressions or features.
12. Next
This part of the guide completes a series of discussions related to the practice of programming in Java and manuals for this programming language. Next time we will return to the features of the language, exploring the world of Java regarding exceptions, their types, 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