Porting your code to the Darien Library
It’s really straightforward. Let’s assume you have the following code:
1public String getFile(String filename) throws FileNotFoundException {
2 String line;
3 StringBuilder resultStringBuilder = new StringBuilder();
4
5 try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename)))) { // file opened here
6 while ((line = br.readLine()) != null) {
7 resultStringBuilder.append(line).append("\n");
8 }
9 } catch (IOException e) {
10 e.printStackTrace();
11 }
12
13 return resultStringBuilder.toString();
14}
This is a typical Java method that takes a parameter, returns a result, and may throw an exception.
As we know, parameters can be null. In this case, even if not null, filename
may indicate a file that does not exist or is inaccessible, leading to some kind of Throwable
.
Looking at the method signature, it appears that if filename
refers to a non-existent file, a FileNotFoundException
would be thrown out to the calling code. However, in this case, that can never
happen because FileNotFoundException
is a subclass of IOException
and the catch
will handle it. If this occurs, the code prints the stack trace and returns an empty string as the string builder
has not had any data appended to it. This may or may not be the intended behaviour. But it gets worse.
The method’s behaviour is ambiguous. We cannot tell the difference between a file that does not exist and one that does exist but is zero length. One solution is to explicitly catch the
FileNotFoundException
and re-throw it so that the calling code is made aware that the file cannot be found.
Moving your code to the Darien Library
There are four areas to consider:
Method parameters that are null
Method parameters that fail a test that could cause your code to fail
Exceptions or errors that are raised
What the method returns
Looking at the above code:
filename
may be nullA valid filename may refer to a file that does not exist
An
IOException
that is not a file not found issue may occur, e.g., in between the file being successfully opened at thetry
on line 5 above and the contents being read withreadLine
, the file may become unavailableThe code either returns an empty string or the contents of the file
For point 3., it is readLine
that might throw an IOException
and the number of ways an IOException
might be raised is varied. For example, the drive the file resides on — such as a USB thumb drive — could be removed from the system. Or an external process deletes the file.
Re-writing the code
1public S getFile(Path filename) {
2 if(FailureUtils.oneIsNull(filename)) {
3 return FailureUtils.theNull(filename);
4 }
5
6 File file = filename.toFile();
7 if(!file.exists()) {
8 return FailureUtils.theFalse(file.exists());
9 }
10
11 String line;
12 StringBuilder resultStringBuilder = new StringBuilder();
13 try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
14 while ((line = br.readLine()) != null) {
15 resultStringBuilder.append(line).append("\n");
16 }
17 } catch (IOException e) {
18 return new FExp(e);
19 }
20
21 return new Success(resultStringBuilder.toString());
22}
Line two prevents filename from being
null
Line seven tests whether the file exists
Line 18 wraps an
IOException
into a returned value. If an empty string is returned at line 21, the caller can be confident the file was zero length, removing our ambiguityLine 21 wraps the file contents
For point 2., if the file exists at line 7 but does not at line 13, an IOException
will be wrapped and returned inside a FailureException
type.
An additional case should be considered. Some of a file could have been read into resultStringBuilder
when an IOException
is raised. If this is the case, the rewrite ignores
the partially read file by returning the failed exception object at line 18. If this partially read file should be passed back, an instance of the type FailurePartialResult
can be used.