Types of Bad Code and How to Fix Them
Writing clean and maintainable code is essential for software development. However, bad code often creeps into projects due to poor design choices, lack of experience, or time constraints. Here, we will discuss various types of bad code and ways to fix them.
We Hate Code — The Joy of Maintaining Dead and Unused Code
Do you love to maintain code that you didn’t write? Probably not. As systems grow and evolve, the codebase inevitably accumulates clutter, including unused or “dead” code. Often, the developers who wrote that code are not even in the company anymore. So how do you know if the code is still used?
Dead code can be confusing, misleading, and even a source of vulnerabilities in your codebase. It’s not only “legacy” code we need to manage, but also “dead” code and even the so-called “zombie” code. This section aims to highlight the struggle of maintaining these types of code and offer an overview of their differences, along with tools that can help eliminate them.
1. Spaghetti Code
Spaghetti code is disorganized, difficult to read, and hard to debug. It often lacks clear structure, making it nearly impossible to maintain or scale.
Example:
if condition1:
if condition2:
if condition3:
execute_action()
Fix: Use modular functions, clear logic, and avoid excessive nesting.
def check_conditions():
return condition1 and condition2 and condition3
if check_conditions():
execute_action()
2. God Object / Big Ball of Mud
A “God Object” is a class that tries to handle too many responsibilities, violating the Single Responsibility Principle (SRP).
Example:
class UserManager:
def register_user(self, user):
pass # Handles database, validation, and authentication
def send_email(self, user, msg):
pass # Handles email notifications
Fix: Split responsibilities into separate classes.
class UserService:
def register_user(self, user):
pass
class EmailService:
def send_email(self, user, msg):
pass
3. Hardcoded Values / Magic Numbers
Hardcoded values in the code reduce flexibility and readability.
Example:
if x > 100:
print("Value too high!")
Fix: Use named constants.
MAX_LIMIT = 100
if x > MAX_LIMIT:
print("Value too high!")
4. Code Duplication
Repeating logic in multiple places makes maintenance difficult and increases the risk of inconsistency.
Example:
def calculate_area_circle(radius):
return 3.14 * radius * radius
def calculate_area_sphere(radius):
return 4 * 3.14 * radius * radius
Fix: Use reusable functions.
PI = 3.14
def calculate_area(shape, radius):
if shape == "circle":
return PI * radius * radius
elif shape == "sphere":
return 4 * PI * radius * radius
5. Dead Code
Dead code is written but never executed during runtime. This can occur due to unreachable code blocks, obsolete features, or deprecated logic. It adds noise, increases maintenance overhead, and can introduce potential vulnerabilities.
Example:
def unused_function():
print("This function is never called")
Fix: Use static analysis tools to detect and remove dead code.
6. Zombie Code
Zombie code refers to code that is still present and may even execute, but serves no real purpose. It often lingers after changes in logic, feature removals, or incomplete refactors. Unlike dead code, zombie code might run but is functionally useless.
Example:
def process_data(data):
result = data * 2
print("Processing...") # This message has no real purpose
return result
Fix: Audit and refactor such code to streamline logic and remove inefficiencies.
7. Long Parameter Lists
Functions that require too many parameters are hard to understand and use.
Example:
def create_user(name, age, email, address, phone, dob, country):
Fix: Use objects or keyword arguments.
class User:
def __init__(self, name, age, email, address):
self.name = name
self.age = age
self.email = email
self.address = address
8. Tightly Coupled Code
When components depend too much on each other, making changes becomes risky.
Example:
class User:
def get_orders(self):
return Order().fetch_orders(self.id)
Fix: Use dependency injection.
class User:
def __init__(self, order_service):
self.order_service = order_service
9. Lack of Error Handling
Not handling exceptions properly can lead to program crashes.
Example:
def divide(a, b):
return a / b # Crashes if b is 0
Fix: Handle exceptions properly.
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return "Cannot divide by zero"
10. Global State / Mutable Global Variables
Using global variables makes it hard to track changes and debug issues.
Example:
global_count = 0
def increment():
global global_count
global_count += 1
Fix: Encapsulate state in objects.
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
11. Overengineering / Premature Optimization
Overcomplicating solutions for simple problems makes the code harder to maintain.
Example:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
(When a simple dictionary would do)
Fix: Follow KISS (Keep It Simple, Stupid) and YAGNI (You Ain’t Gonna Need It) principles.