Practical Code Refactoring, Part 3 – Extensibility
In part 2 of this series I shared questions that dealt with code refactoring for Readability. In this part we’ll spend some time with another aspect, Extensibility. We’ll take the same practical question/discussion approach as in the last part so you’ll be able to get into the new world of refactoring as fast as possible.
Extensible code is a piece of code which follows re-usable, logical, well-known patterns, be it standard design patterns, or normal logical flow. Modular code tends to be highly extensible and monolithic code tends to be non-extensible, but monolithic code might be more efficient, so to solve this conundrum some practices allow developing in a modular way and deploying in a monolithic way so we can get the best of both worlds.
The major aspects which we are to discuss with regard to extensible code are: logical extensibility (normal logical flow and design patterns), modular design, and decoupling and encapsulation.
1. Do most code blocks follow normal logical flow?
When you are dealing with small logical problems, make sure you are using the right constructs (if, for, foreach, while, etc.). What I mean by the “right constructs” is you should be using the most logical and common language feature for the job. For example, looping through simple arrays should be done with foreach; this is common normal flow, whereas using a for loop for such simple iteration is not normal flow in a language like PHP. Using while is even more alien for such a simple task. You may have your reasons, in which case recall from the previous part about documenting any custom practice and you are okay.
2. Do complex problem solutions follow standard design patterns?
When I first started working with PHP, I wasn’t much aware of design patterns. Now I find using design patterns is a must for large scale projects because they are commonly understood and account for future development.
A common complex problem for which you should use a well defined standard pattern to solve is creating various instances of some class. But why and when to use the factory design pattern? It’s probably debatable, but a general guideline, if you have different implementations of the same interface and you require implementing objects to be created dynamically, then the pattern may be suitable. Another case may be when generating a number of dynamic objects of the same class but when the number is only known at runtime. For instance, a modern GUI-intensive web application might necessitate creating rows of form inputs dynamically for database records. Examples are endless of when design patterns can be useful.
1. Do code structures follow modular design?
Modular design means that you divide your application into modules. A large app made up of smaller apps are easier to develop and are easier to extend and maintain. Each module should collect a bunch of related features and functionalities and group them together in a single entity.
Core functionality and application entry points may be treated as modules as well. You may then add functionality in the future by adding new modules. Some people call modules used in this manner plugins. Whatever design and structure you choose for your application though, you need to make sure how modules/plugins are loaded and unloaded, what their basic structure is, etc. and account for it before developing the core module.
Whenever you see groups of code in some module acting as a single sub-entity of it and being consumed by that top module with minimum parameters, why not split it off into a new module? Generally, when I have a sub-entity that splits more than a single class doing some side tasks, I will move it without hesitation to a new module.
Utility modules are a neat solution for orphaned code in a well designed modular application. Whenever I have some orphaned code, I move it to a utility module that handles snippets and mini tasks. The module is made up generally of orphaned functions and small classes. Whenever these tasks are big enough, I start moving them to it’s own separate module in a continuous process of refactoring.
2. Are modules dependency minimum?
Modules should be self-contained as much as possible. Soft module dependencies are natural and good, such as “Inventory” module being dependent on an “Accounting” module for getting a homogeneous e-commerce system, but many hard dependencies are bad. They make debugging and deployment much more difficult.
To ensure less inter-module dependencies, you have to iterate over your code base every now and then to see if there are any hard dependencies between modules. Clear them if you can, and if you can’t then you should make both modules into a single module with a more generic name. For example, in the e-commerce app you might have an “Items” module and an “Inventory” management module, and classes from the inventory are intensively using classes from items and vice-versa. I would merge both and rename the module “Inventory” which has a sub-module for dealing with items.
Decoupling and Encapsulation
1. Are functions, methods, and classes fairly decoupled?
Adding a paging feature to display results coming from a database is a very common task. In fact, early in my PHP development career I was writing some code to do paging for results; the code at first was procedural, constituting very specific functions for dealing with the database and results. I decided then to decouple the paging algorithm from each of the components where I used it with a variation of the Strategy pattern. Whenever you find your self duplicating logic or code, you probably need to do some decoupling of your own to enhance code re-usability and extensibility.
2. Are modules and components fairly decoupled?
When keeping dependencies to a minimum, you are decoupling the right way. There’s no 100% decoupling between any two related things; coupling is natural, so you should always decouple but not much so as not to end up making your code more complicated. As a guideline, decouple until your modules and components of your code base can talk together without having a lot in commonalities duplicated.
Keep in mind that lowering dependencies is directly proportional to usability and extensibility whenever complexity is not increasing. When complexity starts to increase, the relationship starts to be in-directly proportional.
In this part we discussed refactoring your code for extensibility, concentrating the discussion on three main aspects: logical extensibility, modular design, and decoupling and encapsulation. Hopefully now you are starting to gain better insight of how to develop and maintain better applications. In the final part we’ll discuss how to refactor for better efficiency without compromising readability and extensibility.
Image via Fotolia