Marketplace

coding-standards

Comprehensive coding standards for MetaSaver TypeScript projects including SOLID principles, DRY/KISS/YAGNI guidelines, error handling with AppError hierarchy, structured logging with Pino, and code organization rules. Use when implementing features, refactoring code, or establishing coding patterns.

$ 安裝

git clone https://github.com/metasaver/metasaver-marketplace /tmp/metasaver-marketplace && cp -r /tmp/metasaver-marketplace/plugins/metasaver-core/skills/cross-cutting/coding-standards ~/.claude/skills/metasaver-marketplace

// tip: Run this command in your terminal to install the skill


name: coding-standards description: Comprehensive coding standards for MetaSaver TypeScript projects including SOLID principles, DRY/KISS/YAGNI guidelines, error handling with AppError hierarchy, structured logging with Pino, and code organization rules. Use when implementing features, refactoring code, or establishing coding patterns.

Coding Standards Skill

Single source of truth for MetaSaver coding principles and patterns.

Use when:

  • Implementing new features or services
  • Refactoring code for quality
  • Establishing patterns in new modules
  • Reviewing code for standards compliance
  • Teaching or documenting best practices

Core Principles at a Glance

PrincipleApplication
SOLIDOOP design principles for class/module design
DRYExtract shared logic; single source of truth
KISSSimplest working solution; no premature abstraction
YAGNIBuild for current requirements only; delete unused code
ErrorsAppError hierarchy with proper status codes
LoggingStructured Pino logs with action + context

Workflow

1. SOLID Principles Review

Verify each component follows one SOLID principle:

  • S - Single Responsibility: One reason to change
  • O - Open/Closed: Extend via interfaces, not modification
  • L - Liskov Substitution: Subtypes properly substitute base
  • I - Interface Segregation: No fat interfaces
  • D - Dependency Inversion: Inject abstractions, not concretions

See templates/solid-examples.ts.template for examples of all 5.

2. DRY, KISS, YAGNI Check

  • DRY: Extract validation schemas, constants, business rules to single location
  • KISS: Use simplest solution; avoid unnecessary layers or patterns
  • YAGNI: Implement only current requirements; delete speculative code

See templates/dry-kiss-yagni.ts.template for patterns.

3. Error Handling Setup

Implement AppError hierarchy:

  • Base: AppError(message, code, statusCode)
  • Extend: ValidationError, NotFoundError, UnauthorizedError, ForbiddenError
  • Middleware: Centralized error handler catches and formats responses

See templates/error-handling.ts.template for complete setup.

4. Structured Logging

Configure Pino logger with:

  • Development: pretty-printed output with colors
  • Production: JSON output for parsing
  • Child loggers with context (service name, correlationId)
  • Consistent log pattern: { action, userId, error, correlationId }

See templates/logging.ts.template for setup and patterns.

5. Import & Module Standards

StandardPatternExample
Explicit barrel exportsList each export by nameexport { UserService } from './user.service.js'
Current library APIsUse latest non-deprecated methodsrouter.get() not router.addRoute()
Path alias importsUse @/ for src-relative pathsimport { User } from '@/types'

Barrel files (index.ts):

// ✅ GOOD: Explicit exports
export { UserService } from "./user.service.js";
export { UserRepository } from "./user.repository.js";
export type { User, CreateUserDto } from "./user.types.js";

// ✅ ENSURE: Use explicit exports for tree-shaking and clarity (not wildcard re-exports)
// Wildcard re-exports make tree-shaking harder and hide what's exported
// export * from './user.service.js'; // ❌ Only use if absolutely necessary

Import paths:

// ✅ GOOD: Use path aliases for clean imports
import { UserService } from "@/services/user.service.js";
import { validateUser } from "@/utils/validation.js";

// ✅ ENSURE: Use path aliases instead of deep relative paths for maintainability
// Relative paths beyond parent directory are harder to maintain
// import { UserService } from '../../../services/user.service.js'; // ❌ Use @/ alias instead

Library APIs:

// ✅ GOOD: Use current, non-deprecated APIs
const response = await fetch(url, { signal: controller.signal });

// ✅ ENSURE: Use modern fetch API instead of deprecated patterns
// Older patterns like on() callbacks are no longer recommended
// request.on('response', callback); // ❌ Use fetch API instead

Direct Imports

Import directly from source files for optimal treeshaking and explicit dependency tracking.

Benefits

  • Treeshaking - Modern bundlers eliminate unused exports when imports are specific
  • Explicit Dependencies - Each import shows exactly what the file needs
  • Smaller Bundles - Only imported code is included in production builds

Pattern

// Direct import from source file (optimal)
import { validateUser } from "#/users/validation.js";
import type { User } from "#/users/types.js";
import { AuthService } from "#/services/auth.js";

// Package subpath imports (external packages)
import { prisma } from "@metasaver/database/client";
import type { UserSchema } from "@metasaver/contracts/users/schemas";

Key Points

  1. Internal imports use #/ path alias pointing to ./src
  2. External imports use package subpaths (@metasaver/contracts/users/types)
  3. Each file imports only what it needs from specific source files
  4. Bundler treeshaking eliminates unused code at build time

See also: Coder agent (core-claude-plugin:generic:coder) for full import/export patterns

7. Code Organization Rules

TypeIdealMaxIf exceeded
Service~100 lines200 linesSplit by domain
Controller~50 lines100 linesMove logic to service
Utility~50 lines100 linesSplit by function
Type file~50 lines150 linesGroup by entity

Function guidelines:

  • Max 50 lines per function
  • Max 3 parameters (use objects for more)
  • Single level of abstraction
  • Early returns for guard clauses

Quick Reference Checklist

Before committing code:

  • Each class has single responsibility
  • No duplicate logic (DRY)
  • Simplest solution used (KISS)
  • No speculative features (YAGNI)
  • Dependencies injected (not instantiated)
  • Errors use AppError hierarchy
  • Logging includes action + context
  • Functions under 50 lines
  • Files under max line count
  • All types are explicit (use specific types instead of any)
  • All inputs validated with Zod
  • Barrel files use explicit exports (not export *)
  • Library APIs are current (use non-deprecated methods)
  • Imports use @/ alias (instead of deep relative paths)

Examples

Example 1: Service with Error Handling + Logging

import { NotFoundError } from "../errors/index.js";
import { logger } from "../utils/logger.js";

export class UserService {
  private readonly log = logger.child({ service: "UserService" });

  async getUser(id: string): Promise<User> {
    this.log.debug({ action: "getUser", userId: id });

    const user = await this.userRepository.findById(id);
    if (!user) throw new NotFoundError("User", id);

    return user;
  }

  async createUser(data: CreateUserDto): Promise<User> {
    this.log.info({ action: "createUser", email: data.email });

    const existing = await this.userRepository.findByEmail(data.email);
    if (existing) throw new ValidationError("Email in use", "email");

    try {
      const user = await this.userRepository.create(data);
      this.log.info({ action: "userCreated", userId: user.id });
      return user;
    } catch (error) {
      this.log.error({ action: "createUser", error, email: data.email });
      throw error;
    }
  }
}

Example 2: Interface Segregation (SOLID I)

// BAD: Fat interface
interface Worker {
  work(): void;
  eat(): void;
  attendMeeting(): void;
}

// GOOD: Segregated interfaces
interface Workable {
  work(): void;
}

class Developer implements Workable, Eatable, MeetingAttendee {
  work(): void {}
  eat(): void {}
  attendMeeting(): void {}
}

class Robot implements Workable {
  work(): void {} // Only needs work
}

Example 3: DRY with Zod Schema

const emailSchema = z.string().email();

function validateEmail(email: unknown): string {
  return emailSchema.parse(email);
}

async function createUser(data: CreateUserDto): Promise<User> {
  const email = validateEmail(data.email);
  return db.users.create({ ...data, email });
}

async function updateUser(id: string, data: UpdateUserDto): Promise<User> {
  const email = validateEmail(data.email); // Reuse validation
  return db.users.update(id, { ...data, email });
}

Template Files

Complete, copy-paste-ready code examples:

  • templates/solid-examples.ts.template - All 5 SOLID principles
  • templates/dry-kiss-yagni.ts.template - DRY, KISS, YAGNI patterns
  • templates/error-handling.ts.template - AppError hierarchy + middleware
  • templates/logging.ts.template - Pino setup + logging patterns

Detailed Reference

See reference.md for:

  • Extended explanation of each principle
  • Advanced error handling patterns
  • Comprehensive logging strategies
  • Code organization guidelines
  • Related skills and resources

Related Skills

  • /skill domain/monorepo-audit - Monorepo structure validation
  • /skill cross-cutting/serena-code-reading - Progressive code analysis

Repository

metasaver
metasaver
Author
metasaver/metasaver-marketplace/plugins/metasaver-core/skills/cross-cutting/coding-standards
0
Stars
0
Forks
Updated5h ago
Added1w ago