Asymmetric IFs help you keep DRY


Symmetric IF

The following is an example of what I started calling Symmetric IF because all the branches of the IF have equal dignity and weight.

public Observable<File> getSelectedFile() {
   if (selectedFile != null) {
      return selectedFileSubject.asObservable().startWith(selectedFile);
   } else {
      return selectedFileSubject.asObservable();

This code is written to leverage the capabilities of ReactiveX/RxJava, a library that tried to bring support to Reactive Programming in the Java ecosystem. Mainly used for Android development, it can be found also server side.

One design pattern RxJava relies quite heavily upon is chaining methods to define complex behaviors  for its objects starting from relatively simple primitives.

Even without any experience of this library, it should be relatively straightforward to see what that code does: if the class is holding a reference to a file return an Observable that insists on that class, otherwise return an observable with its default settings.

We have 1 IF with 2 return statements. The focus is on the need to diversify the path depending of whether a file has been previously selected or not.

Asymmetric IF

Let’s rewrite the previous code using what I call Asymmetric IF instead.

public Observable<File> getSelectedFile() {
   Observable<File> selectedFileObservable = selectedFileSubject.asObservable()
   if (selectedFile != null) {
      selectedFileObservable = selectedFileObservable.startWith(selectedFile);
   return selectedFileObservable;

Or if you prefer a slightly more compact version:

public Observable<File> getSelectedFile() {
   Observable<File> selectedFileObservable = selectedFileSubject.asObservable()
   if (selectedFile != null) selectedFileObservable = selectedFileObservable.startWith(selectedFile);
   return selectedFileObservable;

Here a branch of logic is promoted as “main flow” while all the variations are mapped with specific, generally smaller, IF branches. There IF branches are closed before the method returns.

Natural choice for the “main flow” is to take the IF branch that in its Symmetric implementation would consist of a subset of the instructions required in the other logical branches.

This code is totally equivalent to its Symmetric counterpart but puts the accent on a different aspect of the logic: the focus on providing a clear separation between what’s common for both situations and what’s specific for one of the 2 possible states.

Symmetry looks more elegant

I’m quite sure that if asked, almost anyone will find the symmetric version more elegant and prefer than the asymmetric one. That makes it also the version that should be preferred.

That should come at no surprise. It’s well known that symmetry is very welcome in visual arts. It’s no different in music where symmetric rhythms like 4/4 are perceived as easier to understand and relate to, they feel more natural. Probably the main reason for this perception is precisely that symmetry is pervasive in living things, meaning that we experience it all around us since we were born.

The code based on Symmetric IF looks visually well balanced. If represented on a flow chart, it would be mapped to 2 parallel branches of very similar length, both rejoining the main flow in the same point.

An Asymmetric IF code instead may even look ugly. Represented on a flow chart it would look like a straight path with a small re-entrant deviation at a certain point.

Still, I believe there are many reasons to prefer asymmetry when mapping a piece of logic that branches out.

Understandable code wins over a high score based on its cosmetics

As we mentioned before, a Symmetric IF makes evident to the reader that the logic changes depending on a certain status (e.g. a file is selected or not). This approach maps closely the way we think when we decompose a problem. That’s probably how we would describe verbally a problem to someone inheriting or reviewing our work.

An Asymmetric IF takes things a step further. Given a certain flow (described perfectly by the Symmetric IF), what else can be said about it? What are the common patterns? Where do the 2 (or more) flows differ from each other?

That’s the kind of information a maintainer of the application will be interested in when troubleshooting an issue or learning a new code base. In that scenario we are usually mainly interested in knowing where the main path diverge and why it’s not working properly. Helping our readers identifying what’s the main path, where it diverges and keeping that divergence as small as possible will make them extremely appreciative of our additional effort and forgiving for the lack of pure elegance of our writings.

When writing code, try to put yourself in the shoes of a maintainer with very little time, not in those of your code reviewer.

Asymmetric is DRY

I think there are very few topics capable to raise an instantaneous and unanimous consensus among developers like the DRY principle.

Still though, when using Asymmetric IFs we are giving away all of its benefits in the name of elegance. “Elegance” which in turn is a just a nice word to say that what we care the most about is to satisfy our hedonistic egos. To show the world that we are cooler.

To do that, in our example we are happy to repeat a call to asObservable(). In this case it’s no big deal, probably nothing really different from a cast. But what if that calls behind the scenes is triggering the observable engine to re-evaluate its whole tree of objects? That’s something that can easily lead to unexpected behaviors.

What if that call was a more explicit call to a function that does something expensive and at risk of failure like opening a connection to a DB?

Allowing the same code to be repeated is not just about extracting similar pieces of code into a single reusable function. Another (less evident) repetition want to avoid is to see the same chain of methods used in multiple places within the same code base.

Symmetry looks easier to understand but common Engineering metrics suggest it’s probably not

Based on my personal experience, I would say that in order to look nice and well balanced ~80% of the methods based on a Symmetric IF end with multiple return statements.

The rules of cyclomatic complexity state that a method that has multiple return statements is more complex to understand, maintain and debug than one that has only 1 return statement. This is so true that an extremely popular tool to perform static analysis of code like Sonarqube has a rule MethodWithExcessiveReturnsCheck dedicated to ensure the amount of return statements present in a method doesn’t exceed a certain threshold.

Assuming standard settings, in our simple scenario Sonarqube would rate the implementation based on Symmetric IF to be ~33% times more complex than the one based on the Asymmetric IF.

Asymmetric IFs are easier to debug

Symmetric IFs are usually based on a one-liner per logic branch. As we have already mentioned in another article, these chains of methods can make debugging harder.

A part from the additional complexity of having to deal with multiple step-into & step-out, when we provide a single exit point from the method we are asking a maintainer to define only 1 breakpoint to see what value is returned by the method.

Similarly, because the job of pre-chewing the logic and organize it in distinct sections has already been done by the person who wrote the code, we are simplifying the work of anyone debugging that piece of code. In fact we are avoiding them to have to go through the additional effort of testing the same line of code for all different possible inputs to verify that it behaves properly in all possible cases. Or to identify what code can be considered tested enough if tested for one of the possible cases and combo of inputs.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s