Skip to main content

Programming Principles

Programming principles are guidelines that help developers write clean, maintainable, and efficient code.

DRY (Don't Repeat Yourself)​

Avoid duplication in code by abstracting common functionality into reusable components.

# Without DRY
length1 = 5
width1 = 10
area1 = length1 * width1 # Repeated logic
print(f"Area of rectangle 1: {area1}")

length2 = 7
width2 = 3
area2 = length2 * width2 # Repeated logic
print(f"Area of rectangle 2: {area2}")
# ----------------------------------
# With DRY
def calculate_area(length, width):
return length * width

# Reusing the function
length1, width1 = 5, 10
area1 = calculate_area(length1, width1)
print(f"Area of rectangle 1: {area1}")

length2, width2 = 7, 3
area2 = calculate_area(length2, width2)
print(f"Area of rectangle 2: {area2}")

KISS (Keep It Simple, Stupid)​

Strive for simplicity in design and implementation. Simple code is easier to understand and maintain.

# Without KISS
def is_even(number):
if number % 2 == 0:
return True
else:
return False

# Usage
print(is_even(4)) # Output: True
print(is_even(5)) # Output: False
# ----------------------------------
# With KISS
def is_even(number):
return number % 2 == 0

# Usage
print(is_even(4)) # Output: True
print(is_even(5)) # Output: False

YAGNI (You Aren't Gonna Need It)​

Don't add functionality until it is necessary. Avoid over-engineering and adding features that may never be used.

# Without YAGNI
class User:
def __init__(self, username, password):
self.username = username
self.password = password

def login(self, entered_password):
return self.password == entered_password

# Unnecessary method added "just in case"
def change_password(self, new_password):
self.password = new_password

# Usage
user = User("admin", "password123")
print(user.login("password123")) # Output: True
# -----------------------------------------------
# With YAGNI
class User:
def __init__(self, username, password):
self.username = username
self.password = password

def login(self, entered_password):
return self.password == entered_password

# Usage
user = User("admin", "password123")
print(user.login("password123")) # Output: True

SOLID principles​

The SOLID principles are a set of five design principles that help developers create robust, maintainable, and scalable software.

  • SRP: A class should have only one responsibility.
  • OCP: Classes should be open for extension but closed for modification.
  • LSP: Subclasses should be substitutable for their base classes.
  • ISP: Create small, specific interfaces instead of large, general ones.
  • DIP: Depend on abstractions, not concrete implementations.

1. Single Responsibility Principle (SRP)​

A class should have only one reason to change, meaning it should have only one job or responsibility.

# Without SRP
class Report:
def __init__(self, data):
self.data = data

def generate_report(self):
print(f"Generating report with data: {self.data}")

def save_report(self, filename):
with open(filename, "w") as file:
file.write(self.data)
# -----------------------------------------------------------
# With SRP
class Report:
def __init__(self, data):
self.data = data

def generate_report(self):
print(f"Generating report with data: {self.data}")

class ReportSaver:
@staticmethod
def save_report(data, filename):
with open(filename, "w") as file:
file.write(data)

# Usage
report = Report("Some report data")
report.generate_report()
ReportSaver.save_report(report.data, "report.txt")

2. Open/Closed Principle (OCP)​

Software entities (classes, modules, functions) should be open for extension but closed for modification.

# Without OCP
class Discount:
def __init__(self, customer_type):
self.customer_type = customer_type

def apply_discount(self, price):
if self.customer_type == "regular":
return price * 0.9
elif self.customer_type == "premium":
return price * 0.8

# With OCP
from abc import ABC, abstractmethod

class Discount(ABC):
@abstractmethod
def apply_discount(self, price):
pass

class RegularDiscount(Discount):
def apply_discount(self, price):
return price * 0.9

class PremiumDiscount(Discount):
def apply_discount(self, price):
return price * 0.8

# Usage
regular_discount = RegularDiscount()
premium_discount = PremiumDiscount()

print(regular_discount.apply_discount(100)) # Output: 90.0
print(premium_discount.apply_discount(100)) # Output: 80.0

3. Liskov Substitution Principle (LSP)​

Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.

# Without LSP
class Bird:
def fly(self):
pass

class Ostrich(Bird):
def fly(self):
raise NotImplementedError("Ostriches can't fly")
# ------------------------------------------------------
# With LSP
class Bird:
pass

class FlyingBird(Bird):
def fly(self):
print("Flying")

class Ostrich(Bird):
pass

# Usage
flying_bird = FlyingBird()
flying_bird.fly() # Output: Flying

ostrich = Ostrich()
# No fly method for Ostrich, which is correct

4. Interface Segregation Principle (ISP)​

Clients should not be forced to depend on interfaces they do not use. Create smaller, more specific interfaces.

# Without ISP
class Printer:
def print(self, document):
pass

def scan(self, document):
pass

def fax(self, document):
pass
# -------------------------------
# With ISP
class Printer:
def print(self, document):
pass

class Scanner:
def scan(self, document):
pass

class FaxMachine:
def fax(self, document):
pass

# Usage
class MultiFunctionDevice(Printer, Scanner, FaxMachine):
def print(self, document):
print("Printing document")

def scan(self, document):
print("Scanning document")

def fax(self, document):
print("Faxing document")

mfd = MultiFunctionDevice()
mfd.print("doc") # Output: Printing document
mfd.scan("doc") # Output: Scanning document
mfd.fax("doc") # Output: Faxing document

5. Dependency Inversion Principle (DIP)​

High-level modules should not depend on low-level modules. Both should depend on abstractions.

# Without DIP
class LightBulb:
def turn_on(self):
print("LightBulb: On")

def turn_off(self):
print("LightBulb: Off")

class Switch:
def __init__(self):
self.bulb = LightBulb()

def operate(self):
self.bulb.turn_on()
# -------------------------------------
# With DIP
from abc import ABC, abstractmethod

class Switchable(ABC):
@abstractmethod
def turn_on(self):
pass

@abstractmethod
def turn_off(self):
pass

class LightBulb(Switchable):
def turn_on(self):
print("LightBulb: On")

def turn_off(self):
print("LightBulb: Off")

class Fan(Switchable):
def turn_on(self):
print("Fan: On")

def turn_off(self):
print("Fan: Off")

class Switch:
def __init__(self, device: Switchable):
self.device = device

def operate(self):
self.device.turn_on()

# Usage
bulb = LightBulb()
switch = Switch(bulb)
switch.operate() # Output: LightBulb: On

fan = Fan()
switch = Switch(fan)
switch.operate() # Output: Fan: On