How to Empower Iterative Design as a Web Developer

Tar Viturawong
NYC Design
Published in
5 min readSep 25, 2018

How code architecture can impact the innovative culture of your team

Designers can and should challenge developers more when we say something can’t be done! Photo by Melvin Thambi on Unsplash

As a web developer, I feel both amused and abashed whenever I have to explain to a designer that owing to architectural reasons, their idea of a simple change is a massive code surgery for us.

(To be precise, I first feel abashed admitting it, then I feel perversely amused watching my poor designer’s what-on-earth-are-you-talking-about-it’s-so-simple reaction as I nod sympathetically).

There are business and motivational costs when the codebase is stuck this way. Developers feel frustrated because of significant code reworking, and are more likely to resist late requirement changes in future. Designers feel frustrated because their creativity is constrained by some technicality that seems beyond common sense. Product owners are frustrated because the product cannot be refined in the way they envisioned. Teams become less agile, and the product misses out on the final touches that — taken together — might make that difference between a hit and a flop. In the extreme case, I have seen leadership getting burnt and calling for a return to the waterfall process where everything is locked down in advance.

Having inherited codebases in such a state (and having been guilty of plunging a couple into one myself), I have spent some time dissecting and bringing codebases out of this predicament. Here are some of the lessons I learnt.

1. Avoid premature architectural optimization

The truth that whichever web project you’re working on, the design rightly keeps changing as the product idea becomes validated and refined. If a project has just started, there is even more uncertainty. Unfortunately, the start of a project is also when developers feel most tempted to optimize the architecture prematurely, based on the incomplete picture of the product at the time.

When I first studied some very good web application architecture, my first reaction was “wow, this is super bloated”. Later, I would realize that the seemingly bloated boilerplate waslike an impressive — but empty — art museum waiting for art installations of various shapes and sizes to move in. Of course, empty art museums don’t usually get many visitors, and so all that engineering stunt seems pointless. But if you then try to shrink such a museum into a cozy little cafe, you will find that you’ll eventually have make do with just a third of the installations; another third wouldn’t fit and the last third wouldn’t be able to be safely housed. Oops.

2. Anticipate reasonable, canonical requirement changes

A resilient architecture therefore exploits low-effort practices to provide breathing room. It lets you support requirements which you can right now reasonably expect from canonical UI design patterns as seen on the interwebs. You do not need to — in fact, should not— actually implement these reasonably anticipated functionalities in advance, but you should consider an architecture that allows such functionalities to be built without a massive rewrite.

For example, whether or not your chat application’s current design includes an indicator that your conversant is typing shouldn’t have a big consequence in how you would separate concerns in the implementation. You anticipate that the design could end up either way, so you make sure that the “typing in progress” state is easily made accessible if needs be. Similarly, one could anticipate (again, not actually implementing) canonical design variations like:

  • asynchronous validation when building synchronous validation
  • validation-on-type when building validation-on-blur
  • upload progress display even when building a static spinner
  • visual elements with dynamic visibility transitioning their entrance and exit
  • swapping a component for another (e.g. a radio group for a dropdown, a number-based paginator for a dot-based paginator)
  • relayouting — visual elements that were together aren’t together anymore. This is especially important if they happen to share state.
  • state persistence — visual elements with dynamic visibility might change from discarding its state when it disappears then reappears to persisting its previous state.

3. Choose third party libraries that don’t lock you in over multiple concerns

We often rely on third party libraries because we know they have been battle-tested to solve certain problems well. If such libraries do not separate concerns the way you do, you may end up being locked into a UI design pattern that your designer does not agree with.

Libraries can force their opinion of styling, UI state transition or domain logic on you, thereby constraining your code’s ability to adopt more flexible requirements. For example, if you depend on a validation library for validation rules, but it also forces opinion on how to manage error state, you are locking yourself in.

That said, not all teams put equal emphasis on all design concerns, and sometimes things are just good enough. If you sense that your organization is OK with not fine-tuning UI aspect that is locked by your thid party library, you might be able to get away with it. Otherwise, go for libraries that do things well over one concern but are not opinionated in the others.

It is also possible that a framework forces you into expressing — and therefore reasoning — about your business intentions in a way that is secondary to their syntax. If your code expresses more of a framework’s formalism than a your application’s intentions, consider a different solution that leaves less technical footprint.

4. Use resilient code patterns

Another thing that I have learnt is to look out framework-specific implementation pattern that looks like it’s going to be highly sensitive to arbitrary changes.

If you know React, you’ll have heard of its state lifting pattern. As a React lover, I think this pattern is unfortunately not very resilient. How far up you lift a state essentially depends on your component hierarchy and where it is shared. This is where small change in requirement can really wreak havoc in your React codebase. Because of this, I personally prefer to manage state exclusively using MobX, and lift any state that can be reasonably expected to be shared out of the React component hierarchy altogether. But this is quite its own topic.

If the paragraph above was complete gobbledygook, watch out for implementation patterns that make you think of Jenga: if a codebase looks like it will come tumbling down with the next requirement change, re-think how it’s built. If possible, avoid building a Jenga tower to begin with.

Conclusion

Iterating design, development and validation and minimizing the time through each iteration is how businesses can innovate at scale. By anticipating reasonably expected requirement changes and choosing an architecture that has the potential to support such changes, we developers can do our part in empowering iterative design, encourage experimentation, and contribute to an environment where everyone in the team feels inspired.

More recently, I worked with a designer that would have none of oh-its-too-difficult-technically-but-you-don’t-get-it attitude. That was a truly inspiring moment!

--

--

Tar Viturawong
NYC Design

I write dev articles. I code, love, laugh, cry, eat, goof, screw up, celebrate, and wonder.