Fellow devs, do you:

I’m pretty sure such above classic question has transformed into a situation where every Android dev should have been familiar. And I believe each of you is able to advocate your own why-s for each above option (or do you even really care?). In fact, the Framework team has already sounded as well related this:

General rule:
During any kind of initialization, let the super class do their work first.
During any kind of finalization, you do your work first.

Dianne Hackborn

Well, regardless of which option you advocate for, or the general rule by Dianne Hackborn, this post doesn’t join in promoting entry which option is the most valid one given with some scenarios, for instance. Which option you should always work with, a best practice. Doesn’t matter.

In fact, this post is more interested in noticing us to a warning, a sign where we’re actually under a critical situation, a situation forcing us to write a brittle procedural code, a buggy one (we just don’t know it yet), an anti-pattern, the temporal coupling.

Temporal Coupling

Or TC for brevity. Based on Wikipedia's definition¹:

Sequential coupling (also known as temporal coupling) is a form of coupling where a class requires its methods to be called in a particular sequence.

Pretty descriptive, but allow me to generalize and extend the definition a little bit:

TC is a coupling when some methods are required to be called in a particular order because there is a hidden knowledge about that order and should be recalled correctly.

Still vague? let’s adhere following example:

We can see line-3 is coupled with the previous line. The snippet most likely implies,

“to calculate the area of the circle, you need to set its radius beforehand. Failing to do so, the program will simply throw an exception.”

See? This is exactly what I refer to as hidden knowledge about the order. Moreover, omitting line-2 will not trigger any lint. You won’t be prompted if you lack something factor to make this program compile. Trying to run it will simply throw an exception. Well, depends on the actual implementation though. The point is you just need to keep this hidden knowledge for yourself, or you write the KDoc² just in case to save yourself and your successors in the future.

Let’s admit the bomb side-effect by the above circle example is pretty straightforward to spot on. You simply receive back the explicit thrown exception report and you instantly appear with your fix. Not a big deal. Now let’s add the second example.

Here we have a few coupled methods, the order is fixed since they share some states mutated somewhere behind those complex multi-layer APIs, and the executions are done in multi-threading.

Given those constraints, one subtle mistake harming the integrated order may probably cause, well, the program still compiles, integration test shows green passes somehow, but sometimes you find it works improperly, unexpected result, intermittent bugs. You have no option left but to plunge yourself back into a massive tangled mess, day and night spent decoding hidden knowledge of every line you/ predecessor had written, all to trace the root cause.

You may wonder, isn’t this example kinda a bit dramatic? Not really. This is based on my experience being junior dev supported legacy code on his 2nd sprint. Sounds hellish? You bet. This second example exhibits the worst BANG effect of TC I’ve witnessed up today.

The Dead-End

Glad to still have you here because the insight part is about to start. Now let’s head back to the very starting example.

As I stated earlier, you have a solid statement for whichever option you represent, but turns out a mere hidden knowledge for you. The call super and your TODO magic are inseparable, coupled, a brittle code you’re producing...

Now, what chances do we have for refactoring? I could say nothing much. What to blame here?

open (Kotlin)/ public (Java) is TC’s agent

I won’t renounce every single open modifier implementation as smell. open for class? Why not? Subtyping is a virtue in OOP world (though I personally suffice with abstract only all this time). open for API? that’s the issue.

Every time you are going to open your method, you should take into account that you may risk its entire hierarchy class system. How so? let me clarify:

  • Is there a contract design that requires the successor to invoke its super call?
  • Is there a contract design that requires the successor should invoke its super call before/ after new implementation?

As far as I remember, there’s no such privilege in today's OOP feature (at least by nature). So if the successor disregards the above points, what could go wrong with your entire class system? — Your answer.

“Read my implementation first, then decide your execution order”, this pretty much sums up what your super method could only hope for. That said, it’s the same as you point a gun to your successors to write their TC structure, so to speak.

CallSuper by Martin Fowler³

I’m pretty sure the framework team who designed the Fragment class aware of the aforementioned risk. But they’re part of Google. They are able to create any workaround to unblock themselves from any particular issue and @CallSuper⁴ is apparently their answer. Solution? Shame. If they could limit its visibility to internal library scope⁵ only, I could respect whatsoever limitation they are having. But the fact is they’re misleading, supporting smell practice that should be eliminated by any chance.

The Mastermind

TC is nothing but a procedural design in essence. But what motivates us to write so?

Take the above examples, what do circle and onDestroy callback samples share in common? We can see both business part executions actually rely on the outcome of a side effect, as some states of a system changed, mutated, see radius for the circle and mCalled flag abstracted over super.onDestroy() invocation. Not want to limited by those examples, as far I’m experiencing, spotting a TC structure always implies a good sign somewhere amid there exists a wild mutation point, a critical point that we cling to all this time. Without it, we then have no reason at all to bundle our procedural calls with a fixed order to remember. We should worry no more about altering the order because it’s side effect free without affecting consequent calls.

Now that we should have grasped much enough about what TC is, we arrive at a point where we should be able to sum up who the mastermind behind the “hidden knowledge” term is.

Mutable Objects

Loads of references suggest preferring immutable over mutable one like stated in Effective Kotlin (recommended one) by Marcin Moskala. The followings are my top 3 highlights:

  1. It is harder to understand and debug a program with many mutating points. The relationship between these mutations needs to be understood, and it is harder to track how they changed when there are more of them. A class with many mutating points that depend on each other is often really hard to understand and to modify. It is especially problematic in case of unexpected situations or errors.

    Couldn’t agree more on this. Similar to everything I’ve been preaching about the journey, the mastermind who gives birth to a bad design, TC.
  2. It requires proper synchronization in multithreaded programs. Every mutation is a potential conflict.

    This can be relatable when you’re the new guy for a big project, you spot a lot of mutation points from the legacy code you support, shared states accessed by multi-thread, and finally new intermittent bug report files in. The inconsistent states one to another debugging session, deadlock, race condition, well well... if I am asked to depict “hell” in the programming world, this is.
  3. When state mutates, often some other classes need to be notified about this change. For instance, when we add a mutable element to a sorted list, once the element is changed, we need to sort this list again.

    Pretty sure Sam Edwards could relate this one after his flow emission incident⁷.

Given the aforementioned drawbacks, it does sound like we try to corner, black sheep-ing object mutation as a smell pattern, error-prone. Well, at least those facts lead us to conclude so. Now, shall we renounce any mutability relevance? Good ambition though, but I’d say still unlikely for today’s enterprise system. We may claim that we totally design our codes with immutability-oriented, but we somehow skip the fact that we literally still depend on framework(s) that rely on mutability idea in essence. Take, for example, class Date from JDK.

My point is that introducing mutating point(s) isn’t totally a sin. Hard to reason code (buoyed by random mutating points premise), a spaghetti code is. A minimal solution I can suggest is by limiting mutability. My 2 cents: design your architecture, a pattern, drive the readers to predict the way you mutate states, take the MVI architecture/ UDF pattern. This way, the more mutating points we can limit, the more maintainable, easier to reason, more scalable our code is.

On the other hand, Kotlin grasps the importance of immutability design, becoming one of the modern programming languages that treat immutability on par with its mutable counterpart. As per this tweet, it’s good to see the team still holds firm to the very commitment, promoting immutability adoption to be as easy as mutable one. A future first-class candidate, hopefully.

Tweets by Roman Elizarov

Elegant Objects — Intermezzo

Did I already realize that TC should be a smell pattern? Yes.

Did I decide it as my personal anti-pattern ever since? Not really.

Even, did I know such a writing structure named temporal coupling? Nope.

Until I stumbled upon an OOP paradigm alternative — Elegant Objects⁸ (EO) by Yegor Bugayenko. Basically, EO renounces some of what we know today’s OOP practices, a new perspective. I find some of its principles fit me, reasonable to work with, Objects Should Be Immutable⁹ (again), Constructors Must Be Code-Free¹⁰, its trump card Composable Decorators¹¹, just to name a few. I just like the way he conveys his objection, some by empirical proven, bold rather than a vague statement, a high recommendation.

Caveat:
After EO, you probably find yourself a bit slower when designing codes at the start. Now you have a new set of standards, you’re no longer what you used to, you’re a better critical thinker now. You have a number of options to be considered before into any decision, all to extract a better design with maintainability and extensibility as your main focus.

Closing

The takeaway from this post is simple. Don’t write temporal coupling code by any chance. Now refer to your codebase, try to spot one. If any, take a lean step and start to refactor it. Don’t even leave it as a temporary tech-debt. TC issue is real, I’ve witnessed the various great damage it could lead to, yet, major developers tend not to see it coming (as far I run myself as a reviewer). Notice the design patterns you’re using, especially the one that mutates objects as it’s a symptom that might lead to producing a brittly TC code.

With the aforementioned credits, external references, give them a shot. Start with a peek, try to reason every statement they concern about, scenario examples, you’ll learn something new there. They’ll shape how you think, make decisions, and contribute to a better design code you will write.

--

--