Tuesday, January 17, 2017

Using @FunctionalInterface annotation

A functional interface has exactly one abstract method, also called, SAM interface (Single Abstract Method). SAM interfaces have a special place in Java 8 because they can be implemented using Lambda expressions or Method references.

@FunctionalInterface annotation allows us to forces a compilation error when an additional, non-overriding abstract method is added to a SAM. Once the SAM contract is broken, the interface could no more be used in Lambda implementations.

BatchConsumer:
package refactor;

import java.util.function.Consumer;

/**
 * A functional interface example
 */
@FunctionalInterface
public interface BatchConsumer<T> extends Consumer<T> {
    default void beforeConsume() {}

    default void afterConsume(){}
}

BatchConsumer contains single abstract method accept() but can contain default methods. The default methods have an implementation and are not abstract so it will still remain a functional interface. If we add one more method to the SAM interface, will result in the below compilation error:
Multiple non-overriding abstract methods found in interface refactor.BatchConsumer.

Batch:
package refactor;

import java.util.Arrays;

public class Batch {

    public static void main(String[] args) {
        consumeIntegers(i -> System.out.println(Arrays.asList(i)));

        consumeIntegers(new BatchConsumer<Integer[]>() {
            public void beforeConsume() {
                System.out.println("before consuming");
            }

            @Override
            public void accept(Integer[] i) {
                System.out.println(Arrays.asList(i));
            }

            public void afterConsume() {
                System.out.println("after consuming");
            }
        });
    }

    private static void consumeIntegers(BatchConsumer<Integer[]> consumer) {
        consumer.beforeConsume();
        consumer.accept(new Integer[]{1, 2, 3, 4});
        consumer.afterConsume();
    }
}

Output:
[1, 2, 3, 4]
before consuming
[1, 2, 3, 4]
after consuming

Monday, January 16, 2017

Exception handling examples

Don't swallow exceptions

Make sure the exceptions are handled and not swallowed.
    public void notifyOthers() {
        try {
            List<party> interestedParties = fetchInterestedPartiesFromDb();
        } catch (Exception e) {
            // do nothing
        }
    }

    private List<party> fetchInterestedPartiesFromDb() throws Exception {
        return fetchFromDb(Party.class);
    }

    private <t> List<t> fetchFromDb(Class<t> partyClass) {
        //interact with db
        return null;
    }

An exception caught must be logged

Make sure the complete exception is logged rather than just logging the message using Exception.getMessage().
    public void notifyOthers() {
        try {
            List<party> interestedParties = fetchInterestedPartiesFromDb();
        } catch (Exception e) {
            LOGGER.severe(e.getMessage());
        }
    }
Instead of the above, we should simply log the exception object itself so that one can see the entire execution stack.
    public void notifyOthers() {
        try {
            List<party> interestedParties = fetchInterestedPartiesFromDb();
        } catch (Exception e) {
            LOGGER.severe("Exception " + e);
        }
    }

Do not catch Throwable

If you catch Throwable even errors will be caught, for example, java.lang.OutOfMemoryError. Below code throws OutOfMemoryError but is caught instead of letting JVM deal with it. Since Throwable is superclass of Error and Exception, it will catch even those errors which indicate serious problem.

Exception hierarchy


ThrowsOom:


package refactor.clean;

import java.util.logging.Logger;

public class ThrowsOom {
    public static void main(String[] args) {
        try {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 1000000; i++) {
                sb.append(i).append(sb);
            }
        } catch (Throwable t) {
            LOGGER.severe("Throwable: " + t);
        }
        LOGGER.warning("Throwable is caught, replace it with Exception");
    }

    private static final Logger LOGGER = Logger.getLogger(ThrowsOom.class.getName());
}

Output:
Jan 16, 2017 5:29:56 PM refactor.clean.ThrowsOom main
SEVERE: Throwable: java.lang.OutOfMemoryError: Java heap space
Jan 16, 2017 5:29:56 PM refactor.clean.ThrowsOom main
WARNING: Throwable is caught, replace it with Exception
Catch Exception instead of Throwable.

ThrowsOom:

package refactor.clean;

import java.util.logging.Logger;

public class ThrowsOom {
    public static void main(String[] args) {
        try {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 1000000; i++) {
                sb.append(i).append(sb);
            }
        } catch (Throwable t) {
            LOGGER.severe("Throwable: " + t);
        }
        LOGGER.warning("Throwable is caught, replace it with Exception");
        try {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 1000000; i++) {
                sb.append(i).append(sb);
            }
        } catch (Exception e) {
            LOGGER.severe("Exception: " + e);
        }
    }

    private static final Logger LOGGER = Logger.getLogger(ThrowsOom.class.getName());
}
Output:
Jan 16, 2017 5:55:11 PM refactor.clean.ThrowsOom main
SEVERE: Throwable: java.lang.OutOfMemoryError: Java heap space
Jan 16, 2017 5:55:11 PM refactor.clean.ThrowsOom main
WARNING: Throwable is caught, replace it with Exception
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at java.util.Arrays.copyOf(Arrays.java:3332)
 at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
 at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
 at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:445)
 at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:459)
 at java.lang.StringBuilder.append(StringBuilder.java:166)
 at refactor.clean.ThrowsOom.main(ThrowsOom.java:20)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:483)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Don't throw generic exception

Below method tries to find an existing issue for the passed in ID issueId. If issue not found throws a RuntimeException.
    public static Issue findById(String issueId) throws RuntimeException {
        Issue issue = fetchFromDb(issueId);
        if (issue == null) {
            throw new RuntimeException("Issue not found");
        }
        return issue;
    }
Instead of a generic exception, one can define and throw a dedicated exception.

IssueNotFoundException:


package refactor;

public class IssueNotFoundException extends RuntimeException {
    public IssueNotFoundException(String message) {
        super(message);
    }
}
Fix findById() to throw specific exception.
    public static Issue findById(String issueId) throws IssueNotFoundException {
        Issue issue = fetchFromDb(issueId);
        if (issue == null) {
            throw new IssueNotFoundException("Issue not found");
        }
        return issue;
    }
Since the method now throws specific exception, the calling methods can handle the exception based on its type. IssueAlreadyResolvedException:

IssueAlreadyResolvedException:


package refactor;

public class IssueAlreadyResolvedException extends Exception {
    public IssueAlreadyResolvedException() {
        super("Issue already resolved");
    }
}
    public void resolve(String issueId) throws IssueAlreadyResolvedException {
        Issue issue;
        try {
             issue = Issue.findById(issueId);
        } catch (IssueNotFoundException e) {
            return;
        }
        if (issue.isResolved()) {
            throw new IssueAlreadyResolvedException();
        }
        issue.resolve();
    }

Friday, January 13, 2017

Refactor nested statements

If you find any nested statements in your code, make sure you simplify them before any further changes. As a rule do not nest more than 3 if/for/while/switch/try statements.

Nested statements

If there are more than three nested statements then the code starts looking really messy. The more complex a code is, the more painful the refactoring becomes. In the below class, we show multiple nested statements, the statement if (relatedIssue.isResolved()) is the fifth nested statement. Since the code is tangled, the anti-pattern is called "Spaghetti code".

IssueTracker:

package refactor;

import java.util.List;

/**
 * Nested loops
 */
public class IssueTracker {
    public void resolve(Issue issue) {
        if (issue.isAssigned()) {
            issue.resolve();
            List relatedIssues = issue.getRelatedIssues();
            boolean closeIssue = true;
            if (relatedIssues != null) {
                for (Issue relatedIssue : relatedIssues) {
                    try {
                        if (!relatedIssue.isResolved()) {
                            closeIssue = false;
                            break;
                        }
                    } catch (Exception e) {
                        //log exception
                    }
                }
            }
            if (closeIssue) {
                issue.close();
            }
        }
    }
}

Nested statements simplified

We simplify the above code by first making sure all the pre-conditions are met.
  1. The issue must is in 'Assigned' status, if not, we don't have to do anything further.
  2. Similarly, we also want to close the issue if the related issues are all resolved.
  3. Write a separate method with an explicit name that indicating our intention.
  4. We want to know whether all our related issues are resolved, we merge the below into a new method verifyRelatedIssuesResolved()
                List relatedIssues = issue.getRelatedIssues();
                boolean closeIssue = true;
                if (relatedIssues != null) {
                    for (Issue relatedIssue : relatedIssues) {
                        try {
                            if (!relatedIssue.isResolved()) {
    
  5. Once it is confirmed all are resolved, close the issue.
IssueTracker:

package refactor.clean;

import refactor.Issue;

import java.util.List;

/**
 * Nested loops simplified
 */
public class IssueTracker {
    public void resolve(Issue issue) {
        if (!issue.isAssigned()) {
            return;
        }
        issue.resolve();
        boolean closeIssue = verifyRelatedIssuesResolved(issue);
        if (closeIssue) {
            issue.close();
        }
    }

    private boolean verifyRelatedIssuesResolved(Issue issue) {
        List relatedIssues = issue.getRelatedIssues();
        if (relatedIssues == null || relatedIssues.isEmpty()) {
            return true;
        }
        for (Issue relatedIssue : relatedIssues) {
            if (!relatedIssue.isResolved()) {
                return false;
            }
        }
        return true;
    }
}