How to avoid calling everything a "<WhatEver>Manager"?
How to Avoid Calling Everything a "<Whatever>Manager" in Your Code
Ever notice how many classes in some codebases end with "Manager"? While it might seem convenient, overusing the <Whatever>Manager
naming convention can lead to vague, bloated, and hard-to-maintain code. Let’s explore why this happens and how to adopt better naming practices and design principles to create clear, efficient, and maintainable code.
Why Overusing "Manager" is Problematic
1. Lack of Clarity
Using "Manager" as a suffix often doesn’t convey the specific responsibility or functionality of the class. For example, UserManager
could be handling user creation, authentication, data storage, or all of the above, making it unclear what the class is truly responsible for.
2. Violates Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one reason to change, meaning it should only have one job. Manager classes tend to accumulate multiple responsibilities, leading to tightly coupled and less modular code.
3. Hinders Maintainability
When a Manager class handles too many tasks, it becomes difficult to manage, test, and extend. Changes in one part of the Manager can inadvertently affect other unrelated functionalities, introducing bugs and increasing the complexity of the codebase.
Strategies to Avoid Overusing "Manager"
1. Embrace the Single Responsibility Principle (SRP)
Ensure each class has a single responsibility. Break down large Manager classes into smaller, more focused classes that handle specific tasks.
Bad Example:
class UserManager: def create_user(self, user_data): # Logic to create user pass def authenticate_user(self, credentials): # Logic to authenticate user pass def delete_user(self, user_id): # Logic to delete user pass
Good Example:
class UserCreator: def create_user(self, user_data): # Logic to create user pass class UserAuthenticator: def authenticate_user(self, credentials): # Logic to authenticate user pass class UserDeleter: def delete_user(self, user_id): # Logic to delete user pass
2. Use More Descriptive Names
Choose names that clearly describe the class's responsibility. Instead of generic "Manager" suffixes, use verbs or specific nouns that indicate the action or role of the class.
Instead of:
UserManager
OrderManager
InventoryManager
Use:
UserService
OrderProcessor
InventoryController
3. Implement Design Patterns
Adopt design patterns that promote clear separation of concerns and modularity. Here are a few patterns that can help:
-
Repository Pattern: Handles data access logic.
class UserRepository: def get_user_by_id(self, user_id): # Fetch user from database pass
-
Service Pattern: Encapsulates business logic.
class UserService: def create_user(self, user_data): # Business logic to create user pass
-
Controller Pattern: Manages interactions between the user interface and the business logic.
class UserController: def handle_create_user_request(self, request): # Handle HTTP request to create user pass
4. Leverage Composition Over Inheritance
Instead of creating monolithic Manager classes through inheritance, compose smaller, reusable components that collaborate to perform complex tasks.
Example:
class EmailService: def send_email(self, to, subject, body): # Logic to send email pass class UserNotifier: def __init__(self, email_service): self.email_service = email_service def notify_user(self, user, message): self.email_service.send_email(user.email, "Notification", message)
Practical Example: Refactoring a Manager Class
Before Refactoring:
class ProductManager: def add_product(self, product): # Add product logic pass def remove_product(self, product_id): # Remove product logic pass def update_product(self, product_id, product_data): # Update product logic pass def list_products(self): # List products logic pass
After Refactoring:
class ProductRepository: def add(self, product): # Add product to database pass def remove(self, product_id): # Remove product from database pass def update(self, product_id, product_data): # Update product in database pass def get_all(self): # Retrieve all products from database pass class ProductService: def __init__(self, repository): self.repository = repository def add_product(self, product): # Business logic before adding self.repository.add(product) def remove_product(self, product_id): # Business logic before removing self.repository.remove(product_id) def update_product(self, product_id, product_data): # Business logic before updating self.repository.update(product_id, product_data) def list_products(self): # Business logic before listing return self.repository.get_all()
Additional Resources
Enhance your object-oriented design skills and prepare for interviews with these DesignGurus.io courses:
- Grokking the Object Oriented Design Interview
- Grokking the System Design Interview
- Grokking the Coding Interview: Patterns for Coding Questions
Helpful Blogs
Dive deeper into software design principles by visiting DesignGurus.io's blog:
- Essential Software Design Principles You Should Know Before the Interview
- Mastering the FAANG Interview: The Ultimate Guide for Software Engineers
Summary
Avoiding the overuse of <Whatever>Manager
involves:
- Adhering to the Single Responsibility Principle: Ensure each class has one clear responsibility.
- Using Descriptive Names: Clearly indicate what the class does through its name.
- Implementing Design Patterns: Utilize patterns like Repository, Service, and Controller to structure your code effectively.
- Favoring Composition Over Inheritance: Build complex functionality by combining smaller, focused components.
By adopting these strategies, you can create a more organized, maintainable, and scalable codebase, making your software projects more efficient and easier to manage.
Happy coding!
GET YOUR FREE
Coding Questions Catalog