I know that sounds strange, but bear with me. Software should absolutely do what your client paid for it to do, otherwise you won't stay in business for very long. But that is not the primary value of a working piece of software. The primary value of software is its ability to change.
This isn't an original idea on my part, it comes from Uncle Bob, a renowned veteran programmer who has lots of great advice for coding professionals. I don't mean to appeal to authority so much as give credit for the idea so that I may expound upon it without false pretenses. Not everyone likes Uncle Bob's advice, and maybe it's not for everyone. That's okay. It works out quite well for me. What follows is his idea, from my experience, in my own words.
The primary value of software is its ability to change. What do we mean when we adopt this premise? The fact is, as long as software is in use, someone will ask for changes to it. Long after the estimated budget is spent and the contract has been fulfilled, someone using your software will want it to change. We want to be able to make quick and easy changes, because that will take less time and cost less money. Developers will also be happier and more relaxed as they work in the code; they won't be fighting deadlines and constantly sweating it out for two hours past closing time.
This is why we practice clean coding, test-driven development, and refactoring the code's design. We strive to make the system easier to understand, and for its design to pop right out to other developers. Remember that 'other developers' includes ourselves three months after we last touched the code. To help other developers and ourselves in the future, it should just make sense where things fit and how things work. Well written unit tests, paired with carefully crafted production code, help us keep our systems simple, loosely coupled, and easy to change.
Making software easier to change takes diligence and discipline, and at first this seems like it adds time and too much effort. Maybe that is true early on when you're first trying things out. But it is worth all the extra effort and any homework you can do to practice it, and it does become second nature. It doesn't take long for it to kick in, either. Once you know how to recognize things, you recognize them quickly and often.
We can learn techniques that give us the ability to spot coding problems early on. Developers call this 'code smells.' We look for issues, or sniff out smells, such as duplicated knowledge, poorly named variables, functions that are too long, complex function signatures, awkward object instantiation procedures, and more.
An easy way to build momentum with diligence is to follow the Red, Green, Refactor TDD cycle. Start with a failing test (RED), write just enough production code to pass the test (GREEN), and then refactor (REFACTOR). We look for the smells early and often, and therefore we clean them up early and often. That way, we're always doing the refactors when refactoring is a small task, and not a large, daunting undertaking. Of course, big refactors will come along once a larger design emerges, but they will be easier to make because most things will fit and fall into place.
Another technique that works for me is to push my code to Github and proofread it there. Github is a great "read-only" editor for code. "Read-only" in quotes because you can edit files there, but for the most part you're just browsing the structure of the files and the code. About three or four times, I go over the components and tests that I have recently written. I slowly scroll up and down each file, making sure that what I wrote is sensible and easy to follow, and giving it the squint test. You literally squint your eyes and see if the shape and spacing of the code is elegant. I also review the tests to see if setting up and using the objects is simple and straight-forward, and doesn't expose too much internal knowledge.
Those are just a few ideas and techniques for keeping an eye on your code's ability to change. Software design is a subjective practice with some well-crafted general patterns and solutions for achieving conventional results. Maybe you have some of your own that work for you, and you should definitely write about them and share them. I certainly hope this has been helpful to anyone out there, and I hope it has shed some light on the mysteries of software craftsmanship.