Unit 4: Menulis Kode Sesuai Guidelines dan Best Practices

Mempelajari cara menulis kode yang bersih, aman, dan mengikuti standar industri dengan fokus pada aksesibilitas dan keamanan aplikasi web.

Estimasi: 8 Jam 4 Sub Materi

Tujuan Pembelajaran

  • Menulis kode yang bersih dan mudah dibaca
  • Menerapkan prinsip DRY (Don't Repeat Yourself)
  • Memahami aksesibilitas web dasar
  • Mengimplementasikan keamanan web fundamental

1. Clean Code (Kode Bersih)

Prinsip Keterbacaan Kode

Kode yang bersih adalah kode yang mudah dibaca, dipahami, dan dimodifikasi oleh developer lain.

Good Example
// Nama function yang deskriptif
function calculateMonthlyPayment(principal, interestRate, termInYears) {
    const monthlyRate = interestRate / 12;
    const numberOfPayments = termInYears * 12;
    
    const monthlyPayment = principal * 
        (monthlyRate * Math.pow(1 + monthlyRate, numberOfPayments)) /
        (Math.pow(1 + monthlyRate, numberOfPayments) - 1);
    
    return Math.round(monthlyPayment * 100) / 100;
}

// Variable dengan nama yang jelas
const loanAmount = 250000;
const annualInterestRate = 0.045;
const loanTermYears = 30;

const payment = calculateMonthlyPayment(
    loanAmount, 
    annualInterestRate, 
    loanTermYears
);
Bad Example
// Nama function tidak jelas
function calc(p, r, t) {
    const mr = r / 12;
    const n = t * 12;
    
    // Magic numbers tanpa penjelasan
    const mp = p * (mr * Math.pow(1 + mr, n)) / (Math.pow(1 + mr, n) - 1);
    
    return Math.round(mp * 100) / 100;
}

// Variable dengan nama singkat
const l = 250000;
const i = 0.045;
const y = 30;

const p = calc(l, i, y);
Indentasi dan Format Kode
Consistent Indentation
if (user.isLoggedIn) {
    if (user.hasPermission('admin')) {
        showAdminPanel();
        logActivity('admin_access');
    } else {
        showUserDashboard();
        logActivity('user_access');
    }
} else {
    redirectToLogin();
}
Line Length Limit
// Maksimal 80-120 karakter per baris
const longCondition = (
    user.isActive && 
    user.hasValidSubscription && 
    user.lastLoginDate > thirtyDaysAgo
);

// Method chaining yang panjang
const processedData = rawData
    .filter(item => item.isValid)
    .map(item => transformItem(item))
    .sort((a, b) => a.priority - b.priority);
Penamaan yang Meaningful
// Boolean variables: gunakan is, has, can, should
const isUserLoggedIn = checkUserStatus();
const hasValidLicense = validateLicense();
const canEditPost = checkPermissions('edit');

// Function names: gunakan verb + noun
function validateEmailAddress(email) { }
function calculateTotalPrice(items) { }
function renderUserProfile(user) { }

// Constants: gunakan UPPER_CASE
const MAX_RETRY_ATTEMPTS = 3;
const DEFAULT_TIMEOUT_MS = 5000;
const API_BASE_URL = 'https://api.example.com';

// Class names: gunakan PascalCase
class UserProfileManager { }
class PaymentProcessor { }
class EmailValidator { }
Clean Code Tips:
  • Satu function hanya melakukan satu tugas
  • Gunakan nama yang dapat diucapkan dan dicari
  • Hindari mental mapping (a, b, c, x, y, z)
  • Konsisten dalam penamaan dan style

2. DRY Principle (Don't Repeat Yourself)

Identifikasi Pola Berulang

DRY adalah prinsip untuk menghindari duplikasi kode dengan mengabstraksi pola yang berulang.

Before: Repetitive Code
// Validasi form login
if (!email || email.length === 0) {
    showError('Email is required');
    return false;
}
if (!email.includes('@')) {
    showError('Invalid email format');
    return false;
}

// Validasi form register
if (!email || email.length === 0) {
    showError('Email is required');
    return false;
}
if (!email.includes('@')) {
    showError('Invalid email format');
    return false;
}

// Validasi form newsletter
if (!email || email.length === 0) {
    showError('Email is required');
    return false;
}
if (!email.includes('@')) {
    showError('Invalid email format');
    return false;
}
After: DRY Implementation
// Reusable validation function
function validateEmail(email) {
    if (!email || email.length === 0) {
        showError('Email is required');
        return false;
    }
    
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
        showError('Invalid email format');
        return false;
    }
    
    return true;
}

// Usage in different forms
function validateLoginForm() {
    return validateEmail(loginForm.email.value);
}

function validateRegisterForm() {
    return validateEmail(registerForm.email.value);
}

function validateNewsletterForm() {
    return validateEmail(newsletterForm.email.value);
}
Refactoring dengan Functions
// Utility functions untuk menghindari repetisi
const FormValidator = {
    // Generic field validation
    validateRequired(value, fieldName) {
        if (!value || value.trim().length === 0) {
            this.showError(`${fieldName} is required`);
            return false;
        }
        return true;
    },
    
    // Email validation
    validateEmail(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(email)) {
            this.showError('Please enter a valid email address');
            return false;
        }
        return true;
    },
    
    // Password strength validation
    validatePassword(password) {
        if (password.length < 8) {
            this.showError('Password must be at least 8 characters');
            return false;
        }
        
        const hasNumber = /\d/.test(password);
        const hasUpper = /[A-Z]/.test(password);
        const hasLower = /[a-z]/.test(password);
        
        if (!hasNumber || !hasUpper || !hasLower) {
            this.showError('Password must contain uppercase, lowercase, and number');
            return false;
        }
        
        return true;
    },
    
    // Error display
    showError(message) {
        const errorDiv = document.getElementById('error-message');
        errorDiv.textContent = message;
        errorDiv.style.display = 'block';
    },
    
    // Clear errors
    clearErrors() {
        const errorDiv = document.getElementById('error-message');
        errorDiv.style.display = 'none';
    }
};

// Usage example
function validateRegistrationForm(formData) {
    FormValidator.clearErrors();
    
    return FormValidator.validateRequired(formData.name, 'Name') &&
           FormValidator.validateEmail(formData.email) &&
           FormValidator.validatePassword(formData.password);
}
CSS DRY dengan Utility Classes
/* Utility classes untuk spacing */
.m-1 { margin: 0.25rem; }
.m-2 { margin: 0.5rem; }
.m-3 { margin: 1rem; }
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-3 { padding: 1rem; }

/* Reusable button styles */
.btn {
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: 0.375rem;
    cursor: pointer;
    transition: all 0.3s ease;
}

.btn-primary {
    background-color: #007bff;
    color: white;
}

.btn-primary:hover {
    background-color: #0056b3;
}

/* Flex utilities */
.d-flex { display: flex; }
.justify-center { justify-content: center; }
.align-center { align-items: center; }
.flex-column { flex-direction: column; }

3. Web Accessibility (Aksesibilitas)

Prinsip Aksesibilitas WCAG

Web Content Accessibility Guidelines (WCAG) memberikan standar untuk membuat web yang dapat diakses semua orang.

Perceivable

Informasi dapat dipersepsi user

Operable

Interface dapat dioperasikan

Understandable

Informasi mudah dipahami

Robust

Dapat diinterpretasi berbagai teknologi

Implementasi Aksesibilitas
<!-- Alt text untuk gambar -->
<img src="chart.jpg" alt="Sales increase 25% from Q1 to Q2 2025">

<!-- Labels untuk form input -->
<label for="username">Username:</label>
<input type="text" id="username" name="username" required 
       aria-describedby="username-help">
<span id="username-help">Must be at least 3 characters long</span>

<!-- ARIA labels untuk complex elements -->
<button aria-label="Close dialog" onclick="closeModal()">
    <i class="fas fa-times"></i>
</button>

<!-- Heading hierarchy -->
<h1>Main Page Title</h1>
    <h2>Section Title</h2>
        <h3>Subsection Title</h3>

<!-- Keyboard navigation -->
<div tabindex="0" role="button" onkeydown="handleKeyDown(event)">
    Custom Button
</div>

<!-- Skip links -->
<a href="#main-content" class="skip-link">Skip to main content</a>
Color Contrast dan Typography
Good Contrast Examples
Dark text on white background (Ratio: 16:1)
White text on blue background (Ratio: 5.3:1)
Dark gray text on light background (Ratio: 7:1)
Poor Contrast Examples
Light gray text on white (Ratio: 1.6:1) ❌
White text on yellow (Ratio: 1.1:1) ❌
Light green on green (Ratio: 2.1:1) ❌
Accessibility Checklist:
  • Semua gambar memiliki alt text yang deskriptif
  • Form input memiliki label yang jelas
  • Color contrast minimal 4.5:1 untuk text normal
  • Website dapat dinavigasi dengan keyboard
  • Heading menggunakan struktur hierarki yang benar

4. Security Basics (Keamanan Dasar)

Input Sanitization

Sanitasi input adalah langkah pertama untuk mencegah serangan injeksi dan XSS.

Demo: XSS Prevention
PHP Security Functions
<?php
// Input sanitization function
function sanitizeInput($input) {
    // Remove whitespace
    $input = trim($input);
    
    // Remove backslashes
    $input = stripslashes($input);
    
    // Convert special characters to HTML entities
    $input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
    
    return $input;
}

// Validate and sanitize form data
function processFormData($data) {
    $cleanData = [];
    
    foreach ($data as $key => $value) {
        // Basic validation
        if (empty($value)) {
            throw new InvalidArgumentException("$key is required");
        }
        
        // Sanitize based on field type
        switch ($key) {
            case 'email':
                $cleanData[$key] = filter_var($value, FILTER_VALIDATE_EMAIL);
                if (!$cleanData[$key]) {
                    throw new InvalidArgumentException("Invalid email format");
                }
                break;
                
            case 'age':
                $cleanData[$key] = filter_var($value, FILTER_VALIDATE_INT, [
                    'options' => ['min_range' => 1, 'max_range' => 120]
                ]);
                if ($cleanData[$key] === false) {
                    throw new InvalidArgumentException("Invalid age");
                }
                break;
                
            default:
                $cleanData[$key] = sanitizeInput($value);
                break;
        }
    }
    
    return $cleanData;
}

// Usage example
try {
    $userData = processFormData($_POST);
    // Safe to use $userData
} catch (InvalidArgumentException $e) {
    echo "Error: " . $e->getMessage();
}
?>
JavaScript Security
// Safe DOM manipulation
function safeSetText(elementId, text) {
    const element = document.getElementById(elementId);
    if (element) {
        // Use textContent instead of innerHTML to prevent XSS
        element.textContent = text;
    }
}

// Safe HTML insertion
function safeSetHTML(elementId, html) {
    const element = document.getElementById(elementId);
    if (element) {
        // Create a temporary div to sanitize HTML
        const tempDiv = document.createElement('div');
        tempDiv.textContent = html; // This escapes HTML
        element.innerHTML = tempDiv.innerHTML;
    }
}

// Input validation
function validateInput(input, type) {
    switch (type) {
        case 'email':
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            return emailRegex.test(input);
            
        case 'phone':
            const phoneRegex = /^\+?[\d\s\-\(\)]+$/;
            return phoneRegex.test(input);
            
        case 'alphanumeric':
            const alphanumericRegex = /^[a-zA-Z0-9]+$/;
            return alphanumericRegex.test(input);
            
        default:
            // Basic sanitization: remove HTML tags
            return input.replace(/<[^>]*>/g, '');
    }
}

// CSRF token handling
function getCSRFToken() {
    const meta = document.querySelector('meta[name="csrf-token"]');
    return meta ? meta.getAttribute('content') : '';
}

// Safe AJAX request
function secureAjaxRequest(url, data, method = 'POST') {
    const headers = {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': getCSRFToken()
    };
    
    return fetch(url, {
        method: method,
        headers: headers,
        body: JSON.stringify(data)
    });
}
Common Security Vulnerabilities:
  • XSS (Cross-Site Scripting): Sanitasi semua input user
  • SQL Injection: Gunakan prepared statements
  • CSRF: Implementasi CSRF tokens
  • Insecure Direct Object Reference: Validasi authorization

Praktik Unit 4: Audit dan Refactor Code

Tugas Praktik:
  1. Audit kode existing untuk clean code principles
  2. Refactor kode dengan DRY principles
  3. Implementasi accessibility features
  4. Tambahkan security measures
Code Quality Checklist
Clean Code:
Security & Accessibility:
Template: Security Utility Functions
// Security utility functions
const SecurityUtils = {
    // Escape HTML to prevent XSS
    escapeHTML(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    },
    
    // Validate email format
    isValidEmail(email) {
        const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return regex.test(email);
    },
    
    // Validate password strength
    isStrongPassword(password) {
        return password.length >= 8 &&
               /[A-Z]/.test(password) &&
               /[a-z]/.test(password) &&
               /\d/.test(password) &&
               /[!@#$%^&*]/.test(password);
    },
    
    // Rate limiting for form submissions
    rateLimiter: {
        attempts: {},
        isAllowed(identifier, maxAttempts = 5, timeWindow = 300000) {
            const now = Date.now();
            const userAttempts = this.attempts[identifier] || [];
            
            // Remove old attempts
            const recentAttempts = userAttempts.filter(
                time => now - time < timeWindow
            );
            
            if (recentAttempts.length >= maxAttempts) {
                return false;
            }
            
            recentAttempts.push(now);
            this.attempts[identifier] = recentAttempts;
            return true;
        }
    }
};