You can normally fix bad code - fixing bad data structures is not usually easy or even possible.
It's why I've still not fully bought in to 'release early release often'.
I prefer to defer releasing for production use until really satisfied with the structures - this way you have no barrier to ripping the foundations up.
If not 100% comfortable with the model - prototype a bare metal improved one (schemaless DBs ftw) - if it feels better start pasting what logic/tests you can salvage from the earlier version and move on.
I'm in the position of maintaining a legacy codebase. I feel like I've shown up half-way through a game of Jenga and management still wants me to play the game with the same speed as the guy who played the 1st half.
Meanwhile, he's been promoted to start work on a brand-new Jenga tower since he's demonstrated such remarkable success in the past.
I've always, only half-snarkily, said that if you have never had to maintain/modify someone else's code then you probably write code like an asshole. Writing code that is both easy to understand and maintain and correct can be difficult, lots of people just go for the latter.
Unfortunately as you pointed out, especially in large companies, people can get promoted before the deficiencies of their previous work become clear. This leads to people never learning because the feedback loop is too long, or worse, they never deal with their past code so are oblivious to all its shortcomings and just think they are awesome. Also management tends to reward based on accomplishments today without an eye for costs to be born down the road, which gives perverse incentives to "get it done" programmers who leave mountains of technical debt in their wake for others to deal with.
> Writing code that is both easy to understand and maintain and correct can be difficult, lots of people just go for the latter.
They believe they go for the latter, but actually they don't. If their code was easy to understand and correct, it would have fewer defects to begin with.
Your second paragraph I totally agree with. I've dealt with such code. Sometimes, I can halve its volume simply by applying local correctness-preserving transformations. That is, without even knowing what the code is doing. I even spotted some bugs in the process.
'correct' is always only about a given specification, that is right for a limited period of time, assuming needs are well understood. It is very well possible to write satisfactory code one day that becomes inadequate the next.
I won't deny the presence of bugs though, there is endless evidence that bugs always exist.
Correct, I was talking mainly about code that 'works' (i.e. is 'correct', for the given spec) but is highly sub-optimal, confusing, tightly coupled with other code, etc...
If only there was a way to measure programmers on the robustness and potential of their code, and not just on "they wrote a lot of it."
The system seems to be that it is much more advantageous to your career to rapidly produce gobs of spaghetti -- and confound everyone around you -- than to build elegant code that enables everyone around you. You look better when everyone but you is confounded; you look replaceable when everyone around you can extract as much value out of your clean, powerful code as you can.
The whole point of continuous delivery is that correcting things like data structures is no longer the big deal it once was because it happens frequently. Rather than letting months (or years!) of data migrations pile up, you have a few days worth (or weeks). In my opinion, it is best to ship something that works today and have a system in place that makes correcting it as painless as possible.
That's the real problem with the current model. The data structures will never be perfect and you cannot know how they will change. Yet they do. Then all the FUD from the last migration that scared everyone prevents the team from due diligence and correcting issues when they are discovered. The team waits until the problem comes to a head, management has to be involved, new FUD is created and people dream about perfect data structures to prevent this whole mess.
As a new programmer I know I should ship a lot faster than I do, but focussing on data structures makes me really slow. I can usually jump a hurdle with a hack on my extant structures, but this introduces code complexity and leaves me at square one when a similar hurdle appears elsewhere. I try to be disciplined about fixing stuff at a data structure level, but changes there set off change propagations throughout the code. Or I introduce hacks into the data structures, which then become convoluted and start acquiring code of their own.
I find unit testing does help with all this. It forces exposure of the data stuctures, essentially documenting them. And good coverage gives a list of breakages and sometimes helps find elegant repairs. But I also find myself wanting high level-tests, I guess essentially integration tests, that check not components but overall behavior, and I find writing and maintaining these becomes a real problem.
But I really, really wish I had better tools / procedures for thinking through the problems and designing a proper data model for solving them.
"Old school developers - achieving a lot with little"
I'll quote:
> [Ken Thompson] debugs only via printf statements, hardly ever uses unit tests, starts his projects by designing the data structures and then works bottom up, with throwaway test stubs.
Also, Joe Armstrong, the father of Erlang:
> He uses prototypes to solve the hard problems first, and for debugging, just uses print statements. He is a critic of Object Oriented Programming, and favours functional programming languages like Haskell. He never uses an IDE, preferring just Emacs and the command line (no mouse required).
Also, Jamie Zawinski:
> During development he hardly ever uses unit tests, believing it slows things down - he thinks there's a lot to be said for getting the code right first time. In his view, its a matter of priorities, "do you want this to be good software or do you want it to be done next week - pick one because you can't have both".
That article summarizes parts of the book Coders at Work, which has all sorts of other interesting stuff in it. The book's web site gives brief bios for all the programmers who are interviewed in it: http://codersatwork.com
Agree, fixing bad data structures is much more painful than fixing bad code. The reason is that the deployment of the refactoring has the complexity of a new deployment, or even higher.
However, given that at large organizations updates and deployments can easily become political issues, it's a good habit to deploy often. That makes your life easier when trying to deploy new changes because those who are watching or performing the deployments get used to it - and the errors occuring during such deployments.
You're just hung up on your definition of "release", which is "release for production use". "Release early release often" doesn't dictate how you release your application, just that you expose it in some form (private beta, public beta, pre-release) to the real world for vetting. Projects that fail to vet their assumptions are more prone to poor data structures and over-engineering.
release early release option gives you chances to fix your mistakes before its too late. waterfall works great if you can manage to get your data structures perfect before production. it begins to fail when it becomes prohibitively expensive to fix mistakes after production, where not unexpectedly breaking old code has precedence over deploying new.
The idea was to use a prototyping language for the Demo and then a production worthy language for release. The problem was a lot of Demo's ended up in production for a literally decades because people tryed to create 'over architected crap' which got scraped or takes decades to release.
Honestly, I think the real problem with most early Development strategy's is so few people have a clue how to actually design good software. Great solutions are designed to create minimal systems to solve the problem which are flexible because they are minimal and programmers can alter code, rather than people conceiving every possible change request up front.
One of the big tricks here is not to be religious about any technique. Pick what fits best given the information that you've got for the situation that you find yourself in and don't be afraid to change the mix over time as the situation changes or you find yourself in the possession of new knowledge that is inconsistent with your past views on the state of affairs.
If you blindly adhere to some method or other then you're going to find out exactly what the limitations are so you are going to have to be flexible and you're going to have to mix-and-match as time goes by.
As an example, 'agile' comes up here with some regularity. It's a great principle but it's not a religious thing. Feel free to adopt some but not all of agile to come out ahead. Adopt all of agile in a religious fashion and you'll come out a loser.
Lifecycles are either sequential or iterative. But there's a lot of differences in the lifecycles, and many more models than usually assumed. For example spiral development, throwaway prototyping, evolutionary prototyping, staged delivery, design-to-tools, design-to-schedule, modified waterfall (a sequential, iterative process) and more.
Read McConnell's 'Rapid development' for a walkthrough of the ones above and then some.
It's why I've still not fully bought in to 'release early release often'.
I prefer to defer releasing for production use until really satisfied with the structures - this way you have no barrier to ripping the foundations up.
If not 100% comfortable with the model - prototype a bare metal improved one (schemaless DBs ftw) - if it feels better start pasting what logic/tests you can salvage from the earlier version and move on.