The Darien Project

The Darien Project for Java code makes handling Java code failure easy. It’s well-known that error-handling code is buggy [1]. Using the library allows you to easily focus on the failure path to build better, working code more quickly.

You can ask questions at info@darien-project.org.

Quick Start

The call to m.getPage below may fail in two ways: its internal HTTP GET might return a status value outside the 200 to 299 success range, or getPage might have encountered an exception. Either way, the failure path will be executed.

 1public static void main(String[] args) {
 2    Main m = new Main();
 3
 4    // Gets a webpage as a String and returns it wrapped inside an S type
 5    S obj = m.getPage("https://www.example.com");
 6
 7    if(obj.eval()) {
 8        String page = (String) obj.unwrap();
 9        
10        System.out.println("The success path");
11    } else {
12        System.out.println("The failure path");
13    }
14}

Darien Library tool support will write the code invocation for you, the if, else, and switch, you see below so that you can focus on what you need to.

We handle the two failure cases like this (the implementation of getPage is defined in getPage):

 1public static void main(String[] argv) {
 2    Main m = new Main();
 3
 4    // Returns a FailureValue, wrapping 404
 5    S obj = m.getPage("https://www.example.com/nosuchpage");
 6
 7    if(obj.eval()) {
 8        System.out.println("Success");
 9    } else {
10        switch (obj) {
11            case FailureValue fv -> System.out.println(fv.getValue());
12            case FailureException fe -> System.out.println(fe.getException());
13            default -> System.out.println("As currently written, not possible.");
14        }
15    }
16}

The switch on page above is an example of pattern matching, released in Java SE 17 (https://openjdk.org/jeps/406) [2].

Running the above, attempting to retrieve https://www.example.com/nosuchpage, results in a 404 being returned, wrapped in a FailureValue.

As below, when getPage is passed https://www.cannotfindthisdomain.com, an instance of FailureException is returned.

 1   public static void main(String[] argv) {
 2       Main m = new Main();
 3
 4      // Returns an instance of FailureException
 5       S obj = m.getPage("https://www.cannotfindthisdomain.com");
 6   
 7       if(obj.eval()) {
 8           String page = (String) obj.unwrap();
 9           System.out.println("Success");
10       } else {
11           switch (obj) {
12               case FailureValue fv -> System.out.println(fv.getValue());
13               case FailureException fe -> System.out.println(fe.getException());
14               default -> System.out.println("As currently written, not possible.");
15           }
16       }
17   }

All failure-describing types (FailureValue, FailureException and others) are subtypes of F (see The Detail below) which in turn is a subtype of S. S’s eval returns true, whereas eval on F and its subtypes returns false. Within the failure path (the else), the appropriate failure instance (fv or fe) is created via the type switch. That is it. You are done.

This approach focuses on the different kinds of failure, cleanly separating all cases, and tool supports write the handling code.

The Detail

S is a type that wraps an instance and defines two methods. unwrap returns the instance and eval returns true. Generics are not used. This is explained in Generics.

1public interface S {	
2    public boolean eval();
3    public Object unwrap();
4}

F is the root of all failure-describing types:

1public interface F extends S {
2}

All subtypes of F override eval to return false.

The failure-describing types below (such as FailureValue) are wrappers around an instance associated with the failure, such as a value or exception.

FailureValue is defined as:

1   public interface FailureValue extends F {
2       public Number getValue();
3   }

FailureValue wraps a Number. This type is useful when an operation has failed and a code value associated with the failure is to be returned, as in the HTTP GET 404 above.

FailureException wraps an exception in the same way:

1   public interface FailureException extends F {
2       public Exception getException();
3   }

getPage

When url is https://www.cannotfindthisdomain.com, getPage returns a FailureException that wraps the thrown java.net.UnknownHostException. If url is https://www.example.com/nosuchpage, getPage will return a FailureValue that wraps the number 404.

 1   public S getPage(String url) {
 2       try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
 3           final HttpGet httpget = new HttpGet(url);
 4   
 5           Result result = httpclient.execute(httpget, response -> {
 6               return new Result(response.getCode(), EntityUtils.toString(response.getEntity()));
 7           });
 8   
 9           if(result.status_code >= 200 && result.status_code <= 299) {
10                   return new Success(result.page);
11           } else {
12                   return new FV(result.status_code);
13           }
14       } catch(java.io.IOException ioe) {
15               return new FExp(ioe);
16       } catch(Exception e) {
17               return new FExp(e);
18       }
19   }

Although getPage looks perfectly reasonable, url may be null. Or url should be rejected if it does not use SSL (https), as implemented below.

 1   public S getPage(String url) {
 2       if(FailureUtils.oneIsNull(url)) {
 3           return FailureUtils.theNull(url);
 4        }
 5
 6       if(new URL(url).getProtocol().equals("http")) {
 7           return new FAFC(url));
 8       }
 9
10       try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
11           final HttpGet httpget = new HttpGet(url);
12   
13           Result result = httpclient.execute(httpget, response -> {
14               return new Result(response.getCode(), EntityUtils.toString(response.getEntity()));
15           });
16   
17           if(result.status_code >= 200 && result.status_code <= 299) {
18                   return new Success(result.page);
19           } else {
20                   return new FV(result.status_code);
21           }
22       } catch(java.io.IOException ioe) {
23               return new FExp(ioe);
24       } catch(Exception e) {
25               return new FExp(e);
26       }
27   }

Note: Result is a static class defined in the same class as getPaage that passes the response code and the retrieved webpage from execute so it can be assigned to result.

1   private static class Result {
2       public final int status_code;
3       public final String page;
4
5       public Result(int i, String str) {
6           this.status_code = i;
7           this.page = str;
8       }
9   }

Generics

Types S and F do not use generics. This means that you must explicitly cast the result of unwrap. A generic S<T> would remove the need for the cast. However, in the failure case (which has more types), you would be required to enter the generic type for the success case, e.g., FailureValue<String>, which is redundant as failure types are containers for failure objects. Primarily, generics have not been used to ensure code brevity.

Using Interfaces

You will note that S, F, and all the failure-describing types, are Java interfaces. You use these types when using the library, as a consumer, as in the main methods in Quick Start above.

When you produce success and failure cases, you use an implementation class of these types, as in getPage (such as the class Success).

As an engineer, you reason about success and failure and how to handle these cases using the types. You give these types concrete meaning at run-time by using the classes in org.darien.types.impl. In this code design, classes are purely a mechanism for expressing code and its reuse.

Focusing on Failure Leads to More Robust Code

By focusing on failure with the above approach, we see that:

  1. Any method parameter can cause your code to fail

  2. All code paths are terminated at a return

  3. Any search code can fail

The Darien approach is to check parameter values for null, returning an appropriate failure instance. The library supports you here with its calls to FailureUtils.oneIsNull and FailureUtils.theNull.

For point 2., the Darien approach is to return exceptions wrapped in a FailureException. This style is preferred over throwing an exception because where an exception is caught might be a long way from where it is generated, reducing options for addressing the issue. However, adopting this style is a matter of preference.

All code that searches for something (or that looks something up or relies on something being present) can fail when the item assumed to be there is absent. To highlight this, the following extracts the right-hand side of a string containing a hyphen of the form “lhs-rhs”.

1   private String rhs(String input) {
2       return input.split("-")[1];
3   }

If input is hyphen-ated, rhs will return ated. But if input is hyphenated, an ArrayIndexOutOfBoundsException will be raised. This code handles that failure case:

 1   private S rhs(String input) {
 2       if(FailureUtils.oneIsNull(input)) {
 3        	return FailureUtils.theNull(input);
 4        }
 5
 6       if(input.indexOf("-") == -1) {
 7         return new FailureValue(-1);
 8       } else {
 9         return new Success(input.split("-")[1]);
10       }
11   }

Other Darien Project Documentation

  1. Using the Darien Library

  2. Porting your code to the Darien Library

Resources