Building Software With Smarts
Two of the things I enjoy most about software development are problem-solving and adding value. The problem-solving aspect gives me the mental stimulation that I enjoy, as well as the buzz of being innovative. However, the notion of creating value is what makes it worthwhile. But what exactly does it mean to create value, and how can this value be increased?
I'm not talking about simply making the software fulfil a set of requirements or functional specifications for a target customer, I'm talking about those non-functional aspects that can determine whether the software is helpful and a joy to use, or some obstacle that becomes a burden that one is obliged to use (because there are aspects that are depended on).
Actually adding value requires us to look beyond the airy-fairy world of buzzwords and focus on real, measurable aspects such as performance, flexibility and ease-of-use. Achieving them doesn't need to consume significantly more time and budget to achieve if the mindset is there from the start. For me in the thick of it, my mindset isn't based on the question, "How can I maximise the value?" Instead I ask questions like:
- How would I solve this problem as a human being?
- How would I expect to use this software as easily, yet effectively as possible?
By asking these sorts of questions, I'm more easily equipped to come up with an appropriate implementation that gives the software a greater degree of smarts, which in turn achieves a combination of factors like improved performance, greater flexibility and usability. This increases the value as a result, and makes the effort worthwhile.
As a recent example, I was involved in a piece of work that I can be proud of despite the challenges that arose – simply because I designed the solution from the outset for flexibility. This was prompted by receiving fuzzy requirements at the outset that indicated a strong certainty of changes being required at a later stage. The task was to create a back-end system that receives mapping data in CSV format for each part of a batch production process, and store these mappings in a database for later retrieval by another component of the solution.
The biggest challenge was that fields to be mapped between tables had different column names in the respective CSV files, and these usually couldn't be changed as they came from independent suppliers. As indicated previously, there was also a strong likelihood of these field names changing as the project gained momentum, especially since the details of the requirements were fuzzy as a result of this project being new for the client and their suppliers.
Whatever the changes, the software solution should remain flexible and not require significant internal changes (that would require redeployment) to adapt – especially if the functionality was to be exactly the same. Internally, the terminology for business entities and attributes remained consistent, while the different CSV field names were stored in the human-editable configuration file as aliases to these internal attributes.
For a developer, it might be tempting to create a whole bunch of different settings, one for each field alias with setting names as methodical as "EntityAField1Alias". However as someone filling in the values, I don't want to maintain a whole bunch of settings if one will do, and I like any necessary instructions to be as handy as possible (e.g. embedded in the comments) for reference. In my perspective, the goal was to store a set of mappings from an internal field to a set of alias mappings, and let the internal workings handle the reading of that. So instead of storing many individual aliases, I stored a set of alias mappings. This becomes a bit more user-friendly from closer alignment to the business problem domain, as well as having the flexibility to cope with multiple aliases. As an aside, it was rather fun using regular expressions to extract these mappings and the aliases inside them, though non-developers don't need to know about the technical intricacies, let alone what regular expressions are.
As a result, the solution adapted to changes in the input specification with ease, thereby requiring very little time for re-work and no need to recompile or redeploy the solution. This was invaluable when such a change occurred at the last minute – a true example of being agile. In the long run, if this client needs to have further changes made, having such a flexible design will certainly save them money.
Of course, flexibility is only one benefit of building greater smarts into software. Another is performance.
Take Sudoku for example. There are many implementations of Sudoku out there, as one can see online in programming articles, source repositories and commercial sites. From what I've seen, a great many solve these by brute force – guessing numbers as long as they fit, and backtracking if necessary. This can take a very long time to complete. I myself wrote a solver back in 2005 while at university, using logical deduction and elimination to decide where to place numbers. Given a puzzle (from the newspaper) as input via text file, my solver was able to solve it instantly. Even a 16x16 'hard' Sudoku puzzle was able to be solved in less than half a second on an average PC – much to the amazement of my peers.
How did I do this? I looked at the way I solve Sudoku puzzles, and translated that into code. I don't randomly choose numbers and see where they fit, instead I look at each cell and work out the possible numbers that could be placed there. The first two logical rules for simplifying these sets of 'candidate' numbers are the easiest: finding cells with only one candidate, and finding candidates that appear only once in any given region (row, column, or rectangle). The latter is the easiest to apply visually, while the former requires more mental effort because of counting.
Naturally, computers are much faster than us at doing this, but only as smart as what they are programmed to do. The big challenge is how to capture our own smarts and translate what comes naturally into code. From experience, a good (though maybe obvious) strategy is to divide the problem into manageable sub-problems and conquer each of them individually. This not only encourages well-structured code (busting the myth that smarter software is unreadable or harder to maintain), but it also achieves these aspects that increase value in a more productive manner.
When working on your current or next project, know the problem and its sub-problems you are trying to solve, and try to keep this question in mind:
"How would I do this myself?"
And remember - the more smarts, the better.