Beautify Code Without Optionals in Swift
Handling of optional data for function returns
Optionality, while safe, can provide us with multiple issues that we need to handle and/or compensate for.
I’ll be going through ways of handling optionals within function returns here so that whoever needs to consume it doesn’t have to deal with optionals. They can be applied to other areas, though. I’ll walk you through from bad to good, and at the end, we will see two different ways of handling this that I would consider best practice.
Force Unwrap
Looking at this code here, it’s quite unsafe if the section string passed in isn’t in the Dictionary. When you force unwrap, you’re saying you expect this to not be nil and to be around however, if it is nil, it will crash your application.
Empty Array
This code is a bit better because it is at least safe and won’t crash; however, there isn’t as much context here. We could add an isEmpty
check after line 12, and this is totally acceptable but when designing for function returns it’s usually better to return something that gives more info.
The next couple of options/routes are equally as good, depending on your use case.
Throwing Errors
So, a few things are going on in this next example. While this article focuses on function returns, let’s take a moment and look at the Section
enum before looking at the throwing of an Error
. It’s better to make an enum type for your keys because that locks in what can be keys in Dictionaries
instead of String
keys where you essentially have unlimited options for keys.
Now, let’s look at this throwing function. When you can, it’s good to throw an Error
, particularly a custom error like the SectionError
. There are a few reasons why this is better than the force unwrap and the empty array we just saw because throwing an Error
gives more context to the issue for whoever might be consuming this. These aspects allow them to correspond appropriately (we will see a more in-depth use case shortly) and on the happy path, it lets the consumer, whoever that might be, deal with actual data.
Result
Now, with the use of Result, the code is basically the same as the previous section, but if the data is there, we return success. If not, a failure. This also allows the consumer to handle the two flows quite easily with a switch/case.
Either way you go, Result
or throw
, it should be the preferred way of handling your functions. Like always, though, it depends on your specific use case.
With either of these, we can easily handle multiple error cases, as we will see.
Throwing of Multiple Errors
Our function could throw multiple Error
s depending on what we want out of it. If we are expecting multiple Error
s, we can quite easily handle them in a Do Catch because Catch can act like an if-else-if statements so we can essentially say catch _____ error. Handling Error
s like this is easier to read and understand but also allows us to handle these specific cases.
Result Failure Multiple Errors
We can do the same with Result, but the readability suffers here as we need a where clause for each case.
Personally, I would choose to throw an Error
over Result
, as the readability is much better.
Either way, Result
or throw
allows for more context and specific error handling. The returning of a blank array if nil works fine, but ideally, we want the consumer of this function to make the call on what to do with it.