Single Responsibility — Clean Code

Single Responsibility Principle (SRP): Setiap fungsi, class, atau module harus punya satu alasan untuk berubah. Jika sebuah fungsi bisa berubah karena 2 alasan

Single Responsibility Principle (SRP): Setiap fungsi, class, atau module harus punya satu alasan untuk berubah. Jika sebuah fungsi bisa berubah karena 2 alasan berbeda, pecah menjadi 2 fungsi.

Analogi: Chef masak, kasir hitung pembayaran, pelayan antar makanan. Kalau satu orang melakukan ketiga hal itu, saat restoran ramai semuanya jadi kacau.

Sebelum — satu fungsi, banyak tanggung jawab:

// BURUK: fungsi ini berubah jika:
// 1. Format laporan berubah
// 2. Cara hitung gaji berubah
// 3. Cara kirim email berubah
function generatePayroll(employees) {
  let report = "LAPORAN GAJI\n";

  for (const emp of employees) {
    // Hitung gaji (tanggung jawab 1)
    let salary = emp.baseSalary;
    if (emp.overtime > 0) {
      salary += emp.overtime * emp.hourlyRate * 1.5;
    }
    if (emp.yearsWorked > 5) {
      salary *= 1.1; // bonus loyalitas
    }

    // Format laporan (tanggung jawab 2)
    report += `${emp.name}: Rp ${salary.toLocaleString()}\n`;
  }

  // Kirim email (tanggung jawab 3)
  sendEmail("[email protected]", "Payroll", report);

  return report;
}

Sesudah — setiap fungsi satu tanggung jawab:

// BERSIH: 3 fungsi, 3 tanggung jawab terpisah

// Tanggung jawab 1: Hitung gaji
function calculateSalary(employee) {
  let salary = employee.baseSalary;
  if (employee.overtime > 0) {
    salary += employee.overtime * employee.hourlyRate * 1.5;
  }
  if (employee.yearsWorked > 5) {
    salary *= 1.1;
  }
  return salary;
}

// Tanggung jawab 2: Format laporan
function formatPayrollReport(payrollData) {
  const lines = payrollData.map(
    ({ name, salary }) => `${name}: Rp ${salary.toLocaleString()}`
  );
  return "LAPORAN GAJI\n" + lines.join("\n");
}

// Tanggung jawab 3: Kirim laporan
function sendPayrollReport(report) {
  sendEmail("[email protected]", "Payroll", report);
}

// Orchestrator: menghubungkan semuanya
function processPayroll(employees) {
  const payrollData = employees.map(emp => ({
    name: emp.name,
    salary: calculateSalary(emp)
  }));
  const report = formatPayrollReport(payrollData);
  sendPayrollReport(report);
  return report;
}

SRP di React Components:

// BURUK: component melakukan terlalu banyak
function UserDashboard() {
  const [user, setUser] = useState(null);
  const [orders, setOrders] = useState([]);
  const [notifications, setNotifications] = useState([]);

  useEffect(() => { fetchUser().then(setUser); }, []);
  useEffect(() => { fetchOrders().then(setOrders); }, []);
  useEffect(() => { fetchNotifications().then(setNotifications); }, []);

  // 200 baris render...
}

// BERSIH: pecah per tanggung jawab
function UserDashboard() {
  return (
    <div>
      <UserProfile />
      <OrderHistory />
      <NotificationList />
    </div>
  );
}

function UserProfile() { /* hanya urusan user profile */ }
function OrderHistory() { /* hanya urusan orders */ }
function NotificationList() { /* hanya urusan notifications */ }

SRP di file/module level:

// BURUK: utils.js berisi 50 fungsi yang tidak berhubungan
// utils.js → formatDate, validateEmail, calculateTax, sortProducts, ...

// BERSIH: file terpisah per domain
// dateUtils.js → formatDate, parseDate, daysBetween
// validation.js → validateEmail, validatePhone, validatePassword
// pricing.js → calculateTax, calculateDiscount, formatPrice

Cara mengecek SRP:

  1. Bisakah kamu menjelaskan fungsi ini dalam satu kalimat tanpa kata "dan"?
  2. Jika ada 2 alasan berbeda untuk mengubah fungsi ini, pecah.
  3. Jika test untuk fungsi ini perlu mock banyak dependency, pecah.

🎭 Analogi sehari-hari

SRP itu kayak divisi kerja di restoran. Chef masak. Kasir hitung pembayaran. Pelayan antar makanan. Cleaner bersihin meja. Kalo SATU orang kerjain semuanya — pas restoran rame, semua langsung kacau (chef telat masak karena lagi bayarin meja 5, customer komplen makanan dingin). Kerja jadi efisien karena tiap role punya satu alasan untuk eksis — chef berubah skill kalau menu ganti, kasir berubah cara kerja kalau payment method baru. Kalo digabung, tiap perubahan kena ke role besar yang ngerjain banyak hal — risk lebih tinggi, test lebih ribet, debug lebih lama.

⚠️ Jebakan yang sering ditemui

🎯 Saatnya pecah SRP — kapan?

  • Kalimat penjelas pakai "DAN" ("validate input DAN save to DB") → pecah
  • Function > 30 baris dengan blank line pemisah jelas → pecah per blok
  • Test setup butuh banyak mock → tanda dependency banyak = banyak responsibility
  • Fungsi punya parameter mode / type yang ngubah behavior drastis → pecah jadi 2 function
  • Ubah fitur A bikin file B kena perubahan = coupling = bukan SRP
  • Component React dengan 3+ state hooks + multiple useEffect → pecah ke sub-component
  • Class dengan method dari domain berbeda → pecah jadi service classes

Aturan: single responsibility = single reason to change. Kalau ada 2+ alasan tidak terkait yang bisa bikin file ini diubah, pecah.

TL;DR: Single Responsibility Principle (SRP) = setiap unit kode (fungsi, class, module) punya SATU alasan untuk berubah. Cek: bisakah dijelasin 1 kalimat tanpa "dan"? File utils.js campur aduk = anti-pattern, pecah per domain. Component React 200 baris = pecah per section. Test yang butuh banyak mock = tanda kebanyakan responsibility. SRP applies di fungsi, class, dan file/module level.