Changing code is hard. Every time you fix something in one place, you caused another bug in somewhere else.
But writing software is all about changing existing code. Only a small percentage of a project spent on writing completely new pieces of code. Most of the time you are changing, adjusting, and expending existing code to add new functionality to an existing program.
So,
how can you make sure you can make changes without breaking old staff?
how can you make sure your code remain maintainable, understandable, and adaptable?
Emergent Architecture
- If you follow the best practices described here, you don’t need to define a large and complicated architecture up-front. Instead, you will see your architecture develop as your application grows larger. This is what is called Agile Architecture or Emergent Architecture.
- The different with the practice of making your design up-front is that you are not trying to predict the future. Instead, you allow yourself to change your architecture without breaking features, by making sure the functionality of your code is continuously verified.
- This way of working is more flexible and, in the end, much faster than trying to predict the future. Experience has shown that we are not very good at that: Predesigned architectures usually also need to be changed. If you designed for change, it is much easier to pull that off, and you didn’t spend time on designing up-front to begin with.
Automated Testing
- Things will change. Your requirements change, your software changes, even the structure of your application or your architecture changes.
- Over time, you learn more about what you want and how you can do it, so change is inevitable, but for the better.
- But you have to make sure that you don’t break things while you are changing them, and to know that, you have to test. But testing software is repetitive and time consuming. The best way to make sure you carry out tests often is to automate the process.
- Automated tests come in:
- Unit Test — testing a single unit of your program, like class or module
- Component Test — testing a bunch of classes or modules that together perform a certain task.
- Integrated Test — proving that components work together as intended
- End-to-end Test — testing the entire application
- For a good test suite, you need tests of all kinds.
- If you have a good suite of tests, you can change everything in your application with confidence. All you have to do is run the tests after every significant change, and fix the bugs that you find.
Test-Driven Development
- Test-driven development (TDD) means you write automated test first, then implement the code to make the tests pass.
- By the time the whole system is ready, you have automated tests for every bit and pieces of it.
- TDD shapes your development in a good way. It encourage you to write testable code because you think about the test before you write code. Testable code is usually loosely coupled code, which is easier to maintain. You will also write minimum amount of code necessary, as there’s no reason to write more code if the tests pass.
- TDD has another advantages: Because you write tests first, you will always have tests for your code. If you write tests last, they are sometimes skipped because of the time pressure or luck of urgency.
- If you find bugs later, you first add tests to reproduce those bugs and only then fix them. Then you run all the tests again.
Continuous Integration
- Continuous integration will help you in making sure that tests are running all the time.
- The concept means that you take all the code written by all the developers regularly, integrate it, and then run all the tests to verify that everything is working correctly. Needless to say, you want to automate this process.
- A build server help you perform these tasks, by building and testing the software for you. Many systems (e.g., Jenkin, Bamboo) exist that can do this automatically when something changes in your version control system.
- That way, you will be warned when anybody in your team broke anything—allowing you to fix it quickly, when the error is still easy to spot and understand.
Refactoring
- Refactoring of code means changing the structure of your code, how functions, objects, and modules work together.
- This is sometimes necessary if a new feature cannot be easily implemented in the structure your code currently has. Indeed, if you don’t create a big design up-front, you might need to change the structure of your code more often.
- But if your test automation and continuous integration are in place, this is not a dangerous thing to do.
- Refactoring is something that you should do continuously and routinely, to make structure of your code more suitable for the new features that you want to implement.
If a program is useful, it will have to be changed.
If a program is useless, it will have to be documented.
Murphy’s laws