Sunday, October 18, 2015

Java: When the compiler crashes the plane

Software design principles on compiled programming languages tend to have one rule in common; Compiler errors over runtime errors. A religiously followed rule. It's a dogma (although based on legitimate reasons). You should always program in a way that most errors would be reported from the compiler and your logic tested by unit testing. Sometimes little things slip through though.

The following scenario describes an easy to do mistake in Java and highlights some good practice to avoid crashing that plane.

Pre-requisites:



Open intelliJ and setup a java project with the following (adapt to match your system):





The example code is this:

import java.util.concurrent.ConcurrentHashMap;


public class FunWithMaps {

    public static void main(String[] args) {
        ConcurrentHashMap map = new ConcurrentHashMap();

        map.put("1", 1);

        System.out.println(map.keySet().getClass());
    }
}

Run. You'll get the following error:

Exception in thread "main" 
java.lang.NoSuchMethodError: 
java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;

So, the compiler used java 8 API and didn't complain despite the fact that we set it 
to compile to java 7. But that was the bytecode version and the java 8 API that was in
the compile classpath didn't cause any problems. 

Consider that this scenario is what the continuous integration (CI) might have. 
This imaginary CI system builds your production level code but you - as a programmer - 
have no control over it. Then, the ConcurrentHashMap code would succeed on your IDE 
(because you would be compiling with java 7 targeting java 7) but the CI would be compiling 
java 8 generating java 7 bytecode without having java 7 API in the classpath. The runtime environment
 would use java 7.

You wouldn't know, the compiler wouldn't know, and the crash would rely on the testing environment
 to be caught. That scenario might cause you a late runtime crash on a live environment.

Let's see the bytecode a bit and see if we get what we expect, i.e. a call to the keySet method that returns the KeySetView.

Open your generated class file with Java bytecode editor.



On line 13, this is quite obvious. When running with java 7 you don't get any error until line 13 is executed by jvm and it tries to find that method, which doesn't exist on java 7. So how can we avoid this with a bit of good practice (although, having in compile time classpath an API of a different version that the one on runtime is the most obvious mistake that needs fixing). But we want our code to be as safe as possible and work even in situations where simple API changes won't affect it.

Let's change the left hand side to the interface definition. The Map.

 import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class FunWithMaps {

    public static void main(String[] args) {
        Map map = new ConcurrentHashMap();

        map.put("1", 1);

        System.out.println(map.keySet().getClass());
    }
}

Before running it, make a build and see the bytecode. It's now a bit different.



As expected, the compiler now generated the bytecode according to the interface visibility of the method.
If you run it now with java 7 it will not fail and will output:
class java.util.concurrent.ConcurrentHashMap$KeySet

Switch run configurations to jre 8. Run again and you get what you expected:
class java.util.concurrent.ConcurrentHashMap$KeySetView


It's always a good practice to define your variables with the highest superclass or interface 
possible, especially if you are using an external API (which is pretty much always the case). 
Interfaces rarely change or at least they change less frequently from implementation code.


That should as well settle the argument of using on the left hand side the instantiating class 
in the variable definition or their superclass (interface they implement).

Sunday, August 30, 2015

Programming with the metric system - Draft ideas

In a paper called 'Software Development for Infrastructure' Bjarne Stroustrup presented the new features of C++11 with some interesting examples. The most fascinating one was derived from the NASA accident in September of 1999. The root of the accident was a mismanagement of the metric system units due to a poorly designed API that basically was relying on comments.

I'm currently working on a project that requires managing metric units correctly. The language used is Java. Also, I have developed an external, open source library, that provides the essential API for managing metric units. It's a very young project so it supports a very small range of metric units (only those needed by the bigger project) but it is sufficient to demonstrate the basic design principles for managing metric units.

You can find the library here.

Let's examine it's usage in a few examples. Let's say we want to keep track of velocity.

Actually, this is already provided in the library.

public final class VelocityUnit {
    public final DistanceUnit DISTANCE_UNIT;
    public final TimeUnit TIME_UNIT;

    public final double DISTANCE_VALUE;

    public VelocityUnit(DistanceUnit distance_unit, TimeUnit time_unit, double distance_value) {
        DISTANCE_UNIT = distance_unit;
        TIME_UNIT = time_unit;
        DISTANCE_VALUE = distance_value;
    }

    public VelocityUnit convert(DistanceUnit distance_unit, TimeUnit time_unit) {

        if (distance_unit == DISTANCE_UNIT && TIME_UNIT == time_unit)
            return this;

        double newTimeUnitWorthOfCurrentTimeUnit = 1/time_unit.convert(1, TIME_UNIT);

        double newTotalDistance = DISTANCE_VALUE*newTimeUnitWorthOfCurrentTimeUnit;

        double newDistanceValue = distance_unit.convert(DISTANCE_UNIT, newTotalDistance);

        return new VelocityUnit(distance_unit, time_unit, newDistanceValue);

    }


    public String getMetricSignature() {
        return DISTANCE_UNIT.signature + "/" + TIME_UNIT.signature;
    }

    public String toString() {
        return String.format("%.2f%s",DISTANCE_VALUE, getMetricSignature());
    }

}
In the constructor, the programmer passes the metric unit of the distance and the time
This implementation is quite simple, so it gets the difference in distance as a third parameter 
and it assumes the value of time is a single unit of whatever metric is passed. 
You can then convert it to use different distance units or time units accordingly. 

But that doesn't tell much about differences between two people, what about hiding the 
information from the third party developer? Let's see another example now of how the 
developers will get what they expect by interacting with a black box class. In the example below, 
the developer can put values in whatever metric unit they like and get it back in whatever 
format they like. The magic is by telling the class to explicitly work with only one metric unit.

package com.vaslabs.units.examples;

import com.vaslabs.units.DistanceUnit;

public class ExampleDistanceCalculation {

    private final DistanceUnit PREF_DISTANCE_UNIT = DistanceUnit.METERS;

    private double pointA;
    private double pointB;

    public ExampleDistanceCalculation() {

    }

    public void setPointA(double value, DistanceUnit distanceUnit) {
        pointA = DistanceUnit.PREF_DISTANCE_UNIT.convert(distanceUnit, value);
    }


    public void setPointB(double value, DistanceUnit distanceUnit) {
        pointB = DistanceUnit.PREF_DISTANCE_UNIT.convert(distanceUnit, value);
    }

    public double getDistance(DistanceUnit distanceUnit) {
        return distanceUnit.convert(DistanceUnit.PREF_DISTANCE_UNIT, (pointB - pointA));
    }
}
The ExampleDistanceCalculation will work with meters while the third party developers can 
choose their own metric system. For instance, you can have a sensor and some software that 
give you values in centimeters. You can have a class like the above as a middleware 
(with CM instead of METERS) and allow all the other developers to work on the metric unit of 
their preference. It is also useful when delivering to the userland, as users may have different 
preferences on metric units.