| name | coding-standards |
| description | Use when reviewing code for quality, naming a class or function, choosing a design pattern, refactoring a code smell, or establishing coding conventions for a new project. |
Coding Standards
A comprehensive reference for writing clean, consistent, maintainable code across Python, TypeScript, and Go.
When to Activate
- Reviewing a pull request for code quality
- Refactoring legacy or unclear code
- Naming a class, function, variable, or file
- Deciding which design pattern to apply
- Setting up coding standards for a new project
- Identifying code smells and their root causes
Naming Conventions
| Construct | Python | TypeScript | Go |
|---|
| Variables | snake_case | camelCase | camelCase |
| Functions / methods | snake_case | camelCase | PascalCase (exported), camelCase (unexported) |
| Classes / types | PascalCase | PascalCase | PascalCase |
| Constants | UPPER_SNAKE_CASE | UPPER_SNAKE_CASE or camelCase | PascalCase (exported), camelCase (unexported) |
| Interfaces | N/A | IFoo (avoid) or just Foo (preferred) | Foo (no I prefix) |
| Files | snake_case.py | kebab-case.ts or camelCase.ts | snake_case.go |
| Test files | test_*.py | *.test.ts / *.spec.ts | *_test.go |
Naming Heuristics
- Be specific:
user_registration_date not date; payment_amount_cents not amount
- Avoid meaningless suffixes:
UserManager, DataHelper, Utils — what does it manage/help/do?
- Boolean names: use
is_, has_, can_, should_ prefix: is_active, has_permission
- Functions: verb phrases —
calculate_tax(), send_email(), validate_schema()
- Avoid abbreviations except universally understood ones (
id, url, http, db)
- Length proportional to scope: loop variable
i is fine; module-level variable needs a full name
def proc(d, f):
temp = d * f
return temp
def calculate_discounted_price(base_price: float, discount_factor: float) -> float:
return base_price * discount_factor
SOLID Principles
S — Single Responsibility
A class should have one reason to change.
class UserService {
createUser(data: CreateUserDto) { }
sendWelcomeEmail(user: User) { }
generateReport(users: User[]) { }
}
class UserService { createUser(data: CreateUserDto) { } }
class EmailService { sendWelcomeEmail(user: User) { } }
class UserReportService { generateReport(users: User[]) { } }
O — Open/Closed
Open for extension, closed for modification.
function calculateDiscount(type: string, price: number): number {
if (type === 'student') return price * 0.8;
if (type === 'senior') return price * 0.75;
}
interface DiscountStrategy { apply(price: number): number; }
class StudentDiscount implements DiscountStrategy { apply(p: number) { return p * 0.8; } }
class SeniorDiscount implements DiscountStrategy { apply(p: number) { return p * 0.75; } }
L — Liskov Substitution
Subtypes must be substitutable for their base type.
class Rectangle {
constructor(protected width: number, protected height: number) {}
setWidth(w: number) { this.width = w; }
setHeight(h: number) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w: number) { this.width = this.height = w; }
setHeight(h: number) { this.width = this.height = h; }
}
interface Shape { area(): number; }
class Rectangle implements Shape {
constructor(private w: number, private h: number) {}
area() { return this.w * this.h; }
}
class Square implements Shape {
constructor(private side: number) {}
area() { return this.side * this.side; }
}
I — Interface Segregation
Many specific interfaces are better than one general interface.
interface Worker { work(): void; eat(): void; sleep(): void; }
interface Workable { work(): void; }
interface Feedable { eat(): void; }
interface Restable { sleep(): void; }
class HumanWorker implements Workable, Feedable, Restable {
work() { }
eat() { }
sleep() { }
}
class RobotWorker implements Workable {
work() { }
}
D — Dependency Inversion
Depend on abstractions, not concretions.
class UserRepository {
private db = new PostgresDB();
find(id: string) { return this.db.query(); }
}
interface Database { query(sql: string, params: unknown[]): Promise<unknown[]>; }
class UserRepository {
constructor(private db: Database) {}
find(id: string) { return this.db.query('SELECT * FROM users WHERE id = $1', [id]); }
}
Design Patterns Quick Reference
| Pattern | Category | One-line use case | When to avoid |
|---|
| Factory | Creational | Create objects without specifying concrete class | When you only have one type |
| Abstract Factory | Creational | Create families of related objects | Overkill for simple factories |
| Singleton | Creational | One shared instance (config, logger) | When it becomes global mutable state |
| Builder | Creational | Step-by-step construction of complex objects | Simple objects with few fields |
| Strategy | Behavioral | Swap algorithms at runtime | When you only have one algorithm |
| Observer | Behavioral | Notify dependents when state changes | When observers outlive the subject |
| Command | Behavioral | Encapsulate a request as an object (undo/redo) | Simple one-off calls |
| Template Method | Behavioral | Define skeleton, let subclasses fill steps | Deep inheritance hierarchies |
| Repository | Structural | Isolate data access from domain logic | When ORM already provides this |
| Adapter | Structural | Wrap incompatible interface | When both interfaces are yours (just fix one) |
| Decorator | Structural | Add behavior to objects dynamically | When subclassing is simpler |
| Facade | Structural | Simplified interface to complex subsystem | Over-used to hide bad design |
Full Code Examples
Factory
interface Logger { log(msg: string): void; }
class ConsoleLogger implements Logger { log(msg: string) { console.log(msg); } }
class FileLogger implements Logger { log(msg: string) { fs.appendFileSync('app.log', msg); } }
function createLogger(type: 'console' | 'file'): Logger {
if (type === 'console') return new ConsoleLogger();
return new FileLogger();
}
const logger = createLogger('console');
logger.log('Server started');
Strategy
interface SortStrategy { sort(data: number[]): number[]; }
class QuickSort implements SortStrategy { sort(d: number[]) { return d; } }
class MergeSort implements SortStrategy { sort(d: number[]) { return d; } }
class DataSorter {
constructor(private strategy: SortStrategy) {}
sort(data: number[]) { return this.strategy.sort(data); }
setStrategy(strategy: SortStrategy) { this.strategy = strategy; }
}
const sorter = new DataSorter(new QuickSort());
sorter.setStrategy(new MergeSort());
sorter.sort([5, 3, 1, 4, 2]);
Observer
type Listener<T> = (event: T) => void;
class EventEmitter<T> {
private listeners: Listener<T>[] = [];
subscribe(fn: Listener<T>) { this.listeners.push(fn); }
unsubscribe(fn: Listener<T>) { this.listeners = this.listeners.filter(l => l !== fn); }
emit(event: T) { this.listeners.forEach(fn => fn(event)); }
}
const orderEvents = new EventEmitter<{ orderId: string; status: string }>();
orderEvents.subscribe(({ orderId }) => sendConfirmationEmail(orderId));
orderEvents.subscribe(({ orderId }) => updateInventory(orderId));
orderEvents.emit({ orderId: '123', status: 'paid' });
Repository
interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
delete(id: string): Promise<void>;
}
class PostgresUserRepository implements UserRepository {
constructor(private db: Database) {}
async findById(id: string): Promise<User | null> {
const [row] = await this.db.query('SELECT * FROM users WHERE id = $1', [id]);
return row ? User.fromRow(row) : null;
}
async save(user: User): Promise<void> {
await this.db.query(
'INSERT INTO users (id, email) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET email = $2',
[user.id, user.email]
);
}
async delete(id: string): Promise<void> {
await this.db.query('DELETE FROM users WHERE id = $1', [id]);
}
}
Code Smells Catalog
| Smell | Symptom | Refactoring |
|---|
| Long Method | Function > 30 lines | Extract Method |
| Long Class / God Class | Class > 300 lines or too many responsibilities | Extract Class |
| Data Clump | Same 3+ params appear together everywhere | Introduce Parameter Object |
| Primitive Obsession | Using strings/ints for domain concepts (money, email) | Introduce Value Object |
| Feature Envy | Method uses data from another class more than its own | Move Method |
| Shotgun Surgery | One change requires edits in many classes | Move Method/Field, consolidate |
| Divergent Change | Class changes for multiple unrelated reasons | Extract Class |
| Duplicate Code | Same logic in multiple places | Extract Method/Function |
| Magic Numbers | Unexplained literals in code | Replace with Named Constant |
| Switch/Match on Type | Repeated instanceof or type checks | Replace with Polymorphism |
Smell Examples
function chargeUser(userId: string, amount: number, email: string) { }
class Money {
constructor(readonly cents: number, readonly currency: 'USD' | 'EUR') {
if (cents < 0) throw new Error('Money cannot be negative');
}
}
class Email {
constructor(readonly value: string) {
if (!value.includes('@')) throw new Error('Invalid email');
}
}
function chargeUser(userId: string, amount: Money, email: Email) { }
if (user.age >= 65) applyDiscount(price * 0.25);
const SENIOR_AGE_THRESHOLD = 65;
const SENIOR_DISCOUNT_RATE = 0.25;
if (user.age >= SENIOR_AGE_THRESHOLD) applyDiscount(price * SENIOR_DISCOUNT_RATE);
Language-Specific Idioms
Python
from dataclasses import dataclass, field
@dataclass
class Order:
id: str
items: list[str] = field(default_factory=list)
total: float = 0.0
with open('file.txt') as f:
data = f.read()
def process_large_file(path: str):
with open(path) as f:
for line in f:
yield parse(line)
if (match := pattern.search(text)) is not None:
print(match.group(0))
result = get_user_and_role()
user = result[0]
role = result[1]
user, role = get_user_and_role()
TypeScript
type Result<T> =
| { success: true; data: T }
| { success: false; error: string };
function handleResult<T>(result: Result<T>) {
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}
}
const config = {
host: 'localhost',
port: 5432,
} satisfies Record<string, string | number>;
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;
type UserPreview = Pick<User, 'id' | 'name'>;
type UserWithoutPassword = Omit<User, 'password'>;
const user = response.data as any;
import { z } from 'zod';
const UserSchema = z.object({ id: z.string(), name: z.string() });
const user = UserSchema.parse(response.data);
Go
type Server struct {
host string
port int
timeout time.Duration
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}
func NewServer(host string, port int, opts ...Option) *Server {
s := &Server{host: host, port: port, timeout: 30 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}
srv := NewServer("localhost", 8080, WithTimeout(5*time.Second))
if err := db.Query(); err != nil {
return fmt.Errorf("fetching user %s: %w", userID, err)
}
func TestAdd(t *testing.T) {
cases := []struct{ a, b, want int }{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tc := range cases {
t.Run(fmt.Sprintf("%d+%d", tc.a, tc.b), func(t *testing.T) {
got := Add(tc.a, tc.b)
if got != tc.want {
t.Errorf("got %d, want %d", got, tc.want)
}
})
}
}
Red Flags
- God class with
Manager, Helper, or Utils suffix — a class named UserManager with 15 methods is a single-responsibility violation waiting to be split; name by what it does, not that it manages things
- Boolean parameter flags controlling fundamentally different code paths —
send_email(user, urgent=True) means two functions in disguise; split into send_urgent_email and send_email with distinct call sites
- Deep inheritance hierarchies (more than 2 levels) — subclasses become entangled with grandparent internals; prefer composition and interfaces, using inheritance only for true IS-A relationships
- Returning
None on error instead of raising an exception or returning a typed Result — callers silently ignore None and propagate nulls deep into the call stack before crashing; be explicit about failure
- Catching a broad exception to log-and-swallow —
except Exception: logger.error(...) hides failures silently; catch the specific exception type and let unexpected ones propagate
any type in TypeScript at a module boundary — any disables type checking for every caller downstream; use unknown and narrow explicitly, or define a proper interface
- Mutable default arguments in Python —
def process(items=[]) shares the same list across all calls; use def process(items=None) and initialize inside the function body
- Long positional argument lists (more than 3 parameters) — hard to read at call sites and easy to transpose; introduce a parameter object or dataclass
Checklist