Practical Code Refactoring, Part 1 – What is Good Code?By Abdullah Abouzekry
- Practical Code Refactoring, Part 1 – What is Good Code?
- Practical Code Refactoring, Part 2 – Readability
- Practical Code Refactoring, Part 3 – Extensibility
- Practical Code Refactoring, Part 4 – Efficiency
Refactoring is about re-thinking your code, everything in it, for the sake of making your code better. In this series on code refactoring, I’ll show you the Why, When, What, Where, and How of refactoring since refactoring is more of an art more than a science. In this part I’ll explain what makes code good, the basis which I build upon later through the rest of the series.
What is Good Code?
The main goal of refactoring is clean code, better code, or whatever you might call it. But what actually constitutes good code? Good code is smelled and tasted. If you’ve written a lot of code, you understand what I mean; you can easily identify whether code is good or bad with experience. But what if you are new to coding but still want to do things right?
We can summarize the aspects of good code with these three guidelines:
When your code follows some standard well-known syntax, style, and documentation practices, or in other words when you and your development colleagues can easily interpret the code with minimal ambiguity, your code is said to be readable and consequently maintainable in terms of style and appearance. To achieve this, you should follow a coding standard. You may use some public standard or create your own if you like, but whichever you choose, your colleagues should find your syntax, style, code documentation, and comments straight forward, clear, and require no further explanation as much as possible. I prefer using a public standard since it can shorten the learning curve for new people joining my development team later.
Readability and extensibility overlap when you use basic constructs of your programming languages for what they were intended as much as possible, and for solving well-known problems you use well-known patterns (design patterns). But what if you have a new problem? Or what if you’ve just crafted a better solution for an old problem? Terrific! Other programmers won’t be able to understand your new billion dollar solution if you don’t document it properly! Keep your mind working at its best and flying as high as you like, but remember to take the time to describe inside your code how and why you made it.
Both readability and extensibility contribute to the maintainability of your code and overlap at certain times. But to sharpen the difference, readable code is maintainable in terms of its style and extensible code is maintainable in terms of its logic. The same argument applies to design patterns since they contribute to both readability and extensibility.
Your code is extensible when it follows some re-usable, logical, well-known patterns, be it defined standard design patterns or normal logical flow. For example, some developers favor using the Singleton design pattern for working with database connections. As another example, most of us would use a standard foreach to iterate over an array, which is a standard flow. One might use a for loop or even a while loop under similar circumstances, but this is fairly uncommon in PHP so it should be documented why such constructs were necessary foreach to avoid confusion.
The major aspects of extensibility are decoupling and encapsulation. Decoupling means that your code (mainly functions/methods and classes) shouldn’t depend or overlap each other; code should be as “pure” as possible, with any overlapping functionality and other non-related entities removed. If you are writing procedural code, your functions should only contain the logic that their names imply; don’t make functions do too much! Use a “toolbox” approach when writing code – small basic routines work together to build big complicated systems.
Encapsulation is an integral part of decoupling. Whenever you decouple components, you have already done some encapsulation. To encapsulate components, continue separating its internals from the global scope and make all interactions with it occur at its interface level. This is a modular approach which makes it easier to later remove or update any part of your system as opposed to a monolithic design which makes your code spaghetti.
You should keep efficiency in mind from the very first line of code in any project since bottlenecks are known to come from some very unwise usage of language constructs, for example nesting several loops and using recursion unwisely, and from some unwise usage of logical patterns, for example not making use of caching and sending/receiving data to/from streams without proper buffering.
Efficiency is important, but don’t compromise the first two rules for the sake of it. In fact, compromising readability and extensibility can actually result in less efficiency! For the majority of cases, efficiency is directly proportional to applying the first two rules. When there is an exceptional situation, do your best to make sure you have other ways of keeping your code good.
In this article I introduced you to what makes code good. I limited the discussion to three major aspects: readability, extensibility, and efficiency, and described what each means.
Now that you know what constitutes good code, we can move on to the next parts in the series. In part 2 I’ll show you some things to be on the look out for when refactoring for better readability.
Image via Fotolia