Yes yes yes. When I'm coaching junior developers (or developers that like over engineering) this is what I try hammering home more than anything.
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.
In the real world, at least mine, once it works you get pressure to move on. Both my own mind saying, it works so it's good enough, and external pressure from a stake holder who says it looks good lets move on. I don't feel like I get to that latter 20% - 30%. As such, I try to be thoughtful on my design, but I also realize the need to get stuff done. It's a balancing act.
The pressure you put on yourself is your responsibility. Don't pretend it's something you have no power over.
The 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.
True, we are the ones responsible for our integrity and we often live with our mistakes, paying for poor design choices during later maintenance cycles.
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.
> 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.
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.
Yeah, I don't see a lot of talk of how hard it is to clean up code. A lot of programmers just aren't good at it, so maybe it's just as well that they don't do much of it...
"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 cleaning up code has positive business ramifications - for example, I took the time to design a component carefully in the past few weeks for work, and when it came to start a new user story for a nearly identical feature for another view, the careful cleaned up design carried over seamlessly & got finished in about a day of work. This will be assuredly reused again for another feature with nearly identical logic/models, and thus saved the company almost 2x the time of development.
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.
Yeah, sometimes even though it works from a functional stand point, you can say it 'isn't working' and still be honest to yourself, since the messy code won't work in terms of long-term maintainability.
The trick is to think of the last little bit that makes it complete in terms of the 'cleaned up' approach, which means you bake in the cleaning up/abstracting as necessary for completion.
When I use the word done I mean done to the point that I'm marking the task/project/module done and telling someone it's done. It's not like a PM is checking the CI server and noticing the build is green and the unit tests pass and telling me to get on to the next task...
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.
In Erlang and OTP in Action, Joe Armstrong is quoted as saying (and I'm paraphrasing here 'cause I don't have the book in front of me) - "First, make it correct. Then, make it beautiful. Then, if you need to, make it performant. Because 9 times out of 10, making it beautiful also makes it performant enough."
"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 ;-)
'Place I think it to be' is completely orthogonal to beautiful code. :P
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.
It is; Joe was restating it differently ('right' is ambiguous. 'Beautiful' is subjective, but at least a more appropriate adjective for code quality), and also offering the insight that prioritizing code quality also often gives you performance.
I suppose I'm similar to the way you write code. However I'd like to note that this 10%-70% in my case (and may be true for you) is applied only in cases when I'm implementing something very unfamiliar to me.
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.
That's definitely true for me. If I'm writing a traditional web app using a language or framework I'm very familiar with then there the difference between 10% and 100% in cleanliness is nearly negligible. But those aren't the interesting projects. When I look at my code and it looks like shit and I've got notes and commented out blocks I feel happy because I'm working on something that my brain can't just spit out a complete answer for. It also feels even nicer to be near the end and see how nice it all looks once my thoughts have formed into a solution.
When I teach teenagers I tell them, there's never time to do it right OR to do it again. Time is money; Engineers are paid to solve the problem and not make a beautiful thing. Get used to just-in-time solutions and just-good-enough implementations.
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.
I do not like this approach, and I think it is not the right way for medium to large software projects. Maintaining lots of interacting 'just-good-enough' code is a nightmare - and it can easily cost you way more than you saved writing it.
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.
Sorry, but I feel I need to be harsh to express how much I disagree; it sounds like you are a terrible teacher.
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"...
It wasn't a personality evaluation, it was a critique of your teaching methodology.
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.
For many, maybe most, engineers it is targeting the minimum thing that works. For every platform or structural problem, there are 1000 apps. And they get old faster than cheese in my refrigerator. Shipping sooner for less cost is a primary metric!
I disagree on the phrasing. When I'm teaching beginners or juniors in a school or professional environment, as I said, I hammer that it doesn't have to be perfect. It never will be. Don't over engineer things. But everything NEEDS to be right. Where right means the problem is solved, the code is understandable, the patterns (and there must be a pattern) are easy to discern and emulate. Along with proper code formatting- I am amazed at how many people in the workforce don't even understand how important formatting is! FFS most IDEs will auto format with a keystroke.
'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.
At least one problem with this advice is you do not define "just good enough". When I was a new developer "good enough" simply meant it passed all obvious tests I could think of.
I'm not sure I object to just-good-enough solutions, but, there's still an awfully large benefit from paying down technical debt, and I hope your students appreciate that.
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).
"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."
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.