Media Pengembangan Web & App | by APPKEY

Pembuatan Aplikasi Aplikasi Android Kesalahan Umum yang Harus Dihindari oleh Programmer Java Pemula

Kesalahan Umum yang Harus Dihindari oleh Programmer Java Pemula

-

Java adalah salah satu bahasa pemrograman yang banyak digunakan sejak lama. Setiap tahunnya programmer Java bertambah setiap tahunnya. Banyak di antara Java Programmer pemula melakukan kesalahan-kesalahan umum.

Anda mungkin juga melakukan kesalahan tersebut. Pepatah mengatakan pengalaman adalah guru terbaik. Dari kesalahan-kesalahan yang sering dilakukan oleh Java Programmer pemula, Anda mungkin dapat belajar bagaimana cara mengoperasikan Java dengan benar.

Untuk itu pada artikel ini kami menyajikan kepada Anda kesalahan-kesalahan umum yang dilakukan oleh Java Programmer pemula. Sebelum membahas kesalahan-kesalahan umum yang dilakukan oleh programmer Java pemula, kami akan sedikit membahas tentang apa itu Java.

Pengertian Java

java 1

Java adalah sebuah bahasa pemrograman serta platform untuk melakukan komputasi yang dirilis oleh Sun Microsystem di tahun 1995. Java adalah bahasa yang cepat, aman, dan andal. Java pun digunakan di mana-mana. Anda dapat mengunduh Java secara gratis di www.java.com.

Meski telah banyak digunakan sejak lama, banyak programmer Java pemula mengalami kesulitan dalam mengoperasikannya dan seringkali membuat kesalahan. Berikut kami merangkum kesalahan Java Programmer Pemula ketika bekerja dengan Java.

1. Mengabaikan Library yang Ada

Banyak programmer maupun developer Java yang mengabaikan keberadaan library padahal library ini sangat membantu dalam programming maupun membangun web serta aplikasi. Cobalah untuk mencari perpustakaan yang tersedia.

Banyak di antara perpustakaan tersebut telah dipoles selama bertahun-tahun keberadaannya dan bebas untuk digunakan. Library ini pun bisa berupa logging library, seperti logback dan Log4j, atau library terkait jaringan, seperti Netty atau Akka. Beberapa library, seperti Joda-Time, telah menjadi standar de facto.

Dalam beberapa proyek nantinya, bagian dari kode yang bertanggung jawab untuk pelolosan HTML ditulis dari awal. Hal ini pun bekerja dengan baik selama bertahun-tahun, tetapi akhirnya menemukan input pengguna yang menyebabkannya berputar menjadi loop tak terbatas. Pengguna, yang menganggap layanan tidak responsif, mencoba lagi dengan masukan yang sama.

Akhirnya, semua CPU di server yang dialokasikan untuk aplikasi ini ditempati oleh loop tak terbatas ini. Jika penulis escape tools HTML yang naif ini memutuskan untuk menggunakan salah satu pustaka terkenal yang tersedia untuk escape HTML, seperti HtmlEscapers dari Google Guava.

Tools ini mungkin tidak akan terjadi. Setidaknya, berlaku untuk sebagian besar perpustakaan populer dengan komunitas di belakangnya, kesalahan akan ditemukan dan diperbaiki lebih awal oleh komunitas untuk library ini.

2. Melupakan Sumber Daya (Resource) yang Gratis

Setiap kali program membuka file atau koneksi jaringan, penting bagi pemula Java untuk melepaskan resource setelah Anda selesai menggunakannya. Perhatian serupa harus diambil jika ada pengecualian yang akan dilemparkan selama operasi pada sumber daya tersebut.

Orang bisa berargumen bahwa FileInputStream memiliki finalizer yang memanggil ‘metode close ()’ pada garbage collection event; namun karena tidak dapat dipastikan kapan siklus garbage collection akan dimulai, aliran masukan dapat menggunakan sumber daya komputer untuk jangka waktu yang tidak terbatas.

Faktanya, ada pernyataan yang sangat berguna dan rapi yang diperkenalkan di Java7 terutama untuk kasus ini, yang disebut try-with-resources:

private static void printFileJava7() throws IOException {
try(FileInputStream input = new FileInputStream("file.txt")) {
int data = input.read();
while(data != -1){
System.out.print((char) data);
data = input.read();
}
}
}

Pernyataan ini dapat digunakan dengan objek apa pun yang mengimplementasikan antarmuka AutoCloseable. Hal ini memastikan bahwa setiap sumber daya ditutup pada akhir pernyataan.

Artikel Terkait  10 Rekomendasi Aplikasi Desain UI Terbaik untuk Pemula

3. Kehilangan Kata Kunci ‘break’ dalam Blok Switch-Case

Kesalahan ini terkadang tetap tidak ditemukan hingga diproduksi. Perilaku fallthrough dalam pernyataan sakelar seringkali berguna dalam penulisan kode namun, kata kunci “break” yang hilang dapat menyebabkan hasil yang buruk.

Jika Anda lupa meletakkan “break” dalam “case 0” pada contoh kode di bawah ini, program akan menulis “Zero” diikuti oleh “One”, karena control flow di dalam sini akan melewati seluruh pernyataan “switch” hingga itu mencapai “break”. Sebagai contoh:

public static void switchCasePrimer() {
int caseIndex = 0;
switch (caseIndex) {
case 0:
System.out.println("Zero");
case 1:
System.out.println("One");
break;
case 2:
System.out.println("Two");
break;
default:
System.out.println("Default");
}
}

Dalam kebanyakan kasus, solusi yang lebih bersih adalah menggunakan polymorphism dan memindahkan kode dengan perilaku tertentu ke dalam kelas yang terpisah. Kesalahan Java seperti ini dapat dideteksi menggunakan penganalisis kode statis misalnya FindBugs dan PMD.

  1. Alokasi Sampah (Garbage) Berlebihan

Alokasi sampah yang berlebihan dapat terjadi saat program membuat banyak objek berumur pendek. Garbage Collection yang ada pada Java akan bekerja terus menerus, menghapus objek yang tidak dibutuhkan dari memori dan hal ini pun berdampak negatif pada kinerja aplikasi. Satu contoh sederhana:

String oneMillionHello = "";
for (int i = 0; i < 1000000; i++) {
oneMillionHello = oneMillionHello + "Hello!";
}
System.out.println(oneMillionHello.substring(0, 6));

Dalam pengembangan Java, string tidak dapat diubah. Jadi, pada setiap iterasi, string baru dibuat. Untuk mengatasi ini kita harus menggunakan StringBuilder yang bisa berubah:

StringBuilder oneMillionHelloSB = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
oneMillionHelloSB.append("Hello!");
}
System.out.println(oneMillionHelloSB.toString().substring(0, 6));

Meskipun versi pertama membutuhkan cukup banyak waktu untuk dieksekusi, versi yang menggunakan StringBuilder menghasilkan hasil dalam jumlah waktu yang jauh lebih cepat.

  1. Kebocoran Memori (Memory Leaks)

Java menggunakan manajemen memori otomatis, dan meskipun melegakan melupakan tentang mengalokasikan dan mengosongkan memori secara manual, itu tidak berarti bahwa pengembang Java pemula tidak harus menyadari bagaimana memori digunakan dalam aplikasi. Masalah dengan alokasi memori masih mungkin terjadi.

Selama program membuat referensi ke objek yang tidak diperlukan lagi, program tidak akan dibebaskan. Di satu sisi, kita masih bisa menyebut kebocoran memori ini. Kebocoran memori di Java dapat terjadi dengan berbagai cara, tetapi alasan paling umum adalah referensi objek yang abadi, karena pengumpul sampah tidak dapat menghapus objek dari heap saat masih ada referensi ke sana.

Seseorang dapat membuat referensi seperti itu dengan mendefinisikan kelas dengan bidang statis yang berisi beberapa kumpulan objek, dan lupa menyetel bidang statis itu ke nol setelah kumpulan tidak lagi diperlukan. Bidang statis dianggap sebagai akar GC dan tidak pernah dikumpulkan.

Alasan potensial lain di balik kebocoran memori tersebut adalah sekelompok objek yang mereferensikan satu sama lain, menyebabkan ketergantungan melingkar sehingga pengumpul sampah tidak dapat memutuskan apakah objek dengan referensi lintas ketergantungan ini diperlukan atau tidak. Masalah lainnya adalah kebocoran di memori non-heap saat JNI digunakan.

Contoh kebocoran yang dapat terlihat sebagai berikut:

final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
final Deque<BigDecimal> numbers = new LinkedBlockingDeque<>();
final BigDecimal divisor = new BigDecimal(51);

scheduledExecutorService.scheduleAtFixedRate(() -> {
BigDecimal number = numbers.peekLast();
if (number != null && number.remainder(divisor).byteValue() == 0) {
System.out.println("Number: " + number);
System.out.println("Deque size: " + numbers.size());
}
}, 10, 10, TimeUnit.MILLISECONDS);

scheduledExecutorService.scheduleAtFixedRate(() -> {
numbers.add(new BigDecimal(System.currentTimeMillis()));
}, 10, 10, TimeUnit.MILLISECONDS);

try {
scheduledExecutorService.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}

Contoh ini untuk membuat dua tugas terjadwal. Tugas pertama mengambil bilangan terakhir dari deque yang disebut “numbers” dan mencetak bilangan dan ukuran deque jika bilangan tersebut habis dibagi 51. Tugas kedua memasukkan bilangan ke dalam deque. Kedua tugas dijadwalkan pada kecepatan tetap, dan dijalankan setiap 10 md.

Jika kode dijalankan, Anda akan melihat bahwa ukuran deque meningkat secara permanen. Hal ini pada akhirnya akan menyebabkan deque diisi dengan objek yang menggunakan semua memori heap yang tersedia.

Untuk mencegah hal ini sambil mempertahankan semantik program ini, kita dapat menggunakan metode berbeda untuk mengambil angka dari deque: “pollLast”. Berlawanan dengan metode “peekLast”, “pollLast” mengembalikan elemen dan menghapusnya dari deque sementara “peekLast” hanya mengembalikan elemen terakhir.

Artikel Terkait  Mengenal Java Virtual Machine untuk Program Java-mu yang Semakin Smooth

  1. Mengabaikan Exception

Seringkali programmer maupun developer Java pemula membiarkan exception tidak tertangani. Namun, praktik terbaik untuk para programmer maupun developer Java pemula dan berpengalaman adalah menanganinya.

Pengecualian diberikan dengan sengaja, jadi dalam banyak kasus kami perlu mengatasi masalah yang menyebabkan pengecualian ini. Jangan abaikan acara ini. Jika perlu, Anda dapat menampilkannya kembali, menampilkan dialog kesalahan kepada pengguna, atau menambahkan pesan ke log.

Paling tidak, harus dijelaskan mengapa pengecualian tidak ditangani agar pengembang lain mengetahui alasannya.

selfie = person.shootASelfie();
try {
selfie.show();
} catch (NullPointerException e) {
// Maybe, invisible man. Who cares, anyway?
}

Cara yang lebih jelas untuk menyoroti ketidakmampuan pengecualian adalah dengan mengenkode pesan ini ke dalam nama variabel pengecualian, seperti ini:

try { selfie.delete(); } catch (NullPointerException unimportant) { }

7. Menggunakan Referensi Null tanpa Diperlukan

Menghindari penggunaan null yang berlebihan sangat disarankan ketika mengoperasikan Java. Misalnya, lebih baik mengembalikan larik kosong atau koleksi dari metode daripada null, karena ini dapat membantu mencegah NullPointerException.

Anda dapat menggunakan metode berikut yang melintasi koleksi yang diperoleh dari metode lain, seperti yang ditunjukkan di bawah ini:

List<String> accountIds = person.getAccountIds();
for (String accountId : accountIds) {
processAccount(accountId);
}

Jika getAccountIds () mengembalikan null saat seseorang tidak memiliki akun, NullPointerException akan dimunculkan. Untuk memperbaikinya, pemeriksaan null akan diperlukan.

Namun, jika alih-alih null, ia mengembalikan daftar kosong, maka NullPointerException tidak lagi menjadi masalah. Selain itu, kodenya lebih rapi karena kita tidak perlu memeriksa variabel accountIds secara null.

Untuk menangani kasus lain ketika seseorang ingin menghindari null, strategi yang berbeda dapat digunakan. Salah satu strategi ini adalah menggunakan tipe Opsional yang bisa berupa objek kosong atau bungkus beberapa nilai:

Optional<String> optionalString = Optional.ofNullable(nullableString);
if(optionalString.isPresent()) {
System.out.println(optionalString.get());
}

Faktanya, Java 8 memberikan solusi yang lebih ringkas:

Optional<String> optionalString = Optional.ofNullable(nullableString);
optionalString.ifPresent(System.out::println);

Tipe opsional telah menjadi bagian dari Java sejak hadir versi 8, tetapi telah dikenal sejak lama di dunia pemrograman fungsional. Sebelumnya, tipe ini tersedia di Google Guava untuk versi Java sebelumnya.

8. Melanggar Kontrak yang ada pada Java

Terkadang, kode yang disediakan oleh pustaka standar atau oleh vendor pihak ketiga bergantung pada aturan yang harus dipatuhi untuk membuat segala sesuatunya berfungsi. Misalnya, ini bisa berupa hashCode dan sama dengan kontrak yang jika diikuti, membuat pekerjaan dijamin untuk satu set koleksi dari kerangka koleksi Java, dan untuk kelas lain yang menggunakan metode hashCode dan sama dengan.

Tidak mematuhi kontrak bukanlah jenis kesalahan yang selalu mengarah pada pengecualian atau merusak kompilasi kode; ini lebih rumit, karena terkadang itu mengubah perilaku aplikasi tanpa tanda-tanda bahaya.

Kode yang salah dapat tergelincir ke dalam rilis produksi dan menyebabkan sejumlah besar efek yang tidak diinginkan. Hal ini dapat mencakup perilaku UI yang buruk, laporan data yang salah, kinerja aplikasi yang buruk, kehilangan data, dan banyak lagi.

Untungnya, bug yang merusak ini jarang terjadi. Saya sudah menyebutkan kode hash dan sama dengan kontrak. Ini digunakan dalam koleksi yang mengandalkan hashing dan membandingkan objek, seperti HashMap dan HashSet. Sederhananya, kontrak tersebut memuat dua aturan:

– Jika dua objek sama, maka kode hashnya harus sama.
– Jika dua objek memiliki kode hash yang sama, maka keduanya mungkin sama atau tidak.
Melanggar aturan pertama kontrak menyebabkan masalah saat mencoba mengambil objek dari peta hash. Aturan kedua menandakan bahwa objek dengan kode hash yang sama belum tentu sama. Mari kita periksa efek dari melanggar aturan pertama:

public static class Boat {
private String name;

Boat(String name) {
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Boat boat = (Boat) o;
return !(name != null ? !name.equals(boat.name) : boat.name != null);
}

@Override
public int hashCode() {
return (int) (Math.random() * 5000);
}
}

Seperti yang Anda lihat, class Boat telah mengganti metode equals dan hashCode. Namun, itu telah melanggar kontrak, karena hashCode mengembalikan nilai acak untuk objek yang sama setiap kali dipanggil.

Kode berikut kemungkinan besar tidak akan menemukan perahu bernama “Enterprise” di hashset, meskipun fakta bahwa kami telah menambahkan perahu semacam itu sebelumnya:

public static void main(String[] args) {
Set<Boat> boats = new HashSet<>();
boats.add(new Boat("Enterprise"));
System.out.printf("We have a boat named 'Enterprise' : %b\n", boats.contains(new Boat("Enterprise")));
}

Contoh kontrak lainnya melibatkan metode penyelesaian. Berikut adalah kutipan dari dokumentasi java resmi yang menjelaskan fungsinya. Kontrak umum penyelesaian adalah bahwa ia dipanggil jika dan ketika mesin virtual JavaTM telah menentukan bahwa tidak ada lagi sarana yang dengannya objek ini dapat diakses oleh utas apa pun (yang belum mati), kecuali sebagai hasil dari tindakan yang diambil dengan menyelesaikan beberapa objek atau kelas lain yang siap untuk diselesaikan.

Metode finalisasi dapat mengambil tindakan apa pun, termasuk membuat objek ini tersedia lagi untuk utas lain; tujuan biasa dari penyelesaian, bagaimanapun, adalah untuk melakukan tindakan pembersihan sebelum objek dibuang secara permanen.

Misalnya, metode penyelesaian untuk objek yang mewakili koneksi input/output mungkin melakukan transaksi I/O eksplisit untuk memutuskan koneksi sebelum objek tersebut secara permanen dibuang.

Seseorang dapat memutuskan untuk menggunakan metode finalize untuk membebaskan sumber daya seperti penangan file, tetapi itu akan menjadi ide yang buruk. Ini karena tidak ada jaminan waktu kapan penyelesaian akan dilakukan, karena itu dipanggil selama pengumpulan sampah, dan waktu GC tidak dapat ditentukan.

Artikel Terkait  Jasa Aplikasi Android Terpercaya dan Terbaik

9. Concurrent Modification Exception

Pengecualian ini terjadi saat koleksi diubah saat melakukan iterasi menggunakan metode selain yang disediakan oleh objek iterator. Misalnya, kami memiliki daftar hat dan kami ingin menghapus semua hat yang memiliki ear flaps:

List<IHat> hats = new ArrayList<>();
hats.add(new Ushanka()); // that one has ear flaps
hats.add(new Fedora());
hats.add(new Sombrero());
for (IHat hat : hats) {
if (hat.hasEarFlaps()) {
hats.remove(hat);
}
}

Jika Anda menjalankan kode ini, “ConcurrentModificationException” akan dimunculkan karena kode mengubah koleksi sambil mengulanginya. Exception yang sama dapat terjadi jika salah satu dari beberapa utas yang bekerja dengan daftar yang sama mencoba mengubah koleksi sementara yang lain mengulanginya.

Modifikasi koleksi secara bersamaan di beberapa utas adalah hal yang wajar, tetapi harus diperlakukan dengan alat biasa dari kotak alat pemrograman bersamaan seperti kunci sinkronisasi, koleksi khusus yang diadopsi untuk modifikasi bersamaan, dan lain-lain. Ada perbedaan tentang bagaimana masalah Java ini dapat diselesaikan dalam kasus single threaded dan kasus multithread.

10. Menggunakan Raw Type Daripada Yang Parameterized

Raw Type menurut spesifikasi Java, adalah tipe yang tidak diparameterisasi, atau anggota kelas R non-statis yang tidak diwarisi dari superclass atau superinterface R.Tidak ada alternatif untuk tipe mentah sampai tipe generik diperkenalkan di Java.

Hal ini mendukung pemrograman generik sejak versi 1.5, dan obat generik tidak diragukan lagi merupakan peningkatan yang signifikan. Namun, karena alasan kompatibilitas ke belakang, sebuah jebakan telah ditinggalkan yang berpotensi merusak sistem tipe. Perhatikan contoh berikut ini:

List listOfNumbers = new ArrayList();
listOfNumbers.add(10);
listOfNumbers.add("Twenty");
listOfNumbers.forEach(n -> System.out.println((int) n * 2));

Di sini kami memiliki daftar angka yang didefinisikan sebagai ArrayList mentah. Karena tipenya tidak ditentukan dengan parameter type, kita dapat menambahkan objek apa pun ke dalamnya. Tetapi di baris terakhir kami memasukkan elemen ke int, menggandakannya, dan mencetak angka yang digandakan ke output standar.

Kode ini akan dikompilasi tanpa kesalahan, tetapi setelah dijalankan akan memunculkan pengecualian waktu proses karena kami mencoba mentransmisikan string ke integer. Jelas, sistem tipe tidak dapat membantu kita menulis kode aman jika kita menyembunyikan informasi yang diperlukan darinya. Untuk memperbaiki masalah ini, kita perlu menentukan jenis objek yang akan kita simpan dalam koleksi:

List<Integer> listOfNumbers = new ArrayList<>();
listOfNumbers.add(10);
listOfNumbers.add("Twenty");
listOfNumbers.forEach(n -> System.out.println((int) n * 2));

Perbedaan dari yang asli hanya pada baris yang mendefinisikan collection:

List<Integer> listOfNumbers = new ArrayList<>();

Kode tetap tidak dapat dikompilasi karena saat mencoba menambahkan string ke dalam koleksi yang diharapkan hanya menyimpan bilangan bulat. Kompilator akan menunjukkan kesalahan dan menunjuk ke baris di mana kita mencoba menambahkan string “Twenty” ke daftar.

Hal ini adalah cara yang bagus untuk membuat parameter jenis generik. Dengan begitu, compiler dapat melakukan semua kemungkinan pemeriksaan jenis, dan kemungkinan pengecualian waktu proses yang disebabkan oleh ketidakkonsistenan sistem jenis dapat diminimalkan.

Demikianlah kumpulan kesalahan programmer Java pemula yang dapat kami rangkum untuk Anda pelajari. Seperti biasa, pengetahuan, praktik, dan banyak-banyak membaca adalah cara terbaik untuk menghindari dan mengatasi kesalahan aplikasi. Jadi kenalilah dengan baik platform yang akan Anda gunakan sebelum menulis program Anda sendiri.


Jasa Pembuatan Aplikasi, Website dan Internet Marketing | PT APPKEY
PT APPKEY adalah perusahaan IT yang khusus membuat aplikasi Android, iOS dan mengembangkan sistem website. Kami juga memiliki pengetahuan dan wawasan dalam menjalankan pemasaran online sehingga diharapkan dapat membantu menyelesaikan permasalahan Anda.

Jasa Pembuatan Aplikasi

Jasa Pembuatan Website

Jasa Pembuatan Paket Aplikasi

Jasa Pembuatan Internet Marketing

Subscribe Sekarang

Dapatkan beragam informasi menarik tentang Website, Aplikasi, Desain, Video dan API langsung melalui email Anda. Subscribe sekarang dan terus belajar bersama kami!

Kategori

Blog Post Ranking 10

Rekomendasi 10 Aplikasi Pembuat Animasi 3D Terbaik. Ayo Cek!

Apakah Anda bercita-cita untuk menjadi seorang animator profesional? Belajar membuat animasi kini sudah menjadi hal mudah yang bisa dilakukan...

Cara Mudah Menambahkan Lokasi Alamat Bisnis Anda di Google Maps

Saat ingin hunting tempat makan atau tempat nongkrong terbaru, tak jarang beberapa dari Anda biasanya mendapatkan informasi terkini melalui...

Proses Komunikasi: Encoding dan Decoding

Jika kita dapat melihat percakapan antar komputer, mungkin akan terlihat seperti ini: "010110111011101011010010110". Bahasa ini disebut dengan biner, encoding...

Cara Membuat Aplikasi di Playstore dengan Mudah

Membuat aplikasi di Playstore bisa Anda lakukan dengan mudah. Terdapat beberapa situs yang bisa membantu Anda untuk membuat aplikasi...

Metadata Adalah? Fungsi dan Jenis-Jenis Metadata

Pernah mendengar istilah metadata? Mungkin, kita sering mendengar istilah metadata. Tetapi, banyak dari kita yang belum tahu arti dari metadata...

Pengertian Internet & Dampak Positif dan Negatif Internet

Internet mungkin bukan sesuatu yang asing lagi, sebab semua kalangan pasti tahu apa itu internet. Hanya saja jika ditanya...

Domain Google? Apa Bedanya Dengan Domain Biasa?

Saat memutuskan untuk membuat website menjadi salah satu bentuk media digital marketing Anda dalam bersaing di zaman digital ini,...

7 Aplikasi Membuat Aplikasi Android Secara Offline

Aplikasi membuat aplikasi android saat ini banyak dicari penekun IT untuk membuat aplikasi Android secara offline tanpa harus menggunakan...

10 Aplikasi Coding Android Terbaik

Ketersediaan aplikasi coding Android memang banyak dicari oleh orang-orang yang sedang atau akan memulai untuk membuat aplikasi android. Jika...

Looping Adalah Algoritma Perulangan: Berikut Contohnya

Jika anda sudah mendalami atau sedang mendalami dunia pemrograman terdapat sebuah konsep yang dapat memudahkan anda dalam menyusun struktur...

Bisnis

Online Service

Peluang Bisnis

Model Bisnis

Entrepreneurship

Uang

Ketrampilan

Outsourcing

Monetize

Pemasaran

SEO

Internet Marketing

Dasar Pemasaran

Strategi Pemasaran

Situs Web Analitik

Iklan

Teknologi

Teknologi Terbaru

AI

Komputer

Jaringan

Paling Sering dibaca
Mungkin Anda Menyukainya