Reduce code complexity by rethinking user experience

Sairam Krish
8 min readFeb 11, 2019
Photo by Kevin Ku from Pexels

IMHO code complexity can be reduced drastically by rethinking the user experience.

Single responsibility principle is a generic programming principle which can be applied anywhere in programming. I believe this is religiously followed while writing the backend services. But when it comes to frontend components, many times we try to merge the responsibilities and build heavy components.

This is a recurring pattern that I keep seeing.

  • Applications incrementally grow with new features
  • Becomes cumbersome to add new functionality on top of existing features.

Situation 1:

  • Wireframe looks amazing but demands a bit complex UI component. For the same need, proven UI component could solve the problem 100% but match wireframe only 90%
  • IMO, we should go with proven UI component than building them in-house ( at this point of Lyric Studio ).
  • UI development team should negotiate with UX team & convince them on the same.
  • Wireframe shouldn’t be thought as a hard constraint. Wireframe should remain a way to capture the idea.

Situation 2:

  • Wireframe conveys a user journey. The user journey deviates from how this kind of user journey exists in other enterprise products → for the similar need
  • IMO, UI development team should negotiate with UX team and bring down the complexity.

Example scenarios:

  • Data driven application centered around a table component
  • Scheduling application centered around a calendar component

Let’s take table component as an example scenario and dive deeper.

UI Components

In the world of reusable UI components, heavily nested components increases the code complexity as the applications evolve. Here we are not talking about the nested nature on the DOM. We are talking about the heavily nested business responsibilities. Composing components and maintaining higher order components makes complete sense. But at times, we loose this boundary and start building tightly coupled heavy components. Signals to understand this:

  • Does many of the business features happen inside one MasterComponent ?
  • Does the team spend more time on becoming an expert in using the MasterComponent or make it a SuperMasterComponent that can accomplish bigger objectives ?
  • Is the business feature straight forward but getting it into the MasterComponent with all other dependencies makes it so hard ?

Table component which drives the data driven application

Let’s say we have an online shopping cart application. We like to have a data table to drill down on the data.

example shopping cart

Features we would like to have

  • View orders and their basic details
  • View orders with product details so we can analyze
  • View orders based on shipping status whether delivered / in progress / canceled etc
  • View orders with payment details so we could analyze the payment data
  • View orders with customer data so we could analyze customer information
  • View orders with shipTo details, so we could analyze locations we served
Credits goes to Ag-grid. Taken from https://www.ag-grid.com/example.php#/ for educational purpose.

One table which does all the above

Of course, all the above features can be done in a single table with libraries like ag-grid. Observe how amazing it looks with so much fine grained control over the data.

So why not buy/build a rich data table component which can do everything

If your business need is to build a best possible table, then of course, go ahead.

If table is a way of expressing your data and core business is not about building best possible table, then there are so many ways to make the UI component as lightweight as possible and the business outcomes as heavy weight as possible.

Let’s say we decide to build our own rich table component.

  • Team starts adding features — layer by layer on top of an open source table component
  • Features could overlap/conflict with each other bringing in different needs
  • A breaking functionality now could have cascading breakage of other functionality
  • While adding any new feature, team spends more time in understanding what’s already going on in those flows than what need to be done for the new feature. This slows down development
  • After few months, everyone in the team will shy away from refactoring / code cleanup because they all believe too many things are going on and doesn’t want to disturb the current stability
  • Backend API(s) performance slowly goes down and complexity goes up. More complex optimizations emerge which only less number of team members can maintain in future.
  • Team tries to fit in new features making minimal change to existing flows, sometimes without thinking, if we do it afresh, does this business flow needs this much complexity.
  • Dead code / Unused code starts to grow, since partially cleaning up certain areas and leaving the rest as it doesn’t stop the application from running.

How can we solve it better with Single responsibility principle

One approach to reduce the responsibility of a component (or to build light weight component) is by transferring the control to other components to take care of different responsibility.

Transferring control to other components

Most of us may be already familiar with following samples. Usually they are referred as different usability. But there is a huge benefit from code complexity perspective., since they break the responsibilities into different flows.

Observe the below table, which decouples row and row details.

credits : https://uxdesign.cc/design-better-data-tables-4ecc99d23356 Taken for educational purpose to showcase a scenario
  • Responsibility of a row is just to fetch it’s required details
  • On clicking a row for details, it just passes the control to completely new component which takes care of what need to be done.
  • In contrast, we could have achieved same flow with a group by column and a nested table. But that would make the whole thing more complex.
  1. A Bug in nested table, would break parent table view.
  2. A bug fix in parent table or feature addition on parent table need to ensure nested table functionalities are intact.
  3. While fetching data, we need to decide if we pre-fetch the nested data or on demand
  4. If we have sort on parent table, need to ensure nested table view is not broken
  5. If we need to have sort on child table, need to ensure parent is stable.
  6. What if nested child table has a sub child ? In that case, repeat all the above checks

Using a Model or Multi model

  • We could also transfer the responsibility by delegating to a model or multi model components — View examples
  • This clearly segregates who does what.
  • It makes the base table light weight on functionality and data needs
  • Based on user’s intent, they move to different areas

Contextual actions

Contextual actions to transfer control
Row to other related actions

Contextual actions like above will also help to decouple responsibilities and transfer the control to other respective components.

Multiple views for same data

There is no rule that says, for example, “Transaction data” should have one table view. The same “Transaction data” could have multiple table views in the front end with different business objectives. Think of it like different database views applied on same set of tables exposing just enough information required in different context

Here is a realistic example, in a share market investment website, we could have a table for all our investments

Investment holdings details
Other views on the same data

We could fit in current holdings, transaction details, tax computations, profit & loss details, etc in a single table. Wow., from user experience, I could accomplish all my minds desire in one place. But mostly as an user I don’t come to one screen with 25 goals in mind.

By providing different views, which in turn has same base data presented in that particular context, we make flows light weight.

Trust & Reliability

By combining heavy features together and presenting them as single window of operation, it brings the risk of loosing users trust on the whole application. How ?

Let’s say we have 25 functionalities on a single table. From user’s perspective, it is this table which let’s them achieve 25 functionalities. Whenever the user is unable to achieve his intent, he emotionally thinks this table is not reliable / stable. From user perspective, there is always some functionality is broken. Old bug in a different functionality may get fixed., but a new functionality is now unstable. Time is an illusion. It makes the user believe, he always finds something wrong and he no longer trust what is displayed.

It’s like if we buy a car and everyday some issue pops up. Though it could be on different parts, overtime you loose trust on the car and won’t rely on it for a long ride. After a longer period of usage, you start loosing the trust on the brand (maker) so you won’t buy the next car from same brand.

By taking users to different flows, we provide them transparency and a chance to empathize with the application (as it’s a being). If things go wrong in one flow, they can empathize, let’s say, A, B, C flows are stable and D and F flows are unstable

Further read

Summary

I completely agree that from user experience perspective there would be a strong push in making UI components heavier.

My 2 cents — Wireframe shouldn’t be thought as a hard constraint. Wireframe should remain a way to capture the idea. It is okay to slightly compromise on user experience, if code complexity can be reduced multi fold. Best user experiences many times turns out to be simple and lightweight.

Single responsibility principle can make

  • front end development more stable and reliable
  • Testing (manual / automated) becomes simpler since there won’t be combination of scenarios to validate.
  • It helps in avoiding cascading failures of functionality. If some thing breaks, only that functionality breaks.
  • Lowers the risk in continuous delivery. Even if something breaks, it is easier to make peace with that area of the application and rollout a fix as soon as possible.

Again, there is nothing wrong in composing components, provided what gets composed has their own single responsibility and their side effects & failures are limited to it’s own boundary.

--

--