From a code-review perspective - the intuitive process
The function maybeUpdateTime
does not seem to be problematic. However, it definitely needs a better name, one that conveys information to a programmer succinctly.
The while loop inside the updateState
function may trigger a false-alarm for a programmer who is reading the code. That is, it makes the reader feel scary, and compels the reader to fetch more information to assure that it is not a bug.
When a programmer reads a while
loop and tries to understand what it does, there are several pieces of information that the programmer needs to grasp (or guess):
- Is this loop going to execute at least once?
- Why does this question matter? If the loop isn't going to execute at all, I'm not too concerned about the correctness or applicability of the code inside the loop.
- Is it going to execute a fixed number of times?
- If the loop count is determined beforehand, I can glean more information by finding out where that loop count is derived from.
- If there is no pre-determined loop count, I will need to find out the dynamic conditions - what is the loop termination condition?
- If the loop termination condition is computed in another function,
- Am I able to guess what that condition is, just by seeing the other function's name?
- If the other function's name didn't provide adequate information (about what that loop-termination condition is), I will have to go to that function and read about it.
Altogether, these thought processes increased the cognitive load of the person reading the code (especially the while
loop). If one or more issues are fixed, the cognitive load will decrease, and the while
loop will become less scary to the other programmer.
From a code-review perspective - objections that may be raised
Note: this section dissects into the code example provided by OP. As OP explains, such detail is not the main focus of this question.
public boolean maybeUpdateTime()
{
if (this.timeReference.isAfter(lastInterval.getBeggining())
{
this.timeReference = lastInterval.getEnd(); // mutation
lastInterval = intervalIterator.next(); // mutation
return true;
}
return false;
}
In the code sample above, changes are made to two instance fields (of the this
object). The function's name will need to communicate either the how or the why these field changes are made.
But how do we explain the why?
We can only explain in a manner that makes sense to fellow humans. For this reason, we (humans) create idioms and analogies, drawing from experience from our everyday, non-software-related lives.
We also try to separate a complex process into orthogonal, composable concerns.
It should be obvious that the code sample above is taken from a class that has certain iterator-like behavior. It is also obvious that the class has other responsibilities as well.
Thus, the first change we'd make is to isolate the iterator-like behavior - to make it an iterator that is extensible, without bundling with the additional responsibility. This is called the Single Responsibility Principle (SRP).
The sample code may have more than one use cases. Some examples are:
- To iterate through the entire collection, one at a time, and perform a bit of work on each item (via the extensibility of an iterator)
- To search for something (i.e. to iterate through the collection until a condition is met), without modifying the collection.
- To execute an algorithm that examines the inter-relationship between adjacent items in the collection, e.g. to find consecutive items that represent time ranges that touch or overlap.
In each of the use case, it would help if the code is refactored (restructured) so that the what is being computed is made obvious, and separate from the act of iterating through.
From a coding style perspective
Given OP's desire to focus on the tension between:
- There is already a public function; its name is NOT going to be changed;
- There is another function that calls that public function; however, the way it is called may look scary to a fellow programmer.
In this case, the advise is to wrap that function so that the wrapper's name conveys the missing context.
Illustrated solution:
// Function name is part of public API
// Function name cannot be changed
// Function name is meaningful when seen from the API's context
// Suggestion to change this function name cannot be fulfilled
public boolean somePublicFunction()
{
...
}
// Because this is a thin wrapper,
// you can change its name to give more context.
private boolean advanceIteratorOnce()
{
somePublicFunction();
}
// If it is necessary to provide more context to the fellow programmer
// reading your code, consider making it obvious what is the stopping
// condition for the while loop.
private void keepDoingUntilSomethingHappens()
{
while (advanceIteratorOnce());
}
public void doMultipleThings()
{
// do some stuff before
// You may use option 1, option 2, or both.
// What is important is that the fellow programmer will not feel scared
// because the important information about the loop has been made obvious.
keepDoingUntilSomethingHappens();
// do more stuff afeter
}
processNextInterval()
. Then it is easy to guesswhile(processNextInterval()) {}
processes intervals as long as there are still some intervals to processmaybeUpdateTime
.List::add
appears to do something even dumber, in that it may never returnfalse
, raising an exception instead. In that case, it may as well just havevoid
as its return value, and if it returns, you can assume it succeeded. It's debatable whether it's the job ofadd
to inform you if adding an element "failed" simply because it was already in the list.