In my work (writing scientific software in C++), I often get asked by the people who use the software to get their work done to add some functionality or change the way things are done and organized right now. Most of the time this is just a matter of adding a new class or a function and applying some glue to do the job, but from time to time, a seemingly simple change turns out to have far-reaching consequences that require me to redesign a substantial amount of existing code, which takes a lot of time and effort, and is difficult to evaluate in terms of time required. I don't think it has as much to do with inter-dependence of modules, as with changing requirements (admittedly, on a smaller scale).To provide an example, I was thinking about the recently-added multi-user functionality in Android. I don't know whether they planned to introduce it from the very beginning, but assuming they didn't, it seems hard to predict all the areas that will be affected by the change (apps preferences, themes, need to store account info somehow, etc...?), even though the concept seems simple enough, and the code is well-organized.How do you deal with such situations? Do you just jump in to code and then sort out the cruft later like I do? Or do you do a detailed analysis beforehand of what will be affected, what needs to be updated and how, and what has to be rewritten? If so, what tools (if any) and approaches do you use?
How can I better manage far-reaching changes in my code?
design;requirements management;change
The short answer is that it is difficult, but not completely impossible to manage changes. You have to realize though that changes always happen and they're most likely new ideas that have never been considered in scope before.Now onto the long answer: there are many different strategies you can consider to manage changes all with pros and cons. Below is a short list from the viewpoint of a programmer to handle changes in their code:First of all do not commit to a change request until you fully understand it and have it planned. You may be ordered to do it anyway, but at least in such situation you have the option of pointing a finger at your superior if the blame game comes.When you happen upon some code that gets changed a lot, refactor it in such a way so that the same kinds of changes are easier to implement in the future. Sometimes refactoring to design patterns will help. Refactoring takes time, so always make sure you plan and estimate your times for this. Also whenever you feel you need to refactor then it is most likely time to consider writing tests. A test suite always helps out refactoring job by pointing out regression bugs. If you're doing Test Driven Development (TDD) right you should be able to refactor easily.Always stick with the simplest solution. This is to avoid over-engineering, i.e. painting yourself into a corner. It is always easier said than done, but doing TDD helps out with that as you should resolve the tests by making the simplest possible solution for them. You know you have either an under-engineered or an over-engineered system when changes are hard to test and implement, the sweet spot in the middle is usually simple to test and implement. Difficult tests usually characterized in difficult to understand set-up/tear-down code; usually due to all the dependencies that need to be initialized. In such cases you can utilize a mock test framework to ease things out and refactor the class to use less dependencies (moving some functionality to other classes).Consider implementing some meta-functionality (such as configuration file, designer, scripting, Domain-specific language, or plug-in functionality) to your app. Meta-functionalities makes some kinds of changes easier to handle or implement than writing new source code.Make your program smart enough to handle changes. The obvious drawback is the time it takes to implement meta-functionality but the benefit is that you'll avoid monkey work in the future.Read up on the SOLID principles (by Uncle Bob). If you're doing your tests right, you will end up following the SOLID principles on your code anyway. I've written this in a Stackoverflow answer about following principles over design.The only drawback with SOLID code, by following it to the letter, is that you'll end up with a lot of classes (ravioli code) and some developers find this problematic and do god-object code instead which is a very lucurative anti-pattern. Given the project is large enough I personally use IoC frameworks for handling class initialization (with complex object graph hierarchies). Finding the balanced sweet spot between ravioli and god-object is usually solved by writing tests as the class dependencies are blatantly noticeable in unit-tests.It's worth mentioning Michael C. Feathers' book Working Effectively with Legacy Code. There are a lot of great tips and hints on how to manage changes on old code or code written by someone else. Most of them boil down to writing tests to find out how existing code works and proceed to refactor it, which is something I mentioned in the second point above. He is actually quite specific about it and made an process algorithm on how to implement changes on legacy code:Identify change pointsFind test pointsBreak dependenciesWrite testsMake changes and refactorAnecdotally this is a process I've successfully used as well when working with legacy code... with some legacy code being; as recent as in I haven't touched it for a month or so, to code I've never worked with before and has been built up for years.Most of these strategies are something that formal roles such as business analyst or a software architect cannot predict as they're usually not in the code. It is up to us developers to see the changes coming, plan accordingly and start refactoring mercilessly (with the safety harness of version control so you can roll-back/revert in case you do mistakes).You can practically boil down the strategies to the following principles about change management:Don't fool yourself; changes always happen even in fixed projects. Period.Deal with it. Make changes easy to implement.Find difficult dependencies. Break them and rebuild them into something simpler.Tests are more useful than you think. Testing usefulness is proportional to the size of project. The more changes you stuff in, the more you'll need a test harness to make sure things don't break (i.e. stumble upon regression bugs).Good luck, handling those change requests!