days
0
-7
hours
0
-7
minutes
-2
-2
seconds
-2
-4
search
These are the error codes that haunt us

A beginner’s guide to Java programming nightmares

Georgi Minkov
java
© Shutterstock / Toma Stepunina

New developers make beginner’s mistakes. How can we support our newbie brethren so they don’t make a mess of our code? Georgi Minkov explores some of the more common Java mistakes and their solutions with a tour of the horrors of his own early code.

In our everyday job, we tackle an enormous amount of problems and challenges, from “How do I secure data?”, “What kind of type is this?” to “What are those concepts supposed to be?” This article is not pointing to a specific programming issue like how an encryption works or avoiding SQL injections. Instead of focusing on one topic or one nightmare, I will go back to the basics and try to find the roots of our horrors.

You’re about to find out more about some of the programming horrors I faced when I was just starting to code. The goal of the article would be to help fellow developers improve their working process and find solutions for some of their ongoing problems. The following examples would be for Java; however, feel free to apply them to the language which concerns you most.

Returning null as base value

From my experience working in a bespoke software development company, there is a fierce discussion on how base scenarios of our program need to be handled. In this basic scenario, we define what value a function needs to return when there are no suitable results. In the next snippet, we have to return workers which name contains string “sup”, but what happens when no names satisfy this condition? One approach is to return null as a result, but that hides some risks. One of the biggest would be that the person working on the assignment after you on a different task may neglect that fact and try to use your result in a different way, which will cause NullPointerException (Listing 1).

 List workers = manufactory.getWorkersByCondition(); // returning null
for (Person worker : workers) { // NullPointerException occurs
  // some logic
}

To prevent that you can return empty List or whatever your collection is. Additionally, your coworkers would be quite happy because they would be able to use this List without triggering the exception.

Of course in some companies returning null as base value is standard. If this is true in your case, head down to the next part.

Checking for nulls

Here’s the situation – you call a service or some other part of your project and expect it to return a result of a specific type. Save this result in a local variable and then use it. Using this variable before checking if there is really a result in it can cause a problem like NullPointerException or a logical malfunction. To prevent that kind of problem, it’s advisable to check whether the result is null and/or empty (Listing 2).

List shoppingCart = session.getShoppingCart();
if (shoppingCart != null) { // you can add && !shoppingCart.isEmpty()
  // operate over cart
} else {
  // if needed specific operations
}

Not documenting your code

Most of us hate documentation or dealing with it in normal daily routine, but in programming, it can save lives. I’m not exaggerating; I’m sure you’ve been in a situation where you needed to use a certain method or class but had no idea what it does and how you could operate with it. In the best case scenario, the code is written so well that it explains itself. However, this is rarely the case.

There are different types of documentation and different tools which could help like Swagger or Doxygen for Spring Boot project documentation. Here are a couple examples of where a line of comments could help both you and the developers coding after you:

  • When you work on a project for a long time and return to an old feature, a couple lines of comments can direct you in why and how certain things are configured.
  • When you work on a project for a long time and return to an old feature, a couple lines of comments can direct you in why and how certain things are configured.
  • When a third person uses your API or some method, they can determine whether this functionality is really needed and how it works with your documentation.

There are different best practices on when and how to write comments, but in my personal opinion most important ones are:

  • When there is something unusual in your code, like “if” without “else” or complicated logic, you can put a comment why you did things the way you did.
  • When there is something unusual in your code, like “if” without “else” or complicated logic, you can put a comment why you did things the way you did.
  • When you create some specific tool or service, you can use some documentation tools or documentation standards for your language to briefly describe important parts and any complex peculiarities.

Next time when you spend hours of coding ask yourself – “Did I forget something?”

Exception handling

Often, it is enticing to neglect exceptions handling, but this would be a big threat. First, you never know where the exception may occur. Second, you just broke project structure. The best practices for beginner and experienced developers are to handle the exceptions, knowing when to throw and catch them.

SEE ALSO: Top 5 Java testing tools

Hiding an exception is another way of handling them that can be a real nightmare. From personal experience, you can lose hours of debugging only to find out that something catches an exception but does nothing with it (Listing 3).

try {
  client = clientFactory.getClientBy(username, password, serverURI);
} catch (CustomException ex) {
  logger.error(ex.getMessage(), ex); // this line can save us from painful debugging
}

Exceptions are thrown on purpose, so you need to take care of them. If needed, you should rethrow them, log information, or make safe logic for handling. Make sure you address the issue causing particular exceptions. If you do not handle it on purpose, write a comment and document it so other developers are up to date with the code.

Trying to do everything by yourself

If you work with Java, you know that there are an enormous number of libraries that can be used by developers. When you are facing a problem or thinking on how to implement specific functionality, it is important to research the available libraries that are applicable to your case. Other developers have spent years on perfecting the libraries. What’s more, they’re free! For example, if you need a logging tool use Log4j; if you need network libraries, Netty could be a great choice. Some of these open source libraries have become standards in their field.

The next time when you need to do some XML parsing or log information, it’s important to ask “Am I the first one who needs this, or has someone else already implemented a solution?”

Freeing up resources

This is especially targeted to newbies in the business. When you open a network connection or connection to a file it’s important to close it after finishing our job. Otherwise, we block resources that can be used for something else.

Here’s an example of a developer’s poor memory: when an exception is thrown during operations over those resources. Yes, implementations like FileInputStream (as files were mentioned) have a finalizer that invoke close() and frees up resource. However, this will be called on garbage collection cycle (and we cannot predict when this will happen). There is some beautiful piece of craft introduced in Java 7 try-with-resources (Listing 4).

try (Scanner scanner = new Scanner(new File("./description.txt"))) {
  while (scanner.hasNext()) {
    System.out.println(scanner.nextLine());
  }
  // ..more logic
} catch (FileNotFoundException ex) {
  // some safe logic
} catch (CustomException ex) {
  // another safe logic
}

This handles any objects that had implemented AutoClosure Interface and closes the resources that we used without needing to explicitly call close().

Memory management

Next, we will briefly look over memory leaks and expensive allocations. Java’s automatic memory management has been a big relief to developers, who no longer need to free the memory manually. However, it’s also important to understand what happens under the hood of our applications and see how the memory is actually used. As long as our program creates references to an unneeded object, it will not be deleted; that could be labeled as a memory leak.

Do you know what expensive allocations or garbage allocations are? In short, it is when a program is creating continuously short living objects. This approach is dangerous because it will cause performance slowdown of the application, continuously creating and deleting unneeded objects. In the next snippet, we can see a simple example in Listing 5.

String answerToEverything = "";
for (int index = 0; index < 100000; ++index) {
   answerToEverything = answerToEverything + "!42!";
}

String is immutable here. So, to fix this Java snippet, we will create new instance with every iteration with StringBuilder (Listing 6).

StringBuilder answerOfEverything = new StringBuilder();
for (int index = 0; index < 100000; ++index) {
  answerOfEverything.append("!42!");
}
System.out.print("Answer of everything and beyond " + answerOfEverything.toString());

Concurrent modification

Sooner or later, you will face ConcuretModificationException and start searching for the killer. This situation occurs when you try to modify а collection while iterating it without tools from iterator object (Listing 7).

List operationSystems = new ArrayList<>(Arrays.asList("Fedora", "Arch", "Debian", "Manjaro"));

for (String system : operationSystems) {
  if (system.contains("ar")) {
    operationSystems.remove(system);
  }
}

This can happen in a one-threaded application, now imagine having a multithreaded one. One thread is iterating and looking over a list with values while a second thread is removing elements from the list. This can be pretty tricky and we need to be careful when working with it. For that, we have things like synchronization locks and implementation for collection specified for concurrency.

It’s important to keep in mind that the phase before the actual coding is just as important. If done wrong, you could be left to deal with some real nightmares. I will share some of my experiences and the solutions I have found below.

Not planning your solutions

When first starting my programming job, one of the biggest nightmares I faced was when I started implementing immediately right after receiving the specifications from the client. This led to six weeks of great effort only to realize that I was developing something that was never required by the client.

SEE ALSO: Java in the 21st century: Are you thinking far enough ahead?

My advice is to take the time to sit down and fully understand the requirements from a client or your PM/PO. Ask as many questions as possible to clear all uncertainties. Get in line with the business goals of the project. You should think of your solution as part of a bigger picture, so you could find the right questions. Try to only code useful pieces without wasting any time. This should guarantee positive feedback.

Not following code standards

Another nightmare I got myself into was not following company standards and coding without considering my colleagues. This led to huge misunderstandings and a lot of tension within the project. In order to avoid getting mixed up in a situation like this, I would highly recommend reading your company’s best development practices and making sure your code is compliant.

This will help when your code is reviewed by clients, managers, and fellow developers. It might actually help you in getting additional knowledge and improving your programming skills. In addition, it is incredibly important to follow the coding convention for the programming language you will be using.

Not testing your code

Another thing I managed to get myself into was spending an entire sprint fixing bugs from my last release after underestimating testing. No matter how many lines of code you’ve actually managed to create, you absolutely have to test how they integrate with other parts of your project. This could be easily done by doing a simple research on testing styles and then choosing what fits you best. You could go with unit testing, system testing, integration, or any of the other testing styles.

All in all

Bugs and technological misconceptions are beautiful mysteries that we create. We hold our own destinies in our own hands; whether we haunt the endless circles of development hell or Sherlock Holmes our way out of these technological nightmares depends on how well we can follow our own coding processes.

Author
java

Georgi Minkov

Georgi Minkov is a Java and Oracle Developer at Dreamix, a custom software development company. He has experience with Java and Oracle technologies. He indulges in experimenting with different technologies, is a teaching assistant at Sofia University, invests in learning AI and hardware trends, and loves photography and knowledge sharing. Follow him on Twitter: @Dreamix_Ltd.


Leave a Reply

Be the First to Comment!

avatar
400
  Subscribe  
Notify of