Bugs are Fun!!
Introduction / Audience
I stumbled across a bug in my code and I couldn't figure out what was causing it. After tracking it down, I felt this would make a good post to highlight a rare and interesting bug. I believe this is a known bug so this post is nothing groundbreaking, but I don't think it is very widely known.
The target audience is anyone from beginners to experts:
- For beginners, you can learn how to approach finding a difficult bug in a logical methodical manner. It will also introduce you to Kotlin Playground if you haven't seen it before.
- For experts, you can test you knowledge and experience and see if you can identify the issue from the original code.
Warning: This post contains bugs and cute images of bugs.
The Bug
The Error
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Node$Option.getCode()" because "it" is null
So a null-pointer error (NPE). Great. This should be easy. I'll basically look through my code for any double bangs (!! - not-null assertion operator) related to Node$Option.getCode().
The Process
I started off by looking for those double bangs, added some exception handling around areas of code that may have caused the issue, and couldn't quickly figure it out.
Hmmm. Interesting. This may be something new.
This was the point I started to find this fun. From what I could see, this wasn't the typical simple mistake I've dealt with a million times before. This actually energized me and made me more interested.
I eventually narrowed things down a little and copied the area of code that was causing the problem into a Raw Kotlin project (still in Android Studio). I couldn't reproduce it. Hmmm. This is strange.
Next, I went back to my original project, copied a large amount of code with a slightly different name and slowly started cutting things out while making sure the bug still occurred.
I must confess, I had already solved the issue at this point. With the help of AI and small sections of code, we were able to guess roughly what was causing it and fix the code; however, I was still curious about seeing exactly why it was happening.
After trimming everything down, I ended up with a minimal example of the code which I can share. I could of course cut this down further, but I want to keep some of the original feel of what it looked like.
Minimal Example
sealed interface Node {
object OptionA: Node
object OptionB: Node
sealed interface Option {
val code: String
data object A : Option { override val code = "A" }
data object B : Option { override val code = "B" }
fun toAction() = when(this) {
A -> OptionA
B -> OptionB
}
companion object {
val childEntries = listOf(A, B)
}
}
}
You are missing around 90% of the original code here, but you still get a sense of what is going on. We have nested sealed interfaces, a node, options, toAction, and childEntries. There are two options with an associated code.
Notice: We have no double bangs anywhere, which was my initial expectation.
Experts, this is where you should stop reading. Can you identify the bug from this code alone without looking any further?
The Next Step
fun bugExample() {
println("Checking Node:")
println("A Code: ${Node.Option.A.code}")
Node.Option.childEntries.forEach {
println(" ${it.code}")
}
println("Codes checked")
}
fun bugExample() {
println("Checking Node:")
println("A Code: ${Node.Option.A.code}")
Node.Option.childEntries.forEach {
println(" ${it.code}")
}
println("Codes checked")
}
Kotlin Playground
Fun - Play!
Findings and Fixes
The Fix
companion object {
val childEntries = listOf(A, B)
}
Other Fixes
- Removing the childEntries.forEach fixes it:
// Node.Option.childEntries.forEach {
// println(" ${it.code}")
// }
- Removing toAction fixes it:
// fun toAction() = when(this) {
// A -> OptionA
// B -> OptionB
// }
- Commenting out the println fixes it:
// println("A Code: ${Node.Option.A.code}")Which Fix?
Summary
- MRE - Making a Minimal Reproducible Example should be your go to if you get stuck on a bug. Cutting out 90% of the code that doesn't affect the issue makes solving it extremely simple. It can be difficult to cut down the code, but it is often far better than trying to fix one tiny bug in thousands of lines.
- Have Fun - Fixing bugs should be fun. Look at it as a new puzzle or game. Programmers are, I believe, by nature very inquisitive and logical. If you can change your mindset to that of play and fun, programming becomes far more enjoyable. Try to become the person in your team everyone wants to consult about awkward bugs.
- Understand - Understanding the issue is important. You can fix bugs with workarounds that will likely lead to errors in the future as all you really did we hack a solution that hides the problem. Just because the code works, it doesn't mean you fixed the issue. You don't want to get bitten by the same issue later in development.




Comments
Post a Comment