Solving Code Issues - With writing more code
A couple of decades ago, when I was in Munich for the BMW IT Manager's meeting; My body was quite new to spirits and liquor, coming from never having had any alcohol it was not habituated or did not know how to break it down when compared to some others. Long story short, I had a little mix of Beer, Champagne and Vodka which did not turn out pretty for me. So after the bouquet of drinks did a number on me, it was bad, in capitals BAD. I did not have the symptoms of a Drunk who was stumbling across or acting silly, speaking louder, it was just that my body was not accepting it or did not know how to assimilate it. I ended up at the bar again and just put my head down as that had the right height to put my head down as compared to the tables that were quite low. The bartender saw my situation and knew what was wrong, she quickly mixed up a couple of things and said have that... to which my reaction was "Are you kidding me? - More alcohol??" She explained that it was something that would help me, well, after that I asked my colleagues to help me up to my room and that was it. No more crossing the line and mixing the drink. The lesson from that, which came as a surprise was to fix the problem, the solution was more of it. So in that sense, to solve the code issues, we could end up writing more code. Which is exactly what we will do in this article.
If you have used swift one of its biggest advantage is also the biggest issue. It may well be for other languages that introduce the same functionality, but most of the Swift code suffers from one of the three issues.
This is the first thing that the swift compiler suggests when you are working with Optionals. It is quite logical as well, if you want the value of a String and you have an optional
Because Xcode is suggesting a fix to add the !, a majority of developers simply accept that and this can lead to runtime errors and crashes because some value might be nil and unwrapping it explicitly will cause a nil error.
A. This is similar to the exclamation marks above but indicate that the variable is not unwrapped and to get a value needs to be unwrapped. On unwrapping the value could be one of the two possibilities,
B. One way to circumvent this is to use the guard statement, which unwraps the value and persists it for the scope of the function. However, the only downside of guard is that it expects you to return from the function.
C. The other way to unwrap and use optional values is to provide them a default. This is easy for primitives but not as easy for other types. Most importantly, if you can use default values then the whole idea of using optionals is lost. If you can represent an index not found with 0 as the default then it defeats the purpose of using an optional and returning nil, you could well return a 0.
This "nil coalescing" makes sense when you want to provide a default instance if none exists, so instead of writing code like
You can now simply write
D.
Ternary operators - these come from the C language where they were used to quickly assign values. An example is
Which eliminates the if blocks which would look something like
How can we make this easier? How can we make the code readable and usable? After all the push is on readability of code. I recollect my conversation with a smart Alec that heads the mobile development for a large organisation. I mentioned to him that I like to look at Clean Code, to which his immediate response was, so you do not want to write clean code? I think clean code is just a buzz word for many and that they do not understand the principles behind it. Anyways, back to the how can we make this easier and still keep it swifty.
Let’s try to logically sound it out on what we are trying to do with code that looks something like this
We are saying that if the value in
And using the function is as simple as
A small caveat, since this is a code closure, the function calls that belong to the class need to be prefixed with a self to work.
Now this is a bit more readable and usable as compared to simply trying to unwrap an optional value and then running the code block using an if.
You can also write another function that is the opposite of what you have written above, it could perform actions if the value is
Similarly, when working with the ternary operator, what we are saying in plain language is that if the condition is true then return the value of the code in the true part otherwise return the value in the false part. This is similar to a function that existed in some languages and still does in Excel called IIF, which is an extension of IF and has the syntax of
This will work but has some issues, namely that first we expect the condition to be of type
This is not very useful as it takes away from that one line of convenience to multiple lines. We could however make a few changes to this to do what we were after.
There is a very interesting attribute available called
This basically takes a function that returns a
BUT… the closures are defined as
Easy said than done… What would the closure function look like? What would it return? A
When you come across such a predicament, it is the time to pull out from the toolbox the Swiss army knife called Generics.
We will have to redefine the signature a little to look like
Calling it is now,
or
A swifty way is to add the parameter names for readability, however if you want to make it simpler, you could simply add the _ in front of the parameter names, and then you can just call it without having to specify the parameter names, like so
While this works as a replacement for the ternary operator, I have seen this cause a lot of problems with code because this was made for a binary decision true and false, but we generally have more options than simply true or false. In the example above we can see that we have three options, we need to check for the pressure for a range. A better alternative for this could be
The only issue with using
2. You can write an extension on Optional that performs a code block on the presence of a value or when nil
3. You can avoid the ternary operator to quickly assign values, because life is not binary and nor are options these days.
4. Use a switch, it helps you cover a range of conditions and if you add an enum to the mix, then you can have better control.
5. Have fun exploring swift, it is a fun language that you can do amazing things with.
6. Defaults are a tricky one, use with discretion, instead throw errors if you find nil values (not covered in this blog post)
If you have used swift one of its biggest advantage is also the biggest issue. It may well be for other languages that introduce the same functionality, but most of the Swift code suffers from one of the three issues.
- Exclamation marks !
- Question marks ? ??
1. The exclamation marks 😱 also known as ImplicitlyUnwrappedOptionals
This is the first thing that the swift compiler suggests when you are working with Optionals. It is quite logical as well, if you want the value of a String and you have an optional
String
, then they are two different types as far as Swift compiler is concerned, as a string
is of type String
where as an optional string
is of type String?
(Optional) that has an associated value of some(String)
.Because Xcode is suggesting a fix to add the !, a majority of developers simply accept that and this can lead to runtime errors and crashes because some value might be nil and unwrapping it explicitly will cause a nil error.
2. Question marks - Optionals
A. This is similar to the exclamation marks above but indicate that the variable is not unwrapped and to get a value needs to be unwrapped. On unwrapping the value could be one of the two possibilities,
nil
or the value of TYPE. So you are left with the task of checking the value when unwrapping to ensure that it is not nil
before you try to use it somewhere. That would involve you writing code like if let someVariable = optionalValue { print("We now have an unwrapped value which is : \(someVariable)") }However the unwrapped value remains for the scope of the curly brackets, and if you wanted to use them later in the function, you have to unwrap it again or add all of the code that requires the unwrapped value to be in the scope of the curly brackets.
B. One way to circumvent this is to use the guard statement, which unwraps the value and persists it for the scope of the function. However, the only downside of guard is that it expects you to return from the function.
C. The other way to unwrap and use optional values is to provide them a default. This is easy for primitives but not as easy for other types. Most importantly, if you can use default values then the whole idea of using optionals is lost. If you can represent an index not found with 0 as the default then it defeats the purpose of using an optional and returning nil, you could well return a 0.
This "nil coalescing" makes sense when you want to provide a default instance if none exists, so instead of writing code like
if myObject == nil { myObject = MyObject() }
You can now simply write
myObject = myObject ?? MyObject()
D.
Ternary operators - these come from the C language where they were used to quickly assign values. An example is
let colour = isCritical ? UIColor.red : UIColor.black
Which eliminates the if blocks which would look something like
//let color: UIColor if isCritical { color = UIColor.red } else { color = UIColor.black }
There is a proposal to remove ternary operators from Swift as they are not the swifty way. Like the
inc++
and inc--
were removed earlier. This is still under discussion on the swift forum.Solving Code problems with writing more Code
How can we make this easier? How can we make the code readable and usable? After all the push is on readability of code. I recollect my conversation with a smart Alec that heads the mobile development for a large organisation. I mentioned to him that I like to look at Clean Code, to which his immediate response was, so you do not want to write clean code? I think clean code is just a buzz word for many and that they do not understand the principles behind it. Anyways, back to the how can we make this easier and still keep it swifty.
Unwrapping Optionals
Let’s try to logically sound it out on what we are trying to do with code that looks something like this
if let value = optionalValue { let result = performAction(with: value) postToAnotherFunction(value: result) }
We are saying that if the value in
optionalValue
is not nil
, then we want to perform the two functions. So from a code perspective, what we want to achieve is first get the value and then perform the code passed as a closure.extension Optional { func ifNotNil(_ codeBlock: (Self) -> Void) { if let _obj = self { codeBlock(self) } } }
And using the function is as simple as
optionalValue.ifNotNil { value in let result = performAction(with: value) postToAnotherFunction(value: result) }
A small caveat, since this is a code closure, the function calls that belong to the class need to be prefixed with a self to work.
optionalValue.ifNotNil { value in let result = self.performAction(with: value) self.postToAnotherFunction(value: result) }
Now this is a bit more readable and usable as compared to simply trying to unwrap an optional value and then running the code block using an if.
You can also write another function that is the opposite of what you have written above, it could perform actions if the value is
nil
.func ifNil(_ codeBlock: (Self) -> Void) { if _obj == nil { codeBlock(self) } }
You could use this to initialise a whole lot of things that you might when the object or value is nil.
Ternary Operator
Similarly, when working with the ternary operator, what we are saying in plain language is that if the condition is true then return the value of the code in the true part otherwise return the value in the false part. This is similar to a function that existed in some languages and still does in Excel called IIF, which is an extension of IF and has the syntax of
IF <condition> ? <truepart> : <falsepart>translates to
IIF(condition, truePart, falsePart)We can write a function that does exactly that to replace the ternary operator
func IIF(condition: Bool, truePart: () -> Void, falsePart: () -> Void) { if condition { truePart() return } falsePart() }
This will work but has some issues, namely that first we expect the condition to be of type
Bool
which means you have to get the value in a Bool
format, to explain further consider the codelet result = isValid ? 1: 0In this example isValid is already of type
Bool
so it works, however you might want to use something likelet message = string.count > 140 ? "New Twitter format" : "Old Twitter format"This will not work in the function above, and to make it work you will have to first determine the
Bool
value by usinglet result = string.count > 140 IIF(condition: result, truePart: { print("") }, falsePart: { print("") })
This is not very useful as it takes away from that one line of convenience to multiple lines. We could however make a few changes to this to do what we were after.
There is a very interesting attribute available called
@autoclosure
, though you might not see it being used quite often as many of the things embedded deep within the Apple Frameworks (… for another blog post) What @autoclosure
does is create a closure from an expression,func IIF(condition: @autoclosure () -> Bool, truePart: () -> Void, falsePart: () -> Void) { }
This basically takes a function that returns a
Bool
, when we use an expression like string.count > 140
, it is infact a function that returns a Bool
value. We can simply pass that as the first parameter to the function. In the code we can simply call the condition as a function which will return the value of that function as a Bool
.IIF(condition: string.count > 140, truePart: {}, falsePart: {})While this works, it does not work to assign a value back which is what was expected, this can easily be resolved by returning a value. So instead of simply calling the
truePart
or the falsePart
, we can use return truePart()
and return falsePart()
. BUT… the closures are defined as
() -> Void
, which means they return nothing so rather than the compiler complain about this, let us change that to return a value.Easy said than done… What would the closure function look like? What would it return? A
String
, Int
, Double
, CGFloat
, UIColor
, etc etc??When you come across such a predicament, it is the time to pull out from the toolbox the Swiss army knife called Generics.
We will have to redefine the signature a little to look like
func IIF <T>(condition: @autoclosure () -> Bool, truePart: @autoclosure () -> T, falsePart: @autoclosure () -> T) { if condition() { return truePart() } return falsePart() }
Calling it is now,
let color = IIF(condition: isCritical, truePart: UIColor.red, falsePart: UIColor.black)
or
let color = IIF(condition: pressure > 100, truePart: UIColor.red, falsePart: UIColor.black)
A swifty way is to add the parameter names for readability, however if you want to make it simpler, you could simply add the _ in front of the parameter names, and then you can just call it without having to specify the parameter names, like so
func IIF&let;T>(_ condition: @autoclosure () -> Bool, _ truePart: @autoclosure () -> T, _ falsePart: @autoclosure () -> T) { ... }
let color = IIF(pressure > 100, UIColor.red, UIColor.black)You can also chain these further like the ternary operators but have little more readability than those of a ternary operator
let color = pressure > 100 ? UIColor.red : pressure < 40 ? UIColor.green : UIColor.orangeVS
let color = IIF(pressure > 100, UIColor.red, IIF( pressure < 40, UIColor.green, UIColor.orange))
While this works as a replacement for the ternary operator, I have seen this cause a lot of problems with code because this was made for a binary decision true and false, but we generally have more options than simply true or false. In the example above we can see that we have three options, we need to check for the pressure for a range. A better alternative for this could be
func getColor(forPressure pressure: Int) -> UIColor { switch pressure { case 0..<40: return UIColor.green case 40...100: return UIColor.orange default: return UIColor.red } }And use it as
let color = getColor(forPressure: pressure)
The only issue with using
switch
as mentioned above is that it is in a function and it is difficult to use it in a more fluid form because the switch is ranged based on rules in the case
clause where as you can have a different condition with ternary operators and the IIF
function that we just wrote. That comes back to the cautionary point that it is binary.Takeaways
1. Avoid the use of ! As much as possible, there are cases that call for the use of ! but if it can be avoided, you should avoid and use the ? instead.2. You can write an extension on Optional that performs a code block on the presence of a value or when nil
3. You can avoid the ternary operator to quickly assign values, because life is not binary and nor are options these days.
4. Use a switch, it helps you cover a range of conditions and if you add an enum to the mix, then you can have better control.
5. Have fun exploring swift, it is a fun language that you can do amazing things with.
6. Defaults are a tricky one, use with discretion, instead throw errors if you find nil values (not covered in this blog post)
Comments
Post a Comment