The often used phrase “Keep it simple, stupid,” abbreviated KISS, is solid advice in the field software development. We strive for simplicity, planning and refactoring continuously to ensure our code is extensible, reusable, and all those other words ending in ‘ble’ that apply. But what is simple? And how can things be kept simple in a complex system with several complex domains?
Simple is subjective. To some it means few moving parts, to others it means code that reads like a book, even if it’s repeated in several places. Disagreements can easily arise when deciding what simple means. Keeping an open mind is critical with regards to the definition of simplicity, because all sides can have valid arguments.
Complexity, like simplicity, is also subjective. Code can be composed in a complex way, however the component parts may be implemented simply. So can only simple things be over complicated?
There’s a saying: work smart, not hard, that applies to software development more deeply than in many other domains. Finding smart solutions usually means less code, fewer moving parts and a more concept-based approach.
To some developers a concept-based approach can be perplexing. Simplicity masquerading as complexity that remains obscure until scrutinised further. It can also, however, be over-engineered — six classes used where one might have sufficed until a later date.
Does this mean those developers who find smart solutions complex aren’t up to scratch, or should the codebase cater to the needs of the team and be legible to all? In my opinion, no. Smart trumps legibility every time, for the simple reason that legibility is subjective and based on the abilities of the reader. Some are baffled by lambdas and some aren’t. This doesn’t mean teams should avoid lambdas, it means teams should shift dead weight.
All of this begs the question: can something that seems complex always be reduced to something simple? In most cases, yes. A video I watched (which I will need to find later as the author escapes me) stated that in most cases he could walk into a company and reduce a code base by a factor of 80%. That is to say that a 100,000 line codebase could be reduced to roughly 20,000 lines of code.
Part of the reason this is, in my eyes, true, is because teams wilfully introduce technical debt, qualifying its introduction with ‘We’ll fix it if we need to later’. This is a flag to me that says “we know we aren’t doing it properly.” This is not counterintuitive to Ayende’s JFHCI — in fact it works with it: work smart, not hard. His example of hard coding is not an introduction of technical debt, it’s a forward thinking solution with minimal down payment now.
So how do we keep things simple in complex domains? Here’s a bulleted list of how it can be done:
Limit your abstractions
Don’t introduce a phony abstraction in order to make it mockable. If you see an IFoo with a single implementation Foo, you’re overcomplicating and you’re missing the point of interfaces in the first place. Code should be written to concepts as per Ayende’s limit your abstractions post.
Test outside in
Test your components using their public API. Don’t test the components internals because you’re then testing implementation. This allows two benefits:
- Get the internals working correctly, quickly, with minimal fuss and with good test coverage
- Once complete, it allows you to refactor into any concepts you may have uncovered along the way.
Work smart, not hard
Highly focussed components with specific jobs connected in a smart way. Some people might not understand them; it’s your job to enlighten them. If they still don’t understand it, cut them loose and hire someone who does. The inverse is true too, though — if everyone disagrees with your code it’s either wrong and you need to learn, or it’s right and you need to leave.