Skip to the content.

Modularization

SmellGirish Suryanarayana et al. Code SmellsModularization

Girish modularization smell

Semua smell di dalam grup ini berkaitan dengan kesalahan dalam merancang modul-modul di dalam hierarki.

Filosofi

Buku adalah konten yang berisikan sekumpulan teks paragraf. Bayangkan apa yang terjadi bila semua teks ditulis mentah-mentah tanpa menggunakan Heading, Chapter, Bab, Indeks maupun sub-bab untuk membatasi isi materi dalam buku? Jawabannya pasti akan mempersulit pembaca lain dalam memahami materi dari buku yang mereka buat tentunya.

Prinsip Modularization

Link Video

Girish Modularization principles

Secara teoretis, Sebuah class/abstraksi seharusnya memiliki tingkat kohesi yang tinggi dan coupling yang rendah sehingga class tersebut mempunyai tanggungjawab yang sesuai, function dan member yang saling berinteraksi satu sama lain tanpa bergantung pada class lain.

Menurut Girish Suryanarayana dkk, terdapat 4 prinsip modularization yaitu:

Berdasarkan pada pengamatan abstraction smell, terdapat pelanggaran prinsip encapsulation antara lain: (field dan method)

Modularization smell Pelanggaran prinsip modularization Penyebab Martin Fowler smells
Broken Modularization Localize related data and methods 1. Pemisahan data dan method dalam class terpisah
2. Method kecolongan bermain dengan class lain
1. Data Class
2. Feature Envy
Insufficient Modularization Decompose abstractions to manageable size 1. Class terlalu besar, tidak ada modularization
2. Class/method terlalu kompleks
1. Large class, divergent changes
2. Long method
Cyclically-dependent Modularization Create acyclic dependencies Class saling dependensi satu sama lain dengan pasangannya atau teman selingkarnya Shotgun Surgery, Feature Envy, Inappropriate Intimacy
Hub-like Modularization Limit dependencies Class terlalu banyak ketergantungan dari class lain atau dependensi masukan dari class lain Shotgun Surgery (bisa terjadi pada class yang depend dengannya)

Broken Modularization

Link Video | Materi

Sejak kehadiran laptop MakBook generasi terbaru, Pabble dengan sengaja dan tega-teganya hanya menyediakan 1 colokan USB-C dan colokan headset di laptop tersebut. Bayangkan, 1 colokan aja tidak cukup hanya buat cas lho! Nyolok Flashdisk aja gk bisa apalagi barengan sama charger. Untungnya, Pabble menjual complete kit dengan colokan HDMI, 2x USB 3.0, LAN Port, dan 2x USB-C NAMUN dijual terpisah dengan harga 1 juta Rupiah! Ya bayangin jika laptop yang anda gunakan hanya disediakan 1 colokan bagaimana pakainya dong?? (Referensi: https://inet.detik.com/consumer/d-2853926/colokan-sapu-jagat-di-macbook-baru-apple)

Smell ini terjadi jika pada data member, field, atau method yang seharusnya dikumpulkan dalam 1 class/abstraksi malah terpisah dan tersebar di abstraksi lain. Smell ini sering dimanifestasikan sebagai:

Penyebab

Penyelesaian

Refactoring move field/member/method

Cara paling mudahnya dalam menyelesaikan smell ini adalah dengan memindahkan member-member dari kelas yang terpisah ke class asal-nya. Jika kasus tersebut terjadi pada:

Contoh

Struktur class dalam aplikasi device management

Masalah

Terdapat dua class dalam kasus yaitu Device.java dan DeviceData.java. Pada kasus tersebut, member-member dari Device dipisahkan ke class lain yang bernama DeviceData, dimana method-method dari Device akan memanggil data-data dari class DeviceData. Hal ini seharusnya tidak boleh dilakukan dalam OOP dikarenakan class seharusnya menampung semua method dan member yang mempunyai tanggungjawab yang sama.

Kasus inilah yang pada akhirnya menimbulkan smell Broken Modularization karena adanya perpecahan modul yang memiliki tanggungjawab yang sama dari kelas seharusnya.

Penyelesaian

Untuk menyelesaikan kasus smell tersebut, gabungkan field dan method menjadi 1 class sehingga class Device dapat menampung method, member, dan field sesuai tanggungjawabnya masing-masing. Pada class Device.java, semua member pindahan dari DeviceData ditampung sebagai private member dari Device.

When to Ignore

Auto-generated code

Jika code tersebut digenerate secara otomatis dari generator (dari higher-level models, terdiri atas beberapa data class), maka hal tersebut memang dapat diabaikan karena dapat menimbulkan efek out of sync jika user melakukan modifikasi pada auto-generated codes. Contoh kasusnya adalah GUI Builder ataupun LinQ database modeling.

Data Transfer Objects (DTOs)

Struktur DTO

Martin Fowler mendefinisikan Data Transfer Objects tersebut sebagai perantara data terhadap Remote Facade (aksesor data). Jika class tersebut merupakan class yang difungsikan untuk parsing data dari/ke API website, dimana class tersebut hanya berisikan data field, setter-getter, parser (JSON/XML/AJAX ke Object), dan serializer tanpa method dan behaviour lainnya. Pada kasus tersebut, smell dapat diabaikan demi alasan mempermudah proses transfer data ke network.

Insufficient Modularization

Link Video | Materi

PT Burung Pipit memproduksi mie instan dan bihun instan. Pada suatu hari, PT Burung Pipit mempunyai ide untuk memproduksi kwetiau instan dengan mesin yang sudah ada. Namun sayangnya karena proses pengolahan kwetiau sangat berbeda jauh dengan mie dan bihun, memodifikasikan mesin membutuhkan waktu hingga berbulan-bulan agar mampu menerima kwetiau untuk dioleh menjadi kwetiau instan. Hmmm… Ribet gak kira-kira mesin-mesin tersebut harus mereka modifikasi setiap mereka menerima jenis mie yang berbeda-beda? Kenapa gak beli mesin baru aja, biar tidak ribet harus ubah-ubah mie yang ada?

Smell ini terjadi karena adanya abstraksi yang terlalu besar dan harus dipecahkan (dekomposisi) ke beberapa abstraksi baru. Terdapat 2 jenis smell yaitu:

Teori: Cyclomatic Complexity

Pada kedua jenis smell ini, kompleksitas pada suatu method dapat diprediksi dengan rumus cyclomatic complexity McCabe (bukan McTerong) yaitu M = E − N + 2P dimana:

Jika magic number cyclomatic complexity dari implementation (satuan method) tersebut lebih dari 10, maka smell Insuffient Modularization tipe bloated implementation bisa terjadi pada abstraksi tersebut.

Untuk mengetahui lebih jauh mengenai kompleksitas suatu method/abstraksi secara cyclomatic, dapat dilihat referensi lebih jauh melalui buku “Software Testing: A Craftsman’s Approach, Third Edition” karangan Paul C. Jorgensen (Bab 9, Hal 139-146) atau melalui rangkuman Software Testing: Perhitungan Cyclomatic Complexity yang dipublikasikan di socs.binus.ac.id.

Penyebab

Penyelesaian

Jika kasus ini berkaitan dengan bloated interface, maka perlu dicek member (method dan field) beserta tanggungjawabnya. Jika member tersebut ternyata berperan diluar tanggungjawab utama class tersebut, extract ke class baru.

Jika kasus ini berkaitan dengan bloated implementation, perlu dicek kompleksitas method sebelum method tersebut dipecahkan/diekstrak ke private helper method. Jika kasus smell ini terjadi berbarengan dengan smell Multifaceted Abstraction, maka pecahkan/ekstrak dan enkapsulasikan setiap pertanggungjawaban dalam masing-masing abstraksi baru atau yang telah ada.

Julukan

When to Ignore

Cyclically-dependent Modularization

Link Video | Materi

Ketergantungan terhadap sebuah komoditas itu kadang tidak ada habis-habisnya. Ada sebuah kisah dimana Hengga membeli sandal di toko Sandal Biru, dimana sandal tersebut kebanyakan merupakan sandal hasil colongan di tempat ibadah (Jangan ditiru ya!). Ketika Hengga hendak sholat Jumat di masjid, sandal tersebut dicolong dan Hengga marah karena sandalnya kecolongan. Eh.. ketika ia pulang dengan kaki terlanjangnya, ia menyadari sandal yang ia miliki dioper kembali ke toko Sandal Biru yang lagi-lagi adalah toko yang ia beli sandalnya.

Secara tidak langsung, Hengga tidak menyadari bahwa toko yang ia beli sandalnya melakukan tindakan yang sungguh tidak bertanggung jawab kepada konsumen karena pada dasarnya pemilik/karyawan toko tersebut bergantung pada sandal-sandal yang mereka colongi agar dapat berjualan dan mendapatkan keuntungan yang besar. Ya kira-kira prosesnya adalah: Hengga beli sandal -> Hengga sholat jumat -> Sandal dicolong oleh karyawannya -> Orang lain beli sandal dari toko dia

Betapa repotnya dan ribetnya ketergantungan hingga nyusahin orang lain. Udah gitu, nyolong sandalnya udah bagaikan lingkaran setan yang gak ada habis-habisnya! :anger: :imp: :sob:

Graph of cyclic dependency

Smell ini terjadi ketika dua atau lebih abstraction saling bergantung satu sama lain baik secara langsung maupun tidak langsung. Smell ini tentunya melanggar Acyclic Dependencies Principle (ADP).

Smell ini tidak hanya terjadi secara langsung secara inheritence dan field member, namun juga secara tidak langsung secara variabel dalam method maupun secara parametrik. Dampak dari smell ini adalah keharusan sang developer untuk menjalankan, tes, dan modifikasi pasangan cyclic class secara bersamaan, yang bahayanya dapat berpotensi terjadinya domino-effect serta menimbulkan smell shotgun-surgery karena harus merubah class yang cyclic secara bersamaan atau dalam kasus fatalnya, bisa menjadi “senjata makan tuan” dari class pemanggil methodnya sendiri.

Penyebab

Contoh

Contoh 1: java.util inter-abstractions cyclic interdepedency

Java interclass cyclic dependency

Dalam buku yang ditulis oleh Girish, Girish menyinggung package java.util yang berisikan 6 abstractions yang sangat kompleks dan sangat cyclic ketergantungan antar classnya. Class tersebut hampir semuanya saling bergantung satu sama lain.

Secara mendasar, Girish juga menuliskan catatan mengenai adanya potensi domino-effect karena adanya perubahan antar class ketika terdapat perubahan dalam satu abstraksi yang secara eksplisit dikutipkan sebagai berikut (hal 108-109, Chapter 5):

In this design, any change to an abstraction involved in this dependency chain has the potential to affect other abstractions that depend on it, causing ripple effects or cascade of changes. A designer must, therefore, strive for designs that do not consist of tangles.

Meski secara hierarki cukup sulit dalam memberantas adanya smell pada tingkat tinggi, apalagi pada level programming language API, namun kasus ini bisa dicegah dari bibit-bibitnya pada contoh kasus berikutnya.

Contoh 2: Order and TaxCalculator

Struktur class Order dan TaxCalculator

Pada kasus class Order.java, TaxCalculator.java, dan Item.java, terdapat perputaran dependensi antar class. Member field class Order terdiri dari (composed of) beberapa Item, namun pada class Order, terdapat method getAmount() dimana dalam isi methodnya melakukan return taxCal.computeAmount(). Pada method computeAmount(), class tersebut mengambil semua item dalam class Order dan menghitung jumlah pajak dari transaksi order dengan memanggil method calculateTax() untuk kemudian ditotalkan dengan transaksi order tersebut dan di-return.

// class Item
public double getCost() {
  return cost;
}

// class Order
public Vector<Item> getItems() {
  Vector<Item> items = new Vector<>();
  for (Item item : this.items) {
    items.add(new Item(item.getCost()));
  }
  return items;
}

public double getAmount() {
  TaxCalculator calc = new TaxCalculator();
  return calc.computeAmount(this);
}

// class TaxCalculator
public double computeAmount(Order order) {
  Vector<Item> items = order.getItems();

  double amount = 0;
  for (Item item : items) {
    amount += item.getCost();
  }

  return amount + calculateTax(amount);
}

private double calculateTax(double amount) {
  return amount * 0.1;
}

Secara tidak langsung, pemanggilan method getAmount() dari class Order secara tidak langsung menyebabkan adanya cyclic dependency yang menyebabkan ketergantungan yang berputar-putar. Selain itu, method dalam class ini dicurigai mempunyai smell lain yaitu Feature Envy dimana method ini hanya mengakses value dari class lain dan secara multidimensional, smell ini terjadi bagaikan lingkaran setan dimana terjadi adu colong sandal antar class sehingga rawan terjadi error karena pasangan class tersebut harus dijalankan, dites, dan diubah bersamaan.

Penyelesaian

Untuk mencegah terjadinya cyclic depedency, ada berbagai cara untuk menyelesaikan kejadian smell ini yaitu:

Pada kasus class Order dan TaxCalculator, diketahui bahwa terdapat method computeAmount() yang sebetulnya dapat ditempatkan di class Order sehingga class TaxCalculator cukup mengkalkulasikan pajak dari total transaksi keseluruhan.

// class Item
public double getCost() {
  return cost;
}

// class Order
public Vector<Item> getItems() {
  Vector<Item> items = new Vector<>();
  for (Item item : this.items) {
    items.add(new Item(item.getCost()));
  }
  return items;
}

public double getAmount() {
  return computeAmount() + calc.calculateTax(amount);
}

private double computeAmount() {
  TaxCalculator calc = new TaxCalculator();
  Vector<Item> items = getItems();

  double amount = 0;
  for (Item item : items) {
    amount += item.getCost();
  }

  return amount;
}

// class TaxCalculator
private double calculateTax(double amount) {
  return amount * 0.1;
}

Sebagai gantinya, kita dapat memindahkan method computeAmount() dari class TaxCalculator ke Order sehingga class TaxCalculator.java tidak perlu bergantung pada Order.java untuk mengambil data-data dari Order dan semua kalkulasi biaya cukup dilakukan dari class Order dan class TaxCalculator cukup mengkalkulasikan berbagai jenis pajak yang akan diaplikasikan kepada total biaya dari class Order.

When to Ignore

Pada kasus Unit cycles between conceptually related abstractions, terutama pada kasus cyclic antar class dalam package java.util.regex, terdapat 2 class yang saling bertukar peran dan saling bergantung satu sama lain yaitu class Matcher dan Pattern. Dalam kasus tersebut, kedua class memang diciptakan terpisah namun terpakai berbarengan. Meski praktik ini tepat untuk diterapkan, namun dalam beberapa kasus tertentu, terutama pada skala besar, class tetap harus dibagi untuk mencegah adanya masalah yang lebih kompleks.

Tentunya akan menjadi masalah yang sangat besar jika dependency antar class tersebut terjadi antar lintas package yang membuat developer semakin kesulitan mengelola berbagai macam package yang mereka urusi.

Catatan Tambahan

Pada beberapa bahasa pemrograman, kasus Cyclic Dependency/Hierarchy yang terjadi pada class-class lintas package dapat menyebabkan code/class/module tidak dapat dieksekusi karena adanya cyclic dependency yang terjadi.

Salah satu bahasa pemrograman yang melarang keras adanya Cyclic Dependency/Hierarchy adalah Go Lang dimana pada bahasa pemrograman tersebut MELARANG adanya cycle imports dimana ketika kedua class tersebut saling mengimport package tersebut, maka terjadi error yang cukup fatal hingga menimbulkan compiler error.

package ara

import {
  "example.com/user/modul/burung" // lot of function required
}

func length(str string) {
  return len(r);
}

func eksekusi(b burung.Burung) {
  // do something here
}

Package ara bergantung pada burung sedangkan modul burung hanya mengambil satu function dari ara meski terjadi lintas package.

package burung

import {
  "example.com/user/modul/ara" // only pick one of function
}

type Burung struct {
  name string,
  species string,
  age int,
  gender bool
}

func foo() { ... }

func bar() { ... }

func hantam() { ... }

func sikat() { ... }

func tendang() { ... }

Mengikuti paradigma Acyclic Dependencies Principle (ADP), kita dapat memecahkan module tersebut secara terpisah yaitu pada modul ara yang dapat dikeluarkan function-function ke module baru agar tidak menganggu modularitas dari package yang akan dipakai oleh burung dan package-package lainnya.

Selain itu, beberapa framework bahasa pemrograman baik NodeJS, Angular, TypeScript, dan ECMAScript (ES) juga menerapkan warning bagi developer jika kedapatan melakukan import module yang terjadi secara cyclic agar dapat memisahkan class/module lintas package yang tentunya dapat mengacaukan jalannya framework.

Referensi: Mantesh Jaihal - dealing with import cycle in go

Hub-like Dependencies

Link Video | Materi

Toserba Pelangi merupakan toko serba ada yang menyediakan berbagai macam kebutuhan di Kerajaan Wantung mulai dari kebutuhan pokok dasar hingga perkakas, elektronik, gadget, dan furniture semuanya mereka sediakan. Bayangkan jika Toserba Pelangi hanya satu-satunya toserba di kerajaan tersebut, maka semua rakyatnya sangat amat bergantung dengan toserba tersebut. Kan ribet kalau misalnya toserba tersebut merugi lalu bangkrut, kemana rakyat harus beli barang? :confused:

Inner and outer dependencies of a class

Smell ini terjadi ketika sebuah abstraksi mempunyai banyak dependensi masukan (fan-in) dan/atau abstraksi tersebut bergantung banyak pada abstraksi lain (fan-out). Smell ini sering kali memiliki kombinasi dengan smell lain seperti Large Class/Insufficient Modularization, Divergent Changes, dan Broken Modularization. Smell ini tentunya melanggar Single Responsibility Principle (SRP) karena banyaknya tanggungjawab yang dibebani olehnya.

Penyebab

Contoh

Kasus class java.awt.Font dan java.util.Component

Pada buku ini, disebutkan bahwa Girish menyinggung 2 class besar yang mempunyai smell ini, yaitu class java.awt.Font dan java.util.Component.

Pada kasus smell class java.awt.Font, class ini merupakan class yang merepresentasikan font dan fungsionalitas untuk render text sebagai kumpulan abjad/karakter pada object Graphic dan Component. Smell ini terjadi karena terhitung ada 19 incoming dependencies (fan-in) dan 34 outgoing dependencies (fan-out). Tentunya jumlah dependensi dalam class ini terbilang cukup banyak karena adanya text drawing dalam komponen-komponen di Java.

Pada kasus smell class java.util.Component, class ini berisikan konsep object grafis yang dapat ditampilkan pada layar, baik sebagai button, panel, dan scroll bar. Secara definitif, class ini merupakan fondasi penting dalam pengembangan user interface dalam bahasa pemrograman Java. Smell ini terjadi karena terhitung ada 498 incoming dependencies (fan-in termasuk java.awt.Font) dan 71 outgoing dependencies (fan-out).

Kedua class besar ini juga belum dihitung jumlah dependensi secara implisit (method implementasi dalam class), dan tentunya memperbludak class management karena banyaknya dependensi yang harus diurus oleh class tersebut.

Kasus utility class

Diketahui sebuah class bernama Utility.java dimana pada class tersebut berisikan berbagai macam jenis utility yang dapat dipergunakan oleh class lain.

public class Utilities {
  public static boolean validateEmail(String email) { ... }
  public static boolean validatePhoneNumber(String phoneNumber) { ... }

  //... other validation methods

  public static String toTitleCase(String s) { ... }

  //... other string manipulation methods
}

Ternyata class tersebut tidak hanya bertanggungjawab atas validasi, namun juga pada manipulasi teks dan fitur-fitur pembantu lainnya sehingga menimbulkan smell hub-like modularization dan Large Class karena adanya pemakaian oleh beberapa class lainnya.

Penyelesaian

Penyelesaian pada kasus java.awt.Font

Salah satu sugesti dari Girish adalah dengan memindahkan class, dikarenakan adanya beberapa class yang mempunyai member (baik method dan attribute) yang dapat tempatkan pada class yang seharusnya, salah satunya adalah method createGlyphVector() yang mempunyai smell Feature Envy dimana method ini berfungsi hanya untuk mengakses class GlyphVector dan membuat object dari class tersebut.

public GlyphVector createGlyphVector(FontRenderContext frc. CharacterIterator ci) {
  return (GlyphVector) new StandardGlyphVector(this, ci, frc);
}

Salah satu saran refactoring adalah dengan memindahkannya ke class GlyphVector sehingga class yang bergantung pada method tersebut dapat mengakses langsung pada class GlyphVector.

Penyelesaian pada kasus utility class

public class Utilities {
  public static String toTitleCase(String s) { ... }
  public static boolean validatePhoneNumber(String phoneNumber) { ... }

  //... other string manipulation methods
}

public class Validation {
  public static boolean validateEmail(String email) { ... }

  //... other validation methods
}

Class Utility memegang tanggung jawab lebih dari 1 yaitu validasi dan string manipulation, oleh karenanya diperlukan extract class ke class baru bernama Validation.java untuk keperluan sehingga pada class Utility.java cukup dipakai untuk keperluan string manipulation.

Julukan

When to Ignore

Pada kasus-kasus aplikasi berskala besar, terkadang ada beberapa core abstractions dimana class tersebut berperan penting dalam aplikasi. Salah satu contoh class bertipekan core abstraction adalah java.lang.Class dimana class ini menjadi pusat abstraksi di JDK dimana class ini dipakai oleh >1000 class dan bergantung pada 40 class. Meski class ini dapat dicap mempunyai smell Hub-like Modularization smell, namun kasus ini sangat tidak memungkinkan untuk dibatasi jumlahnya karena fungsionalitas yang sangat penting bagi class lain.


Referensi Bacaan

Wiki dan repository ini hanyalah rangkuman dari buku Suryanarayana et al. dengan sedikit tambahan informasi lain. Diharapkan mahasiswa juga membaca sumber aslinya pada bab 5 halaman 92 - 122.

Untuk referensi tambahan mengenai materi modularization, terutama pada topik tertentu, dapat disimak pada referensi berikut: