Software Design Principles
Design principles are essential for building maintainable, scalable, and robust software systems.
1. DRY (Don't Repeat Yourself)
Definition: The DRY principle emphasizes reducing code duplication by ensuring that each piece of logic has a single, authoritative source within the system.
Why: Reduces maintenance effort and the risk of inconsistencies or bugs when changes are made.
How: Use functions, classes, and modules to encapsulate reusable logic. In Python and C++, this means abstracting repeated code into functions or classes.
Example: If a calculation appears in multiple places, create a function for it instead of copying the code everywhere.
This violates the DRY principle since logic is repeated.
def calculate_subject1(marks):
total = sum(marks)
average = total / len(marks)
return average
def calculate_subject2(marks):
total = sum(marks)
average = total / len(marks)
return average
Refactor to a single reusable function:
def calculate_average(marks):
if not marks:
return 0
return sum(marks) / len(marks)
subject1_avg = calculate_average([80, 90, 85])
subject2_avg = calculate_average([70, 75, 65])
2. KISS (Keep It Simple, Stupid)
Definition: KISS encourages developers to keep code and design as simple as possible, avoiding unnecessary complexity.
Why: Simple code is easier to read, maintain, and extend.
How: Break down complex problems into smaller, manageable functions or classes. Avoid over-engineering and focus on clear, concise solutions.
Example: Instead of writing a deeply nested function, split logic into smaller, focused functions.
Overengineered Version
def factorial(n):
result = 1
if n< 0:
return "Factorial undefined for negative numbers"
else n==0:
return 1
else:
for i in range(2,n+1):
result *=i
return result
A simple, clean implementation
import math
def factorial(n):
if n< 0:
return "Factorial undefined for negative numbers"
return math.factorial(n)
3. YAGNI (You Aren't Gonna Need It)
Definition: YAGNI advises against implementing features or functionality until they are actually needed.
Why: Prevents code bloat and reduces unnecessary complexity, making the codebase easier to maintain.
How: Focus on current requirements and avoid speculative additions. Add features only when there is a clear need.
Example: Don’t add extra configuration options or extensibility hooks unless there is a real use case.
Violation of YAGNI – Overengineering Example
phone_number
,address
,referral_code
,wants_newsletter
,is_admin
are not required at this stage.Adds unnecessary parameters and complexity — none of them are being used.
Premature features = more code to test, maintain, and secure.
def register_user(username, email, password, phone_number=None, address=None, referral_code=None, wants_newsletter=False, is_admin=False):
# Save user to database
user = {
"username": username,
"email": email,
"password": password,
"phone_number": phone_number,
"address": address,
"referral_code": referral_code,
"newsletter": wants_newsletter,
"admin": is_admin
}
print("User registered:", user)
def register_user(username, email, password):
user = {
"username": username,
"email": email,
"password": password
}
print("User registered:", user)
Does only what’s needed to register a user.
Easy to read, test, and extend only if needed later.
4. Modularity
Definition: Modularity is the practice of dividing a software system into distinct, independent modules that encapsulate specific functionality.
Why: Enhances code reusability, maintainability, and testability by isolating changes to specific modules.
How: In Python and C++, use classes, functions, and separate files or namespaces to organize code into logical modules.
Example: Separate user authentication, data processing, and UI logic into different modules or classes.
5. Abstraction
Definition: Abstraction involves hiding complex implementation details and exposing only the necessary features of a component.
Why: Simplifies usage and reduces dependencies between components, making the system easier to understand and modify.
How: Use abstract classes, interfaces, or function signatures to define what a component does, not how it does it.
Example: Define a
Database
interface with methods likeconnect()
andquery()
and implement the details in subclasses.
6. Encapsulation
Definition: Encapsulation is the bundling of data and methods that operate on that data within a single unit, typically a class, and restricting direct access to some of the object's components.
Why: Protects the internal state of an object and enforces a clear interface for interaction.
How: Use private/protected attributes and public methods in classes (e.g.,
__private_var
in Python,private:
in C++).Example: A
User
Class exposes methods to update user data, but keeps the actual data fields private.
7. Cohesion
Definition: Cohesion refers to how closely related the responsibilities of a single module or class are.
Why: High cohesion means a module or class has a well-defined purpose, making it easier to maintain and understand.
How: Group related functions and data together. Avoid mixing unrelated responsibilities in the same module or class.
Example: A
Logger
class should only handle logging, not user authentication.
8. Coupling
Definition: Coupling is the degree of interdependence between software modules.
Why: Low coupling makes modules more independent, so changes in one module have minimal impact on others.
How: Use interfaces, dependency injection, and clear APIs to minimize dependencies between modules.
Example: A payment module should interact with an order module through a well-defined interface, not by accessing its internal data directly.
Summary Table
DRY
Avoid code duplication
Easier maintenance, fewer bugs
Use functions for repeated logic
KISS
Keep code simple
Easier to read, maintain, extend
Small, focused functions
YAGNI
Don’t add unneeded features
Prevents code bloat
Only implement current requirements
Modularity
Divide code into independent modules
Reusability, testability
Separate classes for each feature
Abstraction
Hide details, expose essentials
Simplifies usage, reduces coupling
Abstract classes/interfaces
Encapsulation
Bundle data and methods, restrict access
Protects state, enforces interface
Private attributes in classes
Cohesion
Group related responsibilities
Easier to maintain and understand
Single-purpose classes
Coupling
Minimize inter-module dependencies
Easier to change and test
Use interfaces, dependency injection
References:
Last updated