The other day I asked myself “what are some good software engineering lesson I’ve learnt in the last years?”. After reflecting on it I’ve decided to write my thoughts in case it helps other software engineers out there.
Let me first tell you a little story of how I learnt these lessons.
Some years ago, I challenged myself to building a structural analysis application targeted at engineering and architecture students. I wanted to grow as a software engineer as well as demonstrate potential employers that I got what it takes to become a good software professional. The result was InkStructure, a native OSX application written in Swift using the native Cocoa framework. When I released it, it received a relatively warm welcome.
Needless to say, my design for the architecture of the application had lots of flaws, which made the software hard to evolve; I was a bit inexperienced building big applications at the time. Some of the bug fixes or new features I added during the following months after the initial release made me struggle with the code I had written… a couple weeks earlier. I’ve learnt tons by reflecting on those design “mistakes”, reading about software architecture, talking with other experienced engineers in my current and past companies and experimenting with more scalable solutions.
Thanks to this retrospective exercise and some extra years of experience, I’m currently re-writing the entire application, and this is how it looks today (not released yet):
The code behind this version of the app is much more modular, easy to understand and test and it serves the users better. So, what happened during this time that made me make better decisions architecting the code in this new version? I could think of a few, but today I want to tell you about two.
“Knowledge” without practical experience is only partial knowledge: one learns by doing. Making the mistakes yourself and suffering the consequences is your best teacher.
I can’t stress this enough. You can read tons of books on software architecture; they’ll make you think about ways of architecting your code you might not come up with yourself alone, but that’s just the first step and it’s certainly not enough. If you want to fully interiorize those learnings, apply them! See how those design decisions stand the test of time inside real software.
As important as knowing about different architectural patterns is knowing when to use them, the way they should be applied, the trade-offs you’re making when choosing one over another, and how to adapt them so they fit your case. And this, this can only be learnt by making mistakes and suffering the consequences, or by getting it right and reflecting on why that was a good choice. Once you’re able to understand why a particular design did or didn’t scale well, you gain a new insight that’s hard to grasp just by reading or attending talks.
Okay, so you need to write lots of code, but your current employer doesn’t have that many opportunities for you to develop your skills this way. Maybe they don’t consider you experienced enough, or maybe you’re part of a big company and just work on a very small part of its system. What should you do? Don’t wait for your company to give you the opportunity to work on a big project; create those opportunities yourself. A good way is working on side projects on your free time: find something you want to build, build it and, most importantly, release it to the world. Challenge yourself trying projects that sound complicated at first, which require you to learn new things.
Now, you can keep on reading architecture books, but this time look for practical applications of your newly learnt patterns. And, if you build something related to what your company does, show it internally! it might be something your coworkers or even customers find interesting. You can also get feedback and advice from other more senior engineers.
An elegant architectural solution that doesn’t satisfy the customers’ needs is like a beautiful painting that’s never shown to anyone.
Yes, you can do it for fun, for the pleasure of it or for the sole sake of learning a new technology, language or framework. But to create good software, software which could fall under the “useful” category, it first needs to satisfy the needs of real users (I’ve been told Amazon calls this “Working Backwards”). And to learn what architectural solutions work and which don’t, you need to hand it to users, fix the bugs that bother them and add the features they need. Did your code accommodate those changes fine?
A software that’s not satisfying its users will hardly need to evolve in any direction: if no one is using it, what changes are you going to make to the code? Good architecture distributes the code in a way which allows it to evolve gracefully.
When I started working on my app, InkStructure, I was too focused on the technical part of the project: all my code had 80% test coverage, I shoehorned design patterns everywhere, all the code was distributed in loosely coupled modules (not really, but so I thought)… but I missed this very important prerequisite; I only took into account my users’ needs once the software was completely finished and architecturally sublime. Big mistake! I found how to meet what users wanted to do with my software, I needed to do big changes which broke seemingly unrelated parts of the application; my sublime architecture was falling to pieces.
One example about something that users needed and I couldn’t satisfy was having new tools that allowed them to modify the geometry in more advanced ways, like moving the structural nodes or adding concentrated loads on the structural elements. The way how I chose to handle mouse events for the selected tool didn’t scale; let me explain.
The user’s mouse events mean different things depending on the current tool: sometimes a mouse drag should be interpreted as the user drawing a new structural bar, but some other times, it might be the user is moving or zooming the drawing. What I thought would be the best idea (and turned to be not so good), is to check for every tool inside each event handler that all three views (the geometry, loads and results view) implement, and handle the events appropriately. Here’s a small extract of the code for the “geometry view”:
This way, creating a new tool in the application forced me to touch the code of every event handler (mouse moved, mouse dragged, mouse clicked…) in all three views (that is three views times four different events, a total of twelve methods!). It took me an average of one week to create a new tool in the application and I added regressions more often than not; the event handling for different tools started to overlap, giving rise to some complex cases.
I suffered the consequences and learnt from them; In the new version of InkStructure, every tool includes its own event handlers, independent from the rest, making it really easy to add a new tool. And better yet, testing how each tool interacts with the drawing canvas is a piece of cake. When this version is live and users require a new tool, this shouldn’t take more than a few hours of work, and anything that’s already working can hardly be broken.
A corollary of this might be a praise to the Lean Startup methodology (derived from Toyota’s Lean Manufacturing). Start by using the minimal architecture your application needs to satisfy your customer needs. You’ll need to hold back your desire for reaching technical perfection before releasing your product to the world. Then, learn fast from what you users need; what’s working for them and what isn’t. Following your learnings you’ll start recognizing patterns on how the software needs to evolve, so you’ll be in a good position to make more educated architectural decisions.
I’ve written a book which is already in pre-sales (take a look here), woohoo! In this book I condense the most important learnings of working in the InkStructure application. The book is meant for those engineers who want to work on writing applications that solve mechanical/civil engineering problems. If you want to know more about it or give me some feedback on it, please don’t hesitate to contact me.