In an article entitled "Boolean Externalities" developer Avdi Grimm describes a difficult debugging experience that resulted from an erroneous boolean return from deep within a series of chained predicate functions summarized in the following sketch:
class TypeA
# ...
def question1?(instance_typeb)
question2? || instance_typeb.question3?
end
# ...
end
class TypeB
# ...
def question3?
question4? && intance_typec.question5?
end
# ...
def question4?
question6? && question7?
end
# ...
def question7?
instance_typed.question8?
end
# ...
end
class TypeD
# ...
def question8?
# ...
end
# ...
end
After an arduous debugging process which he describes as going "Down the rabbit hole" Avdi finally finds the offending predicate function deep within a stack of questions (question8); he fixes the problem but he finds himself perplexed as to what he could have done differently to avoid the situation given that he had followed many modern development techniques.
Avdi's use of the "rabbit hole" metaphor is quite apt for this example because as he describes the process of traveling deeper and deeper into the chain of predicate functions there is quite a visceral sense of decent.
Maybe the problem here is that this whole idea of the predicate function is
wrong; No, not entirely, of course, just some subset of these that do not take
arguments. I think about some of the core predicate operators, for instance
==, >, <,
etc. What these operators almost all have in common is that they
operate on a thing or things; that is to say, they are used to ask a question
about something substantial and immediate. And, in most cases, if the need
arises to find out why a specific predicate is true or false, the answer is
pretty obvious by looking at the arguments (3 > 1), (2 == 4)
In functional programming there is this concept of the pure function that essentially equates a function to a map with a 1 to 1 correlation between arguments and results (i.e. the function always returns the same exact thing given the same input). Well, there's more to it than that but the important thing is that if a function is given no argument, then the answer should always be the same for no arguments.
Are there predicate operators that take no arguments? The only example I can think of for a predicate operator that does not take an argument is "else", since else will always return :true (I'm mixing apples and oranges here because operators aren't functions but for the sake of this argument I think it's OK to think of them in the same way).
In Avdi's example, these predicate functions aren't being passed anything. Therefore, functionally speaking they should be returning the same thing. But one may argue that indeed "something" is being passed; because we're talking objects here, we can think that the implicit argument "passed" to these predicates is always the object it self.
OK then, so we have a way to inspect the object and get the answer? There is the crux. These predicates aren't asking questions of self they are asking things about some other self. And when these things get chained together then it's hard to know what thing it was that was being asked a question about.
I haven't really answered how to solve this in a better way; and sorry for being so hand wavy about my attempt at explaining some of what I think is important to consider. Clearly this is a hard question.
However, I believe one possible solution is to limit these predicate functions such that the thing a question is being asked about is more immediate as is the case for the core predicate operators. But then I'm not certain about the details of how that would be applied to the problem at hand.