Clean Code Principles: SOLID Across ABAP, Python, and JavaScript
Whether you're writing ABAP for SAP systems, Python for data science, or JavaScript for UI5 applications, the principles of clean code remain universal. This guide explores SOLID principles and clean coding practices with examples in all three languages.
The SOLID Principles
| Principle | Definition | Benefit |
|---|---|---|
| S - Single Responsibility | A class should have one reason to change | Easier maintenance |
| O - Open/Closed | Open for extension, closed for modification | Safer enhancements |
| L - Liskov Substitution | Subtypes must be substitutable for base types | Reliable inheritance |
| I - Interface Segregation | Many specific interfaces > one general interface | Flexible design |
| D - Dependency Inversion | Depend on abstractions, not concretions | Loose coupling |
1. Single Responsibility Principle (SRP)
❌ Violation Example
ABAP
" BAD - Class does too much
CLASS zcl_customer_manager DEFINITION.
PUBLIC SECTION.
METHODS:
validate_customer IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_valid) TYPE abap_bool,
save_to_database IMPORTING iv_kunnr TYPE kunnr
iv_name1 TYPE name1,
send_email IMPORTING iv_email TYPE ad_smtpadr
iv_subject TYPE string
iv_body TYPE string,
generate_report IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_pdf) TYPE xstring.
ENDCLASS.Python
# BAD - Class does too much
class CustomerManager:
def validate_customer(self, customer_id: str) -> bool:
# Validation logic
pass
def save_to_database(self, customer_id: str, name: str):
# Database logic
pass
def send_email(self, email: str, subject: str, body: str):
# Email logic
pass
def generate_report(self, customer_id: str) -> bytes:
# Report logic
passJavaScript
// BAD - Class does too much
class CustomerManager {
validateCustomer(customerId) {
// Validation logic
}
saveToDatabase(customerId, name) {
// Database logic
}
sendEmail(email, subject, body) {
// Email logic
}
generateReport(customerId) {
// Report logic
}
}✅ Following SRP
ABAP
" GOOD - Each class has single responsibility
CLASS zcl_customer_validator DEFINITION.
PUBLIC SECTION.
METHODS validate IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_valid) TYPE abap_bool.
ENDCLASS.
CLASS zcl_customer_repository DEFINITION.
PUBLIC SECTION.
METHODS save IMPORTING iv_kunnr TYPE kunnr
iv_name1 TYPE name1.
ENDCLASS.
CLASS zcl_email_service DEFINITION.
PUBLIC SECTION.
METHODS send IMPORTING iv_email TYPE ad_smtpadr
iv_subject TYPE string
iv_body TYPE string.
ENDCLASS.
CLASS zcl_report_generator DEFINITION.
PUBLIC SECTION.
METHODS generate IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_pdf) TYPE xstring.
ENDCLASS.Python
# GOOD - Each class has single responsibility
class CustomerValidator:
def validate(self, customer_id: str) -> bool:
# Only validation
return len(customer_id) == 10
class CustomerRepository:
def save(self, customer_id: str, name: str):
# Only database operations
pass
class EmailService:
def send(self, email: str, subject: str, body: str):
# Only email operations
pass
class ReportGenerator:
def generate(self, customer_id: str) -> bytes:
# Only report generation
passJavaScript
// GOOD - Each class has single responsibility
class CustomerValidator {
validate(customerId) {
return customerId.length === 10;
}
}
class CustomerRepository {
save(customerId, name) {
// Database operations
}
}
class EmailService {
send(email, subject, body) {
// Email operations
}
}
class ReportGenerator {
generate(customerId) {
// Report generation
}
}2. Open/Closed Principle (OCP)
❌ Violation Example
ABAP
" BAD - Must modify class to add new discount type
CLASS zcl_discount_calculator DEFINITION.
PUBLIC SECTION.
METHODS calculate IMPORTING iv_type TYPE string
iv_amount TYPE p
RETURNING VALUE(rv_discount) TYPE p.
ENDCLASS.
CLASS zcl_discount_calculator IMPLEMENTATION.
METHOD calculate.
CASE iv_type.
WHEN 'SEASONAL'.
rv_discount = iv_amount * '0.10'.
WHEN 'LOYALTY'.
rv_discount = iv_amount * '0.15'.
WHEN 'EMPLOYEE'.
rv_discount = iv_amount * '0.20'.
" Need to modify this method for each new type!
ENDCASE.
ENDMETHOD.
ENDCLASS.✅ Following OCP
ABAP
" GOOD - Can add new discount types without modifying base
INTERFACE zif_discount_strategy.
METHODS calculate IMPORTING iv_amount TYPE p
RETURNING VALUE(rv_discount) TYPE p.
ENDINTERFACE.
CLASS zcl_seasonal_discount DEFINITION.
PUBLIC SECTION.
INTERFACES zif_discount_strategy.
ENDCLASS.
CLASS zcl_seasonal_discount IMPLEMENTATION.
METHOD zif_discount_strategy~calculate.
rv_discount = iv_amount * '0.10'.
ENDMETHOD.
ENDCLASS.
CLASS zcl_loyalty_discount DEFINITION.
PUBLIC SECTION.
INTERFACES zif_discount_strategy.
ENDCLASS.
CLASS zcl_loyalty_discount IMPLEMENTATION.
METHOD zif_discount_strategy~calculate.
rv_discount = iv_amount * '0.15'.
ENDMETHOD.
ENDCLASS.
" Add new types by creating new classes, not modifying existing codePython
# GOOD - Strategy pattern for extensibility
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, amount: float) -> float:
pass
class SeasonalDiscount(DiscountStrategy):
def calculate(self, amount: float) -> float:
return amount * 0.10
class LoyaltyDiscount(DiscountStrategy):
def calculate(self, amount: float) -> float:
return amount * 0.15
class EmployeeDiscount(DiscountStrategy):
def calculate(self, amount: float) -> float:
return amount * 0.20
class DiscountCalculator:
def __init__(self, strategy: DiscountStrategy):
self.strategy = strategy
def get_discount(self, amount: float) -> float:
return self.strategy.calculate(amount)
# Usage
calculator = DiscountCalculator(SeasonalDiscount())
discount = calculator.get_discount(100) # 10.0JavaScript
// GOOD - Strategy pattern
class DiscountStrategy {
calculate(amount) {
throw new Error("Must implement calculate method");
}
}
class SeasonalDiscount extends DiscountStrategy {
calculate(amount) {
return amount * 0.10;
}
}
class LoyaltyDiscount extends DiscountStrategy {
calculate(amount) {
return amount * 0.15;
}
}
class EmployeeDiscount extends DiscountStrategy {
calculate(amount) {
return amount * 0.20;
}
}
class DiscountCalculator {
constructor(strategy) {
this.strategy = strategy;
}
getDiscount(amount) {
return this.strategy.calculate(amount);
}
}
// Usage
const calculator = new DiscountCalculator(new SeasonalDiscount());
const discount = calculator.getDiscount(100); // 103. Liskov Substitution Principle (LSP)
❌ Violation Example
Python
# BAD - Square changes Rectangle behavior unexpectedly
class Rectangle:
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def set_width(self, width: float):
self.width = width
def set_height(self, height: float):
self.height = height
def get_area(self) -> float:
return self.width * self.height
class Square(Rectangle):
def set_width(self, width: float):
self.width = width
self.height = width # Violates LSP!
def set_height(self, height: float):
self.width = height
self.height = height # Violates LSP!
# Problem
def test_rectangle(rect: Rectangle):
rect.set_width(5)
rect.set_height(10)
assert rect.get_area() == 50 # Fails for Square!
rectangle = Rectangle(0, 0)
test_rectangle(rectangle) # OK
square = Square(0, 0)
test_rectangle(square) # FAILS! Area is 100, not 50✅ Following LSP
Python
# GOOD - Use composition instead of inheritance
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def get_area(self) -> float:
pass
class Rectangle(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def get_area(self) -> float:
return self.width * self.height
class Square(Shape):
def __init__(self, side: float):
self.side = side
def get_area(self) -> float:
return self.side * self.side
# Now both can be substituted safely
def print_area(shape: Shape):
print(f"Area: {shape.get_area()}")
rect = Rectangle(5, 10)
square = Square(5)
print_area(rect) # 50
print_area(square) # 254. Interface Segregation Principle (ISP)
❌ Violation Example
JavaScript
// BAD - Fat interface forces unnecessary implementations
class IWorker {
work() { throw new Error("Not implemented"); }
eat() { throw new Error("Not implemented"); }
sleep() { throw new Error("Not implemented"); }
}
class Human extends IWorker {
work() { console.log("Human working"); }
eat() { console.log("Human eating"); }
sleep() { console.log("Human sleeping"); }
}
class Robot extends IWorker {
work() { console.log("Robot working"); }
eat() {
// Robots don't eat! But forced to implement
throw new Error("Robots don't eat");
}
sleep() {
// Robots don't sleep! But forced to implement
throw new Error("Robots don't sleep");
}
}✅ Following ISP
JavaScript
// GOOD - Segregated interfaces
class IWorkable {
work() { throw new Error("Not implemented"); }
}
class IFeedable {
eat() { throw new Error("Not implemented"); }
}
class IRestable {
sleep() { throw new Error("Not implemented"); }
}
class Human extends IWorkable {
work() { console.log("Human working"); }
}
// Add mix-ins for eat and sleep
Object.assign(Human.prototype, IFeedable.prototype, IRestable.prototype);
Human.prototype.eat = function() { console.log("Human eating"); };
Human.prototype.sleep = function() { console.log("Human sleeping"); };
class Robot extends IWorkable {
work() { console.log("Robot working"); }
// No need to implement eat or sleep!
}
// Usage
const human = new Human();
human.work();
human.eat();
human.sleep();
const robot = new Robot();
robot.work();
// robot.eat(); // Method doesn't exist - correct!5. Dependency Inversion Principle (DIP)
❌ Violation Example
ABAP
" BAD - High-level depends on low-level concrete class
CLASS zcl_email_sender DEFINITION.
PUBLIC SECTION.
METHODS send IMPORTING iv_to TYPE string
iv_subject TYPE string
iv_body TYPE string.
ENDCLASS.
CLASS zcl_notification_service DEFINITION.
PUBLIC SECTION.
METHODS notify_customer IMPORTING iv_customer_id TYPE kunnr.
PRIVATE SECTION.
DATA mo_email_sender TYPE REF TO zcl_email_sender.
ENDCLASS.
CLASS zcl_notification_service IMPLEMENTATION.
METHOD notify_customer.
" Tightly coupled to concrete email sender
CREATE OBJECT mo_email_sender.
mo_email_sender->send(
iv_to = 'customer@example.com'
iv_subject = 'Notification'
iv_body = 'Message'
).
ENDMETHOD.
ENDCLASS.✅ Following DIP
ABAP
" GOOD - Depend on abstraction (interface)
INTERFACE zif_message_sender.
METHODS send IMPORTING iv_to TYPE string
iv_subject TYPE string
iv_body TYPE string.
ENDINTERFACE.
CLASS zcl_email_sender DEFINITION.
PUBLIC SECTION.
INTERFACES zif_message_sender.
ENDCLASS.
CLASS zcl_email_sender IMPLEMENTATION.
METHOD zif_message_sender~send.
" Email implementation
ENDMETHOD.
ENDCLASS.
CLASS zcl_sms_sender DEFINITION.
PUBLIC SECTION.
INTERFACES zif_message_sender.
ENDCLASS.
CLASS zcl_sms_sender IMPLEMENTATION.
METHOD zif_message_sender~send.
" SMS implementation
ENDMETHOD.
ENDCLASS.
CLASS zcl_notification_service DEFINITION.
PUBLIC SECTION.
METHODS constructor IMPORTING io_sender TYPE REF TO zif_message_sender.
METHODS notify_customer IMPORTING iv_customer_id TYPE kunnr.
PRIVATE SECTION.
DATA mo_sender TYPE REF TO zif_message_sender.
ENDCLASS.
CLASS zcl_notification_service IMPLEMENTATION.
METHOD constructor.
mo_sender = io_sender.
ENDMETHOD.
METHOD notify_customer.
" Can now use any sender implementation
mo_sender->send(
iv_to = 'customer@example.com'
iv_subject = 'Notification'
iv_body = 'Message'
).
ENDMETHOD.
ENDCLASS.
" Usage with dependency injection
DATA: lo_email_sender TYPE REF TO zcl_email_sender,
lo_notification TYPE REF TO zcl_notification_service.
CREATE OBJECT lo_email_sender.
CREATE OBJECT lo_notification
EXPORTING io_sender = lo_email_sender.
lo_notification->notify_customer( iv_customer_id = '0001000001' ).Python
# GOOD - Dependency injection
from abc import ABC, abstractmethod
class MessageSender(ABC):
@abstractmethod
def send(self, to: str, subject: str, body: str):
pass
class EmailSender(MessageSender):
def send(self, to: str, subject: str, body: str):
print(f"Sending email to {to}: {subject}")
class SMSSender(MessageSender):
def send(self, to: str, subject: str, body: str):
print(f"Sending SMS to {to}: {body}")
class NotificationService:
def __init__(self, sender: MessageSender):
self.sender = sender
def notify_customer(self, customer_id: str):
self.sender.send(
to="customer@example.com",
subject="Notification",
body="Your order is ready"
)
# Usage - easily switch implementations
email_service = NotificationService(EmailSender())
email_service.notify_customer("C001")
sms_service = NotificationService(SMSSender())
sms_service.notify_customer("C001")Clean Code Practices Across Languages
Meaningful Names
| ❌ Bad | ✅ Good |
|---|---|
getData() | getCustomersByRegion() |
x, y, z | customerName, orderDate, totalAmount |
list1 | activeOrders |
doStuff() | calculateMonthlyRevenue() |
Functions Should Be Small
❌ Bad - Long Function
// 100+ lines of mixed concerns
function processOrder(order) {
// Validation
if (!order.customerId) throw new Error("Invalid");
if (!order.items || order.items.length === 0) throw new Error("No items");
// Price calculation
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
if (item.discount) total -= item.discount;
}
// Tax calculation
const tax = total * 0.18;
total += tax;
// Inventory check
for (const item of order.items) {
const stock = getStock(item.productId);
if (stock < item.quantity) throw new Error("Out of stock");
}
// Database save
// ... 50 more lines
}✅ Good - Small, Focused Functions
function processOrder(order) {
validateOrder(order);
const total = calculateTotal(order);
checkInventory(order);
saveOrder(order, total);
}
function validateOrder(order) {
if (!order.customerId) throw new Error("Invalid customer");
if (!order.items?.length) throw new Error("No items");
}
function calculateTotal(order) {
const subtotal = calculateSubtotal(order.items);
const tax = calculateTax(subtotal);
return subtotal + tax;
}
function calculateSubtotal(items) {
return items.reduce((sum, item) =>
sum + (item.price * item.quantity) - (item.discount || 0), 0
);
}
function calculateTax(amount) {
return amount * 0.18;
}
function checkInventory(order) {
order.items.forEach(item => {
const stock = getStock(item.productId);
if (stock < item.quantity) {
throw new Error(`Out of stock: ${item.productId}`);
}
});
}
function saveOrder(order, total) {
// Database logic
}DRY (Don't Repeat Yourself)
❌ Bad - Duplication
# ABAP " Duplicated validation logic IF lv_amount < 0. MESSAGE 'Amount must be positive' TYPE 'E'. ENDIF. " ... elsewhere in code IF lv_price < 0. MESSAGE 'Price must be positive' TYPE 'E'. ENDIF. " ... and again IF lv_quantity < 0. MESSAGE 'Quantity must be positive' TYPE 'E'. ENDIF.
✅ Good - Reusable Function
# ABAP
METHOD validate_positive.
IF iv_value < 0.
MESSAGE |{ iv_field_name } must be positive| TYPE 'E'.
ENDIF.
ENDMETHOD.
" Usage
validate_positive( iv_value = lv_amount iv_field_name = 'Amount' ).
validate_positive( iv_value = lv_price iv_field_name = 'Price' ).
validate_positive( iv_value = lv_quantity iv_field_name = 'Quantity' ).Error Handling
ABAP
" Use exceptions, not return codes
METHOD process_order.
TRY.
validate_order( iv_order_id = iv_order_id ).
calculate_total( iv_order_id = iv_order_id ).
save_order( iv_order_id = iv_order_id ).
CATCH zcx_validation_error INTO DATA(lx_error).
MESSAGE lx_error->get_text( ) TYPE 'E'.
CATCH zcx_database_error INTO lx_error.
MESSAGE lx_error->get_text( ) TYPE 'E'.
ROLLBACK WORK.
ENDTRY.
ENDMETHOD.Python
# Use specific exceptions
def process_order(order_id: str):
try:
validate_order(order_id)
total = calculate_total(order_id)
save_order(order_id, total)
except ValidationError as e:
logger.error(f"Validation failed: {e}")
raise
except DatabaseError as e:
logger.error(f"Database error: {e}")
rollback_transaction()
raiseJavaScript
// Use specific error types
async function processOrder(orderId) {
try {
await validateOrder(orderId);
const total = await calculateTotal(orderId);
await saveOrder(orderId, total);
} catch (error) {
if (error instanceof ValidationError) {
console.error("Validation failed:", error.message);
} else if (error instanceof DatabaseError) {
console.error("Database error:", error.message);
await rollbackTransaction();
}
throw error;
}
}Code Review Checklist
| Category | Check |
|---|---|
| Naming | Are names descriptive and meaningful? |
| Functions | Are functions small (<20 lines) and focused? |
| DRY | Is code duplicated anywhere? |
| SOLID | Does design follow SOLID principles? |
| Error Handling | Are errors handled gracefully? |
| Testing | Is code testable and tested? |
| Comments | Are comments necessary or is code self-explanatory? |
Conclusion
Clean code principles like SOLID are universal — they transcend programming languages. Whether you write ABAP, Python, or JavaScript:
- ✅ Write code for humans, not just machines
- ✅ Keep functions small and focused
- ✅ Follow SOLID principles
- ✅ Don't repeat yourself (DRY)
- ✅ Handle errors gracefully
- ✅ Write tests
Clean code isn't about following rules blindly — it's about writing code that your future self (and teammates) will thank you for.
