Clean Code & OOP (full SOLID, one carve-out)
Produce clean, object-oriented, maintainable code by applying SOLID properly. There is exactly one deliberate carve-out, and nothing else about SOLID is loosened.
The single carve-out: SRP is not "one method per class"
The Single Responsibility Principle is routinely misread as "a class may have only one method / one action." That misreading spawns a swarm of single-action classes (LoginAction, RegisterAction, LogoutAction...) that fragment one concept across many files. That extreme is the only thing we drop.
Group methods by domain concept (cohesion), not by count:
✅ AuthenticationController
login() register() logout() verify() refreshToken()
— every method belongs to the same responsibility: authentication
This is not a violation of SRP — it is SRP's actual definition. SRP means "one reason to change," not "one method." All authentication operations change for the same reason, so they live together. We are restoring SRP's real meaning, not weakening it.
That is the entire carve-out. Everything below is applied in full.
Apply the rest of SOLID in full
- S — Single Responsibility: One reason to change per class. Group cohesive methods by concept (the carve-out above). At the layer level, each layer keeps its single responsibility (see next section).
- O — Open/Closed: Open for extension, closed for modification. Where behavior varies — payment providers, notification channels, export formats, auth strategies — use polymorphism/strategy so new variants are added, not bolted into existing code with conditionals.
- L — Liskov Substitution: A subtype must be usable anywhere its base type is, honoring the base contract (no surprising exceptions, no narrowed inputs). Prefer composition over inheritance to avoid LSP traps in the first place.
- I — Interface Segregation: Keep interfaces focused. A client must not be forced to depend on methods it doesn't use. Split a fat interface into role-specific ones (e.g.
Readable / Writable) rather than one bloated contract.
- D — Dependency Inversion: High-level code depends on abstractions, not concretions. Services depend on repository interfaces; external I/O (DB, payment, mail, storage, queue) sits behind an interface so it is swappable and mockable. Inject dependencies via the constructor — never
new them inside the class.
Scope note (this is correct DIP, not a relaxation): DIP applies to dependencies and seams — the volatile, IO-bound, cross-boundary collaborators a class talks to. Pure data carriers (DTOs, value objects) are not "dependencies" to invert; you pass them, you don't wrap them in interfaces. Putting an interface around a DTO is not SOLID, it's noise.
Clean layer separation (each layer = one responsibility)
Classes group cohesive methods, but responsibilities between layers stay separated. Never collapse these into one fat class:
| Layer |
Owns |
Never does |
| Controller / Handler |
Orchestration: receive request → call service → return response. Thin, cohesive by concept. |
Business rules, raw DB access, inline validation |
| Request Validation |
Validating + shaping incoming data (FormRequest, schema, validator) |
Business logic, persistence |
| DTO |
Typed, immutable data crossing layer boundaries |
Behavior, DB awareness |
| Service |
Business logic; orchestrating repositories/other services |
HTTP concerns, query building, response formatting |
| Repository |
Data access behind an interface; returns domain objects/DTOs |
Business rules, HTTP, validation |
| Response / Resource |
Shaping the outgoing payload |
Leaking internal models or DB columns directly |
A controller method should read like a table of contents: validate → delegate to service → return resource. If business logic leaks into the controller, or queries leak into the service, that is the thing to fix.
Clean-code essentials (always on)
- Intention-revealing names.
verifyOtp, not doStuff. A caller shouldn't need the implementation to understand the call.
- Small, focused methods at one level of abstraction. Extract when a block needs a comment to explain "what."
- Guard clauses over nesting. Return/throw early; keep the happy path un-indented.
- No magic values. Name constants and enums.
- Explicit errors. Throw typed/domain exceptions or return result types; don't swallow errors or return ambiguous nulls.
- Immutability where practical. DTOs and value objects are read-only.
- Constructor injection for dependencies, so classes are testable.
Avoid both failure modes
- ❌ Over-fragmented (the carve-out target): one class per method/action, cohesive concepts scattered across files, indirection nobody reuses, patterns applied for their own sake.
- ❌ Under-structured: a 400-line controller method doing validation + business rules + SQL + JSON shaping; God classes; logic copy-pasted across files; dependencies
new-ed inline.
Target: cohesive classes, fully separated layers, proper SOLID applied at real seams.
Workflow
When the user asks to write new code:
- Identify the domain concept(s) → that defines the cohesive class boundaries.
- Build the layers: Controller → Service → Repository, plus Request Validation, DTO, and Response/Resource.
- Apply OCP/LSP/ISP/DIP: depend on abstractions at seams, inject dependencies, use polymorphism for real variation points.
- Write clean methods with revealing names and guard clauses.
When the user asks to refactor / clean up / review existing code:
- Read it and name the concrete smells (fat controller, leaked queries, God class, one-class-per-action fragmentation, magic values, hidden dependencies...).
- Propose the target structure briefly.
- Apply the changes — move logic to its rightful layer, group cohesive methods, invert dependencies, extract names.
- Preserve behavior; don't silently change functionality.
Output format
- Produce clean code in the user's language (or the one they're already using), idiomatic to that language/framework.
- Add a short "Decisions" note for the structural choices — especially the cohesion choice and where each SOLID seam sits. One line each, e.g.:
- Kept
login/register/logout/verify in one AuthenticationController — cohesive concept, not one-action-per-class.
UserRepository behind an interface (DIP) so it's swappable and mockable.
PaymentGateway is an interface with provider implementations (OCP) — new providers added, not edited in.
- Don't annotate every line or lecture on theory. The code plus a few decision notes should speak for themselves.
Reference files
For full, idiomatic before→after examples, read these as needed:
references/layered-architecture.md — detailed layer responsibilities, naming, and dependency direction.
references/examples-php-laravel.md — a fat controller refactored into Request / Service / Repository / Resource, Laravel-idiomatic.
references/examples-typescript-nestjs.md — the same concept in NestJS with DTOs, providers, and dependency injection.
references/examples-python-fastapi.md — the same concept in FastAPI: cohesive auth router, ABC repository, Pydantic DTOs, Depends DI.
references/examples-java-spring.md — the same concept in Spring Boot: port interface, constructor injection, record DTOs, Bean Validation.
references/examples-csharp-dotnet.md — the same concept in ASP.NET Core: interface repository, DI in Program.cs, record DTOs.
references/examples-go.md — the same concept in Go: cohesive handler struct, interface repository (DIP), composition-root wiring.
references/examples-rust.md — the same concept in Rust (axum): cohesive controller, trait repository behind Arc<dyn>, thiserror domain errors.
references/examples-ruby-rails.md — the same concept in Rails: service object, repository over ActiveRecord, form-object validation, serializer.
references/examples-dart-flutter.md — the same concept in a Flutter app: cohesive notifier/controller, abstract repository, DI via injection.
Read a reference file when the task is in that language/framework or when you need the detailed layer contract; the guidance above is enough for most tasks.
1---2name: clean-code-oop3description: Write and refactor clean, object-oriented, maintainable code by applying SOLID properly, with one deliberate carve-out — drop the extreme reading of SRP that demands one class per method or action. Classes are cohesive and grouped by domain concept (one AuthenticationController holding login/register/logout/verify); OCP, LSP, ISP, and DIP are applied in full; and code stays cleanly separated into Request Validation, DTOs, Services, Repositories, and Response/Resource layers, depending on abstractions at real seams. Use this skill WHENEVER the user asks to write, design, scaffold, refactor, clean up, or review code with real structure — controllers, services, business logic, API endpoints, or app modules — even if they never say "clean code", "OOP", or "SOLID". Language-agnostic; examples cover PHP/Laravel, TypeScript/NestJS, Python/FastAPI, Java/Spring, C#/.NET, Go, Rust, Ruby on Rails, and Dart/Flutter.4license: MIT5---67# Clean Code & OOP (full SOLID, one carve-out)89Produce clean, object-oriented, maintainable code by applying **SOLID properly**. There is exactly **one** deliberate carve-out, and nothing else about SOLID is loosened.1011## The single carve-out: SRP is not "one method per class"1213The Single Responsibility Principle is routinely misread as *"a class may have only one method / one action."* That misreading spawns a swarm of single-action classes (`LoginAction`, `RegisterAction`, `LogoutAction`...) that fragment one concept across many files. **That extreme is the only thing we drop.**1415Group methods by **domain concept (cohesion)**, not by count:1617```18✅ AuthenticationController19 login() register() logout() verify() refreshToken()20 — every method belongs to the same responsibility: authentication21```2223This is **not** a violation of SRP — it is SRP's actual definition. SRP means *"one reason to change,"* not *"one method."* All authentication operations change for the same reason, so they live together. We are restoring SRP's real meaning, not weakening it.2425**That is the entire carve-out.** Everything below is applied in full.2627## Apply the rest of SOLID in full2829- **S — Single Responsibility:** One reason to change per class. Group cohesive methods by concept (the carve-out above). At the *layer* level, each layer keeps its single responsibility (see next section).30- **O — Open/Closed:** Open for extension, closed for modification. Where behavior varies — payment providers, notification channels, export formats, auth strategies — use polymorphism/strategy so new variants are *added*, not bolted into existing code with conditionals.31- **L — Liskov Substitution:** A subtype must be usable anywhere its base type is, honoring the base contract (no surprising exceptions, no narrowed inputs). Prefer composition over inheritance to avoid LSP traps in the first place.32- **I — Interface Segregation:** Keep interfaces focused. A client must not be forced to depend on methods it doesn't use. Split a fat interface into role-specific ones (e.g. `Readable` / `Writable`) rather than one bloated contract.33- **D — Dependency Inversion:** High-level code depends on **abstractions**, not concretions. Services depend on repository *interfaces*; external I/O (DB, payment, mail, storage, queue) sits behind an interface so it is swappable and mockable. Inject dependencies via the constructor — never `new` them inside the class.3435**Scope note (this is correct DIP, not a relaxation):** DIP applies to *dependencies and seams* — the volatile, IO-bound, cross-boundary collaborators a class talks to. Pure data carriers (DTOs, value objects) are not "dependencies" to invert; you pass them, you don't wrap them in interfaces. Putting an interface around a DTO is not SOLID, it's noise.3637## Clean layer separation (each layer = one responsibility)3839Classes group cohesive methods, but responsibilities **between layers** stay separated. Never collapse these into one fat class:4041| Layer | Owns | Never does |42|---|---|---|43| **Controller / Handler** | Orchestration: receive request → call service → return response. Thin, cohesive by concept. | Business rules, raw DB access, inline validation |44| **Request Validation** | Validating + shaping incoming data (FormRequest, schema, validator) | Business logic, persistence |45| **DTO** | Typed, immutable data crossing layer boundaries | Behavior, DB awareness |46| **Service** | Business logic; orchestrating repositories/other services | HTTP concerns, query building, response formatting |47| **Repository** | Data access behind an interface; returns domain objects/DTOs | Business rules, HTTP, validation |48| **Response / Resource** | Shaping the outgoing payload | Leaking internal models or DB columns directly |4950A controller method should read like a table of contents: validate → delegate to service → return resource. If business logic leaks into the controller, or queries leak into the service, that is the thing to fix.5152## Clean-code essentials (always on)5354- **Intention-revealing names.** `verifyOtp`, not `doStuff`. A caller shouldn't need the implementation to understand the call.55- **Small, focused methods** at one level of abstraction. Extract when a block needs a comment to explain "what."56- **Guard clauses over nesting.** Return/throw early; keep the happy path un-indented.57- **No magic values.** Name constants and enums.58- **Explicit errors.** Throw typed/domain exceptions or return result types; don't swallow errors or return ambiguous nulls.59- **Immutability where practical.** DTOs and value objects are read-only.60- **Constructor injection** for dependencies, so classes are testable.6162## Avoid both failure modes6364- ❌ **Over-fragmented (the carve-out target):** one class per method/action, cohesive concepts scattered across files, indirection nobody reuses, patterns applied for their own sake.65- ❌ **Under-structured:** a 400-line controller method doing validation + business rules + SQL + JSON shaping; God classes; logic copy-pasted across files; dependencies `new`-ed inline.6667Target: cohesive classes, fully separated layers, proper SOLID applied at real seams.6869## Workflow7071When the user asks to **write new code**:721. Identify the domain concept(s) → that defines the cohesive class boundaries.732. Build the layers: Controller → Service → Repository, plus Request Validation, DTO, and Response/Resource.743. Apply OCP/LSP/ISP/DIP: depend on abstractions at seams, inject dependencies, use polymorphism for real variation points.754. Write clean methods with revealing names and guard clauses.7677When the user asks to **refactor / clean up / review existing code**:781. Read it and name the concrete smells (fat controller, leaked queries, God class, one-class-per-action fragmentation, magic values, hidden dependencies...).792. Propose the target structure briefly.803. Apply the changes — move logic to its rightful layer, group cohesive methods, invert dependencies, extract names.814. Preserve behavior; don't silently change functionality.8283## Output format8485- Produce clean code in the user's language (or the one they're already using), idiomatic to that language/framework.86- Add a **short "Decisions" note** for the structural choices — especially the cohesion choice and where each SOLID seam sits. One line each, e.g.:87 > - Kept `login`/`register`/`logout`/`verify` in one `AuthenticationController` — cohesive concept, not one-action-per-class.88 > - `UserRepository` behind an interface (DIP) so it's swappable and mockable.89 > - `PaymentGateway` is an interface with provider implementations (OCP) — new providers added, not edited in.90- Don't annotate every line or lecture on theory. The code plus a few decision notes should speak for themselves.9192## Reference files9394For full, idiomatic before→after examples, read these as needed:9596- `references/layered-architecture.md` — detailed layer responsibilities, naming, and dependency direction.97- `references/examples-php-laravel.md` — a fat controller refactored into Request / Service / Repository / Resource, Laravel-idiomatic.98- `references/examples-typescript-nestjs.md` — the same concept in NestJS with DTOs, providers, and dependency injection.99- `references/examples-python-fastapi.md` — the same concept in FastAPI: cohesive auth router, ABC repository, Pydantic DTOs, `Depends` DI.100- `references/examples-java-spring.md` — the same concept in Spring Boot: port interface, constructor injection, record DTOs, Bean Validation.101- `references/examples-csharp-dotnet.md` — the same concept in ASP.NET Core: interface repository, DI in Program.cs, record DTOs.102- `references/examples-go.md` — the same concept in Go: cohesive handler struct, interface repository (DIP), composition-root wiring.103- `references/examples-rust.md` — the same concept in Rust (axum): cohesive controller, trait repository behind `Arc<dyn>`, `thiserror` domain errors.104- `references/examples-ruby-rails.md` — the same concept in Rails: service object, repository over ActiveRecord, form-object validation, serializer.105- `references/examples-dart-flutter.md` — the same concept in a Flutter app: cohesive notifier/controller, abstract repository, DI via injection.106107Read a reference file when the task is in that language/framework or when you need the detailed layer contract; the guidance above is enough for most tasks.