MeshiTrack/docs/style-guidelines.md

123 lines
6.2 KiB
Markdown
Raw Normal View History

2026-05-19 16:15:15 +09:00
# MeshiTrack Coding Style & Engineering Guidelines
This document establishes the official coding standards, architectural paradigms, and TypeScript guidelines for the MeshiTrack monorepo. These guidelines are designed to enforce clean, testable, object-oriented codebases with pristine type-safety under strict quality gates.
---
## 🏛️ 1. Encapsulation & Class Member Accessibility
To ensure clear visibility boundaries, clean interfaces, and easy maintainability:
1. **Mandatory Accessibility Modifiers**: Every field, constructor, and method in a class must explicitly declare its accessibility modifier (`public`, `protected`, or `private`).
2. **Mandate Explicit Fields**: Classes that participate in dependency injection (e.g. Services, Repositories, Providers) must explicitly declare their properties at the class level rather than relying on TypeScript constructor parameter properties.
3. **Class Member Ordering**: Group class members in a predictable sequence:
* Static Fields (Public -> Protected -> Private)
* Instance Fields (Public -> Protected -> Private)
* Constructor
* Public Methods
* Protected Methods
* Private Methods
### Example:
```typescript
export class ShoppingListsService {
// 1. Explicit, readonly instance properties
private readonly shoppingListsRepository: ShoppingListsRepository;
// 2. Explicit constructor receiving DI payload
public constructor({ shoppingListsRepository }: Deps) {
this.shoppingListsRepository = shoppingListsRepository;
}
// 3. Public class methods
public async list(householdId: string): Promise<ShoppingListDocument[]> {
return this.shoppingListsRepository.list(householdId);
}
}
```
---
## 🛡️ 2. Boundary Types & Return Types
To prevent type-drift across software boundaries:
1. **Explicit Return Types on Boundary Functions**: All public methods of class instances (Services, Repositories, Providers) and all exported module functions must explicitly declare their return types.
2. **Relegate Inference to Callbacks**: Local, inline variables and anonymous arrow callbacks (e.g. within array map/filter functions) may utilize automatic type inference for conciseness.
### Example:
```typescript
// 🟢 Good: Explicitly defined return type on the boundary method
public async list(householdId: string): Promise<ShoppingList[]> {
const lists = await this.shoppingListsRepository.list(householdId);
return lists.filter(item => item.status === 'active'); // Inline inference is okay
}
```
---
## 🚫 3. Eradicating `any` & Enforcing Type-Safety
TypeScript's `any` type compromises the integrity of our codebase and makes refactoring fragile.
1. **No Explicit `any`**: The use of explicit `any` is strictly prohibited throughout the codebase, including in test files.
2. **Restrict Unsafe Operations**: We enforce strict type-checked rules to prevent the propagation of `any`:
* Assigning an `any` to a variable/property is blocked (`@typescript-eslint/no-unsafe-assignment`).
* Reading a property off of an `any` is blocked (`@typescript-eslint/no-unsafe-member-access`).
* Invoking functions typed as `any` is blocked (`@typescript-eslint/no-unsafe-call`).
* Returning an `any` from a function is blocked (`@typescript-eslint/no-unsafe-return`).
3. **Prefer `unknown` for Dynamic Inputs**: Any unchecked external input (e.g., API payloads, database responses, dynamic JSON files) must be typed as `unknown` and validated at the boundary using **Zod schemas** or type guards.
4. **Type-Safe Mocking**: Instead of casting mocks with `as any`, use highly targeted partial types or precise mocks.
---
## 🏷️ 4. Naming Conventions & File Structures
Uniform naming conventions prevent conflicts and reduce cognitive load:
1. **Directories**: Lowercase `kebab-case` (e.g., `shopping-lists/`).
2. **Files**: `PascalCase` matching the primary class or component exported in the file (e.g., `ShoppingListsService.ts`), ensuring class name and file name are aligned.
3. **Identifiers**:
* **PascalCase**: Classes, Interfaces, Type Aliases, Enums.
* **camelCase**: Properties, Methods, Variables, Parameters, Functions.
* **UPPER_CASE**: Top-level static read-only constants.
4. **Prohibit Hungarian Notation**: Do not prefix interfaces with `I` (e.g. write `ShoppingListsRepository`, NOT `IShoppingListsRepository`).
---
## 🛑 5. Structured Domain Exceptions
Exception mapping must be uniform, semantic, and highly testable:
1. **Prohibit Generic `Error` Throwing**: The service and business layers must never throw generic `new Error('message')` for operational domain failures.
2. **Domain-Specific Exceptions**: Throw subclasses of `AppError` (e.g. `NotFoundError`, `ConflictError`, `BadRequestError`) containing semantic properties like status codes and optional structured sub-details.
3. **Prevent Stack Leakage**: Our custom exceptions map directly to HTTP responses in central middleware, ensuring internal database schemas or runtime details are never leaked to external clients.
### Example:
```typescript
// 🟢 Good: Highly testable, structured domain-level exception
if (!updated) {
throw new NotFoundError('Shopping list not found');
}
```
---
## 🧱 6. OOP vs. Functional Boundaries
We keep class-based OOP strictly segregated from pure stateless helpers:
1. **Stateful Layers -> Classes**: Services, Repositories, and Providers must be class-based to properly encapsulate state, DI, and persistence hooks.
2. **Stateless Operations -> Standalone Pure Functions**: Math utilities, date/string formatters, and mapping helpers must be written as standalone, exported functions in pure modules.
3. **No Utility Classes**: Do not write static utility classes (classes composed solely of static helper methods). Use direct ES module exports.
---
## ⚡ 7. Asynchronous Programming Integrity
To prevent background crashes, memory leaks, and silent failures:
1. **Mandate `async/await`**: Use `async/await` exclusively instead of Promise chains (`.then()`, `.catch()`).
2. **Block Floating Promises**: Every Promise must be `await`ed or returned.
3. **Explicit Voiding for Intentional Background Tasks**: If a task must run in the background (fire-and-forget), it must be explicitly voided (e.g. `void this.trackEvent()`) and handle its exceptions internally to prevent uncaught runtime rejections.