Skip to main content

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

PrincipleDefinitionBenefit
S - Single ResponsibilityA class should have one reason to changeEasier maintenance
O - Open/ClosedOpen for extension, closed for modificationSafer enhancements
L - Liskov SubstitutionSubtypes must be substitutable for base typesReliable inheritance
I - Interface SegregationMany specific interfaces > one general interfaceFlexible design
D - Dependency InversionDepend on abstractions, not concretionsLoose 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
        pass

JavaScript

// 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
        pass

JavaScript

// 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 code

Python

# 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.0

JavaScript

// 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);  // 10

3. 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)  # 25

4. 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, zcustomerName, orderDate, totalAmount
list1activeOrders
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()
        raise

JavaScript

// 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

CategoryCheck
NamingAre names descriptive and meaningful?
FunctionsAre functions small (<20 lines) and focused?
DRYIs code duplicated anywhere?
SOLIDDoes design follow SOLID principles?
Error HandlingAre errors handled gracefully?
TestingIs code testable and tested?
CommentsAre 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.

About the Author: Yogesh Pandey is a passionate developer and consultant specializing in SAP technologies and full-stack development.