User Tools

Site Tools


blog:tech:solid_unix

❖ SOLID, Clean Architecture & Unix philosophy

As software systems grow more complex, it's easy for code to become tangled, with classes that do too much, logic that's hard to reuse, and changes in one place breaking unrelated parts of the app. To combat this, developers lean on design principles, guidelines that help us write clean, modular, and maintainable code. One of the most influential sets of principles in modern software engineering is known as 'SOLID'. SOLID outlines five key rules for object-oriented design. But even beyond OOP, these principles help teams write better code , whether you're building a mobile app, scripting in shell, or designing a backend service.

Understanding SOLID means understanding how to break software into well-defined, independent parts — so each part is easier to change, extend, and reason about.

This article contains 3 separate parts:

- Does Unix follow SOLID principles?

- When does a system become large enough to require separation of responsibilities?

- When does it start making sense to introduce Clean Architecture into a project?


Does Unix follow SOLID principles?

Not literally, the SOLID principles were coined for object-oriented programming by Robert C. Martin (Uncle Bob). Unix, on the other hand, is rooted in process-based design, with utilities built primarily in C and shell scripts, not classes and objects.

But philosophically, Unix aligns with many of the same ideas, especially the most important one: Single Responsibility Principle (SRP).

What is SOLID? Why it Matters

SOLID is an acronym for five design principles that aim to make software easier to understand, maintain, and extend.

Principle Description
Single Responsibility A class should have only one reason to change
Open/Closed Software should be open for extension, but closed for modification
Liskov Substitution Subtypes must be substitutable for their base types
Interface Segregation Prefer many small, specific interfaces over large, general ones
Dependency Inversion Depend on abstractions, not concrete implementations

These principles are key in OOP and Clean Architecture — but they also echo Unix's composable, minimalist philosophy.


S - Single Responsibility Principle

In OOP:

A class should have one, and only one, reason to change.

It means every class (or function, or module) should do one thing well. If it has multiple responsibilities, it will be harder to maintain and more prone to bugs.

In Unix:

Each command-line utility is designed to do one job and do it well.

Examples:

  • grep - filters lines that match a pattern
  • cut - extracts specific columns or fields
  • sort - sorts lines in a file or stream

These tools are intentionally small and composable. You combine them using pipes (|) to build complex behaviors.

Example:

cat access.log | grep 404 | cut -d' ' -f1 | sort | uniq -c | sort -nr

Each tool:

  • Does one thing
  • Can be updated independently
  • Has a clear reason to change

That’s SRP — just not in class-based code, but in process-based design.


Other SOLID principles in Unix

Principle Unix Alignment
Single Responsibility ✅ Strong: each tool has a narrow purpose
Open/Closed 🟡 Somewhat: tools are extended through scripting, not internal modification
Liskov Substitution ❌ Not really applicable — inheritance isn’t used
Interface Segregation ✅ Strong: tools use minimal, focused inputs/outputs (text streams, args)
Dependency Inversion 🟡 Weak: tools call each other via scripts but lack abstraction mechanisms
Unix doesn't enforce these principles through classes — but its *culture* encourages similar separation of concerns.

SRP in Android App Architecture

SRP isn’t just for classes. In an Android app (especially with Clean Architecture), SRP applies to:

Layers

  • Presentation layer: handles UI state only
  • Domain layer: handles business logic
  • Data layer: handles data access

Components

  • ViewModel: handles user interactions and state
  • UseCase: handles one specific business operation (e.g., GetUserPosts)
  • Repository: abstracts data source access (e.g., DB, network)

If a component does too many things — UI + business logic + data access — it becomes hard to test, understand, or change.

SRP Examples

Component Responsibility
LoginViewModel Manage login UI state and events
LoginUseCase Handle login logic and validation
UserRepo Fetch and cache user data (from DB or API)

Analogy: SRP in Unix vs Android

Aspect Unix Utility Android Component
One Concern cut extracts fields LoginUseCase handles login only
Composability Combine via pipes (|) Combine via layers (domain/UI)
Reason to Change grep only changes if filtering changes UserRepo changes only if data layer changes

Visual Diagram: Unix Shell Pipeline vs Android Architecture

Here’s a conceptual side-by-side comparison:

Unix Shell Pipeline Android Architecture Flow
cat file.txt Repository provides raw data
grep error UseCase filters meaningful business data
cut -d' ' -f1 ViewModel extracts/display-relevant state
sort | uniq -c UI Layer formats and displays info

Both systems:

  • Break problems into small, testable steps
  • Compose functionality through clearly defined boundaries
  • Emphasize separation of concerns

Interpreting Open/Closed and Dependency Inversion in Unix

Open/Closed Principle (OCP) in Unix

"Software entities should be open for extension but closed for modification."

How Unix achieves this:

  • You 'extend' commands by combining them in scripts
  • You 'wrap' commands with functions or aliases (without changing internal code)
  • Environment variables (e.g., $PAGER, $EDITOR) let programs integrate external tools

Example:

mygrep() {
  grep --color=auto "$@" | less
}

The grep command wasn’t modified — it was extended.

Dependency Inversion Principle (DIP) in Unix

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

Unix tools don't have formal abstractions (like interfaces or abstract classes), but:

  • They rely on text streams — a form of implicit interface
  • You can swap tools if they follow common conventions (e.g., replacing less with more)
  • You can script plug-in architectures with exec calls or sourcing shell functions dynamically

Example:

if [ -x ./mybackend ]; then
  ./mybackend "$@"
else
  default_backend "$@"
fi

This allows swappable implementations — an inversion of control.


Summary

  • Unix strongly embodies SRP: each tool does one thing.
  • Other SOLID principles (especially Interface Segregation and Open/Closed) show up in Unix behavior, if not structure.
  • Whether in pipes or layers, the key is small, composable, testable units that reduce coupling and increase maintainability.


When does a system become large enough to require separation of responsibilities?

This is one of the hardest and most important questions in applying the Single Responsibility Principle (SRP) well. There’s no universal line, it’s a judgment call, but there are practical guidelines and heuristics to help you decide when a piece of code needs to be split due to too many responsibilities.

Don’t split prematurely, SRP is about clarity and safety, not bureaucracy.

Understanding Responsibility

A responsibility is a reason to change, not just a “thing it does.”

**Robert C. Martin (Uncle Bob):**“A responsibility is a reason to change.”

So the key test is: If a change request comes in, which part of this component will it affect?


🧭 Guiding Principles to Decide When to Split

1. ❗ You’re Changing for More Than One Reason

Signal: If two or more distinct types of changes would require touching the same file/class/module.

Examples:

  • You need to change a UI layout and also fix a bug in database syncing — and both require touching the same class? Split it.

2. 🧪 It's Hard to Unit Test

If you're struggling to write a small, focused unit test, the class probably does too much.

Heuristic:

  • If you must mock too many unrelated dependencies to test a method, that method may not have a single responsibility.

3. It Uses Unrelated Domains

Signal: The class uses concepts from more than one domain.

Android example:

snippet.kotlin
class UserProfileManager {
    fun drawUI() { ... }
    fun fetchFromServer() { ... }
    fun saveToDatabase() { ... }
}

This class:

  • knows how to draw,
  • talks to network,
  • and stores to local DB.

These are 3 separate domains → split it.

4. 🔁 You Keep Coming Back to It for Unrelated Fixes

When one class keeps being involved in a wide variety of issues or changes — it's a red flag.

5. Cognitive Load (Lines of Code is a symptom, not a cause)

There’s no magic LOC number, but if:

  • You can't explain what a class does in one sentence,
  • Or a function needs multiple scrolls to read through,

…it likely violates SRP.


Scaling Thresholds for Separation

Project Size SRP Enforcement Strategy
Small scripts / proof-of-concepts Don't over-engineer. A bit of mess is fine.
Single-developer apps Start separating when logic becomes harder to reuse or test.
Growing teams or production apps Enforce SRP early. Clean separation avoids tech debt.
Large codebases SRP is mandatory — otherwise, changes become dangerous.

Summary

Split when:

  • You have more than one reason to change,
  • Code spans multiple unrelated concerns,
  • Testing becomes awkward,
  • Changes become risky or confusing.


When does it start making sense to introduce Clean Architecture into a project?

That’s a deep and important question, and the answer depends on balancing project scope, risk, and maintenance horizon.

Minimum Scale Where Clean Architecture Becomes Worthwhile

My minimum: For a solo developer or small team, I’d adopt Clean Architecture principles (modular layers, separation of concerns, testability) as soon as:

  • The app has multiple distinct business rules, user flows, or data sources.
  • It’s expected to evolve or be maintained for months or more.
  • There’s a need for testing logic independently of the UI or Android framework.

That threshold can be surprisingly low — for example:

  • An Android podcast player with downloads, metadata fetching, playback, favorites
  • A task manager with offline sync and reminders
  • A health tracking app with charts and history

If your app:

  • Has more than one core feature,
  • Or touches more than one domain (UI, DB, network),
  • Or is not trivial to unit test as-is…

…then going with a modular layered approach like Clean Architecture pays off early.


When Clean Architecture Is Overkill

Clean Architecture is not needed (and can actively slow you down) if:

  • You’re building a throwaway prototype or MVP.
  • The app has one screen, one user flow, or just UI + local data.
  • You’re solo, and need to ship fast, with low future change risk.

Example: A flashlight app, a simple calculator, a QR scanner, you can write these with just an Activity and a couple helper classes. No need for UseCases, Repositories, DataSources, etc.


Heuristics to Decide

If you have… Consider
>1 data source (e.g., DB + API) ✅ Use layers (repository, datasource)
Business logic that must be tested ✅ Use usecases or interactors
Multiple devs / long-term codebase ✅ Clean separation helps scale teamwork
Tight deadline + short-lived MVP ❌ Keep it flat, maybe refactor later
Simple CRUD with no complex logic ❌ Stick to MVVM or even plain Activity + ViewModel
External SDKs or unstable APIs ✅ Use abstractions to decouple

Evolution Strategy (Start Simple → Grow into Clean)

You don’t need to go full Clean from day one.

Here’s a path:

  1. Start flat with clear separation: UI → ViewModel → Repo → Network/DB.
  2. As logic grows, extract UseCases to isolate operations.
  3. If the app keeps growing, split into domain, data, and presentation modules.
  4. Add interfaces and DI (like Hilt/Koin) to fully decouple and test.

This is sometimes called “clean-lite” or “onion-by-need”.


Summary

You should adopt Clean Architecture if:

  • The app has nontrivial logic, multiple domains, or business rules.
  • You plan to maintain/test/grow it seriously.
  • You want a clear separation of concerns.

You shouldn’t go full Clean if:

  • It’s just UI + a bit of state.
  • You're prototyping.
  • Overhead will slow you more than help.

snippet.cpp
by:
▖     ▘▖▖
▌ ▌▌▛▘▌▙▌
▙▖▙▌▙▖▌ ▌
 
published: June 16 2025.
blog/tech/solid_unix.txt · Last modified: by luci4