In the world of software development, certain principles guide developers towards writing clean, efficient, and maintainable code. Here, we’ll explore seven fundamental programming principles—KISS, DRY, SOLID, YAGNI, SoC, LoD, and COI—each with examples to illustrate their importance and application.
Here are 7 Must-Know Programming Principles
1. KISS (Keep It Simple, Stupid)
Principle: Strive to keep your code as simple as possible. Avoid unnecessary complexity. The simpler the code the easier it is to understand, Debug and Maintain.
Example:
# Complex way
def calculate_area(radius):
return radius * radius * 3.14159
# Simple way
import math
def calculate_area(radius):
return math.pi * radius ** 2
In the above example, the first version computes the area of a circle using a hardcoded value of pi, whereas the second version makes use of Python’s built-in math library, making the code simpler and more accurate.
2. DRY (Don’t Repeat Yourself)
Principle: Aimed at reducing the repetition of information and stated as “Every piece of knowledge should have a single, unambiguous, authoritative representation.”
It essentially means that when writing code, you should consider the modularization feature you use more than once.
Example:
# Without DRY
def print_admin_message():
print("Hello Admin, welcome back!")
def print_user_message():
print("Hello User, welcome back!")
# With DRY
def print_message(user_type):
print(f"Hello {user_type}, welcome back!")
print_message("Admin")
print_message("User")
By using a single function print_message and passing a parameter, we avoid code duplication and make it easier to maintain.
3. SOLID Principles
Principle: A collection of five design principles for object-oriented programming to make software designs more understandable, flexible, and maintainable.
1. Single Responsibility Principle (SRP): A class should have only one reason to change, meaning it should have only one job or responsibility.
Example:
# Without SRP
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save_user(self):
# Save user to database
pass
def send_email(self):
# Send email to the user
pass
# With SRP
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save_user(self, user):
# Save user to database
pass
class EmailService:
def send_email(self, user):
# Send email to the user
pass
In the second example, the User class is responsible only for user data, UserRepository for database operations, and EmailService for sending emails.
2. Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification.
Example:
# Without OCP
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
class AreaCalculator:
def calculate_area(self, shape):
if isinstance(shape, Rectangle):
return shape.width * shape.height
# Additional shape types would require modifying this class
# With OCP
class Shape:
def calculate_area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
class AreaCalculator:
def calculate_area(self, shape):
return shape.calculate_area()
The second example allows for new shapes to be added without modifying the AreaCalculator class.
3. Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types without altering the correctness of the program.
Example:
# Without LSP
class Bird:
def fly(self):
pass
class Ostrich(Bird):
def fly(self):
raise Exception("I can't fly")
# With LSP
class Bird:
def move(self):
pass
class FlyingBird(Bird):
def move(self):
return "Flying"
class Ostrich(Bird):
def move(self):
return "Running"
In the second example, Ostrich and other birds can be used interchangeably without causing unexpected behaviour.
4. Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use.
Example:
# Without ISP
class Worker:
def work(self):
pass
def eat(self):
pass
class Robot(Worker):
def work(self):
# Robot's implementation of work
pass
def eat(self):
raise NotImplementedError("Robots don't eat")
# With ISP
class Workable:
def work(self):
pass
class Eatable:
def eat(self):
pass
class Human(Workable, Eatable):
def work(self):
# Human's implementation of work
pass
def eat(self):
# Human's implementation of eating
pass
class Robot(Workable):
def work(self):
# Robot's implementation of work
pass
In the second example, Robot is not forced to implement the eat method since it doesn’t need it.
5. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Example:
# Without DIP
class LightBulb:
def turn_on(self):
print("LightBulb: Bulb turned on")
def turn_off(self):
print("LightBulb: Bulb turned off")
class Switch:
def __init__(self, bulb):
self.bulb = bulb
def operate(self):
self.bulb.turn_on()
# With DIP
class Switchable:
def turn_on(self):
pass
def turn_off(self):
pass
class LightBulb(Switchable):
def turn_on(self):
print("LightBulb: Bulb turned on")
def turn_off(self):
print("LightBulb: Bulb turned off")
class Switch:
def __init__(self, device: Switchable):
self.device = device
def operate(self):
self.device.turn_on()
The second example abstracts the Switchable interface, allowing Switch to operate any device that implements this interface, not just LightBulb.
4. YAGNI (You Aren’t Gonna Need It)
Principle: Do not add functionality until it is necessary.A minimalist approach to code can save time and reduce complexity.
Example:
# Without YAGNI
class User:
def __init__(self, name, email, age=None, address=None, phone=None):
self.name = name
self.email = email
self.age = age
self.address = address
self.phone = phone
# With YAGNI
class User:
def __init__(self, name, email):
self.name = name
self.email = email
Avoid adding methods or features based on anticipated future needs.
Focus on current requirements to keep the codebase clean and manageable.
5. SoC (Separation of Concerns)
Principle: Different concerns or functionalities should be separated into distinct sections of code.
Example: Separate data access logic from business logic. Each function handles a specific task.
def parse(data):
# Parse data
return parsed_data
def validate(data):
# Validate data
return True
def save(data):
# Save data
pass
def process_data(data):
parsed_data = parse(data)
if not validate(parsed_data):
return "Invalid data"
save(parsed_data)
return "Data processed successfully"
6. LoD (Law of Demeter)
Principle: A module should not know the inner details of the objects it manipulates. Each unit should only talk to its immediate friends.
Example: Avoid chaining method calls.
# Without LoD
class Engine:
def start(self):
print("Engine started")
class Car:
def __init__(self):
self.engine = Engine()
def start(self):
self.engine.start()
class Driver:
def __init__(self):
self.car = Car()
self.engine = Engine()
def drive(self):
self.engine.start()
# With LoD
class Engine:
def start(self):
print("Engine started")
class Car:
def __init__(self):
self.engine = Engine()
def start(self):
self.engine.start()
class Driver:
def __init__(self):
self.car = Car()
def drive(self):
self.car.start();
The driver should only interact with Car and not directly with Engine.
7. COI (Convention Over Implementation)
Principle: It suggests using of conventions if available.
This reduces the need to specify configurations explicitly, speeding up development and reducing errors.
Understanding and applying these principles will lead to cleaner, more maintainable, and more efficient codebases. Each principle brings its own set of benefits and, when combined, they provide a robust foundation for any software project.
Learn More With AlgoRythm Solutions: Discover how these Programming Principles can revolutionize your coding.