Imagine that you are writing a program to calculate the price of overseas purchases. Your company is based in England, and you need to calculate the local price of something purchased in US dollars. Someone else has already written a Python module which downloads the exchange rate, so your program starts out looking something like the following:
def calc_local_price(us_dollar_amount): exchange_rate = get_exchange_rate("USD", "EUR") local_amount = us_dollar_amount * exchange_rate return local_amount
So far so good. Your program is included in your company's online ordering system and the code goes into production. However, two months later, your company starts ordering products not just from the US, but from China, Germany, and Australia as well. You scramble to update your program to support these alternative currencies, and write something like the following:
def calc_local_price(foreign_amount, from_country): if from_country == "United States": exchange_rate = get_exchange_rate("USD", "EUR") elif from_country == "China": exchange_rate = get_exchange_rate("CHN", "EUR") elif from_country == "Germany": exchange_rate = get_exchange_rate("EUR", "EUR") elif from_country = "Australia": exchange_rate = get_exchange_rate("AUS", "EUR") else: raise RuntimeError("Unsupported country: " + from_country) local_amount = us_dollar_amount * exchange_rate return local_amount
Once again, this program goes into production. Six months later, another 14 countries are added, and the project manager also decides to add a new feature, where the user can see how the price of a product has changed over time. As the programmer responsible for this code, you now have to add support for those 14 countries, and also add support for historical exchange rates going back in time.
This is a contrived example, of course, but it does show how programs typically evolve. Program code isn't something you write once and then leave forever. Your program is constantly changing and evolving in response to new requirements, newly discovered bugs, and unexpected consequences. Sometimes, a change that seems simple can be anything but. For example, consider the poor programmer who wrote the
get_exchange_rate() function in our previous example. This function now has to support not only the current exchange rate for any given pair of currencies, it also has to return historical exchange rates going back to any desired point in time. If this function is obtaining its information from a source that doesn't support historical exchange rates, then the whole function may need to be rewritten from scratch to support an alternative data source.
Sometimes, programmers and IT managers try to suppress change, for example by writing detailed specifications and then implementing one part of the program at a time (the so-called waterfall method of programming). But change is an integral part of programming, and trying to suppress it is like trying to stop the wind from blowing—it's much better to just accept that your program will change, and learn how to manage the process as well as you can.
Modular techniques are an excellent way of managing change in your programs. For example, as your program grows and evolves, you may find that a particular change requires the addition of a new module to your program:
You can then import and use that module in the other parts of your program that need to use this new functionality.
Alternatively, you might find that a new feature only requires you to change the contents of a module:
This is one of the major benefits of modular programming—since the details of how a particular feature is implemented is inside a module, you can often change the internals of a module without affecting any other parts of your program. The rest of your program continues to import and use the module as it did before—only the internal implementation of the module has changed.
Refactoring may involve moving code between modules as well as creating new modules, removing old ones, and changing the way modules work. In essence, refactoring is the process of rethinking the program so that it works better.
In all of these changes, the use of modules and packages help you to manage the changes you make. Because the various modules and packages each perform a well-defined task, you know exactly which parts of your program need to be changed, and you can limit the effects of your changes to only the affected modules and the parts of the system that use them.