If you write feature code, you also have to write error handling code. There’s no clean code without clean error handling, if you don’t do it, your user has to deal with it in the form of your application crashing.
There are a few simple rules you should follow to, starting with using exceptions rather than return codes, we already talked about it when dealing with clean functions. Using unchecked exceptions let’s you concentrate in the flow of your code without interweaving it with a myriad of if-else statements to check every return code (or worse forget to do it and propagate an error state).
You should throw your exception thinking about how your caller will deal with it. So you need to provide context about the error in the message of the exception, and don’t litter your caller with very specific exception classes, if all of them will be dealt with in the same way (log and show error).
Remember that exceptions should be used to handle the weird flow not your normal flow. Don’t use them like if statements. If you have to integrate with a library that does just that wrap it and expose a sane interface that the rest of your system can deal with.
Avoid nulls like a plague. This probably deserves a whole article but to give you the gist of it, don’t return null, return something else, like an empty list or a special case object. Don’t pass null as a parameters. You don’t want to deal with it neither the writer of the function you are calling (likely yourself), don’t pass a null as an argument, unless it’s a function of a third-party library.