I recently was reminded how often we take our programming languages and libraries for granted, thinking that they are “stable” and forgetting that they are also evolving, getting better and performant all the time.
I was researching for another post on Kotlin value class (coming soon) and came across these lines of code in value classes KEEP notes
fun main() {
val a = "Kotlin"
val b = a.filter { it != ' ' } // Still "Kotlin"
println(a == b) // true -- same value
println(a === b) // false -- different instance
}
This code is fragile and the source of fragility is String.filter from Kotlin standard library. The fragility comes from the fact that filter implementation could be changed to return the same reference to a in the future as there are no guarantees by the function regarding referential equality.
This example might not be interesting or relevant to everyone but depicts that software fragility (specifically behaviour sensitivity) can arise not just from our own code but from our dependencies as well and we should be careful regardless the source.
We can mitigate these kinds of scenarios by:
- Being intentional: Become more intentional when relying on dependencies in your code, be skeptical and always remind yourself that the performance characteristics and/or implementation might change
- Testing rigorously: Have proper test suite. Unit tests to isolate the root cause and Integration tests to have contingencies against any cascading effects of such changes. So, if you decide on updating your dependencies automagically (looking at you Dependabot), you can identify regressions caused by updates to libraries and catch them before they wreak havoc in production
- Keep yourself informed: Go through the changes (at least the highlights of changes) in a library when migrating to a new major release version. We want to use the new shiny thing but let’s not shoot ourself in the foot with it