This is an EXAMPLE file. Use at your own risk. It is provided as a reference template for development standards and coding conventions. Adapt it to your project's needs before adopting. No guarantees are made about its completeness or suitability for any specific use case.
Language-agnostic. Framework-agnostic.
- Core Philosophy
- Design Principles
- Code Quality
- Architecture
- File Structure
- Error Handling
- Security
- Observability
- Testing
- Code Review
- Documentation
- Pre-Delivery Checklist
- Correctness — Code does what it should
- Simplicity and readability — Code is easy to understand
- Testability — Code is easy to test
- Performance — Code is fast enough
- Abstraction and reuse — Code is DRY
- Read and understand existing code before modifying it.
- Follow the project's existing patterns and conventions.
- If a requirement is ambiguous, ask before writing code.
- Prefer incremental delivery: core logic first, then edge cases, then refinements.
- Do not overengineer. Build for today's requirements, not hypothetical future ones.
| Principle | Rule | Common Mistake |
|---|---|---|
| SRP — Single Responsibility | Each class/function/file has ONE reason to change. If you need "and" or "or" to describe it, split it. | Interpreting SRP as "one function per class." SRP means one axis of change. |
| OCP — Open/Closed | Add new behavior by writing new code, not modifying existing code. Use polymorphism or strategy patterns where change is expected. | Over-engineering with premature abstractions. Apply OCP where you have evidence of changing requirements. |
| LSP — Liskov Substitution | Subclasses must honor the contract of their parent. Prefer composition over inheritance when "is-a" is not strict. | Overriding a method to throw NotImplementedError or do nothing. |
| ISP — Interface Segregation | Define small, role-specific interfaces. Clients depend only on methods they use. | Creating one "service" interface with 15+ methods. |
| DIP — Dependency Inversion | Depend on abstractions at module boundaries, not concrete implementations. Domain logic must never import from infrastructure. | Confusing DIP with "just use dependency injection." DIP is about inverting the direction of source-code dependency. |
- Extract shared logic when the exact same business rule is duplicated in 3+ places (Rule of Three).
- Single source of truth for configuration, constants, and schema definitions.
- Prefer duplication over wrong abstraction. Two pieces of code that look similar but serve different business purposes are NOT duplication — merging them creates accidental coupling.
- "Wrong abstraction" means: premature generalization, unclear purpose, or coupling unrelated concerns.
- Choose the simplest implementation that satisfies current requirements.
- Prefer standard library solutions over custom implementations.
- A plain function call beats metaprogramming. A dictionary beats a class when all you need is data grouping.
- Do not add design patterns, abstractions, or frameworks "just in case."
- Implement features only when there is a concrete, current requirement.
- Do not build generic/extensible frameworks before you have at least two concrete use cases.
- Delete speculative code and unused feature flags regularly.
- Three similar lines of code is better than a premature abstraction.
- Use clear, meaningful, intention-revealing names. The name should answer why it exists and what it does.
- Functions use verbs:
get,create,update,delete,validate,format,parse. - Booleans use prefixes:
is,has,can,should. - No abbreviations unless universally understood (
id,url,api). - No generic names:
data,result,obj,thing,temp,misc,utils. - No names with "and", "or", "then" — that signals multiple responsibilities.
- Use strong typing everywhere. Avoid
any,object,dynamic,Object. - Use typed parameters and return types for all public functions.
- Never cast to
anyjust to make something compile.
- Default to immutable. Use
const,readonly,final,frozen,tuple,frozenset. - Return new objects from transformation functions instead of mutating inputs.
- Never expose mutable internal collections. Return copies or read-only views.
- Mutable local variables inside a function are fine — mutable shared state is the danger.
- Validate preconditions at the top of functions and return/throw early.
- Reduce nesting by inverting conditions and returning early.
- Keep the "happy path" at the lowest indentation level.
- Extract repeated numbers and strings to named constants.
- Use descriptive variable names instead of inline literals.
- Do not comment obvious code. Prefer self-explanatory code through good naming.
- Comments explain WHY, never WHAT.
- No commented-out code — use version control.
- No TODO comments without ticket references.
- Keep functions short with a single level of abstraction.
- One function does one thing. If it does two things, split it.
- Do not use boolean parameters that switch behavior — split into two named functions.
- Eliminate dead code and unused imports on every change.
- Separate domain, application, and infrastructure concerns.
- Domain/business logic must have zero imports from frameworks, databases, or HTTP layers.
- Keep side effects (I/O, logging, metrics) at the edges. Business logic should be pure.
- Use DTOs or value objects at layer boundaries — never pass ORM models or HTTP request objects into business logic.
| Layer | CAN | CANNOT |
|---|---|---|
| Handler/Controller | Receive input, delegate to service, return output | Contain business logic, call DB directly |
| Service/Orchestrator | Coordinate operations, apply business rules | Know about HTTP/transport, execute SQL directly |
| Repository/Data Access | Execute queries, map data | Make business decisions, call external APIs |
| Helper | Transform data, validate, format | Have side effects, do I/O, maintain state |
| External Client | Communicate with external services | Contain business logic, access database |
- Inject dependencies through constructors or method parameters. Make all dependencies explicit.
- Inject I/O boundaries (database, HTTP clients, filesystem, clock) so they are swappable in tests.
- Keep the composition root at the application entry point, separate from business logic.
- If a class needs more than ~4 injected dependencies, it is doing too much — split it.
- Only inject things that have side effects or vary between environments. Do not inject pure utility functions.
- Apply DDD concepts only if the domain complexity clearly justifies it.
- Keep domain logic independent from frameworks and infrastructure.
- Use Entities, Value Objects, and Aggregates only when they add real value.
- Model errors and invariants as part of the domain.
| Metric | Guideline |
|---|---|
| Lines of code (excluding imports, types, docs) | ~500 lines (up to ~530 OK; 600+ is a red flag) |
| Functions with DIFFERENT responsibilities | 5 functions max |
| Functions with SAME responsibility (same prefix) | 10 functions max |
| Main classes per file | 1 class |
| Small related classes (exceptions, DTOs, enums) | 5 classes (if all same type) |
Every file MUST have one reason to exist and one reason to change.
The Test: Can you describe this file's purpose in ONE sentence WITHOUT using "and" or "or"?
Functions MUST be grouped by responsibility category. Functions with DIFFERENT prefixes MUST NOT coexist in the same file.
| Responsibility | Function Prefixes | Separate File |
|---|---|---|
| Types/Models | Type definitions, interfaces, classes without logic | {feature}_types |
| Constants | MAX_*, DEFAULT_*, enums |
{feature}_constants |
| Validation | validate*, check*, is_valid* |
validation |
| Formatting | format*, build*, serialize*, to_* |
formatting |
| Parsing | parse*, extract*, from_* |
parsing |
| External calls | fetch*, send*, call*, request* |
{service}_client |
| Data access | save*, load*, find*, delete*, query* |
{feature}_repository |
| Orchestration | Main entry points, coordination | {feature}_service |
| Handlers | Endpoints, controllers, views | {feature}_handler |
- Do NOT create a separate file for 1-2 trivial functions with less than 20 lines total.
- Private helpers (
_func) stay in the file that uses them. - One-liner utilities are not extracted to separate files.
- Split when you have clear, reusable responsibilities. Keep together when separation adds complexity without benefit.
- NEVER use generic names:
utils,helpers,misc,common,sharedas standalone files. - Follow the project's existing naming convention.
feature/
├── {feature}_service # Orchestration
├── {feature}_types # Type definitions
├── {feature}_constants # Constants and enums
├── helpers/
│ ├── validation # ONLY validation functions
│ ├── formatting # ONLY formatting functions
│ └── parsing # ONLY parsing functions
├── services/
│ └── {external}_client # ONLY external API communication
├── repositories/
│ └── {feature}_repository # ONLY data persistence
└── handlers/
└── {feature}_handler # ONLY request handling
- Handle expected errors explicitly. No silent failures.
- Do not use generic exceptions (
Exception,Error,object). Use domain-relevant error types. - Return or throw errors with meaningful context (what failed, what input caused it, how to fix it).
- Errors are part of the API contract.
- Validate inputs at system boundaries. Fail fast on invalid data.
- Distinguish between recoverable errors and fatal exceptions.
- Never silently coerce or fix invalid input — reject with a clear message.
# BAD
try:
result = do_something()
except:
pass
# GOOD
try:
result = do_something()
except ValidationError as e:
logger.warning("Validation failed", extra={"error": str(e), "field": e.field})
raise DomainError(f"Invalid input: {e.field}") from e- Sanitize and validate all user and external inputs at the boundary.
- Never trust data from outside the system boundary.
- Use allowlists, not denylists. Reject by default, accept only known-good patterns.
- Use schema validation libraries (Pydantic, zod, JSON Schema) — do not hand-roll validation for complex structures.
- Keep secrets out of code. Use environment variables or secret managers.
- No hardcoded API keys, tokens, or passwords.
- SQL queries use parameterized statements — no string concatenation.
- Do not expose internal details in error messages to end users.
- Validate on the server side always — client-side validation is a UX convenience, not a security measure.
- Use fake/anonymized data in tests — never real user data.
- Use structured logging (key-value / JSON), not formatted strings.
- Log at key decision points and boundaries, not inside tight loops.
- Include: operation name, relevant IDs, outcome (success/failure), duration if relevant.
- Use consistent field names across the entire codebase.
| Level | When to Use |
|---|---|
| ERROR | Something is broken and needs human attention |
| WARN | Degraded but self-recoverable |
| INFO | Significant business events |
| DEBUG | Diagnostic detail, off in production |
- NEVER log: email addresses, user names, phone numbers, physical addresses, tokens, passwords.
- Approved identifiers:
auth_id,user_id,internal_id. - No
print()/console.log()with user data — these go to production logs.
Test code is production code. It receives the same care, review, and quality standards.
- Write unit tests for all core logic.
- Follow Arrange-Act-Assert (AAA) structure. ONE act per test, ONE logical assertion per test.
- Tests MUST be independent, deterministic, and not depend on execution order.
- Mock or fake all external dependencies (DB, APIs, filesystem, time, randomness).
- Name tests clearly:
should_[expected]_when_[condition].
Happy path tests are the foundation — they validate the code works under normal conditions. Always start with these.
But happy path tests ALONE are not enough. You MUST also write adversarial tests that actively try to break the code and find defects:
- Unexpected input types:
None,"",[],{},0,-1 - Boundary values: max int, max length, exactly at the limit, one past the limit
- Malformed data: missing fields, extra fields, wrong types, invalid formats
- Error states: what happens when dependencies fail?
- What should NOT happen: verify that forbidden states are correctly rejected
- Error messages and types: not just that it fails, but how it fails
Write tests based on REQUIREMENTS/SPEC, not on what the source code currently does. This is how you catch bugs where the code diverges from expected behavior.
When a test fails: first ask if the CODE is wrong, not the test. Do NOT silently change a failing assertion to match the current code without understanding WHY.
| Metric | Guideline |
|---|---|
| Lines per file | ~1000 lines guideline — above this, consider splitting, but not required if covering a single module |
| Tests per file | No hard limit — split only when covering unrelated behaviors |
| Setup (Arrange) | ~20 lines max per test (extract to helpers/factories if exceeded) |
Split test files based on LOGICAL SEPARATION, not arbitrary line counts. One file per module/service is perfectly fine, even at 800+ lines.
- Target: 80%. Minimum acceptable: 75%. Below 75% the task is not complete.
- Focus on branch coverage (both sides of
if/else, allcatchblocks), not just line coverage. - High coverage with no assertions is worthless. Every test MUST have at least one meaningful assertion.
- Coverage must be run and shown at the end for ALL created tests (backend AND frontend).
# Python
pytest tests/your_tests.py --cov=src/module_under_test --cov-report=term-missing --cov-branch -v
# JavaScript/TypeScript (Jest)
npx jest tests/your_tests.test.ts --coverage --collectCoverageFrom="src/module/**/*.{ts,tsx}"
# JavaScript/TypeScript (Vitest)
npx vitest run tests/your_tests.test.ts --coverage- Every test you create or modify MUST pass. Zero failures. Zero exceptions.
- Never disable, skip, or delete a test to hide a failure.
- Never leave a test "to fix later" — fix it NOW.
- If coverage is below 75%: write more tests, re-run, repeat until the minimum is met.
- Simple getters, setters, trivial mappers — not worth testing.
- Implementation details (method call order, internal state) — test behavior instead.
- Do not inflate coverage with meaningless assertions.
| Pattern | Problem |
|---|---|
| The Liar | Test passes but doesn't verify the behavior it claims to test |
| The Mirror | Test reads the source code and asserts exactly what the code does — finds zero bugs |
| The Giant | 50+ lines of setup, multiple acts, dozens of assertions — should be 5+ separate tests |
| The Mockery | So many mocks that the test only tests the mock setup |
| The Inspector | Coupled to implementation details, breaks on any refactor |
| The Chain Gang | Tests depend on execution order or share mutable state |
| The Flaky | Sometimes passes, sometimes fails with no code changes |
- Security & PII — No PII in logs, no hardcoded secrets, input validation
- DRY — No duplicate types, classes, functions, or logic
- File Structure — Limits respected, responsibilities separated
- Architecture — Single responsibility, proper layer separation
- Code Quality — SOLID, strong typing, error handling
- Testing — Both happy path AND adversarial tests, coverage met
- Observability — Structured logging, no PII
- "Are there BOTH happy path AND adversarial tests?"
- "Would these tests catch a regression if someone broke the logic?"
- "Are there edge cases or failure modes that aren't being tested?"
- "If I remove a line of business logic, will at least one test fail?"
- Do NOT prolong bad patterns — even if surrounding code is bad, write good code.
- Do NOT copy-paste from legacy code without reviewing quality.
- Isolate new code from legacy where possible.
- Generate feature documentation after implementation is complete.
- Documentation lives alongside code in the repository (Markdown).
- Use ubiquitous language — same terms in docs, code, and communication.
| Level | Audience | Content |
|---|---|---|
| Context (L1) | Product / Stakeholders | System in its environment |
| Container (L2) | Both | Applications, databases, queues |
| Component (L3) | Engineering | Internal service details |
- Overview — Summary, business context, bounded context
- Ubiquitous Language Glossary — Domain terms with code references
- Domain Model — Aggregates, entities, value objects, events
- Behavior Specifications — Gherkin scenarios (happy path, edge cases, errors)
- Architecture Decision Records — Context, decision, consequences
- Technical Specification — Dependencies, API contracts, error codes
- Observability — Metrics, logs, dashboards
- Deployment & Rollback — Feature flags, migrations, rollback plan
BEFORE delivering ANY code, verify ALL items.
- No PII in any logs, prints, or webhook messages
- No secrets or credentials in code
- No duplicate types, classes, or logic (DRY)
- No file exceeds ~500 lines (production code) or ~1000 lines (test code)
- No mixed responsibility prefixes in same file
- All user inputs validated at system boundaries
- Each file/function has single responsibility
- Proper error handling (no silent failures, meaningful errors)
- Strong typing (no
any,object,dynamic) - Types in dedicated types file, constants in dedicated constants file
- Domain logic independent from frameworks/infrastructure
- Unit tests for all core logic
- Both happy path AND adversarial tests exist
- All created/modified tests pass — zero failures
- Coverage report ran and output shown (backend AND frontend)
- Coverage >= 75% minimum (target 80%)
- No test anti-patterns (Liar, Mirror, Giant, Mockery, Inspector)
- Structured logging at key decision points
- Comments explain WHY, not WHAT
- No over-engineering (no files with 1-2 trivial functions)
- No legacy bad patterns prolonged
- Linter ran on all changed files — zero errors
- Formatter ran on all changed files — zero diffs
- Type checker ran (if applicable) — zero errors
This guide applies to every line of code in the Langflow project. When in doubt, choose simplicity. When trade-offs arise, follow the priority order in Section 1. Build for correctness first. Optimize later. Test always.