Inspections, Instructions & Intensions
20 Apr 2014IntelliJ IDEA offers a formidable array of inspections (632 and counting), each thoughtfully crafted to help you deliver clean, bug-free code. One which recently caught my attention, Suspicious Name Combination, is an inspection that detects “suspicious” argument-parameter assignments by analyzing lexical tokens in the method signature. This frequently occurs in dyads such as setSize(width, height)
, or triads like assertEquals(message, actual, expected)
where the order of shared-type parameters can be easily confused and will often slip through static type-checks unnoticed. For example, IntelliJ will alert you when passing a variable named btnHeight
into a parameter named width
.
If you think about it, this is a Non. Trivial. Inspection. Writing good inspections is a fairly delicate process to begin with due to the tradeoff between sensitivity and specificity. If an inspection is too specific, it will lack adequate coverage - if it is too general, then you’ll end up supporting a bunch of exceptions to handle the edge cases. In many ways, this is the same dilemma faced by diagnostic tests for cancer and other medical conditions. Taking the analogy a bit further, if software is the patient, bugs are the cancer and inspections are the diagnostics. So how exactly does IntelliJ Suspicion work? It’s either very ad-hoc or very clever. Let’s go to the source for a closer look. The first thing we notice is that identifiers are grouped by similarity into so-called name groups.
It looks like they’re building a data structure to detect set membership or something. Let’s go a little further. There are three areas of suspicion: at the assignment, call site, and return statement (nice). We’ll focus on the call site, since that’s the most general case.
Forget about what a PSI is for now (it’s something like XPath for ASTs with built-in reflection, you can almost get a feel for it here), this method basically iterates through an argument list. If the argument in question belongs to an existing name group, then it matches that argument name refName
to its respective parameter’s name parameters[i].getName()
, from the method signature and performs some sort of check.
Now if the parameter’s name group findNameGroup(name)
doesn’t match the argument’s name group findNameGroup(referenceName)
then we have a problem. There is a semantic contradiction. So far we’re just peeling back the implementation layers. The heart of our algorithm lies in findNameGroup(String name)
. Let’s take a look.
Tokenize and scan the name for salient keywords, with a sentinel value: an inconsistent group. Here’s the catch. Not immediately obvious here, but it’s a precaution against false positives! Someone was worried about ambiguous names like bottomRight
being flagged for suspicion. If and only if name
belongs to exactly one name group then return that group, otherwise return null
.
All things considered, this is a very modest heuristic, accomplishing just what it sets out to do. If it were any more ambitious it might attempt to solve a longest common substring against neighboring parameter name groups, and it might also fail twice as often. The truth is difficult to escape: there is no sure substitute for writing clean code. IntelliJ will catch a limited subset of specific name substitutions, but will not prevent a misplaced argument in an arbitrary function with consecutive, order-dependent, shared-type parameters. Until telepathic connectivity is fully supported, best favor monadic functions, use the builder pattern, and name variables intelligibly.
Further Reading
-
Martin, Robert C. (2009) “Clean Code, A Handbook of Agile Software Craftsmanship.” Chapter 3, pp. 40-43. Function Arguments.
-
Intension vs. Intention, or, “What you must know in order to determine the reference of an expression,” Wordnet