Apr 30, 2015

Impossible User Flows, Patterns and Code


Product Development Iteration
After an initial spurt of development and v1 of a product, software engineering processes and product life cycles kick in. We move into an iteration phase where a product undergoes continuos improvements and changes to serve its customers and their changing needs. Processes of scrum and agile steadily fit the product to its current and future customer needs. A product at that point has matured so to say even though it might still be in its infancy with regards to business goals.

Business logic and technical debt
Such a mature product needs processes for development. They have complex business logic and every line of code that is changed is tied to some business purpose. Little things start taking a long time to implement and technical debt takes the blame. Many time the product development slows down to a point where a major refactor or rewrite becomes inevitable. Companies solve this problem by adding more developers and building stronger QA teams. Most such companies that survive are making enough revenue to be able to support this growth. They become a big company so to say at that point.

There are no edge cases in code
Having seen pretty simple products reach this kind of complexity and slow down, I believe the iterations break the patterns with which the v1 was developed. Iterations happen in the form of supporting one edge case over other and because of time constraints many of those result in hacks in code that harden the product. In code there are no edge cases. The code might execute as many times for the edge case as for a happy scenario and nothing changes for it. On the other hand, edge cases are not that important hence don't justify the time they require to fix the pattern.

Patterns of the product
Product flows do have patterns. It is very difficult to see those because we see the needs coming one part at a time. Also, there are lot of missing links, things the user can never do that we never see hence never code for. We could place use case feasibility  vs Product into a 2 x 2 as follows:

1. Feasible use cases + Product supports
2. Feasible use cases + Product does not support
3. Infeasible use cases + Product supports
4. Infeasible use cases + Product does not support

The iterations of product generally revolve around #2 above. But when we try to build a pattern for our use cases lot of #4 also falls within that premise. There is also the part of #2 that is way down in priority.

Such scenarios are often seen in onboarding flows. The reason is that registration and login is such a simple process that v1 is put in place fairly quickly without thinking much about future scenarios. Most of the time using some external libraries or gems. Thereafter in every iteration, the new needs are implemented. Here are some of the scenarios that creep into onboarding flows -
  • Forgot password
  • Forgot user name
  • User name validation
  • Access codes
  • 3rd party authentication
  • User type customizations
  • Partner customization
  • Marketing campaign tracking
  • Campaign specials
  • Special URLs
  • Authentication via passcodes
  • Mobile app notifications
  • Web to mobile app flows
The business logic around these can be tremendously complex although the basic use case remain simple. Let us think of some of the use cases which are infeasible in onboarding -
  • Using access code as password
  • 3rd party authentication using local authentication
  • Tracking partner campaigns
  • Authentication via user name and password in URLs
  • Customization for base product
  • Web to mobile app flow on desktop
  • ...you get the point!
Advantages of supporting impossible flows
When we combine the feasible and the infeasible flows including the flows that are way down in priority, we start seeing the pattern of the product. When we see that, we can break things down into functions and components and their responsibility. We can make a list of factors that affect their behaviors. The thought process of looking at the iterations of the product changes. We start prioritizing changes in the product patterns instead of user needs and edge cases.

Product pattern design
In conclusion, failing to maintain the product pattern design will lead to increased technical debt and slow down. On the other hand over focus on product patterns and technical debt will also lead to slow down. Product design should consider patterns an integral part of it. Priorities should be based not only on business needs but should consider product patterns. Some slow down that can improve the pattern will result in much faster iterations in the long run. Think improving a screen vs improving a UI component. One affects a much needed business change, the other improves the overall product. Similarly thinking of functions, components and external factors will simplify flows greatly. Implementing all those additional use cases that no one asked for and that no one will ever use, will actually improve iteration speed and make the products simpler.