Abstract
null
has been the cause for countless troubles in the history of software development. Today, 'null
' and 'the billion-dollar mistake' are synonyms. Therefore some developers try to avoid null
by using techniques such as 'return zero instead of null
', the NullObject
pattern or the Optional/Maybe
pattern.
Should you use these techniques in your source code? Do they lead to more reliable software? Or is there a better solution?
These are the questions this article tries to answer.
Introduction
Many developers dislike null
.
Some hate null
.
null
is the reason for the infamous null pointer error (called NullPointerException
in Java).
Practice shows: The null pointer error is the most frequent bug occurring in popular programming languages.
Professor John Sargeant from the Manchester school of computer science puts it like this:
Of the things which can go wrong at runtime in Java programs, null pointer exceptions are by far the most common.
In Java, the NullPointerException
is the only exception that has deserved its own acronym: NPE. If you tell a Java programmer something like "This code is full of NPEs", he/she knows exactly what you are talking about.
null
has been the cause of:
-
billions of dollars of damages caused by software bugs
-
millions of hours spent by developers to prevent or repair null
pointer errors
-
frustration and bad headaches among developers, managers and users of software products
Tony Hoare, the inventor of null
, calls it a "billion-dollar mistake".
Therefore some developers try to avoid null
.
For example, they use 0
instead of null
in case of a number value, or their functions return an empty list or an empty string instead of null
.
Others try to get completely rid of null
with techniques like the NullObject
pattern or the Optional/Maybe
pattern.
We will have a look at these techniques later.
Now one might wonder: Why does the title of this article suggest we should love null
?
Let’s see!
In real life, we are all used to the notion of 'nothing' appearing in thousands of different ways.
It is easy to come up with examples even a child would understand:
More than an astonishing 99.999% of the universe is nothing - just empty space.
It is therefore more than evident that any programming language (or, more generally, any technique used to represent data) must provide a way to express the notion of 'nothing'.
In 1965, Tony Hoare had the brilliant idea to simply use the keyword (or symbol) null
.
Some languages use other keywords such as nil
, void
, none
or nothing
, but the concept is the same.
null
is indeed an incredibly important concept.
null
is what allows us to state things like:
-
Alice's birth date has not yet been entered in the database (alice.birth_date = null
)
-
No orders have yet been placed by this customer (customer.order_list = null
)
-
The operation succeeded without any errors (error = null
)
-
We don't know if there is beer in the fridge (fridge.beer = null
)
Before looking at the merits of null
, let's have a look at some simple source code.
|
Note |
In this article, I use Java in the source code examples. You might want to try out these simple examples in any other language you prefer. |
If Alice has an email address, we can write code like:
String email = "alice@company.com";
And if she hasn't an email address (or the address is unknown), we simply write:
String email = null;
Then we can check for the existence of an email address and take appropriate action:
if ( email != null ) {
System.out.println ( "Alice's email address is " + email );
} else {
System.out.println ( "Alice doesn't have an email address." );
}
Simple, elegant and practical!
So, what's the problem? Why does null
cause so many troubles?
It turns out that there is a very simple and unique reason for the many troubles with null
:
Sometimes, we (the programmers) forget to consider the fact that a value might be null
.
Yes, it is that easy:
if ( you_never_forget_to_check_for_null ) {
System.out.println ( "Bravo!!!" );
System.out.println ( "Enjoy and be proud of an application free of null pointer errors." );
}
Unfortunately, forgetting to check for null
happens often in practice. And that's the reason for so many null pointer errors, some of them leading to catastrophic consequences.
Suppose, for instance, that in the above source code example, we forget to check if Alice has an email address. We leave of the if
and simply write.
System.out.println ( "Alice's email address is " + email );
There are two important questions:
-
What should happen if email
points to null
at runtime?
-
What will happen?
There are at least 3 things that could happen:
-
The program simply ignores null
and prints:
Alice's email address is
-
The program prints:
Alice's email address is null
-
The program throws a NullPointerException
This is a trivial example of a null
handling situation. Nevertheless, the only way to know what will happen is to try it out because it really depends on the programming language and the compiler we use. Ask any experienced Java programmer and you will see that most of them (me included) will struggle to predict the actual behaviour without hesitation. I tried out the code with Java version 8 and was eager to see the result because I didn't have any idea. Here is the result displayed in the system console:
Alice's email address is null
One could argue that this is nice because the system clearly informs us that the email is unknown. But - most importantly - no NullPointerException
is thrown.
We'll come back to the question "What should happen?" in a while.
|
Note |
If we want to understand how the NPE is avoided in the above example, we have to look at the source code of the Java Development Kit (JDK). We won't go into the details of how the above string concatenation (using the operator +) works behind the scenes. But the interesting part is method valueOf in class java.lang.String :
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
As we can see, if the input value is null then the string "null " is used. That's why the above code displays:
Alice's email address is null
Note: You can yourself have a look at the JDK source code by unzipping file src.zip in your JDK's root directory and then opening file java/lang/String.java in a text editor. If you want more information about Java's + operator you might have a look at the top rated answer in the Stackoverflow question Concatenating null strings in Java.
|
We have just seen an example of a practice used to suppress the risk for a null pointer error.
A crucial question arises: Are practices like the above one - used to avoid null
pointer errors - to be recommended. Do they increase the quality and reliability of software? Should you use these techniques in your code?
Before discovering the answer, let's have a look at three other popular techniques aiming to 'avoid null
pointers'.
-
using zero instead of null
for numbers
-
the Null object
pattern
-
the Optional/Maybe
pattern
Using Zero Instead of Null
Suppose we are writing an on-line shop application that allows customers to buy products via internet. Suppose also that the shop owner must be allowed to enter a new product without entering a price, because the price is still unknown and will be entered later.
Which approach is better:
-
We don't allow null
for the price field because this eliminates the risk for a null
pointer error. If the price is unknown, we simply store the value zero (0) in the database.
-
If the price is unknown, we store null
in the database and open the door for null
pointer errors.
The answer becomes obvious if we look at an excerpt of an excellent comment made by user Jay in the Stackoverflow question
Is it better to return null or empty collection?
Many programs are unable to distinguish "don't know" from blank or zero, which seems to me a potentially serious flaw. For example, I was shopping for a house a year or so ago. I went to a real estate web site and there were many houses listed with an asking price of $0. Sounded pretty good to me: They're giving these houses away for free! But I'm sure the sad reality was that they just hadn't entered the price. In that case, you may say, "Well, OBVIOUSLY zero means they didn't enter the price -- nobody's going to give a house away for free."
But the site also listed the average asking and selling prices of houses in various towns. I can't help but wonder if the average didn't include the zeros, thus giving an incorrectly low average for some places. i.e. what is the average of $100,000; $120,000; and "don't know"? Technically the answer is "don't know". What we probably really want to see is $110,000. But what we'll probably get is $73,333, which would be completely wrong. Also, what if we had this problem on a site where users can order on-line? (Unlikely for real estate, but I'm sure you've seen it done for many other products.) Would we really want "price not specified yet" to be interpreted as "free"?
User Jay also says:
I've often been frustrated by systems that cannot distinguish between zero and no answer. I've had a number of times where a system has asked me to enter some number, I enter zero, and I get an error message telling me that I must enter a value in this field. I just did: I entered zero! But it won't accept zero because it can't distinguish it from no answer.
How would the web shop behave if we used the second approach, that is to say we use null
for product prices not yet entered?
The risk of displaying a price of zero and buying a product for free is instantly eliminated.
We would be required to test for null
and display a message like "price not yet available" or "please call us". If we forget to do this, then a null
pointer error is thrown. This is undesirable, but much better than letting the user buy a house for free, or - why not - the set of all free houses available on the website.
The risk of calculating a wrong average price is also instantly eliminated.
Again, we would be required to test for null
and ignore products with null
prices. If we forget it, a null
pointer error appears as soon as there is a price with null
in the database. But this is of course much better than silently delivering wrong results.
We could easily find more examples of software defects caused by using zero (0) instead of null
.
Moreover, we have to be aware that the same problems exist with similar approaches such as:
In all cases, the conclusion is obvious:
Instead of using a non-null value to represent 'no data available' it is (in most cases) much better to use null
.
Yes, we increase the risk for null pointer errors. But we decrease the risk for outcomes that are worse, such as delivering wrong results without any error message and other unacceptable or catastrophic situations - for example letting users buy houses for free.
If our application crashes because of a null
pointer error, our customer is unhappy.
If our application delivers wrong results or does other stupid or dangerous things, our customer is very unhappy.
'unhappy' is better than 'very unhappy'.
Hence, we should embrace null and make it our friend and ... love it!
So, what can we say about our introductory example in which we saw that the following code:
String email = null;
System.out.println ( "Alice's email address is " + email );
produces this result:
Alice's email address is null
In this specific case, the result might be acceptable. But the language/compiler shouldn't decide this. As we have seen already, it would be much better to throw a NullPointerException
because this would inform the programmer quickly about a problem with the code. null
is virtually always a special case and as such it should explicitly be treated as a special case in the source code.
Even in this simple case, the correct solution:
if ( email != null ) {
System.out.println ( "Alice's email address is " + email );
} else {
System.out.println ( "Alice doesn't have an email address." );
}
is superior, because saying...
Alice doesn't have an email address.
...is obviously better (more understandable - especially for non-programmers) than saying:
Alice's email address is null
|
Note |
In many cases, Java does throw a NullPointerException (or another exception, like IllegalArgumentException ) if an input value is null .
For example, let's compare two alternatives:
The following code:
String s = "foo";
s = s + null;
System.out.println ( s );
doesn't throw a NullPointerException , but displays:
foonull
However, the behaviour is different if, instead of using the + operator, we use method concat in class String :
String s = "foo";
s = s.concat ( null );
System.out.println ( s );
Now a NullPointerException is thrown, because method concat doesn't accept null as input.
|
The Null Object pattern takes the previously described practice - used for simple scalar values - a step further by applying the same idea to composite objects, that is to say objects composed of several data fields and operations.
Instead of using null
, a special non-null
object is used to represent 'there are no data', thus eliminating the risk for null
pointer errors.
Let's look at an example.
Suppose we have the following interface in Java:
import java.io.PrintWriter;
public interface ICustomer {
public String getName();
public void writeOrders ( PrintWriter writer );
}
A standard implementation of this interface would look like this:
import java.io.PrintWriter;
public class Customer implements ICustomer {
private String name;
public Customer ( String name ) {
this.name = name;
}
public String getName() { return name; }
public void writeOrders ( PrintWriter writer ){
writer.println ( "List of orders:" );
}
}
And here is another implementation that applies the Null
object pattern:
import java.io.PrintWriter;
public class NullCustomer implements ICustomer {
public NullCustomer () {}
public String getName() { return ""; }
public void writeOrders ( PrintWriter writer ) {
}
}
Now, instead of using null
, a shared instance of NullCustomer
would be used each time a customer doesn't exist. For example, look at the following method signature:
public static ICustomer getCustomerByName ( String name );
This method would return an instance of NullCustomer
(instead of returning null
) if there is no customer with the given name in the database.
Because the basic idea of the null
object pattern is exactly the same as for practices like 'use zero instead of null
', the null
object pattern suffers from the same inconveniences and dangers described before: it easily leads to defects that are worse than a null
pointer error.
In a comment to an interesting article with the title No more excuses to use null references in Java 8 (and with comments worth reading) user Henk De Boer eloquently describes what can happen:
I do agree with especially Erwin, that this style of programming does carry a risk with it. If you're not careful, you really do obscure the cause of problems. In absence of no data (where not allowed), your code will simply keep running until all operations that you programmed are done and then things stop.
The result of the above is that simply "nothing has happened". This can be nice (don't bug the user or programmer with nasty errors), but you can actually be in a worse situation. The action that the user thought has happened, actually hasn't happened at all. Yet there is no error and no indication anywhere that something has gone wrong.
Imagine a hotel booking example. The user should have entered a city, but forgot it. That value is now null
, but since we're functional advocated, we hate null
and have None. All our code still runs. We locate the hotel correctly (it returns another None, no error) and we book the None hotel "correctly" (e.g. our operation is a noop).
When the user actually arrives at the hotel, with the wife and children, he finds no room has been booked at all. The hotel is completely full, it's 11:00 PM and there are no other hotels nearby. I'm sure the user is not going to be happy, but hey, we didn't had to deal with a NPE!
I'm slightly exaggerating perhaps, but this stuff DOES happen in "null friendly" languages.
The Null
Object pattern might be useful in some rare situations, but we should be very careful before deciding to use it. For more information, refer to the Wikipedia article and look at the Criticism section which starts like this:
This pattern should be used carefully as it can make errors/bugs appear as normal program execution.
It is also interesting to note that appearances of null objects are virtually non-existent in real life. Such objects would be of little use and lead to funny, bizarre or even dangerous behaviour. Imagine a NullEmployee
in real life: You pay him a salary but he doesn't do anything. Or: You buy and eat plenty of NullFood
, but it doesn't have any taste and you still feel hungry.
The final conclusion is obvious:
Don't use the Null Object pattern just to avoid null pointer errors.
Returning an empty collection instead of 'null'
There is a popular and widely accepted advice in the world of software development that says:
You should always return an empty collection instead of 'null'!
Although many programmers apply this rule, its rationale has to be questioned. There is a lot to say about it - you can read more in the follow-up article Is it Really Better to 'Return an Empty List Instead of null'?, divided into four parts:
The Optional/Maybe Pattern
The basic idea of the Optional/Maybe pattern is this:
Instead of providing a non-null
value or null
, always provide a non-null
container object that either contains a value or doesn't contain a value.
You can also think of this container as a list having 0 or 1 elements.
This pattern is used in different languages, with different names for the container type. For example, in Java version 8, in Google's Guava library and in Swift it is called Optional
, in Scala it is called Option
and Haskell uses Maybe
as name.
To understand how it works, let's have a look at a stripped version of the JDK source code of class Optional
in Java version 8:
package java.util;
public final class Optional<T> {
public static<T> Optional<T> empty()
public static <T> Optional<T> of(T value)
public static <T> Optional<T> ofNullable(T value)
public boolean isPresent()
public T get()
}
The first three static
methods are used to create an Optional
object:
-
empty()
is used to create an empty container - the pendant to null
.
-
of(T value)
is used to create a container with a non-null
value.
-
ofNullable(T value)
is used to create a container with a nullable value (i.e. value
can be null
or non-null
).
isPresent()
is used to check if the container holds a non-
null
value.
get()
is used to retrieve the non-null
value in case isPresent()
returns true
.
|
Note |
There are more methods in Java's Optional class, but they are irrelevant for the discussion in this article. |
Now let's have a look at a simple example of how Optional
could be used in practice.
Suppose the following method exists - used to find a customer in a database by the customer's name:
public static ICustomer findCustomerByName ( String name ) {
ICustomer customer = null;
}
Obviously, this method can lead to a NullPointerException
because null
is returned if a customer with the given name doesn't exist in the database.
To avoid this, we can return a non-null
Optional<ICustomer>
(instead of a nullable ICustomer
) as follows:
public static Optional<ICustomer> findCustomerByName ( String name ) {
ICustomer customer = null;
if ( customer != null ) {
return Optional.of(customer);
} else {
return Optional.empty();
}
}
As we can see, this method never returns null
.
If the customer is found, it is stored in the Optional
container object.
If the customer doesn't exist, an empty Optional
container object is returned.
We would use the new version of findCustomerByName
like this:
Optional<ICustomer> customerOptional = findCustomerByName ( "Albert" );
if ( customerOptional.isPresent() ) {
ICustomer customer = customerOptional.get(); System.out.println ( "Customer found: " + customer.toString() );
} else {
System.out.println ( "Customer not found." );
}
If the else
branch is not needed we can use ifPresent()
and simplify the code:
Optional<ICustomer> customerOptional = findCustomerByName ( "Albert" );
customerOptional.ifPresent ( c -> System.out.println ( "Customer found: " + c.toString() ) );
There is an undeniable advantage of using the Optional pattern.
By looking at the method signature, we can see that an Optional<ICustomer>
is returned (instead of simply an ICustomer
). This makes it clear that the method returns with an empty Optional
in case of 'no data found'. Compare this with the ambiguity of the first version of the method which doesn't use an Optional
. In that case, without further information provided by a comment or an annotation, the API doesn't tell us what happens in case of 'no data found'. null
could be returned, or a Null Object
could be returned or an exception could be thrown. So, knowing that an Optional
is returned, we also know that we have to check the presence of data with method isPresent()
, before retrieving the value with method get()
.
Another advantage of the Optional/Maybe pattern is that you can use it in any programming language. If your preferred programming language doesn't provide Optional/Maybe components in the standard libraries then you can simply create your own version and customize it to your specific needs.
Unfortunately, there are a number of considerable inconveniences with this pattern.
|
Note |
The following observations are related to class java.util.Optional in Java 8. They don't necessarily apply to variations of this pattern in other libraries or other programming languages. |
-
A number of misuses cannot be checked and prevented by the compiler and therefore exceptions can still be thrown. For example:
-
If we call get()
without first checking the presence of data with isPresent()
then we get a NoSuchElementException
if there is no value present. Here is a simple example:
Optional<String> o = Optional.empty();
String name = o.get();
This is similar to the situation of forgetting to check for null
and then getting a NullPointerException
. However, an advantage is that the NoSuchElementException
is thrown immediately when get()
is called. On the other hand, if we retrieve a null
value from a method then a NullPointerException
doesn't occur immediately. It occurs (maybe much later) when a method is called on the null
value. This can make the code more difficult to debug.
-
If a method is declared to return an Optional
, nothing prevents the programmer who implements the method from (involuntarily) returning null
instead of returning a non-null
Optional
.
Example:
public static Optional<ICustomer> findCustomerByName ( String name ) {
return null;
}
-
If we accidentally construct an Optional
by calling method of()
with null
as input, we get a NullPointerException
.
Example:
String name = null;
Optional<String> o = Optional.of ( name );
-
The compiler cannot force programmers to use Optional
in their own methods. It is up to the programmer to decide whether he/she returns an Optional
or simply sticks to returning null
in case of no data.
-
Existing standard Java libraries cannot be retrofitted to use Optional
because this would break backwards compatibility.
-
Using Optional
is a bit cumbersome and requires more code.
Compare the traditional version of method findCustomerByName
with the version that uses Optional
:
Traditional version:
public static ICustomer findCustomerByName ( String name ) {
ICustomer customer = null;
return customer;
}
Version that uses Optional
:
public static Optional<ICustomer> findCustomerByName ( String name ) {
ICustomer customer = null;
return Optional.ofNullable(customer);
}
Using findCustomerByName
also requires more code to write and read.
Traditional code:
ICustomer customer = findCustomerByName ( "Albert" );
if ( customer != null ) {
System.out.println ( "Customer found: " + customer.toString() );
} else {
System.out.println ( "Customer not found." );
}
Code that uses Optional
:
Optional<ICustomer> customerOptional = findCustomerByName ( "Albert" );
if ( customerOptional.isPresent() ) {
ICustomer customer = customerOptional.get();
System.out.println ( "Customer found: " + customer.toString() );
} else {
System.out.println ( "Customer not found." );
}
-
Using Optional
consumes more memory and slows down execution because of the additional container (wrapper) objects that need to be created. In practice this might be totally negligible or a real performance bottleneck in exceptional cases.
On the other hand null
is always cheap in terms of memory and speed.
-
Class Optional
is not serializable.
-
Class Optional
cannot be used with primitive values. However, there are three specialized classes called OptionalDouble
, OptionalInt
and OptionalLong
that can be used with double
, int
and long
values.
For more information, refer to this article and don't miss the FAQ and reader comments.
We can conclude:
Under certain conditions the Optional/Maybe
pattern in Java helps to prevent null pointer errors. However, some misuses cannot be prevented by the compiler and the pattern requires more verbose code. Therefore a good amount of discipline is required. Another issue is that the wrapper objects create overhead at runtime.
In any case, the Optional/Maybe
pattern doesn't lead to 100% null-safety.
|
Note |
Some functional programming languages (the most prominent one being probably Haskell) have been built from ground up with the Maybe monad in mind (which is not the case in Java). In these languages Maybe is consistently used in the libraries (in Java this is done only in new APIs introduced in version 8). In idiomatic source code, pattern matching (which doesn't exist in Java) is used to check whether there is a value or not, and the compiler emits a warning or error when the code doesn't check for 'no data'.
Hence, the usefulness of the Option/Maybe pattern depends on how it is implemented and used in a language. Moreover, the Option/Maybe type (or monad) typically provides other useful features such as function composition and map/reduce functionality for collections. However, the compiler cannot protect against misuses and null pointer errors can therefore still occur.
Yin Wang puts it like this in an interesting post Null reference may not be a mistake:
"After years of using languages both with null pointers (e.g. Java) and without them (e.g. Haskell), I found that null pointers are much easier and more natural to use than its counterparts (e.g. Haskell’s Maybe type)."
|
So, Is There A Better Solution?
We saw before that the trouble with null
is this:
We sometimes forget to check for null
in our code and take appropriate actions. This leads to potential null pointer errors.
Hence it is easy to come up with the ideal solution: If we sometimes forget to check for null
then we simply need a compiler that forces us to check for null
. If we forget to check for null
or are too lazy to check for null
or wrongly assume that a value will never be null
at runtime then the compiler should nag and refuse to create executable code and force us to fix the code until all potential null
pointer errors have been completely eliminated.
Is it technically feasible to build such a compiler?
Yes, it is!
It is indeed possible to design a language and implement a compiler with so-called null-safety built in.
It is possible to achieve 100% null-safety.
This is certainly an ultimate goal worth to be achieved because it means that the most frequent bug in applications will definitely be eliminated - at no costs for the developers. The benefits are obvious: less time and resources needed to debug and maintain code, more reliable software delivered to customers, and more satisfaction among all people involved in the project.
And it means we are back to our simple syntax for null
checks:
if ( expression != null ) {
} else {
}
If we forget the null
check the compiler barks and gently reminds us to write reliable code. We are always on the safe side. We don't need any of the unsatisfactory 'avoid null
' practices we saw before. And we won't suffer from their inconveniences and dangers. We enjoy null
-safe code.
Are there any programming languages with compile-time null-safety built-in?
I made some research and here are my observations:
|
Note |
The following observations are incomplete and based on information I found at the time of writing (May 2014). If you see any omissions or outdated information, then please send me a comment and I will make an update. |
-
Among the 20 most popular programming languages listed at the Tiobe index (Basic, C, C++, C#, Java, JavaScript, Lisp, Objective-C, Perl, PHP, Python, Ruby and more) none of them has null-safety built-in.
- The following 4 languages have
null
-safety natively built in:
PPL is an object-oriented, JVM-targeted programming language I created and is still a work in progress. PPL has been specifically designed from the ground up to help writing more reliable code in less time. This goal should be attained by embedding effective Fail-fast! features natively into the language and libraries. A maximum of bugs should be caught at compile-time or else as early as possible at runtime.
Of all the Fail-fast! features natively built into PPL (such as null-safety, Design by Contract, immutable objects, etc.), I consider null
-safety to be the most effective feature contributing to more reliable code - for the reasons explained in this article.
The fundamental principle for achieving null
-safety in PPL is this:
PPL makes a distinction between nullable and non-nullable types.
By default all values are non-nullable. Non-nullable values cannot be null
at runtime and therefore can't lead to a null pointer error.
Values that are allowed to be null
at runtime must explicitly be declared as nullable
in the source code. The compiler requires that nullable values must be checked for null
before an operation can be executed on them. Thus null
pointer errors can't occur at runtime.
There are specific instructions and operators to support writing compact and readable null
-safe code.
To see how PPL source code looks like, let's rewrite our initial example in PPL.
The following code asks the user to enter an email address in the system console. If he/she doesn't enter a value (just hits the <Enter> key) then email
will be null
. The second instruction checks for null
and prints an appropriate message.
const nullable string email = system.console?.ask_string ( "Please enter Alice's email address: " )
if email is not null then
system.out.write_line ( """Alice's email address is {{email}}""" )
else
system.out.write_line ( "Alice doesn't have an email address." )
.
In the above code:
-
The const
keyword declares a script constant (i.e. a variable whose value cannot be re-assigned) named email
of type string
.
-
The nullable
keyword states that email
is allowed to be null
. By default (if nullable
is omitted) null
is not allowed for object references in PPL.
-
A Java application can be run with or without a system console. Therefore we must use the so-called safe navigation operator (?.
instead of .
) after system.console
. If no system console is available then ask_string
will not be executed and email
will be null
.
Note: An improvement in the above source code would be to print an error message if no console is available.
-
The check for null
(if email is not null
) is required. If we omitted it, the compiler would generate an error in the instruction that prints out the email address. This is the key to compile-time null
-safety.
-
The expression
"""Alice's email address is {{email}}"""
uses PPL's
triple quoted string literal which allows us to embed expressions in a
string
literal between
{{
and
}}
.
|
Note |
If you want to try out this example yourself, proceed as follows after installing PPL:
|
Instead of using an if is null
instruction we could also use the if_null:
operator which is similar to the so-called Elvis operator (?:
) in other languages:
const nullable string email = system.console?.ask_string ( "Please enter Alice's email address: " )
system.out.write_line ( """Alice's email address is {{email if_null: "not yet defined."}}""" )
Now, if email
is null
, the output will be:
Alice's email address is not yet defined.
There would be much more to say about null
-safety in PPL. For more information and examples, please refer to chapter Null safety in PPL's on-line manual.
There is, however, one important point to stress.
Look at the following source code:
var nullable string email = "alice@company.com"
if email is not null then
system.out.write_line ( """Alice's email address is {{email}}""" )
else
system.out.write_line ( "Alice doesn't have an email address." )
.
We can immediately see that in this case, the check for null
is completely superfluous, because at runtime email
contains a non-null
value and the else
branch will therefore never be executed.
The PPL compiler is able to see this too. Internally it uses static
code analysis and keeps track of the null
-state of all object references.
Therefore, we have to omit the if
in the above example and simply write:
var nullable string email = "alice@company.com"
system.out.write_line ( """Alice's email address is {{email}}""" )
This peculiarity also works fine in non-trivial cases such as a variable which is changed several times in complex nested if
s and loops, because the compiler knows the null
-state of any object reference in any location of the source code.
It turns out that this distinguishing feature is really useful in day to day programming because situations like the above one appear often in source code. Null
-checks in PPL are only required if the null
-state of a value cannot reliably be determined at compile-time - for example in case of a value read from an external resource at run-time. Hence, we are relieved from a lot of unnecessary checks for null
.
More PPL source code examples and a demonstration of how null-safety helps to write more reliable code in less time can be found in the last part of this article series.
null
is an extremely important concept in every programming language.
Although null
has caused plenty of bugs, often with severe consequences, we have to be very careful if we use techniques to avoid null
(for example the NullObject
pattern or the Optional/Maybe
pattern), because doing so can lead to outcomes that are worse than null
pointer errors.
The best solution is to use a programming language that provides compile-time null
-safety and good support for handling null
values in the source code. You can then use null
as often as needed with no fear of null
pointer errors. You enjoy 100% null
-safety without any drawbacks, you are more productive and you deliver more reliable software.
Article History
2014-06-19: first version
2014-07-22: added chapter 'Returning an empty collection instead of null'; added F# to list of languages with support for null-handling; added usage of method 'ofNullable()' in Java Optional example
2014-08-20: added note at end of chapter 'The Optional/Maybe pattern'; plus some other small changes in this chapter; added Haskell to list of languages with support for null-handling
2014-11-18: added quote at end of chapter 'The Optional/Maybe pattern'; added links to follow-up articles (part 1 to 4); adapted syntax of PPL examples