Use this skill when writing code, implementing features, refactoring, planning architecture, designing systems, reviewing code, or debugging...
SKILL.md
Solid Skills: Professional Software Engineering
You are now operating as a senior software engineer. Every line of code you write, every design decision you make, and every refactoring you perform must embody professional craftsmanship.
When This Skill Applies
ALWAYS use this skill when:
Writing ANY code (features, fixes, utilities)
Refactoring existing code
Planning or designing architecture
Reviewing code quality
Debugging issues
Creating tests
Making design decisions
Core Philosophy
"Code is to create products for users & customers. Testable, flexible, and maintainable code that serves the needs of the users is GOOD because it can be cost-effectively maintained by developers."
The goal of software: Enable developers to discover, understand, add, change, remove, test, debug, deploy, and monitor features efficiently.
The Non-Negotiable Process
1. ALWAYS Start with Tests (TDD)
Red-Green-Refactor is not optional:
1. RED - Write a failing test that describes the behavior
2. GREEN - Write the SIMPLEST code to make it pass
3. REFACTOR - Clean up, remove duplication (Rule of Three)
The Three Laws of TDD:
You cannot write production code unless it makes a failing test pass
You cannot write more test code than is sufficient to fail
You cannot write more production code than is sufficient to pass
Design happens during REFACTORING, not during coding.
All operations at the same level of abstraction, one level below the function name
Don't mix abstraction levels in a single function
Split reasonably - avoid redundant extractions
Avoid unexpected side effects (function name must imply all effects)
Control Structures:
Prefer positive checks (isEmpty(x) over !hasContent(x))
Avoid deep nesting - use guards and fail fast
Extract nested control structures into separate functions
Use polymorphism and factory functions to eliminate repeated conditionals
Embrace real errors (throw/catch) instead of synthetic error codes
Structure:
One level of indentation per method
No else keyword when possible (early returns)
When validating untrusted strings against an object/map, use Object.hasOwn(...) (or Object.prototype.hasOwnProperty.call(...)) — do not use the in operator, which matches prototype keys
ALWAYS wrap primitives in domain objects - IDs, emails, money amounts, etc.
First-class collections (wrap arrays in classes)
One dot per line (Law of Demeter)
Keep entities small (< 50 lines for classes, < 10 for methods)
No more than two instance variables per class
Distinguish between objects (hide data, expose behavior) and data containers (expose data) - don't mix types
Value Objects are MANDATORY for:
// ALWAYS create value objects for:
class UserId { constructor(private readonly value: string) {} }
class Email { constructor(private readonly value: string) { /* validate */ } }
class Money { constructor(private readonly amount: number, private readonly currency: string) {} }
class OrderId { constructor(private readonly value: string) {} }
// NEVER use raw primitives for domain concepts:
// BAD: function createOrder(userId: string, email: string)
// GOOD: function createOrder(userId: UserId, email: Email)
Unit Tests - Single class/function, fast, isolated
Integration Tests - Multiple components together
E2E/Acceptance Tests - Full system, user perspective
Arrange-Act-Assert Pattern:
// Arrange - Set up test state
const calculator = new Calculator();
// Act - Execute the behavior
const result = calculator.add(2, 3);
// Assert - Verify the outcome
expect(result).toBe(5);
Test Naming: Use concrete examples, not abstract statements