JavaRush /Java Blog /Random EN /Why is NULL bad?
Helga
Level 26

Why is NULL bad?

Published in the Random EN group

Why is NULL bad?

Here is a simple example of using NULL in Java: public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return null; } return new Employee(id); } What's wrong with this method? It can return NULL instead of an object - that's what's wrong. Using NULL is a terrible practice in OOP and should be avoided at all costs. Quite a number of different opinions have already been published on this issue, including Tony Hoare’s presentation “Zero Links: A Billion Dollar Mistake” and David West’s entire book “Object-Oriented Thinking.” Here I will try to summarize all the arguments and show examples of how you can avoid using NULL by replacing it with suitable object-oriented constructs. First, let's look at two possible alternatives to NULL. The first is the Null Object design pattern (best implemented with a constant): public Employee getByName(String name) { int id = database.find(name); if (id == 0) { return Employee.NOBODY; } return Employee(id); } The second possible alternative is to "fail fast" by throwing an exception if the object cannot be returned: public Employee getByName(String name) { int id = database.find(name); if (id == 0) { throw new EmployeeNotFoundException(name); } return Employee(id); } Now let's look at the arguments against using NULL Before writing this During the post, I became acquainted, in addition to the above-mentioned presentations by Tony Hoare and the book by David West, with a number of publications. These are "Clean Code" by Robert Martin, "Clean Code" by Steve McConnell, "Say No to NULL" by John Sonmez, and a discussion on StackOverflow called "Is returning NULL a bad practice?"
Handling errors manually
Every time you receive an object as input, you must check whether it is a reference to a real object or a NULL. If you forget to check, your program may be interrupted mid-execution by a thrown NullPointerExeption (NPE). Because of this, your code begins to fill with numerous checks and if/then/else branches. // this is a terrible design, don't reuse Employee employee = dept.getByName("Jeffrey"); if (employee == null) { System.out.println("can't find an employee"); System.exit(-1); } else { employee.transferTo(dept2); } This is how exceptions should be handled in C and other strictly procedural programming languages. In OOP, exception handling was introduced mainly to get rid of manually written processing blocks. In OOP, we let exceptions bubble up until they reach the application-wide error handler, and this makes our code much cleaner and shorter: dept.getByName("Jeffrey").transferTo(dept2); Consider NULL references a relic of the procedural programming style and use 1) Null Objects or 2) Exceptions instead.
Ambiguous understanding
To accurately convey the meaning of what is happening in the name, the method getByName() should be renamed to getByNameOrNullIfNotFound(). The same must be done for each method that returns an object or NULL, otherwise there will be ambiguity when reading the code. Thus, in order for method names to be accurate, you must give methods longer names. To avoid ambiguity, always return a real object, a null object, or throw an exception. One might argue that sometimes we just need to return NULL to achieve the desired result. For example, a Map get() interface method in Java returns NULL when there are no more objects in the Map. Thanks to Map's use of NULL, this code only needs one search cycle to get a result. If we rewrite Map so that the get() method throws an exception if nothing is found, our code will look like this: Obviously, this method is twice as slow as the original one. What to do? There is (no offense intended) a design flaw in the Map interface. Its get() method would have to return an Iterator, and then our code would look like this: By the way, this is exactly how the STL map::find() method is designed in C++. Employee employee = employees.get("Jeffrey"); if (employee == null) { throw new EmployeeNotFoundException(); } return employee; if (!employees.containsKey("Jeffrey")) { // first search throw new EmployeeNotFoundException(); } return employees.get("Jeffrey"); // second search Iterator found = Map.search("Jeffrey"); if (!found.hasNext()) { throw new EmployeeNotFoundException(); } return found.next();
Computational vs. Object Oriented Thinking
The line of code if (employee == null) is quite understandable to someone who knows that an object in Java is a pointer to a data structure, and NULL is a pointer to nothing (in Intel x86 processors - 0x00000000). However, if you start thinking in object style, this line makes a lot less sense. Here's what our code looks like from an object point of view:
- Hello, is this the software development department? - Yes. - Please, invite your employee Jeffrey to the phone. - Wait a minute... - Hello. - Are you NULL?
The last question sounds a little strange, doesn't it? If, instead, after your request to invite Jeffrey to the phone, the person on the other end simply hangs up, this will cause certain difficulties for us (Exception). In this case, we can try to call back, or we can report to our boss that we were unable to talk to Jeffrey and complete our main task. In addition, on the other side you may be asked to talk to another person who, although not Jeffrey, can either help you with most of your questions, or refuse to help if we need to know something that only Jeffrey (Zero Object) knows ).
Slow failure
Instead of exiting quickly , the code above tries to die slowly, killing others along the way. Instead of letting everyone know that something has gone wrong and that it needs to start processing the exceptional event immediately, it tries to hide its failure from the client. This is very similar to the manual exception handling we discussed above. Making your code as fragile as possible and allowing it to break if needed is good practice. Make your methods extremely demanding of the data they work with. Allow them to complain by throwing exceptions if the data they are given is insufficient, or if the data is simply not suitable for use in the method as intended. Otherwise, return a Null Object that behaves in some conventional way and throws exceptions in all other cases. public Employee getByName(String name) { int id = database.find(name); Employee employee; if (id == 0) { employee = new Employee() { @Override public String name() { return "anonymous"; } @Override public void transferTo(Department dept) { throw new AnonymousEmployeeException( "I can't be transferred, I'm anonymous" ); } }; } else { employee = Employee(id); } return employee; }
Mutable and incomplete objects
In general, it is strongly recommended to design objects to be immutable. This means that an object must receive all the necessary data when it is created and never change its state during its entire life cycle. NULL values ​​are very often used in the Lazy Loading design pattern to make objects incomplete and mutable. Example: public class Department { private Employee found = null; public synchronized Employee manager() { if (this.found == null) { this.found = new Employee("Jeffrey"); } return this.found; } } Despite the fact that this technology is widespread, it is an anti-pattern for OOP. And mainly because it makes the object responsible for performance problems with the computing platform, which is exactly what the Employee object cannot be aware of. Instead of managing its state and behaving in a manner consistent with its intended purpose, an object is forced to worry about caching its own results - this is what lazy loading results in. But caching is not at all what an employee does in the office, is it? Exit? Don't use lazy loading in such a primitive way as in the above example. Instead, move problem caching to another layer of your application. For example, in Java you can take advantage of aspect-oriented programming features. For example, jcabi-aspects has a @Cacheable annotation that caches the return value of a method. import com.jcabi.aspects.Cacheable; public class Department { @Cacheable(forever = true) public Employee manager() { return new Employee("Jacky Brown"); } } I hope this analysis was convincing enough for you to stop NULLing your code :) Original article here . You may also be interested in such topics as: • DI Containers are Code PollutersGetters/Setters. Evil. Period. Anti-Patterns in OOPAvoid String ConcatenationObjects Should Be Immutable
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION