#oops&java record
Explore tagged Tumblr posts
Text
Don't use null! Null is evil!
Often called the "billion-dollar mistake", the null symbol is a tremendous foe when programming, if mishandled.
The following article shows a definition of nulity, why it's bad, when can it be good, and what options do we have to avoid spending too much time handling nullity in the software we write.
What is nulity?
Without getting too philosophical about it, nulity represents the ability of a programming language to let us express not only the absence of a record or value, but rather the absence of definition behind a symbol. Something that is null is something that doesn't exist, doesn't exist yet, is undefined, or is invalid in some manner.
In other words, nulity in programming is an implicit way to establish nothingness, and by implication, whether the result of an operation or any given memory state is valid or not.
If something is null, we assert, such a something doesn't exist, or is not even defined to begin with, or is the result of an operation that has failed.
Do not conflate nulity with emptiness. Note, for example in a Java context, null is different from symbols like 0 or "", a String literal representing an object that has no character information associated to it, even though they may seem the same to the naive or the newbie.
It's a good thing that we have null, in a way, because the concept of nulity is natural to life. Think of the TP that you put next to your toilet.
Having an empty roll present is not the same than having no roll at all, because you can assess that a roll is empty if the cardboard tube is there, but you cannot make such an assessment on the length of the toilet paper remaining if there's no roll to begin with!
Keep this in mind: if you try to access information on something that's not existent, then you have a problem.
And why is this a problem in software? Because it gives us an easy way to shoot ourselves in the foot at every step of the million thousand step road we traverse.
Why is nulity bad?
The problem with including a symbol to represent nulity in programming languages is that it takes extra intellectual effort to decode its meaning in your code.
null is also global state, and keeping global state in your systems, unless you have a tremendously good reason to do so is usually a huge red flag that you need to redesign.
Since the meaning of a null isn't tacit and explicit, nor typed, when you use null in your programs, then other programmers have to take additional time and care to read through your code and understand the implications of nulity and mitigate them when working with your code. These can be a dime a dozen, dependent on context on one of the thousands of different parts of their systems.
Why is this noisy? Why is this bad? Why do I hate it? Well, NullPointerExceptions are the bread and butter of debugging. All of us have run into stupid, unexplainable, I-have-no-idea-why-this-is-happening NullPointerExceptions when coding. All of us have seen a web application error out with NullPointerException on the browser. So, null enables shitty code craftsmanship because some times it's easy to forget when a variable can be null. Simple as that. It makes for software that doesn't fail gracefully and that isn't resillient. Hundreds of additional engineering hours have to be poured into software to make it null-safe.
This isn't good form. We can make the programmer's job easier so they can focus on creating well-thought-out software that's a joy to use rather than having them write if(str == null || str.length() == 0) for the umpteenth time. We should develop services that don't ping us at 5 AM on Saturday because of an NPE provoked by a transient outage on a database server. We can do better.
How do different languages use nulity?
Let's make a brief pause to explore how different programming languages allow us to use different representations of nulity:
Traditional ANSI C has a macro NULL, referencing the type void *. Hence, there's no null in the same sense that more modern languages use, which makes it even less memory-safe (lol). The interpretation that we can give to this macro is that it's related to the concept of a null pointer, which is a pointer that points to nothing in memory (void*), thus, conveying the meaning that the pointer points to an invalid value, that is, points to nothing. An area undefined in memory in brute.
Memory-safe C-likes like Java have a standard null symbol, usually as a reserved word by itself. As we said, Java has null, Ruby has nil and Python has the singleton object None. PHP has the constant NULL. C# also has null. Most JVM-derived languages include the traditional java null in its syntax.
Javascript, funnily enough, has two forms of null: null and undefined. undefined means that a variable hasn't been assigned a value to, yet, but has been declared, and null is an assignment value that indicates nulity. Marvelous!
Scala, presents many different incarnations of it: Null, which is a Scala trait which can be applied to functions or objects, null which is a translation of the Java null, Nil, which is a zero-length list, Nothing, which is a trait, and None, which is a subclass of the Option type, together with the Some type (more on that in a moment) and can be used as a return value and explicitly helps deal with undefined symbols avoiding null-pointer exceptions. None is what is most commonly seen in Scala software.
Go nils are similar to C null-pointers. Idiomatically, they are actually Interfaces meant to be used as the zero-value of a struct, or value otherwise. That is, nil in Go doesn't work like null in other languages in the sense that it doesn't imply invalidity but rather only means uninitialization. In Go you cannot assign values to nil, but rather you can assign pointers and interfaces to nil. Thus, a bit more ceremony is necessary when making null-checks in Go since a simple value == nil will not suffice.
As we'll see in a few moments, Scala provides one of the very best ways to elegantly achieve null-safety, which is the Option type, and which allows one to elegantly and expressively enforce null-safety through the type system itself. Most programming languages have third-party implementations of the Option type, or in the case of Java since Java 8, built-in into the language as the Optional type.
How to avoid common null-related pitfalls
The first strategy, of course, is conscientiousness! Be conscious of your code! Think! Be aware! On OOP languages, before you access any member of anything that could be null, make a null-check!
Person alice = personManager.getPersonByName("Alice"); //uh-oh! this can be null! Month monthOfBirth = Month.of(alice.getBirthday().getMonth()); String result = "Woo! Alice was born in " + monthOfBirth.name(); System.out.println(result);
Consider the following:
What if alice is never found? alice is then null, so what will happen when you try alice.getBirthday()?
What if alice has no birthday information associated to her on the database yet? alice is not null, but maybe you'd like to set up a "safe default" to display in such a case.
What if alice entered a Year of birth but for some reason there's no result from the database for getMonth() in runtime? What happens when you try to invoke monthOfBirth.name()? Shit hits the fan.
You're coding exclusively against the happy-path. This is terrible programming practice. So, simply, evaluate against all of those scenarios defensively:
String result; Person alice = personManager.getPersonByName("Alice"); if(alice != null && alice.getBirthday() != null){ Month monthOfBirth = Month.of(alice.getBirthday().getMonth()); String monthOfBirthName; if(monthOfBirth != null) monthOfBirthName = monthOfBirth.name(); else monthOfBirthName = "an unknown month!"; result = "Woo! Alice was born in " + monthOfBirthName; System.out.println(result); }
Presto, no more NullPointerException!
(Yes, I am aware that both Date and Month are deprecated in modern Java. Bear with me, this is only an example to exemplify how to deal with null)
So basically, be aware: - If null is a reasonable input parameter for your method, fix the method to account for it. - If null is unacceptable, fix the caller instead. What does "reasonable" mean here? You decide.
(NOTE: Here, we are also leveraging the fact that the java operator && is short-circuiting. In some other languages it works differently but on languages similar to java, logical AND operations are evaluated left-to-right, and upon first failure of the logical check by finding a false, the program gives up and stops evaluating the statement.)
But this is too noisy and too bulky! People may even want to write more to evaluate against different members of the alice object. Yuck!. What options do we have?
Null-safe navigation and null-coalescing
Well, the second strategy is that we can use a language that has features for safe navigation (also called null-conditional), and null-coalescing. Let's think of the Groovy language now.
In groovy, the idea of safe-navigation is the ability to access members on an object without triggering NPE's. This syntactic sugar is baked right into the language through the use of the ?. operator. C# also has the ?. operator and Ruby, as of 2.3, has the &. operator, which does the same. This will gracefully handle failure in case one of the dependencies of your object is null by returning null the very moment you try to access a member on null, so you can stop worrying so much about handling null checks.
In groovy there's also null-coalescing with the ?: operator, funnily called the "Elvis" operator (because if you look at it sideways it looks like Elvis' hair and eyes), which takes two expressions and will immediately default to the right hand expresion if the expression on the left is null or false.
The previous blob of java code becomes, thus:
def alice = personManager.getPersonByName("Alice") def monthOfBirthName = Month.of(alice?.getBirthday()?.getMonth())?.name() ?: "an unknown month!" String result = "Woo! Alice was born in " + monthOfBirthName println(result)
Doesn't that feel a lot better? It does for me.
Scala Option and Java 8 Optional
Finally, the last option is to leverage a language's type system to expressively state that a value can be null and whatever rules you want to set around it to handle what will your program do when the value is valid and existing or the contrary.
Scala has one of the best implementations of this in the Option type, documented in depth here. Basically we're letting the compiler know that, in runtime, an object may not be defined. Used in tandem with other constructs in this language you can come up with very expressive ways to handle values that can possibly be null. Following the same example, take a look at this highly idiomatic example with FP:
val alice : Option[Person] = personManager getPersonByName "Alice" //let's assume this returns a None val monthOfBirthName: Option[String] = alice map { _.getBirthday } map { b => Month.of(b.getMonth) } map { _.name } println("Woo! Alice was born in " + (monthOfBirthName getOrElse "an unknown month!"))
Here, the Option type gives us the getOrElse method which is used to provide a safe default whenever we try to inspect that object.
Here's the kicker: when you have an Option object which really is defined, you get a Some value, else, it's None. If you try to map Some value, then the result will be Some, but if you try to map a None, the result will also be None and getOrElse will just fall back to the safe default!
Using Option types liberally is universally preferred against null usage in Scala. You should never, ever return or handle nulls in scala. You should always return an Option whenever possible and let the handler "unwrap" the objects to their convenience.
So, in good Java tradition, 7 years later, probably after discussing mutexes and locks for too long, the Java language finally caught up to Scala and added the Optional type together with all the sweet Functional Programming sauce they've been adding to java and it makes our job easier. Now, being exclusively Java-8 idiomatic:
Optional<person> alice = Optional.ofNullable(personManager.getPersonByName("Alice")); String result = "Woo! Alice was born in " + alice.map(Person::getBirthday).map(Date::getMonth) .map(Month::of) .map(Month::name) .orElse("an unknown month!"); System.out.println(result);
Which is still quite verbose but not as quite as we started with, not to mention that it plays Fantastically with streams. Cool, right?
Conclusion
We have now seen the most modern alternatives in three different languages to handle nulity and you should be at least superficially acquainted with the implications of its use.
What do you think?
Please keep in mind this when writing your next piece of software and if you have any additions to contribute or anything that you'd like to discuss about this or other subjects in programming, please toss me a line and I'll get in touch with you.
#works#null#nulity#programming#programming languages#scala#java#c#javascript#best practices#null safety#null-safety
0 notes
Text
Second year intermediate vocational CSE practical record
2nd CSE Record
View On WordPress
0 notes