Diamondforge

Modern software development is no longer just about writing functional code. It is now an art, a science and a collective practice aimed at creating elegant, sustainable and scalable solutions. In this article, I will explore the foundations of Software Craftsmanship, integrating key concepts and practices that reflect the very essence of this philosophy.

Software Craftsmanship: Mastering SaaS Design and Development

What is Software Craftsmanship?

Software Craftsmanship goes far beyond simply writing functional code. It’s a philosophy that emphasizes quality, sustainability and ethics in software development. What’s more, it applies at all levels: all the professions involved, from design to product management to the board of directors, contribute to the implementation of these principles. Popularized in 2009 by the Software Craftsmanship Manifesto, this movement rests on four fundamental pillars:

  1. Not only working software, but also well-crafted software
    Well-designed code is more than an immediate solution to a problem: it’s a solid foundation that facilitates maintenance, evolution and collaboration.

  2. Not only responding to change, but also steadily adding value
    Good software doesn’t just meet current requirements. It anticipates future needs and adapts to change.

  3. Not only individuals and interactions, but also a community of professional
    Continuous learning and improvement are essential, as is the sharing of knowledge within a team or community.

  4. Not only customer collaboration, but also productive partnerships
    Building strong, collaborative relationships with stakeholders ensures that software meets real strategic objectives.
software craftmanship principles

The Software Craftsmanship movement grew out of the Agile software development movement of the 1990s, which emphasized both the rigor of engineering and the craft of development. In 1992, Jack W. Reeves’ essay “What Is Software Design?” defined software development as both a craft and an engineering discipline. Later, Andy Hunt and Dave Thomas’s The Pragmatic Programmer (1999) and Pete McBreen’s Software Craftsmanship (2001) advocated treating developers as craftsmen rather than engineers.

Basic principles, the pillars of Software Craftsmanship

The Software Craftsmanship Manifesto set the stage, but how can we bring its values to life? By applying core principles, developers create software that’s not just functional but also maintainable, scalable, and aligned with real needs. Let’s explore these practices in action.

Clean Code: Writing for humans, not just machines
At the heart of Software Craftsmanship is Clean Code, a philosophy popularized by Robert C. Martin (Uncle Bob). Clean code prioritizes readability, simplicity and intent. It’s not just about making code work; it’s about making it understandable to others – or even to yourself in six months’ time.
 
Use meaningful variable and method names.
Keep functions short and focused on a single responsibility.
Delete unused code or redundant comments.
 
Remember: every line of code you write is a message to future developers (including yourself). Clear, clean code is the foundation of all crafting principles.
S.O.L.I.D. rules: designed to be maintainable (with sample typescripts)
The S.O.L.I.D. principles provide a blueprint for designing robust, extensible software. These five rules help avoid common pitfalls such as tight code coupling or design rigidity.

S – Single Responsibility Principle (SRP)

A class must have a single responsibility and a single reason to change.
Why should it? It makes the code clearer, easier to maintain and less prone to errors.

Do’s
class UserManager {
  createUser(userData: { name: string; email: string }) {
    console.log("User created:", userData);
  }
}

class EmailService {
  sendWelcomeEmail(email: string) {
    console.log("Welcome email sent to:", email);
  }
}

// Usage
const userManager = new UserManager();
userManager.createUser({ name: "John Doe", email: "john@example.com" });

const emailService = new EmailService();
emailService.sendWelcomeEmail("john@example.com");
class UserManager {
  createUser(userData: { name: string; email: string }) {
    console.log("User created:", userData);
  }

  sendWelcomeEmail(email: string) {
    console.log("Welcome email sent to:", email);
  }
}

// Responsibilities are mixed: managing users and sending emails.

O – Open/Closed Principle (OCP)

Code must be open to extension, but closed to modification.
Why? This allows new functionalities to be added without modifying existing components, thus limiting risks.

Do’s
interface PaymentMethod {
  pay(): void;
}

class CreditCardPayment implements PaymentMethod {
  pay() {
    console.log("Processing credit card payment.");
  }
}

class PayPalPayment implements PaymentMethod {
  pay() {
    console.log("Processing PayPal payment.");
  }
}

class PaymentProcessor {
  processPayment(paymentMethod: PaymentMethod) {
    paymentMethod.pay();
  }
}

// Usage
const processor = new PaymentProcessor();
processor.processPayment(new CreditCardPayment());
processor.processPayment(new PayPalPayment());
class PaymentProcessor {
  processPayment(type: string) {
    if (type === "credit_card") {
      console.log("Processing credit card payment.");
    } else if (type === "paypal") {
      console.log("Processing PayPal payment.");
    }
  }
}

// Adding new payment types requires modifying this class.

L – Liskov Substitution Principle (LSP)

A derived class must be able to replace its parent class without disturbing the system’s behavior.
Why? This principle guarantees that subclasses respect the contract of their parent class.

Do’s
class Bird {}

class FlyingBird extends Bird {
  fly() {
    console.log("Flying...");
  }
}

class Penguin extends Bird {
  swim() {
    console.log("Swimming...");
  }
}

// Usage
const sparrow = new FlyingBird();
sparrow.fly();

const penguin = new Penguin();
penguin.swim();
class Bird {
  fly() {
    console.log("Flying...");
  }
}

class Penguin extends Bird {
  fly() {
    throw new Error("Penguins can't fly!");
  }
}

// Usage
const penguin = new Penguin();
penguin.fly(); // Throws an error, violating LSP.

I – Principle of interface separation (ISP)

Interfaces must be specific rather than general.
Why this principle? To avoid classes implementing unnecessary methods.

Do’s
interface Printable {
  print(): void;
}

interface Scannable {
  scan(): void;
}

class Printer implements Printable {
  print() {
    console.log("Printing...");
  }
}

class Scanner implements Scannable {
  scan() {
    console.log("Scanning...");
  }
}

// Usage
const printer = new Printer();
printer.print();

const scanner = new Scanner();
scanner.scan();
interface Machine {
  print(): void;
  scan(): void;
}

class Printer implements Machine {
  print() {
    console.log("Printing...");
  }

  scan() {
    throw new Error("Printer cannot scan!");
  }
}

// Printer is forced to implement scan, violating ISP.

D – Dependency inversion principle (DIP)

High-level modules must not depend on low-level modules. Both must depend on abstractions.
Why? This makes the code less coupled and easier to test and modify.

Do’s
interface NotificationChannel {
  send(): void;
}

class EmailSender implements NotificationChannel {
  send() {
    console.log("Email sent!");
  }
}

class SmsSender implements NotificationChannel {
  send() {
    console.log("SMS sent!");
  }
}

class NotificationService {
  private channel: NotificationChannel;

  constructor(channel: NotificationChannel) {
    this.channel = channel;
  }

  notify() {
    this.channel.send();
  }
}

// Usage
const emailService = new NotificationService(new EmailSender());
emailService.notify();

const smsService = new NotificationService(new SmsSender());
smsService.notify();
class EmailSender {
  sendEmail() {
    console.log("Email sent!");
  }
}

class NotificationService {
  private emailSender = new EmailSender();

  notify() {
    this.emailSender.sendEmail();
  }
}

// NotificationService directly depends on EmailSender, violating DIP.

An art applied to development, but not limited to developers!

Software Craftsmanship isn’t just about knowing the principles; it’s about applying them with skill, creativity, and, sometimes, the sheer determination to resist the urge to throw your keyboard across the room. Beyond Clean Code and S.O.L.I.D., there is a wide range of concepts which, when properly mastered, can take SaaS from “functional” to “fabulous.”

These concepts cover a wide range of areas, so don’t think they’re just for developers who get their hands dirty with code. It starts with the strategic side, with practices such as OKRs (Objectives and Key Results) and SMART goals, moves into UX design with approaches such as design systems and atomic design, and continues with product design methodologies such as Double Diamond and DDD (Domain-Driven Design).

It also includes rigorous testing techniques such as TDD (Test-Driven Development), E2E testing, integration testing, and unit testing, as well as continuous improvement through Refactoring, Kaizen, and Lean Development.

We’re going to explore a few more development concepts together to complete this article, but it will be necessary to come back (via other articles) to the ones I’ve just mentioned, as it’s important to be aware of them to prevent anyone from falling into the trap of technical debt or functional lock-in, the two biggest drops of sweat that will slide down your back if you have a poorly designed SaaS (which often shuts down a start-up).

Applying these concepts is a way of respecting your customers, teams, product, and code.

A few more concepts from the code world

YAGNI (You Aren’t Gonna Need It)

Imagine you’re packing for a weekend trip. Do you really need a tuxedo “just in case”? YAGNI says no. Build only what you need today—future-proofing is for fortune-tellers, not developers.

Don’ts
class Car {
  start() {}
  fly() {} // Just in case Elon Musk likes my app
}
class Car {
  start() {
    console.log("Engine started!");
  }
}

KISS (Keep It Simple, Stupid)

It’s a reminder that simple code works best. If your method requires a flowchart to explain, it’s time to simplify.

Don’ts
function calculate(a: number, b: number, operation: string): number {
  if (operation === "add") {
    return a + b;
  } else if (operation === "subtract") {
    return a - b;
  } else if (operation === "multiply") {
    return a * b;
  } else if (operation === "divide") {
    return a / b;
  } else {
    throw new Error("Invalid operation");
  }
}
const operations = {
  add: (a: number, b: number) => a + b,
  subtract: (a: number, b: number) => a - b,
  multiply: (a: number, b: number) => a * b,
  divide: (a: number, b: number) => a / b,
};

function calculate(a: number, b: number, operation: keyof typeof operations) {
  return operations[operation](a, b);
}

The Boy Scout Rule: Leave the Code Better Than You Found It

Boy Scouts have a rule: always leave the camp cleaner than you found it. In coding, this means improving messy code every time you come across it, even if it’s just a matter of renaming a variable from x to a meaningful name (and when you do, make sure you have a commit dedicated to these improvements).

Before
function doStuff(a: any, b: any) {
  return a + b;
}
function calculateTotal(price: number, tax: number): number {
  return price + tax;
}

DRY: Don’t Repeat Yourself (Unless You’re Telling a Good Joke)

Repetition in code is like deja vu—it’s confusing, unnecessary, and makes debugging a nightmare. DRY says to centralize logic to avoid duplicating it.

Don’ts
function calculateTax(price: number): number {
  return price * 0.2;
}

function calculateDiscount(price: number): number {
  return price * 0.1;
}
function applyRate(price: number, rate: number): number {
  return price * rate;
}

const tax = applyRate(100, 0.2);
const discount = applyRate(100, 0.1);

Use Design Patterns: Solving Common Problems Elegantly

Sometimes, the simplest patterns can make the biggest difference. Let’s look at a straightforward but powerful example: the Singleton Pattern.

The Singleton Pattern: One Instance to Rule Them All
The Singleton Pattern ensures that a class has only one instance and provides a global point of access to it. This is perfect for managing shared resources like configuration settings or logging.

Scenario: Managing Application Configuration
Imagine an app where various parts of the codebase need access to the same configuration settings. Without a Singleton, each part might create its own configuration object, leading to inconsistencies.

Don’ts
class Config {
  settings = { theme: "dark", language: "en" };
}

const config1 = new Config();
const config2 = new Config();

config1.settings.theme = "light";
console.log(config2.settings.theme); // Still "dark" - they are separate instances!
class Config {
  private static instance: Config;
  settings = { theme: "dark", language: "en" };

  private constructor() {}

  static getInstance(): Config {
    if (!Config.instance) {
      Config.instance = new Config();
    }
    return Config.instance;
  }
}

// Accessing the singleton instance
const config1 = Config.getInstance();
const config2 = Config.getInstance();

config1.settings.theme = "light";
console.log(config2.settings.theme); // "light" - both share the same instance!

Conclusion: Building Better Software, Together

Software Craftsmanship is more than a set of principles or practices; it’s a state of mind, a commitment to excellence that covers every part of the software development lifecycle. From writing clean, maintainable code, to designing scalable systems, to promoting collaboration and continuous improvement, this philosophy ensures that every aspect of SaaS development reflects quality and care.

By applying concepts such as clean code, S.O.L.I.D., TDD and design patterns, developers can create solutions that stand the test of time. But Software Craftsmanship isn’t just about the code – it’s about the people who write it, the teams who collaborate to refine it, and the customers who rely on it to meet their needs. Adopting practices such as YAGNI, KISS and the Boy Scout rule reinforces the respect we owe to ourselves, our teams and our users.

This is not just a philosophy for developers, but a universal approach! It applies to all fields, and only really works if everyone involved in a SaaS project follows its concepts – from the board to the developers, from the product manager to the UX designer (not forgetting QA team) – to build products that are as elegant as they are effective.

By mastering these tools and principles, we don’t just provide features or functionality, we create products that inspire confidence and promote success. In doing so, we ensure that our SaaS solutions are not just functional, but a true work of craftsmanship. After all, the true mark of a craftsman lies not only in the work he produces, but also in the legacy he leaves behind. Let’s build that legacy and inspire others to contribute to it.

What’s Next?

Although I’ve covered some of the key principles and practices of Software Craftsmanship, there are still many concepts that deserve attention – but this article can’t be too long! I’ll explore them in future articles, but here’s a non-exhaustive list of topics that deserve attention (and which I’ve been able to cite without detail):

  • OKRs (Objectives and Key Results): Aligning technical efforts with strategic business objectives.
  • SMART Goals: Setting clear, actionable, and measurable goals for teams and projects.
  • Design Systems: Creating reusable, consistent UI components that scale across projects.
  • Atomic Design: Building interfaces from small, composable elements for maintainability and adaptability.
  • Double Diamond: A framework for tackling complex problems by exploring and defining solutions.
  • DDD (Domain-Driven Design): Connecting code to business needs through clear domain modeling.
  • TDD (Test-Driven Development): Writing tests first to drive clean, reliable implementations.
  • E2E Testing: Simulating real-world user scenarios to ensure the entire system works as expected.
  • Integration Testing: Ensuring that different modules work seamlessly together.
  • Unit Testing: Verifying the smallest parts of the code in isolation.
  • Refactoring: Continuously improving code quality without changing its behavior.
  • Kaizen: Embracing continuous improvement to optimize processes and solutions.
  • Lean Development: Delivering value efficiently by reducing waste and focusing on essentials.

As we delve into these topics, we’ll explore how Software Craftsmanship extends beyond code to influence strategy, design, testing and continuous improvement. Together, these concepts form the backbone of building robust, scalable and truly enjoyable SaaS solutions.

Stay tuned, there’s still a lot to discover in the world of Software Craftsmanship!

@nthoGac

SaaS Engineer & Writer