Sandi Metz also talks about this: http://www.sandimetz.com/blog/2016/1/20/the-wrong-abstractio...
Sandi Metz also talks about this: http://www.sandimetz.com/blog/2016/1/20/the-wrong-abstractio...
If you looked at my code at 10-70% solution complete you'd find so many egregious DRY errors, magic values (likely with a comment above for my own memory), functions that are too long, and a whol ball of mess. If you looked at it 90-100% complete it would be nearly unrecognizable in writing style to what you saw earlier.
Figure out how to solve the problem first, spend all your efforts on that, then figure out how to clean up your code and make things nice/performant/secure/etc.
I've found that as I've gotten better my sloppy is better than a lot of people's clean, which means I'm spending less time on cleanliness, and getting done faster.
With that in mind, its important to have some discipline, to make your 'just good enough' not too crappy. That's the difference between an apprentice and an expert.
People get lazy and sloppy early enough. I would try to encourage teenagers to go for the best and most beautiful solution that they can possibly find.
My approach is to pay down the technical debt before starting the next big change. I consider it akin to prepping my work site. As an example: consider a project where you're modifying some large continuously running system in production, creating an alternate codepath to ultimately replace some legacy codepath. You'll likely add the new codepath first and then migrate some subset of users to it.
There might be some diminishing returns from migrating a long tail of users to the new codepath (perhaps they were better served by the legacy codepath for reasons that don't quite justify the maintenance of both codepaths). And even when they're migrated, you quite likely don't need to rip out the legacy codepath. So at some point, perhaps before all the users are on the new path and perhaps before you rip out the legacy codepath, you'll consider the project a success and move on.
To me, that's quite reasonable. But consider then that any project on the same codebase in the future has to account for both of these potential codepaths. That adds an expense to every future change you make, as you have to maintain and support twice as many things. In my experience paying down that debt will pay for itself over the course of an astonishingly few (but greater than zero) number of future changes.
I mention greater than zero because quite a lot of systems get decommissioned, for one reason or another. If someone is going to bulk delete your code and everything near it, it doesn't really matter how the code smell was right before: the code that smells the least is the code that doesn't exist.
This is why I make various forms of cleaning up code - in-depth refactoring, deleting all unused code and tests, etc. - not the last thing I do when I finish a project, but rather the first thing I do when I start the next project. It's also much easier to explain why you're doing it to outside observers at that point - you're doing it not just because it "improves code smell" you're doing it because it makes what you're trying to do now substantially easier.
The only time I think this can start to fail is if you aren't the person who would be doing the next project / you could do the code cleanup far more efficiently than the person who is doing the next project. In which case this all becomes much more hard, and you should just remember to treat others as you'd wish to be treated. But I'm not sure it totally doesn't work -- for the person inheriting your work, getting to do some amount of refactoring and cleanup may well end up being a decent way of them getting to know the codebase better (if they knew it as well as you they could clean it up just the same).
Beside feeding them with the "just write mediocre code" you are also taking away the beauty of software development: "you have to write something just good enough and move on"...
So no I take nothing away from the art, in the right place and time. And thanks for the personality evaluation!
Responsible engineering isn't targeting the minimum thing that works. That's short sighted thinking that involves taking on way too much technical debt up front to pay for a faster initial release. This method might work if you're writing one-off tiny projects, but it will slam you if the project needs to adapt to new requirements in the future.
That's the difference between writing software and building a bridge. Three weeks before a bridge is completed, nobody can say, "oh BTW, this needs to also carry two trains." With software major requirements can change last minute and you better have written your code to deal with that possibility.
'Right' is definitely subjective, so to you, right be what I call perfect and we're talking about the same thing. But I feel like if someone is hearing me say it doesn't have to be right, I'm excusing bad behavior and shit code. Whereas when I say it doesn't have to be perfect I'm saying a small amount of code smell or some tricky areas are acceptable if they can't be helped given the money and time constraints.
"Make it work, Make it work well, Make it work fast"
I would disagree with beauty == performance, most performance problems I have found to be in the last place I would think them to be :-) On one (moderately large) system it was a hashcode() calculation ;-)
From my own experience, many, many times, definitely a majority of them, when I have had performance issues, and finally found the bottleneck, and fixed it, my code was more beautiful afterwards. Algorithms were simplified, duplicated effort was removed, etc. One major one in my mind was where code was attempted to be reused, but in doing so it caused an O(n^2) behavior, where an O(n) could have been done instead. Upon finding it, and refactoring it, the code had fewer interdependencies, the logic was far more straightforward, both things I find to make for 'beautiful' code, and it ran far, far faster (from 'this particular operation takes an hour' to 'this particular operation takes < 1 second'). Had I invested time up front after making it work to clean up the code (rather than stop at 'it works, good enough') I would have stumbled upon the same fix, even before seeing the performance issue.
Make it work
Make it right
Make it fast
http://c2.com/cgi/wiki?MakeItWorkMakeItRightMakeItFastThe stake holder should be your customer, not your boss. Even if that dynamic is broken, you don't need to tell them it's shippable until you're happy with the code.
Just be aware that it can be easy to get lost in a yak-shaving exercise doing your cleanup refactoring. I recommend timeboxing this phase to no more than an hour, or prioritizing one or two specific tasks so that you don't go off the deep end.
Don't forget that others are depending on you to get their work done. There may be a 2-hour cleanup task somewhere else in the code that will help your teammates far more than the itch you want to scratch here and now.
When I read comments like this I think to myself "How can they possibly be so productive that refactoring can be done inside of an hour?".
Then I remember that some developers split up projects/problems into really small components and can complete a feature or two in a single day. Well then an hour is actually a fair bit of time to spend refactoring.
"Convert your working code to good design" is not a trivial task, and like you say, a big part of it is to pick your battles.
Sometimes you just have to argue for the effort being expended, and my managers all had the foresight to push for doing it right when it had large ramifications.
There's done as in the spirit of the assignment is completed to a satisfactory level of cleanliness and then there's perfectly done, which means you'll never ship it. I think that's hard for a lot of people to come to terms with, people of all skill levels. The code needs to be easy to navigate and the patterns should be as easy as possible to learn and emulate for others entering- but you don have to start there, you just need to wind up there before you call it done.
I think this explains it neatly.
If I'm working on some familiar problem or in a familiar area I tend to write nice, clean code from the beginning. As I have more and more experience in various areas it reduces number of times when I have to do this process of dirty code->refactor.
It's a bit the opposite of Vigil, a programming language who punishes functions throwing errors.