Skip to main content

Change Preventers

Semua jenis Change Preventers, penjelasan, contoh kode nyata, dan cara refactoringnya

Code smell ini menyebabkan perubahan menjadi sangat sulit. Ketika kamu mengubah satu bagian, kamu terpaksa mengubah banyak bagian lain — program menjadi rapuh dan mahal untuk di-maintain.

Sumber: Refactoring Guru


Divergent Change

Satu class sering diubah karena banyak alasan yang berbeda-beda. Setiap perubahan — logika database, format export, aturan bisnis — selalu buka class yang sama.

Masalah

Ini melanggar Single Responsibility Principle: sebuah class harusnya punya satu dan hanya satu alasan untuk berubah. Class yang punya terlalu banyak tanggung jawab disebut "God Class".

Risikonya: merge conflict tinggi, side effect tidak terduga, dan tidak bisa test satu fitur tanpa load dependency fitur lain.

Explorer
DivergentChange
src
main
Report.java
ReportDB.java
ReportExporter.java
Main.java
DivergentChange/src/main/Report.java
package main;

public class Report {
private String data;

// Tanggung jawab 1: Generate data laporan
public void generateReport() {
this.data = "LHSS Semester 3";
System.out.println("Generating LHSS...");
}

public String getData() {
return data;
}
}

Solusi

Pisahkan tanggung jawab ke class yang berbeda. Setiap class hanya punya satu alasan untuk berubah.

Sebelum — satu God Class dengan semua tanggung jawab
// God Class: satu alasan perubahan dari mana saja
public class Report {
private String data;

public void generateReport() { /* logic generate */ }

public void saveToDatabase() { /* logic DB */ }

public void exportToPDF() { /* logic PDF */ }

public void exportToXML() { /* logic XML */ }
}
Sesudah — tanggung jawab dipisah ke class masing-masing
// Setiap class punya satu tanggung jawab
public class Report {
public void generateReport() { /* hanya generate */ }
}

public class ReportDB {
public void saveToDatabase(Report r) { /* hanya DB */ }
}

public class ReportExporter {
public void exportToPDF(Report r) { /* hanya PDF */ }
public void exportToXML(Report r) { /* hanya XML */ }
}

Perbandingan

FiturSebelumSesudah
KeterbacaanClass jadi "laci sampah" berisi code tidak berkaitanSetiap class punya tujuan tunggal yang jelas
PengujianTest logika data harus load dependency DB dan PDFBisa test Report tanpa menyentuh database
PemeliharaanGanti library PDF bisa merusak logika save DBPerubahan DB hanya berdampak pada ReportDB
ReusabilitasTidak bisa pakai data laporan tanpa drag logika PDFClass Report bisa dipakai di bagian mana saja

Shotgun Surgery

Satu perubahan kecil memaksa kamu mengedit banyak class berbeda sekaligus. Seperti tembakan shotgun — pelurunya kemana-mana.

Masalah

Validasi umur tersebar di AccountService dan LoanService. Ketika aturan berubah (batas umur naik dari 20 ke 21), kamu harus cari dan edit semua class yang punya logika tersebut.

Risikonya: sangat mudah lupa satu file, redundant effort, dan logika inti tidak terpusat.

Explorer
ShotgunSurgery
src
Main
AgeValidation.java
AccountService.java
LoanService.java
Main.java
ShotgunSurgery/src/Main/AgeValidation.java
package Main;

public class AgeValidation {
public static void validateAge(int age) throws Exception {
if (age < 20) {
throw new Exception("Age is not old enough");
}
}
}

Solusi

Pindahkan logika yang tersebar ke satu class terpusat. Semua yang butuh logika itu cukup memanggil class tersebut.

Sebelum — validasi duplikat di semua service
public class AccountService {
public void register(String name, int age) throws Exception {
if (age < 20) { // logika tersebar di sini
throw new Exception("Age is not old enough");
}
System.out.println("User " + name + " registered.");
}
}

public class LoanService {
public void applyLoan(String name, int age) throws Exception {
if (age < 20) { // logika yang sama persis ada di sini juga!
throw new Exception("Age is not old enough");
}
System.out.println("Loan approved for " + name);
}
}
Sesudah — validasi terpusat di AgeValidation
public class AgeValidation {
public static void validateAge(int age) throws Exception {
if (age < 20) throw new Exception("Age is not old enough");
}
}

public class AccountService {
public void register(String name, int age) throws Exception {
AgeValidation.validateAge(age); // satu sumber kebenaran
System.out.println("User " + name + " registered.");
}
}

public class LoanService {
public void applyLoan(String name, int age) throws Exception {
AgeValidation.validateAge(age); // sama, satu sumber
System.out.println("Loan approved for " + name);
}
}

Perbandingan

FiturSebelumSesudah
KeterbacaanTidak jelas di mana "sumber kebenaran" sebuah aturanLogika punya rumah yang jelas dan bernama
PengujianHarus test tiap class untuk pastikan aturan konsistenTest logikanya sekali di class terpusat
PemeliharaanUbah satu aturan = berburu ke banyak fileUbah di satu tempat, semua pemanggil ikut
ReusabilitasLogika terkurung dan berulang di class tidak berkaitanClass baru mana pun bisa pakai AgeValidation

Parallel Inheritance Hierarchies

Setiap kali buat subclass di satu class, kamu terpaksa buat subclass di class lain juga. Dua hierarki tumbuh paralel seperti bayangan satu sama lain.

Masalah

Ada Vehicle → Car, Truck dan ada VehiclePrinter → CarPrinter, TruckPrinter. Tambah Bus? Harus buat dua class: Bus dan BusPrinter. Double kerja yang tidak perlu.

Explorer
ParallelInheritanceHierarchies
src
deleted
VehiclePrinter.java
CarPrinter.java
TruckPrinter.java
main
Vehicle.java
Car.java
Truck.java
Main.java
ParallelInheritanceHierarchies/src/deleted/VehiclePrinter.java
package deleted;

// Hierarki paralel yang dihapus
// Hierarki 2 — bayangan dari hierarki Vehicle
public interface VehiclePrinter {
public void print();
}

Solusi

Pindahkan method print() langsung ke hierarki Vehicle. Hapus hierarki VehiclePrinter sama sekali.

Sebelum — dua hierarki paralel tumbuh bersamaan
// Hierarki 1
abstract class Vehicle { }
class Car extends Vehicle { }
class Truck extends Vehicle { }

// Hierarki 2 — bayangan dari Hierarki 1
interface VehiclePrinter { void print(); }
class CarPrinter implements VehiclePrinter {
public void print() { System.out.println("Printing Car..."); }
}
class TruckPrinter implements VehiclePrinter {
public void print() { System.out.println("Printing Truck..."); }
}

// Tambah Bus? Wajib buat Bus + BusPrinter!
Sesudah — satu hierarki, print() ada di Vehicle
// Hanya satu hierarki
abstract class Vehicle {
abstract String getName();
public void print() {
System.out.println("Printing " + getName() + " info...");
}
}
class Car extends Vehicle {
String getName() { return "Toyota Avanza"; }
}
class Truck extends Vehicle {
String getName() { return "Mitsubishi"; }
}
// Tambah Bus? Cukup satu class!

Perbandingan

FiturSebelumSesudah
KeterbacaanHarus loncat antara dua pohon class untuk satu "tipe"Satu hierarki, semua perilaku ada di sana
PengujianHarus tulis test duplikat untuk Vehicle dan Printer-nyaCukup test Vehicle dan subclass-nya
PemeliharaanTambah tipe baru = ingat buat dua class di dua tempatTambah tipe baru = buat satu subclass, selesai
ReusabilitasLogika CarPrinter terikat ketat pada CarMethod print() bisa dipakai ulang lewat polimorfisme