How would you maintain legacy applications [closed] - legacy

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
How would you maintain the legacy applications that:
Has no unit tests have big methods
with a lot of duplicated logic have
have No separation of concern
have a lot of quick hacks and hard coded
strings
have Outdated and wrong
documentation
Requirements are not properly documented! This has actually resulted in disputes between the testers, developers and the clients in the past. Of course there are some non-functional requirements such as shouldn't be slow, don't clash and other business logics that are known to the application users. But beyond the most common-sense scenario and the most common-sense business workflow, there is little guidance on what should be ( or not) done.
???

You need the book Working Effectively with Legacy Code by Michael C. Feathers.

Write tests as soon as you can. Preferably against the requirements (if they exist). Start with functional tests. Refactor in small chunks. Anytime you touch code, leave it cleaner and better than when you started.

Two things.
Write unit tests as you have the chance.
Once you have enough unit tests to be confident, start refactoring.
The rate at which you accomplish this may be slow.... Typically, you're supposed to "just maintain it" not fix it.
During the "learning how to maintain it" phase, however, you can write a lot of unit tests.
Then, as bugs are found and enhancements requested, you can add yet more tests.
It's Agile, applied to legacy.

I have seen, worked and am working in a codebase which satisfies all the conditions that is mentioned in the question :-)
The approach followed in maintaining this codebase is NOT TO BREAK ANYTHING. FWIW, the code works and the end users are happy. No one is going to listen to the developer cries that there is duplication of code, hard coded strings etc. We just steal some time to fix whatever possible and take the utmost care to not introduce new bugs..

I think I would create a small set of Up To Date information: What Action calls which functions etc.
From there, I would look at refactoring. Duplicated Logic seems to be something that could be refactored, but remember that
That can be a huge task when you realize in how many many places that logic is called and
Two function that seem similar may have a tiny difference, i.e. a - instead of a +
I think the biggest urge to resist is "Just rebuild the whole damn thing!" and get an overview of the system first, to demystify the beast.

sudo rm -rf /
But more seriously, I think it has to be evaluated. If the code continually is a source of requests for change and the changes are difficult then before long you have to consider if it is worth it to try and refactor/re-engineer the system into something more modern. Of course this isn't always practical, so you often end up with just a few people on the team who are responsible for maintaining the legacy parts. As much as possible, everyone on the team should be able to maintain all parts of the system......
One more thing that I think is important is to track the amount of time and effort that a team spends working on a legacy system doing maintenance/feature requests. These metrics can be convincing when evaluating the planning of a new effort to replace the legacy systems/components.

I basically agree with everything Paul C said. I'm not a TDD priest, but anytime you're touching a legacy codebase -- especially one with which you're not intimately familiar -- you need to have a solid way to retest and make sure you've followed Hippocrates: First, do no harm. Testing, good unit and regression tests in particular, are about the only way to make that play.
I highly recommend picking up a copy of Reversing: Secrets of Reverse Engineering Software if it's a codebase with which you're unfamiliar. Although this book goes to great depths that are outside your current needs (and mine, for that matter), it taught me a great deal about how to safely and sanely work with someone else's code.

Related

Avoiding TDD making big refactorings harder [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
I'm still relatively a beginner in TDD, and I often end up into the trap where I've designed myself into a corner at some point when trying to add a new piece of functionality.
Mostly it means that the API that grew out of the, say, first 10 requirements, doesn't scale when adding the next requirement, and I realize I have to do a big redesign on the existing functionality, including the structure, to add something the new stuff in a nice way.
This is fine, except in this case the API would then change, so all the initial tests would then have to change. This is usually a bigger thing than just renaming methods.
I guess my question is twofold: How should I have avoided getting into this position in the first place, and given that I get into it, what are safe patterns of refactoring the tests and allowing new functionality with a new API to grow?
Edit: Lots of great answers, will experiment will several techniques. Marked as solution the answer I felt was most helpful.
How should I have avoided getting into this position in the first place
The most general rule is: write tests with such refactorings in mind. In particular:
The tests should use helper methods whenever they construct anything API-specific (i.e. example objects.) This way you have only one place to change if the construction changes (e.g. after adding mandatory fields to the constructed object)
Same goes for checking the output of the API
Tests should be constructed as "diff from default", with the default provided by the above. For example if your test checks the effect of a method on field x, you should only set the x field in the test, with the rest of the fields taken from the default.
In fact, these are the same rules that apply to code in general.
what are safe patterns of refactoring the tests and allowing new functionality with a new API to grow?
Whenever you find out that an API change makes you change the tests in multiple places, try to figure out how to move the code into a single place. This follows from the above.
Make your tests small. A good test calls maybe 1-3 methods on the subject of the test and does some assertions on the result. These test will only need to change when one of these three methods change.
Make your test code clean. If you haven't already read 'Clean Code' by Robert C. Martin. Apply the rules to your production code AND your test code. This has the tendency to reduce the affected surface area of any refactoring.
Do refactor more often. Instead of (possibly unconsiously) waiting until you must do a large refactoring, do small refactorings a lot.
If you are faced with a huge refactoring, break it down in a couple (or if necessary a couple hundred) tiny refactorings.
In this case, I suggest you get the features locked down, and the iterations short.
Because the iterations are short, features will be grouped together into smaller, isolated groups. It lessens the need to think up of some grand design, which might not be adaptive to the needs of the users. The code for this week, will only work with the code for this week. That lessens the chances of new stuff mucking up the old stuff.
Yeah, it's a problem that is hard to avoid with TDD, since the whole idea behind it is to avoid the over-engineering caused by doing big designs upfront. So with TDD, your design is going to change, and often. The more tests you have, the more effort is required for each refactoring, which effectively discourage it, going against the whole idea behind TDD.
Even though your design will change, your basic requirements will be rather stable, so at the "high level", the way your app works shouldn't change too much. That's why I advise to put all your tests at the "high level" (integration testing, kinda). Low level tests are a liability because you have to change them when you refactor. High level testing is a bit more work, but I think it's worth it in the end.
I wrote an article about that a year ago: http://www.hardcoded.net/articles/high-level-testing.htm
The following might help..
Follow good coding standards and practices, including TDD and utilising design patterns to produce well structured designs. The application as a whole should then be easier to extend to include new features.
Good separation of concerns. If your API is well separated from the other functionality (calculations, database access etc) then changing the functionality without changing the API or vice-versa should be easier.
Use BDD to provide automated tests at a higher level (ie. more like user tests than unit tests). These should help to give you confidence while refactoring even if all your unit tests are breaking due to the refactoring.
Use a Dependency Injection Container such as Windsor. By abstracting away your class dependencies when those dependencies change it creates much less re-work (particularly if you have many unit tests) than having them coded into your classes.
You shouldn't find yourself "in a corner" with TDD. The eleventh test shouldn't so seriously change the design that the first ten tests need to change. Think hard about why so many tests need to change - look at it in detail, one by one - and see if you can come up with a way to make the change without breaking your existing tests.
If, for instance, you need to add a parameter to a method that they all call:
you could leave the existing fewer-parameter method in place, delegating to the new method and adding a default parameter;
you could have all the tests call a utility method (or perhaps a setup method) that calls the old method, so you need to change the method call in only one place;
you may be able to let your IDE do all the changes with a single command.
TDD and Refactoring work symbiotically; each helps the other. Because you emerge from TDD with comprehensive unit tests, refactoring is safe; because you have the organizational, intellectual, and editing tools to refactor freely, you can keep your tests well synchronized with your design. You say you are a beginner at TDD; perhaps you need to be growing your refactoring skills while you learn TDD.

When should I release my code? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 7 years ago.
Improve this question
I've been holding off on releasing a library I wrote because it is the first library which I'll be releasing publicly. Here are my concerns:
The library isn't complete it is in a very usable state, I'd say it is version 0.3, however it still lacks a number of features which I would like to at some point implement, and control how they're implemented (meaning not merging someones implementation).
I'm fearful of criticism, I know there are a few things which should be reorganized/refactored, but I wrote the initial class quickly to be functional for another project I am working on.
So when is the best time to release? Should I just throw it up on github and work on the issues post-release? Or should I wait until I refactor and feel completely comfortable with what I have written?
Most classes/libraries I see are always very elegantly written, however I have not seen any in very early release stages, are a lot of classes fairly sloppy upon initial release?
Release early, release often.
Criticism is a good thing as long as its constructive. Ignore the haters, pay attention to the folks filing bug reports and commenting.
The internal structure of the code matters, but it matters more if it works for its intended purpose. In general, refactoring will change how code works internally but will not affect how it is used. Same inputs and outputs.
You need to get something half-way
useful first, and then others will say
"hey, that almost works for me", and
they'll get involved in the project.
Linus Torvalds
Linux Times (2004-10-25).
It depends on why you are doing this. If it's to provide something useful and it's useful and has benefits that no other library has, then go for it. Just list the status and what's coming next.
If you are doing this to point to on a resume, get it in good shape (the code, not necessarily feature complete). Imagine a future employer poking around the code to see what it looks like, not downloading and running the code.
Whether you release the code in an incomplete state or not, it's always worthwhile having enough documentation to allow users to understand how to use the library.... even if it's only API docs. Make sure that anything incomplete is tagged as TO DO - it helps to maintain a target list of tasks to complete, and lets users know that the feature/method/whatever hasn't been forgotten.
Providing a set of code style/standard documents (perhaps with architectural notes on class relationships) allows other developers to contribute more readily, and in a manner that enhances the library rather than making it a hotch-potch of spaghetti code. It's never easy releasing a library, then having to refactor, while maintaining backward compatibility for users who have already taken up and are using that library in a production setting.
EDIT
Don't be afraid of criticism... it goes with the territory.
Some critcism can be constructive (take heed of that).
There'll be plenty of other people who criticise your code (for whatever their reason) without being constructive, or who just denegrate your work. The difference is, you've produced the goods, they probably haven't ever contributed to any OS product/library.
Users expect you to fix their problems immediately, or to write their code for them to use your library, or simply say "it doesn't work" without any explanation of what they mean. You have to learn to live with that 24x7x365.
But just once in a while, somebody will thank you for saving them hours of work, or for providing something useful... and suddenly all the stress and hassle feels worthwhile.
I read a document by Joshua Bloch, a pricipal software engineer at Google that talked a lot about the best type of API design. Basically, once you release it, it is more or less set. He says
Public APIs are forever - one chance to get it right
You can check out the slides here. It's definitely worth reading. I have a PDF of it as well; let me know if you need it.

What is "over-engineering" as applied to software? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
I wonder what would be a good definition of term "over-engineering" as applied to software development. The expression seems to be used a lot during software design discussions often in conjunction with "excessive future-proofing" and it would be nice to nail down a more precise definition.
Contrary to most answers, I do not believe that "presently unneeded functionality" is over-engineering; or it is the least problematic form.
Like you said, the worst kind of over-engineering is usually committed in the name of future-proofing and extensibility - and achieves the exact opposite:
Empty layers of abstraction that are at best unnecessary and at worst restrict you to a narrow, inefficient use of the underlying API.
Code littered with designated "extension points" such as protected methods or components acquired via abstract factories - which all turn out to be not quite what you actually need when you do have to extend the functionality.
Making everything configurable to "avoid hard-coding", with the effect that there's more (complex, failure-prone) application logic in configuration files than in source code.
Over-genericizing: instead of implementing the (technically uninteresting) functional spec, the developer builds a (technically interesting) "business rule engine" that "executes" the specs themselves as supplied by business users. The net result is an interpreter for a proprietary (scripting or domain-specific) language that is usually horribly designed, has no tool support and is so hard to use that no business user could ever work with it.
The truth is that the design that is most easily adapted to new and changing requirements (and is thus the most future-proof and extensible) is the design that is as simple as possible.
Contrary to popular belief, over-engineering is really a phenomena that appears when engineers get "hubris" and think they understand the user.
I made a simple diagram to illustrate this:
In the cases where we've considered things over engineered, it's always been describing software that has been designed to be so generic that it loses sight of the main task that it was initially designed to perform, and has therefore become not only hard to use, but fundimentally unintelligent.
To me, over-engineering is including anything that you don't need and that you don't know you're going to need. If you catch yourself saying that a feature might be nice if the requirements change in a certain way, then you might be over-engineering. Basically, over-engineering is violating YAGNI.
The agile answer to this question is: every piece of code that does not contribute to the requested functionality.
There is this discussion at Joel on Software that starts with,
creating extensive class hierarchies for an imagined future problem that does not yet exist, is a kind of over-engineering, and is therefore, bad.
And, gets into a discussion with examples.
If you spend so much time thinking about the possible ramifications of a problem that you end up interfering with the solving of the problem itself, you may be over-engineering.
There's a fine balance between "best engineering practices" and "real world applicability". At some point you have to decide that even though a particular solution may not be as "pure" from an engineering standpoint as it could be, it will do the job.
For example:
If you are designing a user management system for one-time use at a high school reunion, you probably don't need to add support for incredibly long names, or funky character sets. Setting a reasonable maximum length and doing some basic sanitizing should be sufficient. On the other hand, if you're creating a system that will be deployed for hundreds of similar events, you might want to spend some more time on the problem.
It's all about the appropriate level of effort for the task at hand.
I'm afraid that a precise definition is probably not possible as it's highly dependent on the context. For example, it's much easier to over-engineer a web site that displays glittering ponies than it is a nuclear power plant control system. Redundancies, excessive error checking, highly instrumented logging facilities are all over-engineering for a glittering ponies app, but not for a nuclear power plant control system. I think the best you can do is have a feeling about when you are applying too much overhead to your features for the purpose of the application.
Note that I would distinguish between gold-plating and over-engineering. In my mind, gold-plating is creating features that weren't asked for and will never be used. Over-engineering is more about how much "safety" you build into the application either by coding checks around the code or using excessive design for a simple task.
This relates to my controversial programming opinion of "The simplest approach is always the best approach".
Quoting from here: "...Implement things when you actually need them, never when you just foresee that you need them."
To me it is anything that would add any more fat to the code. Meat would be any code that will do the job according to the spec and fat would be any code that would bloat the code in a way that it just adds more complexity. The programmer might have been expecting a future expansion of the functionality; but still it is fat.!
My rough definition would be 'Providing functionality that isnt needed to meet the requirements spec'
I think they are the same as Gold plating and being hit by the Golden hammer :)
Basically, you can sit down and spent too much time trying to make a perfect design, without ever writing some code to check out how it works. Any agile method will tell you not to do all your design up-front, and to just create chunks of design, implement it, reiterate over it, re-design, go again, etc...
Over-engeneering means architecting and designing the applcation with more components than it really should have according to the requirements list.
There is a big difference between over-engeneering and creating an extensible applcaiton, that can be upgraded as reqirements change. If I can think of an example i'll edit the post.
Over-engineering is simply creating a product with greater functionality, quality, generality, extensibility, documentation, or any other aspect than is required.
Of course, you may have requirements outside a specific project -- for example, if you forsee doing future similar applications, then you might have additional requirements for extendability, dependent on cost, that you add on to the project specific requirements.
When your design actually makes things more complex instead of simplifying things, you’re overengineering.
More on this at:
http://www.codesimplicity.com/post/what-is-overengineering/
Disclaimer #1: I am a big-picture BA. I know no code. I read this site all the time. This is my first post.
Funny I was just told by my boss that I over-engineered a new software produce we're planning for mentoring (target market HR people). So I came here to look up the term.
They want to get something in place to sell now, re-purposing existing tools. I can't help but sit back and think, fewer signups, lower retention, if it doesn't allow some of the flexibility we talked about. And mainly, have a highly visual UI that a monkey could use.
He said we could plan future phases to improve the product, especially the UI. We have current customers waiting on "future improvements" that we still aren't doing. They need it though, truly need it.
I am in the process of resigning so I didn't push back.
But my definition would be.............making sure it only does as little as possible, for as cheap as possible, and still be passable for the thing you say it is. Beyond that is over engineering.
Disclaimer #2: This site helped me land my next job implementing a more configurable software.
I think the best answers to your question can be found in this other qestion
The beauty of Agile programming is that it's hard to over engineer if you do it right.

stopping code rot [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
Given that working features are better value for a company than good code at any given point in time and that bad code makes adding more features difficult:
How do you stop the code from deteriorating over time?
At any point, getting a feature to work is higher priority than getting it to work with well engineered code which takes longer. Even though as time goes on the effort for each feature increases.
How do you stop the code turning into an un-maintainable mush over time?
A comprehensive set of unit tests
edit: and it's helpful if they are well written to accurately test all your classes / interfaces in a human readable way.
edit 2: as svelil says, refactor your code to keep it clean, but being able to do this is a consequence of having the unit tests.
Unit tests will not stop the rot on their own. I can still write horrendous, unmaintainable code that passes a unit test.
A better answer is unit tests. + regular refactoring + peer review (either at pairing stage or after) + standards
You do know there is no silver bullet.
Use an iterating development process:
Implement function
Refactor code
Jump to 1.
You have to have some discipline, but without it you will end up having a mess. Even if you think "Oh, the code is readable enough", don't skip step 2. Of course, development should always be accompanied by testing.
Periodic refactorings, particularly in the section of code in which you're currently working (the "Boy Scout" rule).
The top and accepted answer in this question should be "Comprehensive unit tests".
This answer is not going to repeat that.
However adding Unit Tests to an existing project is much harder and generally is poor imitation of what can be achieved if the application code it self were written with Unit Testing in mind.
Also extreme schedule pressure can make it impossible to consider, those who haven't experienced using unit testing its still a big punt.
My recommendation in those conditions is write as well as you can to achieve the current goals. Be prepared to refactor existing code before adding new functions. Whilst unit testing would make this approach way way safer, this approach is still useful even without unit testing.
Of course good general testing and QA is important.
A decent set of coding standards.
They don't need to be complete, but they should mean that you know what things like your brace-indentation is so you have less to think about (and it means that places where the code was rushed and not formatted properly will stand out like a sore thumb)
In my job, as we approach code freeze then the 'fast and dirty' approach is sometimes necessary. This usually results in some pretty dodgy code that works but offends thine eyes.
However, immediately after shipping there is a period of relative calm. This is the perfect opportunity to revisit some of that smelly code and knock it into a bit of shape.
It helps to keep a list of areas that you would like to tidy up and assign some sort of priority to them. Despite how good you believe your memory to be, you WILL forget.
This approach works quite well for me but I suppose it depends upon your particular project workflow.

Should I prepare my code for future changes? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
Should I prepare my code for possible/predicted future changes so that it's easier to make these changes even if I don't really know if these changes will be required anytime?
I am likely to get lynched for my opinion on this, but here I go.
While I have had this hammered into me over years of reading idealistic articles and sitting through far too many seminars and lectures categorically stating the nirvana like benefits of this, I too had similar questions in my mind. This line of thought can lead to massive over-engineering of the code, adding many man hours or more to design, development and testing estimates, increasing cost and overheads, when in reality this is not often the case. How many times have you actually reused your code or a library. If it is going to be used in many places, through numerous projects, then yes you should.
However, most of the time this is not the case. You will often find it more economical (in time and money) to only refactor your code for reuse and configurability when you actually know that you are going to use it again. The rest of the time the real benefits are lost.
This is not, I repeat NOT, an excuse to write sloppy, poorly designed, poorly documented code. This should be a fundamental that is so wholly ingrained in you that you could not break it, but writing a class for reuse is a waste most of the time as it will never get reused.
There are obvious exceptions to this. If you are writing third party libraries then obviously this is not the case and reuse and expansion should be key to your design. Certain other types of code should be obvious for reuse (Logging, Configuration etc.)
I asked a similar question here Code Reusability: Is it worth it It might help.
Within reason and certainly if its not much effort.
I don't think you can always apply this, as it can make you over-engineer everything and then it takes too long and you don't make much money. Consider how likely the client is to implement something, how much extra it will take to prepare for it now and how much time it will save later.
If it requires a lot of work, but makes sense to save money, you could raise it with the client.
I seem to be in disagreement with a lot of people here, who say always - but I've seen a lot of things where effort has been put into make future features easy to implement ... but they've never been implemented. If a client hasn't paid for the time spent making the feature easy to implement, that's money straight off your bottom line.
Edit: I think its relevant to point out that I'm coming from an agency environment. If you are working on code for yourself, you can probably predict future development with a greater level of certainty, and so its probably feasible to do this in more cases.
yagni.
http://en.wikipedia.org/wiki/YAGNI (*inserted by friendly editor :-) *)
fix the bugs in that horrenous code you're writing today.
refactor when the time comes.
If you work in a refactoring-friendly lanuguage I'd say NO. (In other languages I'm not sure)
You should make your code as loosley coupled as possible and keep things as simple as possible. Stay specific and dont generalize to unknown use cases.
This will make your code base prepared for the things the future will bring.
(And frankly, most anticipations of the future tend to be sufficiently off-mark not to warrant coding for it today)
Edit: It also depends on what you're doing. Designing apis for external users is not the same as developing a web app for your company
Yes -- by doing less.
You won't know what the future requirements for your code. The best preparation for the future is not to implement anything that's not needed right away, and have good unit-test coverage everything you do implement.
Scalability in your code is one thing you should always consider.
The more time you spent today in catering for scalable solutions, the less time you will spend in the future when actually expanding
Predicted or very likely changes - yes, generally it's good to have them in mind.
"Take anything that might ever happen in the universe into account" - no. You don't know what could happen, trying to cover for everything unknown is just over engineering.
Remember that most of you code will be changed/refactored. If you know that you will have to change your code within the next week, prepare it. But don't start making everything exchangeable and modular by default. Just because "maybe in the future" you shouldn't create a framework, if three lines of code do the job for now.
But think twice, if the system behind makes refactoring difficult (databases).
One thing I learned in my mere year of coding for the company I work for, everything you do, no matter how perfect you think it is will come back haunting you for an update or needs to be altered because client X suddenly decided not to like it.
Now I am making my code highly customizable so when that day comes to do some adjustments, it would be ready in no time and I can continue with my work.
In a word, yes.
In a few more words, you should always make your code as readable as possible, include comments, and always assume that at some time in the future, you will be called upon, or someone else will be, to modify the code.
If that someone in the future comes across a block of code, uncommented, unformatted, with no indication of what it does or should do, then they will curse you forever :)
No, never. Write good code that is easy to reuse/refactor but preparing for half thought out enhancements is, imo, the brother of premature optimisation; you'll likely end up doing things you don't need or that push you down a certain (possibly non-optimal) design path at a future date. As mfx says, do the minimum required now and unit test everything; it makes extending code a doddle.
In two words: yes, always.
What you describe is part and parcel of being a good developer.
See On Writing Maintainable Code
The obvious answer is YES. But the more important question is how!
Orthogonality, Reversibility, Flexible architecture, Decoupling, and Metaprogramming are some of the keywords that address this problem. Check out chapters 2 and 5 of "The Pragmatic Programmer"
I find it is generally a better strategy to design a "change-accommodating" architecture than trying to specify specific changes that might (or might not) happen. It is a good exercise, though, to ask "What may change in the future?", and then resist the temptation to prematurely implement potentially unnecessary features, but rather have such possibilities in mind when creating the application architecture.
I find that there is a parallel here to something I heard on test-driven development recently. The person talking about it had observed that while at first it could be a little annoying to always write unit tests and think about how your code can be written to be testable, it turned out that at some point it just begins to come naturally to write test friendly code.
The point is that if you always write with modifiability in mind, you might end up doing it more or less by reflex, thereby making the overhead of writing the extra code very small. If you can reach a state where high quality, extendable code is what comes naturally to you, I think that would definately be worth the initial cost. I do still believe, though, that you must to it in moderation and that sometimes it's just not the right thing to do for a given customer.
Two simple principles to follow:
KISS
Always avoid dependencies
Thats a good start
Yes, but only by writing maintainable, easily refactored code.
You should definitely NOT try to guess what might be required in the future. Such efforts are not only pointless and time-wasting for your current targets, but are more often than not counterproductive when any future changes become apparent.
This is really important. It needs to be done well, but takes experience.
If I count up the number of edits (via "diff") after implementing a typical requirements change, numbers like 10-50 are common.
If I make a mistake on any of them, I've inserted a bug.
So personally, I always try to design to keep that number down.
I also try to leave instructions in the code for how to make anticipated changes. If I'm maintaining code, I really appreciate it if the prior author also did this.
To balance the effort with the
benefits is the skill of design.
Not all our code needs to be flexible. Some things will not be changing.
No wasted effort. Finding the right parts to devote our attention to.
Tricky.
Yes, always think of where your code may need to evolve in the future. In the current project I am working on there are thousands of files and every single one of them has atleast one revision to it. Even leaving aside bug fixes plenty of those revisions are to make way for additional software features.
I wouldn't change my could to prepare for an unknown future feature.
But I would refactor to get the best solution to the current problem.
You can't design against an unknown (future), and as other people have said, trying to build a flexible design can easily lead to overengineering, so I think that the best pragmatic approach is think in terms of avoiding things that you know will make it harder for you to maintain your code in future. Every time that you make a design decision, just ask yourself whether you are making it harder to change things in future, and if so, what you are going to do to limit the problem.
Obvious things that will always cause problems:
Scattered configuration information - you need to be able to check and change this stuff easily
Untested code - you need tests to make changes with confidence
Mingling of storage and output concerns with the core logic - you will switch database instances and output formats, if only for testing
Complex architecture - you need to have a clear mental model of the system
Arrangements that require manual intervention or updates to keep them running