Initial commit
This commit is contained in:
562
skills/code-quality-reviewer/EXAMPLES.md
Normal file
562
skills/code-quality-reviewer/EXAMPLES.md
Normal file
@@ -0,0 +1,562 @@
|
||||
# Code Quality Examples
|
||||
|
||||
Real-world examples demonstrating DRY, KISS, and CLEAN CODE principles in practical scenarios.
|
||||
|
||||
## Example 1: User Authentication Module
|
||||
|
||||
### Scenario: User Login and Registration Logic
|
||||
|
||||
#### ❌ Before: Violates DRY and CLEAN CODE
|
||||
|
||||
```javascript
|
||||
// auth.js
|
||||
export function loginUser(email, password) {
|
||||
// Validate email
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
throw new Error('Invalid email format');
|
||||
}
|
||||
|
||||
// Validate password
|
||||
if (!password || password.length < 8) {
|
||||
throw new Error('Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
// Authenticate
|
||||
const user = database.findUserByEmail(email);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
if (!bcrypt.compareSync(password, user.passwordHash)) {
|
||||
throw new Error('Invalid password');
|
||||
}
|
||||
|
||||
return generateToken(user);
|
||||
}
|
||||
|
||||
export function registerUser(email, password, confirmPassword) {
|
||||
// Validate email
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
throw new Error('Invalid email format');
|
||||
}
|
||||
|
||||
// Validate password
|
||||
if (!password || password.length < 8) {
|
||||
throw new Error('Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
// Validate confirm password
|
||||
if (password !== confirmPassword) {
|
||||
throw new Error('Passwords do not match');
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
const existingUser = database.findUserByEmail(email);
|
||||
if (existingUser) {
|
||||
throw new Error('Email already registered');
|
||||
}
|
||||
|
||||
// Create user
|
||||
const passwordHash = bcrypt.hashSync(password, 10);
|
||||
return database.createUser({
|
||||
email,
|
||||
passwordHash
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Issues**:
|
||||
- Email validation duplicated (DRY violation)
|
||||
- Password validation duplicated (DRY violation)
|
||||
- Error messages not consistent
|
||||
- Unclear variable names (emailRegex could be EMAIL_REGEX)
|
||||
- Magic number 8 for password length (CLEAN CODE)
|
||||
- Magic number 10 for bcrypt rounds (CLEAN CODE)
|
||||
|
||||
#### ✅ After: DRY, KISS, CLEAN CODE
|
||||
|
||||
```javascript
|
||||
// auth.js
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const MIN_PASSWORD_LENGTH = 8;
|
||||
const BCRYPT_ROUNDS = 10;
|
||||
|
||||
function validateEmail(email) {
|
||||
if (!EMAIL_REGEX.test(email)) {
|
||||
throw new Error('Invalid email format');
|
||||
}
|
||||
}
|
||||
|
||||
function validatePassword(password) {
|
||||
if (!password || password.length < MIN_PASSWORD_LENGTH) {
|
||||
throw new Error(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loginUser(email, password) {
|
||||
validateEmail(email);
|
||||
validatePassword(password);
|
||||
|
||||
const user = database.findUserByEmail(email);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
const isPasswordValid = bcrypt.compareSync(password, user.passwordHash);
|
||||
if (!isPasswordValid) {
|
||||
throw new Error('Invalid password');
|
||||
}
|
||||
|
||||
return generateToken(user);
|
||||
}
|
||||
|
||||
export async function registerUser(email, password, confirmPassword) {
|
||||
validateEmail(email);
|
||||
validatePassword(password);
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
throw new Error('Passwords do not match');
|
||||
}
|
||||
|
||||
const existingUser = database.findUserByEmail(email);
|
||||
if (existingUser) {
|
||||
throw new Error('Email already registered');
|
||||
}
|
||||
|
||||
const passwordHash = bcrypt.hashSync(password, BCRYPT_ROUNDS);
|
||||
return database.createUser({ email, passwordHash });
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- ✅ DRY: Email and password validation extracted to reusable functions
|
||||
- ✅ CLEAN CODE: Magic values (8, 10) defined as named constants
|
||||
- ✅ CLEAN CODE: Variable names are clear (isPasswordValid vs implicit boolean)
|
||||
- ✅ KISS: Functions are shorter and focused
|
||||
- ✅ DRY: Validation logic centralized
|
||||
|
||||
---
|
||||
|
||||
## Example 2: E-commerce Order Processing
|
||||
|
||||
### Scenario: Calculating Order Total with Taxes, Discounts, and Shipping
|
||||
|
||||
#### ❌ Before: Violates KISS and CLEAN CODE
|
||||
|
||||
```javascript
|
||||
function calc(items, c, s, t) {
|
||||
let st = items.reduce((a, b) => a + (b.p * b.q), 0);
|
||||
let d = 0;
|
||||
|
||||
if (c) {
|
||||
if (c.type === 1) d = st * (c.val / 100);
|
||||
else if (c.type === 2) d = c.val;
|
||||
else if (c.type === 3 && c.minAmount && st > c.minAmount) {
|
||||
d = st * (c.val / 100);
|
||||
}
|
||||
}
|
||||
|
||||
let sh = s ? (st > 100 ? 0 : 15) : 0;
|
||||
let tx = t ? ((st - d + sh) * 0.1) : 0;
|
||||
|
||||
return {
|
||||
st: st,
|
||||
d: d,
|
||||
sh: sh,
|
||||
tx: tx,
|
||||
tt: st - d + sh + tx
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Issues**:
|
||||
- Cryptic parameter names (c, s, t, items)
|
||||
- Cryptic variable names (st, d, sh, tx, tt)
|
||||
- Magic numbers without explanation (1, 2, 3, 100, 15, 0.1)
|
||||
- Over-nested conditions (KISS violation)
|
||||
- No documentation about what the function does
|
||||
- Return object has abbreviated keys
|
||||
- Hard to understand discount logic
|
||||
|
||||
#### ✅ After: KISS and CLEAN CODE
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Calculates order total including taxes, discounts, and shipping
|
||||
* @param {Object[]} items - Array of {price, quantity}
|
||||
* @param {Object} discount - Optional {type, value, minAmount}
|
||||
* @param {boolean} includeShipping - Whether to add shipping cost
|
||||
* @param {boolean} includeTax - Whether to add sales tax
|
||||
* @returns {Object} Order summary with breakdown
|
||||
*/
|
||||
function calculateOrderTotal(items, discount = null, includeShipping = true, includeTax = true) {
|
||||
const SHIPPING_COST = 15;
|
||||
const FREE_SHIPPING_THRESHOLD = 100;
|
||||
const TAX_RATE = 0.1;
|
||||
|
||||
// Calculate subtotal
|
||||
const subtotal = items.reduce((total, item) => {
|
||||
return total + (item.price * item.quantity);
|
||||
}, 0);
|
||||
|
||||
// Calculate discount
|
||||
const discountAmount = calculateDiscount(subtotal, discount);
|
||||
|
||||
// Calculate shipping
|
||||
const shippingCost = calculateShipping(subtotal, includeShipping);
|
||||
|
||||
// Calculate tax (applies to subtotal after discount + shipping)
|
||||
const taxableAmount = subtotal - discountAmount + shippingCost;
|
||||
const taxAmount = includeTax ? taxableAmount * TAX_RATE : 0;
|
||||
|
||||
const total = subtotal - discountAmount + shippingCost + taxAmount;
|
||||
|
||||
return {
|
||||
subtotal,
|
||||
discountAmount,
|
||||
shippingCost,
|
||||
taxAmount,
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
function calculateDiscount(subtotal, discount) {
|
||||
if (!discount) return 0;
|
||||
|
||||
const PERCENTAGE_DISCOUNT = 'percentage';
|
||||
const FIXED_DISCOUNT = 'fixed';
|
||||
const TIERED_DISCOUNT = 'tiered';
|
||||
|
||||
switch (discount.type) {
|
||||
case PERCENTAGE_DISCOUNT:
|
||||
return subtotal * (discount.value / 100);
|
||||
|
||||
case FIXED_DISCOUNT:
|
||||
return discount.value;
|
||||
|
||||
case TIERED_DISCOUNT:
|
||||
const meetsMinimum = discount.minAmount && subtotal > discount.minAmount;
|
||||
return meetsMinimum ? subtotal * (discount.value / 100) : 0;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function calculateShipping(subtotal, includeShipping) {
|
||||
const SHIPPING_COST = 15;
|
||||
const FREE_SHIPPING_THRESHOLD = 100;
|
||||
|
||||
if (!includeShipping) return 0;
|
||||
return subtotal > FREE_SHIPPING_THRESHOLD ? 0 : SHIPPING_COST;
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- ✅ CLEAN CODE: Descriptive function and parameter names
|
||||
- ✅ CLEAN CODE: Magic numbers extracted to named constants
|
||||
- ✅ CLEAN CODE: Clear documentation with JSDoc
|
||||
- ✅ KISS: Complex nested logic extracted to separate functions
|
||||
- ✅ CLEAN CODE: Return object has meaningful property names
|
||||
- ✅ CLEAN CODE: Discount types defined as constants instead of magic numbers
|
||||
|
||||
---
|
||||
|
||||
## Example 3: Data Validation Utilities
|
||||
|
||||
### Scenario: Form Field Validation
|
||||
|
||||
#### ❌ Before: Violates DRY
|
||||
|
||||
```javascript
|
||||
// validationUtils.js
|
||||
export function validateEmail(email) {
|
||||
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return regex.test(email);
|
||||
}
|
||||
|
||||
export function validatePassword(password) {
|
||||
return password && password.length >= 8;
|
||||
}
|
||||
|
||||
export function validatePhone(phone) {
|
||||
const regex = /^(\+1)?(\d{3})[-.]?(\d{3})[-.]?(\d{4})$/;
|
||||
return regex.test(phone);
|
||||
}
|
||||
|
||||
// userForm.js
|
||||
function handleUserSubmit(userData) {
|
||||
if (!userData.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(userData.email)) {
|
||||
return { error: 'Invalid email' };
|
||||
}
|
||||
|
||||
if (!userData.password || userData.password.length < 8) {
|
||||
return { error: 'Password too short' };
|
||||
}
|
||||
|
||||
if (!userData.phone || !/^(\+1)?(\d{3})[-.]?(\d{3})[-.]?(\d{4})$/.test(userData.phone)) {
|
||||
return { error: 'Invalid phone' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// billingForm.js
|
||||
function handleBillingSubmit(billingData) {
|
||||
if (!billingData.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(billingData.email)) {
|
||||
return { error: 'Invalid email' };
|
||||
}
|
||||
|
||||
if (!billingData.phone || !/^(\+1)?(\d{3})[-.]?(\d{3})[-.]?(\d{4})$/.test(billingData.phone)) {
|
||||
return { error: 'Invalid phone' };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
```
|
||||
|
||||
**Issues**:
|
||||
- Validation utilities exist but aren't used (DRY violation)
|
||||
- Regex patterns duplicated in multiple files (DRY violation)
|
||||
- Validation logic scattered across components
|
||||
- No centralized validation error handling
|
||||
|
||||
#### ✅ After: DRY, Reusable Validators
|
||||
|
||||
```javascript
|
||||
// validationUtils.js
|
||||
const VALIDATORS = {
|
||||
EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
||||
PHONE: /^(\+1)?(\d{3})[-.]?(\d{3})[-.]?(\d{4})$/,
|
||||
STRONG_PASSWORD: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/
|
||||
};
|
||||
|
||||
const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
export const validators = {
|
||||
email: (value) => VALIDATORS.EMAIL.test(value),
|
||||
password: (value) => value && value.length >= MIN_PASSWORD_LENGTH,
|
||||
phone: (value) => VALIDATORS.PHONE.test(value),
|
||||
required: (value) => !!value && value.trim() !== ''
|
||||
};
|
||||
|
||||
export function validateForm(data, rules) {
|
||||
const errors = {};
|
||||
|
||||
Object.entries(rules).forEach(([field, validatorFn]) => {
|
||||
if (!validatorFn(data[field])) {
|
||||
errors[field] = `Invalid ${field}`;
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(errors).length === 0
|
||||
? { success: true }
|
||||
: { error: errors };
|
||||
}
|
||||
|
||||
// userForm.js
|
||||
import { validators, validateForm } from './validationUtils';
|
||||
|
||||
function handleUserSubmit(userData) {
|
||||
return validateForm(userData, {
|
||||
email: validators.email,
|
||||
password: validators.password,
|
||||
phone: validators.phone
|
||||
});
|
||||
}
|
||||
|
||||
// billingForm.js
|
||||
import { validators, validateForm } from './validationUtils';
|
||||
|
||||
function handleBillingSubmit(billingData) {
|
||||
return validateForm(billingData, {
|
||||
email: validators.email,
|
||||
phone: validators.phone
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- ✅ DRY: Regex patterns defined once in VALIDATORS object
|
||||
- ✅ DRY: Validation logic centralized in reusable functions
|
||||
- ✅ CLEAN CODE: Named constants for validation rules
|
||||
- ✅ KISS: Simple validator functions composed together
|
||||
- ✅ DRY: Generic validateForm handles all form validation
|
||||
|
||||
---
|
||||
|
||||
## Example 4: API Response Handling
|
||||
|
||||
### Scenario: Consistent Error and Success Response Handling
|
||||
|
||||
#### ❌ Before: Violates DRY and CLEAN CODE
|
||||
|
||||
```javascript
|
||||
// userService.js
|
||||
export async function getUser(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/users/${id}`);
|
||||
if (!response.ok) {
|
||||
console.error('HTTP error', response.status);
|
||||
return { data: null, error: 'Failed to fetch user' };
|
||||
}
|
||||
const data = await response.json();
|
||||
return { data, error: null };
|
||||
} catch (err) {
|
||||
console.error('Request failed', err);
|
||||
return { data: null, error: 'Network error' };
|
||||
}
|
||||
}
|
||||
|
||||
// productService.js
|
||||
export async function getProduct(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/products/${id}`);
|
||||
if (!response.ok) {
|
||||
console.error('HTTP error', response.status);
|
||||
return { data: null, error: 'Failed to fetch product' };
|
||||
}
|
||||
const data = await response.json();
|
||||
return { data, error: null };
|
||||
} catch (err) {
|
||||
console.error('Request failed', err);
|
||||
return { data: null, error: 'Network error' };
|
||||
}
|
||||
}
|
||||
|
||||
// orderService.js
|
||||
export async function createOrder(orderData) {
|
||||
try {
|
||||
const response = await fetch('/api/orders', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(orderData)
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error('HTTP error', response.status);
|
||||
return { data: null, error: 'Failed to create order' };
|
||||
}
|
||||
const data = await response.json();
|
||||
return { data, error: null };
|
||||
} catch (err) {
|
||||
console.error('Request failed', err);
|
||||
return { data: null, error: 'Network error' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Issues**:
|
||||
- Error handling pattern repeated in every function (DRY violation)
|
||||
- Try-catch-response handling duplicated (DRY violation)
|
||||
- Generic error messages ("Network error", "Failed to fetch")
|
||||
- No distinction between HTTP errors and network errors
|
||||
|
||||
#### ✅ After: DRY with Centralized API Handling
|
||||
|
||||
```javascript
|
||||
// apiClient.js
|
||||
/**
|
||||
* Centralized API request handler with consistent error handling
|
||||
*/
|
||||
export async function apiCall(url, options = {}) {
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new APIError(
|
||||
`HTTP ${response.status}: ${response.statusText}`,
|
||||
response.status,
|
||||
errorData
|
||||
);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof APIError) {
|
||||
throw error;
|
||||
}
|
||||
throw new APIError('Network request failed', null, error);
|
||||
}
|
||||
}
|
||||
|
||||
export class APIError extends Error {
|
||||
constructor(message, statusCode = null, details = null) {
|
||||
super(message);
|
||||
this.name = 'APIError';
|
||||
this.statusCode = statusCode;
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps async functions to return {data, error} format
|
||||
*/
|
||||
export async function handleAPIRequest(asyncFn) {
|
||||
try {
|
||||
const data = await asyncFn();
|
||||
return { data, error: null };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof APIError
|
||||
? error.message
|
||||
: 'Unexpected error';
|
||||
return { data: null, error: errorMessage };
|
||||
}
|
||||
}
|
||||
|
||||
// userService.js
|
||||
import { apiCall, handleAPIRequest } from './apiClient';
|
||||
|
||||
export const userService = {
|
||||
getUser: (id) => handleAPIRequest(() => apiCall(`/api/users/${id}`)),
|
||||
updateUser: (id, data) => handleAPIRequest(() =>
|
||||
apiCall(`/api/users/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
// productService.js
|
||||
import { apiCall, handleAPIRequest } from './apiClient';
|
||||
|
||||
export const productService = {
|
||||
getProduct: (id) => handleAPIRequest(() => apiCall(`/api/products/${id}`)),
|
||||
listProducts: () => handleAPIRequest(() => apiCall('/api/products'))
|
||||
};
|
||||
|
||||
// orderService.js
|
||||
import { apiCall, handleAPIRequest } from './apiClient';
|
||||
|
||||
export const orderService = {
|
||||
createOrder: (orderData) => handleAPIRequest(() =>
|
||||
apiCall('/api/orders', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(orderData)
|
||||
})
|
||||
)
|
||||
};
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- ✅ DRY: API request and error handling centralized in apiClient
|
||||
- ✅ DRY: All services use the same apiCall and handleAPIRequest
|
||||
- ✅ CLEAN CODE: APIError class provides clear error structure
|
||||
- ✅ CLEAN CODE: Service functions are concise and readable
|
||||
- ✅ KISS: Error handling logic is simple and consistent
|
||||
- ✅ CLEAN CODE: Consistent {data, error} response format
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
These examples demonstrate how applying DRY, KISS, and CLEAN CODE principles makes code:
|
||||
- **More maintainable** - Changes in one place affect all usages
|
||||
- **More readable** - Intent is clear, naming is descriptive
|
||||
- **Less error-prone** - Less duplicated code means fewer places to fix bugs
|
||||
- **Easier to test** - Centralized logic is easier to unit test
|
||||
- **Easier to extend** - Adding new features doesn't require duplicating existing patterns
|
||||
Reference in New Issue
Block a user