
- Anti-pola arsitektural — Anti-pola ini muncul ketika struktur suatu sistem dirancang (umumnya oleh seorang arsitek).
- Anti-pola manajemen/organisasi — Ini adalah anti-pola dalam manajemen proyek, biasanya ditemui oleh berbagai manajer (atau kelompok manajer).
- Anti-pola pengembangan — Anti-pola ini muncul saat sistem diimplementasikan oleh pemrogram biasa.
1. Kelumpuhan analitis
Kelumpuhan analisisdianggap sebagai anti-pola manajemen klasik. Ini melibatkan analisis situasi yang berlebihan selama perencanaan, sehingga tidak ada keputusan atau tindakan yang diambil, yang pada dasarnya melumpuhkan proses pembangunan. Ini sering terjadi ketika tujuannya adalah untuk mencapai kesempurnaan dan benar-benar mempertimbangkan segalanya selama periode analisis. Anti-pola ini dicirikan dengan berjalan dalam lingkaran (loop tertutup run-of-the-mill), merevisi dan membuat model terperinci, yang pada gilirannya mengganggu alur kerja. Misalnya, Anda mencoba memprediksi hal-hal pada suatu level: tetapi bagaimana jika pengguna tiba-tiba ingin membuat daftar karyawan berdasarkan huruf keempat dan kelima dari nama mereka, termasuk daftar proyek yang paling banyak menghabiskan waktu kerja mereka antara Tahun Baru dan Hari Perempuan Internasional selama empat tahun terakhir? Intinya, itu' terlalu banyak analisis. Berikut adalah beberapa tip untuk melawan kelumpuhan analisis:- Anda perlu mendefinisikan tujuan jangka panjang sebagai suar untuk pengambilan keputusan, sehingga setiap keputusan Anda membawa Anda lebih dekat ke tujuan daripada menyebabkan Anda mandek.
- Jangan berkonsentrasi pada hal-hal sepele (mengapa membuat keputusan tentang detail yang tidak penting seolah-olah itu adalah keputusan terpenting dalam hidup Anda?)
- Tetapkan tenggat waktu untuk sebuah keputusan.
- Jangan mencoba menyelesaikan tugas dengan sempurna — lebih baik melakukannya dengan sangat baik.
2. Tuhan keberatan
Objek Tuhan adalah anti-pola yang menggambarkan konsentrasi berlebihan dari semua jenis fungsi dan sejumlah besar data yang berbeda (objek yang berputar di sekitar aplikasi). Ambil contoh kecil:
public class SomeUserGodObject {
private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";
private static final String FIND_ALL_CUSTOMERS = "SELECT id, u.email, u.phone, u.first_name_en, u.middle_name_en, u.last_name_en, u.created_date" +
" WHERE u.id IN (SELECT up.user_id FROM user_permissions up WHERE up.permission_id = ?)";
private static final String FIND_BY_EMAIL = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_dateFROM users WHERE email = ?";
private static final String LIMIT_OFFSET = " LIMIT ? OFFSET ?";
private static final String ORDER = " ORDER BY ISNULL(last_name_en), last_name_en, ISNULL(first_name_en), first_name_en, ISNULL(last_name_ru), " +
"last_name_ru, ISNULL(first_name_ru), first_name_ru";
private static final String CREATE_USER_EN = "INSERT INTO users(id, phone, email, first_name_en, middle_name_en, last_name_en, created_date) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)";
private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
........
private final JdbcTemplate jdbcTemplate;
private Map<String, String> firstName;
private Map<String, String> middleName;
private Map<String, String> lastName;
private List<Long> permission;
........
@Override
public List<User> findAllEnCustomers(Long permissionId) {
return jdbcTemplate.query( FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
}
@Override
public List<User> findAllEn() {
return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
}
@Override
public Optional<List<User>> findAllEnByEmail(String email) {
var query = FIND_ALL_USERS_EN + FIND_BY_EMAIL + ORDER;
return Optional.ofNullable(jdbcTemplate.query(query, userRowMapper(), email));
}
.............
private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
switch (type) {
case USERS:
return findAllEnUsers(permissionId);
case CUSTOMERS:
return findAllEnCustomers(permissionId);
default:
return findAllEn();
}
}
..............…
private RowMapper<User> userRowMapperEn() {
return (rs, rowNum) ->
User.builder()
.id(rs.getLong("id"))
.email(rs.getString("email"))
.accessFailed(rs.getInt("access_counter"))
.createdDate(rs.getObject("created_date", LocalDateTime.class))
.firstName(rs.getString("first_name_en"))
.middleName(rs.getString("middle_name_en"))
.lastName(rs.getString("last_name_en"))
.phone(rs.getString("phone"))
.build();
}
}
Di sini kita melihat kelas besar yang melakukan segalanya. Ini berisi kueri basis data serta beberapa data. Kami juga melihat metode fasad findAllWithoutPageEn, yang menyertakan logika bisnis. Objek Tuhan seperti itu menjadi sangat besar dan canggung untuk dipertahankan dengan baik. Kita harus mengotak-atiknya di setiap bagian kode. Banyak komponen sistem yang mengandalkannya dan terkait erat dengannya. Menjadi semakin sulit untuk mempertahankan kode seperti itu. Dalam kasus seperti itu, kode harus dipecah menjadi kelas-kelas terpisah, yang masing-masing hanya memiliki satu tujuan. Dalam contoh ini, kita dapat membagi objek God menjadi kelas Dao:
public class UserDaoImpl {
private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";
........
private final JdbcTemplate jdbcTemplate;
........
@Override
public List<User> findAllEnCustomers(Long permissionId) {
return jdbcTemplate.query(FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
}
@Override
public List<User> findAllEn() {
return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
}
........
}
Kelas yang berisi data dan metode untuk mengakses data:
public class UserInfo {
private Map<String, String> firstName;
…..
public Map<String, String> getFirstName() {
return firstName;
}
public void setFirstName(Map<String, String> firstName) {
this.firstName = firstName;
}
....
Dan akan lebih tepat untuk memindahkan metode dengan logika bisnis ke layanan:
private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
switch (type) {
case USERS:
return findAllEnUsers(permissionId);
case CUSTOMERS:
return findAllEnCustomers(permissionId);
default:
return findAllEn();
}
}
3. Lajang
Singleton adalah pola yang paling sederhana. Ini memastikan bahwa dalam aplikasi single-threaded akan ada satu instance kelas, dan menyediakan titik akses global ke objek ini. Tapi apakah itu pola atau anti-pola? Mari kita lihat kerugian dari pola ini:-
Status global Ketika kita mengakses instance kelas, kita tidak mengetahui status kelas saat ini. Kami tidak tahu siapa yang mengubahnya atau kapan. Keadaan mungkin tidak seperti yang kita harapkan. Dengan kata lain, kebenaran bekerja dengan singleton bergantung pada urutan aksesnya. Ini berarti bahwa subsistem saling bergantung satu sama lain dan, akibatnya, desain menjadi lebih kompleks.
-
Singleton melanggar prinsip SOLID — prinsip tanggung jawab tunggal: selain tugas langsungnya, kelas singleton juga mengontrol jumlah instance.
-
Ketergantungan kelas biasa pada singleton tidak terlihat di antarmuka kelas. Karena instance tunggal biasanya tidak diteruskan sebagai argumen metode, melainkan diperoleh langsung melalui getInstance(), Anda perlu masuk ke implementasi setiap metode untuk mengidentifikasi ketergantungan kelas pada singleton — cukup dengan melihat publik kelas kontrak tidak cukup.
Kehadiran singleton mengurangi testabilitas aplikasi secara keseluruhan dan kelas yang menggunakan singleton pada khususnya. Pertama-tama, Anda tidak dapat mengganti singleton dengan objek tiruan. Kedua, jika singleton memiliki antarmuka untuk mengubah statusnya, maka pengujian akan bergantung satu sama lain.
Dengan kata lain, singleton meningkatkan kopling, dan semua yang disebutkan di atas tidak lebih dari konsekuensi dari peningkatan kopling.
Dan jika Anda memikirkannya, Anda dapat menghindari penggunaan singleton. Misalnya, sangat mungkin (dan memang perlu) untuk menggunakan berbagai jenis pabrik untuk mengontrol jumlah instance dari suatu objek.
Bahaya terbesar terletak pada upaya untuk membangun seluruh arsitektur aplikasi berdasarkan lajang. Ada banyak alternatif bagus untuk pendekatan ini. Contoh terpenting adalah Spring, yaitu wadah IoC-nya: mereka adalah solusi alami untuk masalah pengendalian pembuatan layanan, karena mereka sebenarnya adalah "pabrik steroid".
Banyak perdebatan yang tak berkesudahan dan tidak dapat didamaikan sekarang berkecamuk tentang hal ini. Terserah Anda untuk memutuskan apakah singleton adalah pola atau anti-pola.
Kami tidak akan berlama-lama di atasnya. Sebagai gantinya, kita akan beralih ke pola desain terakhir untuk hari ini — poltergeist.
4. Hantu
Poltergeist adalah anti-pola yang melibatkan kelas tidak berguna yang digunakan untuk memanggil metode kelas lain atau hanya menambahkan lapisan abstraksi yang tidak perlu . Anti-pola ini memanifestasikan dirinya sebagai objek berumur pendek, tanpa status. Objek ini sering digunakan untuk menginisialisasi objek lain yang lebih permanen.
public class UserManager {
private UserService service;
public UserManager(UserService userService) {
service = userService;
}
User createUser(User user) {
return service.create(user);
}
Long findAllUsers(){
return service.findAll().size();
}
String findEmailById(Long id) {
return service.findById(id).getEmail();}
User findUserByEmail(String email) {
return service.findByEmail(email);
}
User deleteUserById(Long id) {
return service.delete(id);
}
}
Mengapa kita membutuhkan objek yang hanya sebagai perantara dan mendelegasikan pekerjaannya kepada orang lain? Kami menghilangkannya dan mentransfer fungsionalitas kecil yang dimilikinya ke objek yang berumur panjang. Selanjutnya, kita beralih ke pola yang paling menarik bagi kita (sebagai pengembang biasa), yaitu development anti-patterns .
5. Pengodean keras
Jadi kita sampai pada kata yang mengerikan ini: hard coding. Inti dari anti-pola ini adalah bahwa kode tersebut sangat terikat pada konfigurasi perangkat keras dan/atau lingkungan sistem tertentu. Ini sangat memperumit porting kode ke konfigurasi lain. Anti-pola ini terkait erat dengan angka ajaib (anti-pola ini sering terjalin). Contoh:
public Connection buildConnection() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8&characterSetResults=UTF-8&serverTimezone=UTC", "user01", "12345qwert");
return connection;
}
Sakit, bukan? Di sini kami membuat kode keras pengaturan koneksi kami. Akibatnya, kode hanya akan berfungsi dengan benar dengan MySQL. Untuk mengubah basis data, kita perlu menyelami kode dan mengubah semuanya secara manual. Solusi yang baik adalah dengan meletakkan konfigurasi di file terpisah:
spring:
datasource:
jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: user01
password: 12345qwert
Pilihan lain adalah menggunakan konstanta.
6. Jangkar kapal
Dalam konteks anti-patterns, jangkar kapal berarti menjaga bagian-bagian dari sistem yang tidak lagi digunakan setelah melakukan beberapa optimasi atau refactoring. Selain itu, beberapa bagian kode dapat disimpan "untuk penggunaan di masa mendatang" kalau-kalau Anda tiba-tiba membutuhkannya. Pada dasarnya, ini mengubah kode Anda menjadi tempat sampah. Contoh:
public User update(Long id, User request) {
User user = mergeUser(findById(id), request);
return userDAO.update(user);
}
private User mergeUser(User findUser, User requestUser) {
return new User(
findUser.getId(),
requestUser.getEmail() != null ? requestUser.getEmail() : findUser.getEmail(),
requestUser.getFirstName() != null ? requestUser.getFirstName() : findUser.getFirstNameRu(),
requestUser.getMiddleName() != null ? requestUser.getMiddleName() : findUser.getMiddleNameRu(),
requestUser.getLastName() != null ? requestUser.getLastName() : findUser.getLastNameEn(),
requestUser.getPhone() != null ? requestUser.getPhone() : findUser.getPhone());
}
Kami memiliki metode pembaruan yang menggunakan metode terpisah untuk menggabungkan data pengguna dari database dengan data pengguna yang diteruskan ke metode (jika pengguna yang diteruskan ke metode pembaruan memiliki bidang nol, maka nilai bidang lama diambil dari basis data) . Kemudian misalkan ada persyaratan baru bahwa catatan tidak boleh digabungkan dengan yang lama, tetapi sebaliknya, meskipun ada bidang nol, mereka digunakan untuk menimpa yang lama:
public User update(Long id, User request) {
return userDAO.update(user);
}
Ini berarti bahwa mergeUser tidak lagi digunakan, tetapi akan sangat disayangkan untuk menghapusnya — bagaimana jika metode ini (atau ide dari metode ini) mungkin berguna suatu hari nanti? Kode semacam itu hanya memperumit sistem dan menimbulkan kebingungan, yang pada dasarnya tidak memiliki nilai praktis. Kita tidak boleh lupa bahwa kode dengan "dead piece" seperti itu akan sulit untuk diteruskan ke rekan kerja ketika Anda pergi ke proyek lain. Cara terbaik untuk menangani jangkar kapal adalah dengan memfaktor ulang kode, yaitu menghapus bagian kode (memilukan, saya tahu). Selain itu, saat menyiapkan jadwal pengembangan, perlu memperhitungkan jangkar tersebut (untuk mengalokasikan waktu untuk merapikan).
7. Obyek tangki septik
Untuk mendeskripsikan anti-pola ini, pertama-tama Anda harus mengenal pola kumpulan objek . Kumpulan objek (kolam sumber daya) adalah pola desain kreasional , sekumpulan objek yang diinisialisasi dan siap digunakan. Saat aplikasi membutuhkan objek, objek tersebut diambil dari kumpulan ini alih-alih dibuat ulang. Ketika suatu objek tidak lagi dibutuhkan, itu tidak dihancurkan. Sebaliknya, itu dikembalikan ke kolam. Pola ini biasanya digunakan untuk objek berat yang membutuhkan waktu lama untuk dibuat setiap kali dibutuhkan, seperti saat menghubungkan ke database. Mari kita lihat contoh kecil dan sederhana. Berikut adalah kelas yang mewakili pola ini:
class ReusablePool {
private static ReusablePool pool;
private List<Resource> list = new LinkedList<>();
private ReusablePool() {
for (int i = 0; i < 3; i++)
list.add(new Resource());
}
public static ReusablePool getInstance() {
if (pool == null) {
pool = new ReusablePool();
}
return pool;
}
public Resource acquireResource() {
if (list.size() == 0) {
return new Resource();
} else {
Resource r = list.get(0);
list.remove(r);
return r;
}
}
public void releaseResource(Resource r) {
list.add(r);
}
}
Kelas ini disajikan dalam bentuk pola/anti-pola singleton di atas , yaitu hanya ada satu objek dari jenis ini. Ini menggunakan Resource
objek tertentu. Secara default, konstruktor mengisi kumpulan dengan 4 instance. Saat Anda mendapatkan objek, objek tersebut dihapus dari kumpulan (jika tidak ada objek yang tersedia, objek dibuat dan segera dikembalikan). Dan pada akhirnya, kami memiliki metode untuk mengembalikan objek tersebut. Objek sumber daya terlihat seperti ini:
public class Resource {
private Map<String, String> patterns;
public Resource() {
patterns = new HashMap<>();
patterns.put("proxy", "https://en.wikipedia.org/wiki/Proxy_pattern");
patterns.put("bridge", "https://en.wikipedia.org/wiki/Bridge_pattern");
patterns.put("facade", "https://en.wikipedia.org/wiki/Facade_pattern");
patterns.put("builder", "https://en.wikipedia.org/wiki/Builder_pattern");
}
public Map<String, String> getPatterns() {
return patterns;
}
public void setPatterns(Map<String, String> patterns) {
this.patterns = patterns;
}
}
Di sini kita memiliki objek kecil yang berisi peta dengan nama pola desain sebagai kunci dan tautan Wikipedia terkait sebagai nilainya, serta metode untuk mengakses peta. Mari kita lihat utama:
class SomeMain {
public static void main(String[] args) {
ReusablePool pool = ReusablePool.getInstance();
Resource firstResource = pool.acquireResource();
Map<String, String> firstPatterns = firstResource.getPatterns();
// use our map somehow...
pool.releaseResource(firstResource);
Resource secondResource = pool.acquireResource();
Map<String, String> secondPatterns = firstResource.getPatterns();
// use our map somehow...
pool.releaseResource(secondResource);
Resource thirdResource = pool.acquireResource();
Map<String, String> thirdPatterns = firstResource.getPatterns();
// use our map somehow...
pool.releaseResource(thirdResource);
}
}
Semuanya di sini cukup jelas: kita mendapatkan objek kumpulan, mendapatkan objek dengan sumber daya dari kumpulan, mendapatkan peta dari objek Sumber Daya, melakukan sesuatu dengannya, dan meletakkan semua ini pada tempatnya di kumpulan untuk digunakan kembali lebih lanjut. Voila, ini adalah pola desain kolam objek. Tapi kita berbicara tentang anti-pola, kan? Mari pertimbangkan kasus berikut dalam metode utama:
Resource fourthResource = pool.acquireResource();
Map<String, String> fourthPatterns = firstResource.getPatterns();
// use our map somehow...
fourthPatterns.clear();
firstPatterns.put("first","blablabla");
firstPatterns.put("second","blablabla");
firstPatterns.put("third","blablabla");
firstPatterns.put("fourth","blablabla");
pool.releaseResource(fourthResource);
Di sini, sekali lagi, kita mendapatkan objek Resource, kita mendapatkan peta polanya, dan kita melakukan sesuatu dengan peta tersebut. Tapi sebelum menyimpan peta kembali ke kumpulan objek, itu dibersihkan dan kemudian diisi dengan data yang rusak, membuat objek Resource tidak cocok untuk digunakan kembali. Salah satu detail utama dari kumpulan objek adalah ketika objek dikembalikan, objek tersebut harus dikembalikan ke keadaan yang sesuai untuk digunakan kembali lebih lanjut. Jika objek yang dikembalikan ke pool tetap dalam keadaan salah atau tidak terdefinisi, maka desain kita disebut object cesspool. Apakah masuk akal untuk menyimpan objek yang tidak cocok untuk digunakan kembali? Dalam situasi ini, kita dapat membuat peta internal tidak dapat diubah di konstruktor:
public Resource() {
patterns = new HashMap<>();
patterns.put("proxy", "https://en.wikipedia.org/wiki/Proxy_pattern");
patterns.put("bridge", "https://en.wikipedia.org/wiki/Bridge_pattern");
patterns.put("facade", "https://en.wikipedia.org/wiki/Facade_pattern");
patterns.put("builder", "https://en.wikipedia.org/wiki/Builder_pattern");
patterns = Collections.unmodifiableMap(patterns);
}
Upaya dan keinginan untuk mengubah konten peta akan memudar berkat UnsupportedOperationException yang dihasilkannya. Anti-pola adalah jebakan yang sering dihadapi pengembang karena kurangnya waktu, kecerobohan, kurangnya pengalaman, atau tekanan dari manajer proyek. Terburu-buru, yang biasa terjadi, dapat menyebabkan masalah besar untuk aplikasi di masa mendatang, jadi Anda perlu mengetahui tentang kesalahan ini dan menghindarinya terlebih dahulu. Ini menyimpulkan bagian pertama artikel. Bersambung...
GO TO FULL VERSION