The original article was published on February 13, 2025.
Hello, I'm Munetoshi Ishikawa, a mobile client developer for the LINE messaging app.
This article is the latest installment of our weekly series "Improving code quality". For more information about the Weekly Report, please see the first article.
Words cannot express
Suppose there are two types of parameter string formats, ABC and XYZ, as follows.
- ABC:
a=foo,b=bar,c=baz - XYZ:
x:foo;y:bar;z:baz
We want to extract the parameters (foo, bar, baz) from these strings. Therefore, we defined an object Regexes to define regular expressions and an object Parser to perform parsing as follows. (In Kotlin, object is a keyword to create a singleton.)
class AbcParams(val a: String, val b: String, val c: String)
class XyzParams(val x: String, val y: String, val z: String)
object Regexes {
val ABC_REGEX: Regex = """a=(?<a>\w+),b=(?<b>\w+),c=(?<c>\w+)""".toRegex()
val XYZ_REGEX: Regex = """x:(\w+);y:(\w+);z:(\w+)""".toRegex()
}
object Parser {
fun parseAbcParams(string: String): AbcParams? {
val groups = Regexes.ABC_REGEX.matchEntire(string)?.groups
?: return null
val a = groups["a"]?.value.orEmpty()
val b = groups["b"]?.value.orEmpty()
val c = groups["c"]?.value.orEmpty()
return AbcParams(a, b, c)
}
fun parseXyzParams(string: String): XyzParams? {
val values = Regexes.XYZ_REGEX.matchEntire(string)?.groupValues
?: return null
return XyzParams(values[1], values[2], values[3])
}
}
Is there any problem with this code?
Keep secrets in your heart
The function parseAbcParams in Parser expects the regular expression ABC_REGEX to have named groups a, b, c. Similarly, parseXyzParams expects x, y, z to be present in this order within the regular expression XYZ_REGEX.
However, what these functions "expect" from the regular expressions is not explicit. If you forget to update the functions when changing the regular expressions, it won't result in a compile error, and you might only notice the bug at runtime.
To mitigate this issue, it is desirable to confine the relationship of "implicit expectations" within the scope of classes or functions. When extracting parameters from strings, it is better to separate the objects into an ABC parser and an XYZ parser rather than separating them into Regexes and Parser.
object AbcParser {
private val REGEX: Regex = """a=(?<a>\w+),b=(?<b>\w+),c=(?<c>\w+)""".toRegex()
fun parse(string: String): AbcParams? {
val groups = REGEX.matchEntire(string)?.groups
?: return null
val a = groups["a"]?.value.orEmpty()
val b = groups["b"]?.value.orEmpty()
val c = groups["c"]?.value.orEmpty()
return AbcParams(a, b, c)
}
}
object XyzParser {
private val REGEX: Regex = """x:(\w+);y:(\w+);z:(\w+)""".toRegex()
fun parse(string: String): XyzParams? {
val values = REGEX.matchEntire(string)?.groupValues
?: return null
return XyzParams(values[1], values[2], values[3])
}
}
When there are implicit expectations between functions or values, placing them close together (within the same function, class, or module) makes it easier to clarify that relationship.
Open secret
Such implicit relationships occur in various situations. Here are some examples.
- Paired logic: serialization and deserialization, await and notify
- Order of function calls: setup -> execution -> teardown
- Number or type of placeholders in strings: UI text, SQL queries
- Values with or without edge cases excluded:
UIntexcluding 0,Stringexcluding empty strings - Unprocessed/processed strings: encrypted strings and plaintext, escaped/unescaped strings
Of course, if you can explicitly define the relationship with types, that's ideal. If you want to distinguish between encrypted text and plaintext, it's safe to define EncryptedText and PlainText. However, there are cases where defining types is excessive or cannot be verified until runtime.
The next best approach is to limit implicit relationships to functions/classes/modules or ensure them with unit tests or integration tests. If that's difficult, it's a good idea to explicitly state the existence of implicit relationships in documentation comments.
In a nutshell
When implicit expectations are necessary, confine the relationship of those expectations within functions/classes/modules.
Keywords: implicit expectation, dependency, encapsulation