Clean code principles, error handling patterns, and coding standards for PACT Code phase. Use when: implementing features, refactoring code, reviewing code quality, establishing coding conventions,...
Clean code principles for the Code phase of PACT. This skill provides essential coding guidelines and links to detailed patterns for implementation.
Each function, class, or module should have exactly one reason to change.
// BAD: Multiple responsibilities
class UserManager {
createUser(data) { }
validateEmail(email) { }
sendWelcomeEmail(user) { }
generateReport() { }
updateUserPreferences(userId, prefs) { }
}
// GOOD: Single responsibility each
class UserService {
createUser(data) { }
updateUser(userId, data) { }
}
class EmailValidator {
validate(email) { }
}
class NotificationService {
sendWelcomeEmail(user) { }
}
class UserReportGenerator {
generate(criteria) { }
}
Extract duplicated logic into reusable functions.
// BAD: Duplicated validation logic
function createUser(data) {
if (!data.email || !data.email.includes('@')) {
throw new Error('Invalid email');
}
// ...
}
function updateUser(id, data) {
if (data.email && !data.email.includes('@')) {
throw new Error('Invalid email');
}
// ...
}
// GOOD: Extracted validation
function validateEmail(email) {
if (!email || !email.includes('@')) {
throw new ValidationError('Invalid email format');
}
}
function createUser(data) {
validateEmail(data.email);
// ...
}
function updateUser(id, data) {
if (data.email) {
validateEmail(data.email);
}
// ...
}
Choose the simplest solution that works.
// BAD: Over-engineered
class ConfigurationFactoryBuilderManager {
static getInstance() {
return new ConfigurationFactoryBuilder()
.withDefaults()
.withEnvironment()
.build()
.getConfiguration();
}
}
// GOOD: Simple and direct
const config = {
port: process.env.PORT || 3000,
dbUrl: process.env.DATABASE_URL,
debug: process.env.NODE_ENV !== 'production'
};
Validate inputs, handle edge cases, and fail gracefully.
function processOrder(order) {
// Guard clauses
if (!order) {
throw new ValidationError('Order is required');
}
if (!order.items || order.items.length === 0) {
throw new ValidationError('Order must have at least one item');
}
if (order.total < 0) {
throw new ValidationError('Order total cannot be negative');
}
// Safe property access
const customerEmail = order.customer?.email ?? 'no-email@placeholder.com';
// Proceed with valid data
return {
id: generateOrderId(),
items: order.items.map(item => ({
...item,
price: Math.max(0, item.price) // Ensure non-negative
})),
total: order.total,
customerEmail
};
}
// Use verb + noun pattern
function getUser(id) { } // Retrieval
function createOrder(data) { } // Creation
function updateProfile(id, data) { } // Mutation
function deleteComment(id) { } // Deletion
function validateEmail(email) { } // Validation
function calculateTotal(items) { } // Calculation
function formatDate(date) { } // Transformation
function isActive(user) { } // Boolean check
function hasPermission(user, action) { } // Boolean check
function canEdit(user, resource) { } // Boolean check
// Descriptive nouns
const userCount = 42; // Not: n, count, uc
const activeUsers = users.filter(); // Not: arr, filtered
const maxRetryAttempts = 3; // Not: max, retries
// Boolean variables
const isActive = true; // is/has/can/should prefix
const hasPermission = false;
const canEdit = user.role === 'admin';
const shouldRetry = attempts < maxRetryAttempts;
// Collections are plural
const users = []; // Not: userList, userArray
const orderItems = []; // Not: items (too generic)
// Maps/objects describe content
const userById = {}; // Not: userMap
const priceByProductId = {}; // Describes key-value relationship
// SCREAMING_SNAKE_CASE for true constants
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_PAGE_SIZE = 20;
const API_BASE_URL = 'https://api.example.com';
// Configuration objects
const CONFIG = Object.freeze({
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10)
}
});
// PascalCase, noun-based
class UserRepository { } // Not: UsersRepo, UserRepo
class OrderService { } // Not: OrdersService
class EmailValidator { } // Not: ValidateEmail
class PaymentGateway { } // Not: PaymentGatewayService
// Interface names (TypeScript)
interface Cacheable { } // Adjective for capabilities
interface UserRepository { } // Noun for contracts
async function createUser(data) {
// Validate early
if (!data.email) {
throw new ValidationError('Email is required');
}
if (!isValidEmail(data.email)) {
throw new ValidationError('Invalid email format');
}
// Check business rules
const existing = await userRepo.findByEmail(data.email);
if (existing) {
throw new ConflictError('Email already registered');
}
// Proceed with operation
try {
const user = await userRepo.save(data);
await emailService.sendWelcome(user.email);
return user;
} catch (error) {
// Handle specific errors
if (error instanceof DatabaseError) {
logger.error('Database error creating user', { error, data });
throw new ServiceError('Unable to create user, please try again');
}
throw error; // Re-throw unexpected errors
}
}
// Base application error
class AppError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.statusCode = statusCode;
Error.captureStackTrace(this, this.constructor);
}
}
// Specific error types
class ValidationError extends AppError {
constructor(message, details = []) {
super(message, 'VALIDATION_ERROR', 400);
this.details = details;
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} with id ${id} not found`, 'NOT_FOUND', 404);
this.resource = resource;
this.resourceId = id;
}
}
class ConflictError extends AppError {
constructor(message) {
super(message, 'CONFLICT', 409);
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Authentication required') {
super(message, 'UNAUTHORIZED', 401);
}
}
For comprehensive error patterns: See references/error-handling-patterns.md
| Level | When to Use | Example |
|---|---|---|
| ERROR | Failures requiring attention | Database connection failed |
| WARN | Potentially harmful situations | Rate limit approaching |
| INFO | Normal operational events | User created, order placed |
| DEBUG | Detailed debugging info | Function parameters, intermediate values |
const logger = require('./logger');
// BAD: Unstructured logging
console.log('User created: ' + user.id);
console.log('Error: ' + error.message);
// GOOD: Structured logging
logger.info('User created', {
userId: user.id,
email: user.email,
source: 'signup'
});
logger.error('Failed to process payment', {
orderId: order.id,
amount: order.total,
error: error.message,
errorCode: error.code,
stack: error.stack
});
// Request logging middleware
app.use((req, res, next) => {
const requestId = req.headers['x-request-id'] || uuidv4();
req.requestId = requestId;
logger.info('Request received', {
requestId,
method: req.method,
path: req.path,
userAgent: req.headers['user-agent'],
ip: req.ip
});
res.on('finish', () => {
logger.info('Request completed', {
requestId,
statusCode: res.statusCode,
duration: Date.now() - req.startTime
});
});
next();
});
// 1. Imports (external first, then internal)
const express = require('express');
const { validate } = require('class-validator');
const { UserService } = require('../services/UserService');
const { logger } = require('../utils/logger');
// 2. Constants and configuration
const MAX_PAGE_SIZE = 100;
const DEFAULT_PAGE_SIZE = 20;
// 3. Main exports (class, function, or router)
class UserController {
constructor(userService) {
this.userService = userService;
}
async getUsers(req, res, next) {
// Implementation
}
}
// 4. Helper functions (private to module)
function validatePagination(page, limit) {
// Implementation
}
// 5. Export statement
module.exports = { UserController };
When a design doc spec'd an OPTIONAL cross-link sentence, closing paragraph, or sister-section reference, the CODE author MUST evaluate the optional content's logical relationship to the preceding paragraph before deciding how to render it.
The judgment lives at write time: read the preceding paragraph and ask "does this optional content add to that paragraph's directive, or does it start a new one?" Strengthening = fold; discursive = keep.
Design-doc phrasings like "CODE may include or drop", "optional closing cross-link", or "optional discursive paragraph" mark the entry condition. Apply the strengthening-vs-discursive judgment before composing the section; do not default to either form mechanically.
In pact-plugin/skills/pact-testing-strategies/SKILL.md, the section "Sibling-file convention for parametrized noise-budget regression" closes its canonical-mitigation paragraph with an inline cross-reference to the sister "Author-blindness in HANDOFF arithmetic" section rather than a standalone closing paragraph. The cross-link functions as a strengthening clause on the elevated-priority directive that precedes it — telling the reader where the pair-with-cross-stream-verifier rule lives — so it folds into the same paragraph instead of opening a new one. The fold preserves the cross-reference content while keeping the canonical mitigation a single tight directive.
Before completing CODE phase:
Behavioral tests cannot see an unused import — a dead import os passes
every test green. The import-hygiene check closes that blind spot
mechanically. Run it over EXACTLY the .py files you modified before
running the test suite:
bash <skill-directory>/scripts/lint-check.sh --files '/abs/path/to/modified_a.py' '/abs/path/to/modified_b.py'
One separately quoted absolute path per file, never one quoted list — a filename is untrusted shell input.
(When your dispatch names a plugin root, the script lives at
<plugin-root>/skills/pact-coding-standards/scripts/lint-check.sh.)
A bare lint-check.sh with no arguments is refused. Do not present it as a lint command.
Whole-tree project-type detection is reachable only with a directory argument:
# From within the skill directory:
chmod +x scripts/lint-check.sh
./scripts/lint-check.sh /path/to/project
Verdict-line contract — the LAST stdout line is always exactly one of:
| Verdict | Meaning | Exit code |
|---|---|---|
IMPORT-HYGIENE: PASS |
No findings in the checked files | 0 |
IMPORT-HYGIENE: FINDINGS (n) |
n findings printed above the verdict | 1 |
IMPORT-HYGIENE: SKIPPED (<reason>) |
Check could not run (no arguments given, arguments given but none a checkable .py file, or no usable checker) | 0 |
Record the verdict line verbatim in your HANDOFF produced field — every
run ends in a verdict, and a SKIPPED must be visible, never silent.
What to do with findings:
# noqa: F401 # re-export: <why>. Never a bare
# noqa without a reason.How it checks (internal — the coder-facing contract is just the command
and the verdict): an execution-probed ladder tries ruff, then pyflakes, then
flake8, each restricted to the unused-import/undefined-name classes only
(--select F401,F821) so you are never asked to fix style noise; when no
linter is installed it falls back to the stdlib AST checker
scripts/check_unused_imports.py (unused imports only, try/except-scoped
imports treated as advisory). A checker crash degrades to SKIPPED — the
check fails open and never blocks you on its own breakage.
When you add or change an agent-facing CLI, teach it with examples that
paste-execute verbatim: build every example as
python3 "<script-path>" <verb> ... — bare python3, never a pinned
interpreter, and always quote the script path. In --help, the epilog lists
all of the CLI's examples; a usage error appends exactly one example — the
offending subcommand's own — separated from the error line by a blank line.
Keep each CLI's pre-existing usage-error exit code; teaching examples never
changes an exit contract.
For comprehensive coding guidance:
Clean Code Principles: references/clean-code-principles.md
Error Handling Patterns: references/error-handling-patterns.md