Managers and executives are becoming increasingly aware of the problem of “Bad Code.” A few relatable questions are: Why are the new features taking so long? Why are we dealing with so many customer issues? Why is the turnover in our IT development group so high? All of these issues can be attributed to bad code, but what is “Bad Code” exactly, and how is it affecting the business? Asking ten developers what bad code is might result in ten different responses. To understand how “Bad Code” is affecting a business, the terms “Bad Code” and “Good Code” must be accurately defined.
What’s the Difference?
The difference between “Good Code” and “Bad Code” can be understood most easily in comparison. Below is a comparison chart between good coding practices and bad coding.
The compounding effects of each of these comparisons help to understand why new features are taking so long, customers are dealing with continual issues, and stress in the IT department is causing low morale and possibly turnover. In my view from a Software Engineering perspective, I have provided some information below to help understand the comparisons more effectively.
Easily Understandable vs. Difficult to Follow
Code that is easily understandable allows developers to quickly and accurately make changes to the codebase in order to add new features and resolve customer issues. However, difficult-to-follow code has multiple problems. Bad code makes new features difficult to implement, customer issues are difficult to resolve, and onboarding new developers becomes a lengthy and difficult process.
Logically Separate vs. Inseparable Modules
Logically separated modules allow many developers to work on different aspects of a solution without interfering with each other. However, inseparable interdependent modules make it impossible for developers to make any changes without a comprehensive understanding of the application. This causes longer onboarding for new developers, decreased performance of the dev team in general, and developer dissatisfaction.
Testable vs Untestable Code
Testable code with unit and integration testing is vital to give developers confidence in adding or updating features. Untestable Code lengthens the development cycle by requiring extensive manual testing and regression testing for the code change. Fragile tests are sometimes worse than no tests at all because they will require the developer to maintain multiple codebases without any discernible benefit. Tests that have to have extensive rewriting to facilitate any code or business logic change need to be refactored to test modules in a more robust way.
Descriptive vs Arbitrary Names
Descriptive naming conventions add to the readability and maintainability of the code. Arbitrary names cause confusion. Idx, count, temp, buf, etc. All these names devoid of context are meaningless. A new developer will have the question of what are we indexing or counting? Temporary what or buffer for what?
Well-documented vs Poorly Documented Scripts
Well-documented means appropriate for the situation. If a class or module is very well named and has clear control, flow documentation can be brief. If it is confusing, difficult, or requires extensive domain knowledge, then documentation should reflect the complexity.
Documentation is never a replacement for good architecture and clear code. External APIs and interfaces must be thoroughly documented or there will be ongoing issues with code integration. Poor documentation may still be substantial but can be out of date or trying to cover for poorly designed code.
Up-to-date vs Depreciated External Packages
External packages are very necessary to avoid “reinventing the wheel” on every project. Keeping packages up-to-date and avoiding deprecated APIs is essential to successfully integrate external code into a project. Packages that are ending support or no longer being developed need to be replaced. Out-of-date or deprecated external dependencies will reduce the effectiveness of developers by causing them to spend inordinate amounts of time understanding and debugging code they did not write and for which they are not responsible.
Fixed vs. Flexible Architecture
The architecture of a project is something that does not spring into being fully formed. By necessity, the architecture needs to be flexible to changing business needs and requirements. Refactoring architecture to reduce complexity is essential whenever business logic significantly changes. Ignoring the need to update architecture because of the time required is taking on tech debt that will drown the development team and cause high turnover. No one wants to be responsible for risky architecture changes once the problem has gotten overwhelming.
After looking at some of the comparisons between good and bad code it is clear why good coding practices are so important. As technical debt grows from bad coding practices, feature development slows to a crawl, customer issues become more common and more difficult to resolve, and finding good talent to work on the codebase as well as retaining talent becomes completely unmanageable. Solving this problem is difficult without being able to explain why good coding practices are necessary for a healthy growing company. Pointing out the comparison between good and bad practices allows non-technical stakeholders to see why they must budget time and resources to maintain a good clean codebase, in order to keep new features easy to develop, manage customer satisfaction, and retain talented developers.