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:
- Bisakah kamu menjelaskan fungsi ini dalam satu kalimat tanpa kata "dan"?
- Jika ada 2 alasan berbeda untuk mengubah fungsi ini, pecah.
- 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
- "Satu fungsi" ambigu — "fetch user" satu hal, "fetch user dan log audit" dua hal. Tolak sambungan dengan "dan".
- Class God Object — class 1000 baris yang ngerjain semua. SRP wajib di class level juga, bukan cuma fungsi.
- File
utils.jscampur aduk — date util + validation + price + parse XML. Pecah per domain. - SRP terlalu fanatik — pecah
add(a, b)jadivalidate()+compute()+format()= over-engineering. Konteks penting. - Single Responsibility != Single Function call — fungsi yang panggil 3 fungsi lain TETAP single responsibility (orchestrate), asal level abstraksinya sama.
- Component React tanpa SRP — Dashboard yang fetch 5 endpoint + render 5 section + handle 5 state = pecah per section.
- Database model dengan banyak responsibility — User model handle login + profile + notifications + permissions = pecah jadi service classes.
🎯 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/typeyang 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.