33. Clean Code Principles (DRY, KISS, YAGNI)

Menulis kode yang berjalan dengan baik hanyalah langkah awal. Tantangan sebenarnya dalam pengembangan perangkat lunak skala besar—seperti produk-produk di Bit Group Asia—adalah menulis kode yang mudah dipahami, dipelihara, dan dikembangkan oleh developer lain di masa mendatang.

Berikut adalah tiga pilar utama Clean Code yang wajib diterapkan di Bit Group Asia untuk menjaga kualitas basis kode (codebase):


A. DRY (Don't Repeat Yourself) - Single Source of Truth

Prinsip Utama: Setiap bagian dari pengetahuan atau logika dalam sistem harus memiliki representasi tunggal, jelas, dan berwibawa (Single Source of Truth).

Mengapa DRY Penting?

Jika logika yang sama ditulis di beberapa tempat, perubahan kebutuhan bisnis akan memaksa kita memperbarui semua tempat tersebut secara manual. Ini meningkatkan risiko bug akibat kelalaian pembaruan (desinkronisasi logika).

Kapan Harus Abstrak vs. Kapan Harus Duplikasi?

[!IMPORTANT] Peringatan Premature Abstraction: Terlalu berambisi menerapkan DRY dapat mengarah pada abstraksi prematur (premature abstraction). Seperti kata Sandi Metz: "Duplication is far cheaper than the wrong abstraction." (Duplikasi jauh lebih murah daripada abstraksi yang salah).

Gunakan Rule of Three:

  1. Tulis pertama kali: Selesaikan masalah langsung di tempat.
  2. Tulis kedua kali: Duplikasi diperbolehkan jika belum yakin dengan pola umumnya.
  3. Tulis ketiga kali: Saatnya melakukan ekstraksi logika menjadi fungsi utilitas atau custom hook yang modular.

Contoh Kasus: Validasi & Format Input Mata Uang (Rupiah)

// ❌ HINDARI (Logika pemformatan dan validasi diulang-ulang di setiap input formulir):
// Di ComponentA.tsx
const ComponentA = () => {
  const [price, setPrice] = useState(0);
  const formattedPrice = new Intl.NumberFormat("id-ID", {
    style: "currency",
    currency: "IDR",
    minimumFractionDigits: 0
  }).format(price);
  
  return <div>Harga: {formattedPrice}</div>;
};

// Di ComponentB.tsx
const ComponentB = () => {
  const [salary, setSalary] = useState(0);
  const formattedSalary = new Intl.NumberFormat("id-ID", {
    style: "currency",
    currency: "IDR",
    minimumFractionDigits: 0
  }).format(salary);
  
  return <div>Gaji: {formattedSalary}</div>;
};
// ✅ SELALU GUNAKAN (Ekstraksi ke helper utility atau custom hook):
// utils/currency.ts
export const formatRupiah = (value: number): string => {
  return new Intl.NumberFormat("id-ID", {
    style: "currency",
    currency: "IDR",
    minimumFractionDigits: 0
  }).format(value);
};

// Di ComponentA.tsx
import { formatRupiah } from "@/utils/currency";

const ComponentA = () => {
  const [price, setPrice] = useState(0);
  return <div>Harga: {formatRupiah(price)}</div>;
};

// Di ComponentB.tsx
import { formatRupiah } from "@/utils/currency";

const ComponentB = () => {
  const [salary, setSalary] = useState(0);
  return <div>Gaji: {formatRupiah(salary)}</div>;
};

B. KISS (Keep It Simple, Stupid)

Prinsip Utama: Sistem akan bekerja paling baik jika dijaga tetap sederhana daripada dibuat menjadi kompleks. Hindari kecanggihan yang tidak perlu (unnecessary cleverness).

Mengapa KISS Penting?

Kode dibaca jauh lebih sering daripada ditulis. Menggunakan sintaksis trik yang sangat pendek atau pustaka (library) yang kompleks untuk tugas sederhana membuat kode sulit di-debug oleh developer lain, bahkan oleh diri Anda sendiri beberapa bulan kemudian.

Contoh Kasus: Transformasi Array & Status Render

// ❌ HINDARI (Menggunakan trik conditional rendering / filter yang terlalu "smart" dan sulit dibaca):
const getActiveUserRoles = (user: any) => {
  return user?.roles?.filter((r: any) => r.active && (r.level > 2 || r.isAdmin)).map((r: any) => r.name.toUpperCase()).join(', ') || 'NONE';
};
// ✅ SELALU GUNAKAN (Langkah-langkah yang jelas, terdefinisi dengan tipe data yang kuat, mudah dibaca & di-debug):
interface Role {
  name: string;
  active: boolean;
  level: number;
  isAdmin: boolean;
}

interface User {
  roles?: Role[];
}

const getActiveUserRoles = (user: User): string => {
  const roles = user.roles;
  if (!roles || roles.length === 0) return "NONE";

  const activeAndAuthorizedRoles = roles.filter((role) => {
    const isActive = role.active;
    const hasAccess = role.level > 2 || role.isAdmin;
    return isActive && hasAccess;
  });

  if (activeAndAuthorizedRoles.length === 0) return "NONE";

  return activeAndAuthorizedRoles
    .map((role) => role.name.toUpperCase())
    .join(", ");
};

C. YAGNI (You Ain't Gonna Need It)

Prinsip Utama: Jangan menambahkan fungsionalitas sampai benar-benar dibutuhkan. Desainlah aplikasi berdasarkan kebutuhan saat ini, bukan kebutuhan spekulatif masa depan.

Mengapa YAGNI Penting?

  1. Beban Maintenance: Kode yang ditulis tetapi tidak digunakan tetap harus dibaca, diuji, dan diperbarui saat dependensi berubah.
  2. Kekacauan API: Menambahkan properti/parameter "cadangan" memperumit kontrak antarmuka (interface contracts) tanpa memberikan nilai tambah langsung.
  3. Fokus Tim: Waktu yang dihabiskan untuk membangun fungsionalitas spekulatif adalah waktu yang terbuang untuk menyempurnakan fitur utama yang sedang berjalan.

Contoh Kasus: Mendesain Komponen UI Button

// ❌ HINDARI (Membuat prop spekulatif "siapa tahu nanti butuh" untuk fitur yang belum diminta):
interface ButtonProps {
  label: string;
  onClick: () => void;
  // Spekulatif props (belum ada kebutuhan riil di task saat ini):
  shadowColor?: "red" | "blue" | "green";
  enableLongPress?: boolean;
  onLongPress?: () => void;
  animateOnHoverType?: "bounce" | "slide" | "fade";
  badgeValue?: number;
}

const Button = ({ label, onClick, shadowColor, enableLongPress, ...props }: ButtonProps) => {
  // Logika rumit untuk menangani event long press & animasi hover dinamis
  return <button onClick={onClick}>{label}</button>;
};
// ✅ SELALU GUNAKAN (Sederhana, fokus pada requirements yang disepakati saat ini. Tambahkan props baru HANYA saat ada kebutuhan nyata):
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: "primary" | "secondary";
  isLoading?: boolean;
}

const Button = ({ label, onClick, variant = "primary", isLoading = false }: ButtonProps) => {
  return (
    <button 
      onClick={onClick} 
      disabled={isLoading}
      className={`btn btn-${variant}`}
    >
      {isLoading ? "Loading..." : label}
    </button>
  );
};

D. Panduan Keputusan Refactoring (Cheatsheet)

SituasiTindakanPrinsip Utama
Ada 2 blok kode serupa di 2 halaman berbedaBiarkan dahulu. Tunggu kemunculan tempat ketiga sebelum melakukan ekstraksi.DRY (Avoid premature abstraction)
Menulis fungsi dengan baris kode sangat panjang karena percabangan bersarangLakukan refactoring. Gunakan early returns untuk memisahkan kondisi kegagalan dari alur sukses utama.KISS (Maintain readability)
Butuh library besar untuk fitur chart padahal hanya menampilkan total angka statisGunakan native HTML/CSS. Tampilkan visualisasi sederhana tanpa overhead library pihak ketiga.KISS & YAGNI
Ada ide untuk mendukung rendering dinamis 3 dimensi di masa depanTulis di backlog/catatan, jangan diimplementasikan ke dalam PR sekarang.YAGNI

Hubungan dengan Code Review

Sebelum mengajukan Pull Request, pastikan Anda menjawab tiga pertanyaan ini secara jujur:

  1. Apakah saya menduplikasi logika bisnis di tempat lain? Jika ya (dan sudah ketiga kalinya), ekstrak menjadi utilitas.
  2. Jika rekan satu tim saya membaca kode ini, apakah mereka akan langsung paham dalam 10 detik? Jika tidak, sederhanakan penamaan dan alurnya.
  3. Apakah ada baris kode atau props yang saya tambahkan tanpa ada hubungannya dengan fungsionalitas tiket saat ini? Jika ya, hapus segera.

© 2026 Bit Group Asia. All rights reserved. Technical Excellence Standard.

© 2026 Bit Group Asia. All rights reserved.

ArchitectureSecurityGuidelines