CodeGym/Blog Java/rawak/Peraturan Pengekodan: Kuasa Nama Betul, Komen Baik dan Bu...
John Squirrels
Tahap
San Francisco

Peraturan Pengekodan: Kuasa Nama Betul, Komen Baik dan Buruk

Diterbitkan dalam kumpulan
Peraturan Pengekodan: Kuasa Nama Betul, Komen Baik dan Buruk - 1Berapa kerapkah anda terpaksa menggali kod orang lain? Daripada dua jam, anda mungkin menghabiskan dua hari untuk memahami logik apa yang berlaku. Perkara yang lucu ialah bagi orang yang menulis kod itu, semuanya jelas dan telus sepenuhnya. Ini tidak menghairankan: selepas semua, kod yang sempurna adalah konsep yang sangat kabur, kerana setiap pembangun mempunyai visi sendiri tentang dunia dan kod itu juga. Lebih daripada sekali saya berada dalam situasi apabila saya dan rakan sekerja melihat kod yang sama dan mempunyai pendapat berbeza tentang ketepatan dan kebersihannya.Peraturan pengekodan: kuasa nama yang betul, komen baik dan buruk - 2Kedengaran biasa, bukan? Namun, terdapat beberapa prinsip yang telah diuji masa yang harus dipatuhi. Pada akhirnya, mereka akan berfaedah untuk anda, kerana jika anda meninggalkan kod anda dalam keadaan di mana anda sendiri ingin menerimanya, maka dunia akan menjadi lebih bahagia dan lebih bersih. Dalam artikel kami sebelum ini(atau lebih tepat, panduan kecil) tentang peraturan pengekodan, kami mendapat sedikit rasa cadangan untuk menulis sistem secara keseluruhan dan bahagian konstituennya, seperti objek, antara muka, kelas, kaedah dan pembolehubah. Dalam artikel yang sama, saya secara santai menyebut penamaan elemen tertentu yang betul. Saya ingin bercakap tentang perkara ini hari ini, kerana nama yang betul menjadikan kod berkali-kali lebih mudah dibaca. Kami akan menutup topik kod yang betul dengan beberapa refleksi, contoh kecil ulasan dalam kod, dan pertimbangan sama ada ini bagus atau tidak begitu baik. Baiklah, mari kita mulakan.

Nama yang betul

Nama yang betul meningkatkan kebolehbacaan kod, sekali gus mengurangkan masa yang diperlukan untuk membiasakan diri anda dengan kod, kerana menggunakan kaedah adalah lebih mudah apabila namanya secara kasar menggambarkan fungsinya. Segala-galanya dalam kod terdiri daripada nama (pembolehubah, kaedah, kelas, objek, fail, dll.), jadi perkara ini menjadi sangat penting apabila mencipta kod yang betul dan bersih. Berdasarkan perkara di atas, nama harus menyampaikan makna, contohnya, mengapa pembolehubah itu wujud, apa yang dilakukannya, dan bagaimana ia digunakan. Saya akan perhatikan lebih daripada sekali bahawa komen terbaik untuk pembolehubah adalah untuk memberikannya nama yang baik.Peraturan pengekodan: kuasa nama yang betul, komen baik dan buruk - 3

dari siri TV "Sherlock" (2010-2017)

Menamakan antara muka

Antara muka biasanya mempunyai nama yang bermula dengan huruf besar dan ditulis dalam CamelCase. Apabila menulis antara muka, ia pernah dianggap sebagai amalan yang baik untuk menambah awalan "I" untuk menetapkannya sebagai antara muka (contohnya, IUserService), tetapi itu kelihatan agak hodoh dan mengganggu. Dalam kes sedemikian, adalah lebih baik untuk meninggalkan awalan (UserService) dan menambah "Impl" sebagai akhiran kepada nama pelaksanaannya (cth. UserServiceImpl). Atau mungkin, sebagai pilihan terakhir, tambahkan awalan "C" pada nama pelaksanaan (cth CUserService).

Nama kelas

Sama seperti antara muka, nama kelas menggunakan huruf besar dan menggunakan CamelCase. Tidak kira jika kita menghadapi kiamat zombi, tidak kira jika penghujungnya sudah dekat — jangan sekali-kali, jangan sekali-kali nama kelas menjadi kata kerja! Nama kelas dan objek mestilah kata nama atau kata nama majmuk (UserController, UserDetails, UserAccount, dan sebagainya). Anda tidak sepatutnya meletakkan singkatan aplikasi pada hujung nama setiap kelas, kerana itu hanya akan menambah kerumitan yang tidak perlu. Sebagai contoh, jika kami mempunyai aplikasi Migrasi Data Pengguna, maka sila jangan tambahkan "UDM" pada setiap kelas, iaitu UDMUserDetails, UDMUserAccount, UDMUserController.

Nama kaedah

Biasanya, nama kaedah bermula dengan huruf kecil, tetapi mereka juga menggunakan gaya kes unta (camelCase). Di atas, kami berkata bahawa nama kelas tidak boleh menjadi kata kerja. Di sini keadaannya adalah sebaliknya: nama kaedah mestilah kata kerja atau frasa kata kerja: findUserById, findAllUsers, createUser dan sebagainya. Apabila mencipta kaedah (serta pembolehubah dan kelas), jadi gunakan konvensyen penamaan yang konsisten untuk mengelakkan kekeliruan. Sebagai contoh, untuk mencari pengguna, kaedah boleh dinamakan getUserById atau findUserById. Dan satu lagi: jangan gunakan jenaka dalam nama kaedah, kerana orang lain mungkin tidak memahami jenaka itu. Akibatnya, mereka mungkin gagal memahami apa yang dilakukan oleh kaedah itu.

Nama boleh ubah

Dalam kebanyakan kes, nama pembolehubah bermula dengan huruf kecil dan juga menggunakan camelCase, kecuali apabila pembolehubah ialah pemalar global. Dalam kes sedemikian, semua huruf nama ditulis dalam huruf besar dan perkataan dipisahkan dengan garis bawah ("_"). Untuk kemudahan, anda boleh menggunakan konteks yang bermakna apabila menamakan pembolehubah. Dalam erti kata lain, apabila pembolehubah wujud sebagai sebahagian daripada sesuatu yang lebih besar, contohnya, firstName, lastName atau status. Dalam kes sedemikian, anda boleh menambah awalan yang menunjukkan objek yang menjadi milik pembolehubah ini. Contohnya: userFirstName, userLastName, userStatus. Anda juga harus mengelakkan nama yang serupa untuk pembolehubah apabila ia mempunyai makna yang sama sekali berbeza. Berikut ialah beberapa antonim yang sering ditemui digunakan dalam nama pembolehubah:
  • mula/akhir
  • pertama terakhir
  • dikunci/dikunci
  • min/maks
  • seterusnya/sebelumnya
  • lama/baru
  • dibuka/ditutup
  • nampak/tak nampak
  • sumber/sasaran
  • sumber/destinasi
  • atas bawah

Nama pembolehubah pendek

Apabila kita mempunyai pembolehubah seperti x atau n atau sesuatu seperti itu, kita tidak segera melihat niat orang yang menulis kod tersebut. Ia tidak jelas apa yang n buat. Memikirkan perkara itu memerlukan renungan yang lebih teliti (dan ini bermakna masa, masa, masa). Sebagai contoh, katakan kita mempunyai medan yang mewakili id ​​pengguna yang bertanggungjawab. Daripada beberapa nama pembolehubah seperti x atau hanya id, kami akan menamakan pembolehubah ini "responsibleUserId", yang dengan serta-merta meningkatkan kebolehbacaan dan kandungan maklumat. Yang berkata, nama pendek seperti n mempunyai tempat sebagai pembolehubah tempatan dalam kaedah kecil, di mana blok kod yang melibatkan pembolehubah ini hanya beberapa baris panjang, dan nama kaedah menggambarkan dengan sempurna apa yang berlaku di sana. Melihat pembolehubah sedemikian, pembangun memahami bahawa ia adalah kepentingan kedua dan mempunyai skop yang sangat terhad. Akibatnya, skop mempunyai pergantungan tertentu pada panjang nama pembolehubah: semakin panjang nama, semakin global pembolehubah dan sebaliknya. Sebagai contoh, berikut ialah kaedah untuk mencari pengguna yang terakhir disimpan mengikut tarikh:
public User findLastUser() {
   return findAllUsers().stream()
           .sorted((x, y) -> -x.getCreatedDate().compareTo(y.getCreatedDate()))
           .findFirst()
           .orElseThrow(() -> new ResourceNotFoundException("No user exists"));
}
Di sini kita menggunakan pembolehubah bernama pendek x dan y untuk mengisih strim, dan kemudian kita melupakannya.

Panjang optimum

Mari kita teruskan dengan topik panjang nama. Panjang nama optimum adalah antara n dan maximumNumberOfUsersInTheCurrentGroup. Dalam erti kata lain, nama pendek mengalami kekurangan makna, manakala nama yang terlalu panjang memanjangkan program tanpa menambah kebolehbacaan, dan kami terlalu malas untuk menulisnya setiap kali. Selain daripada kes yang diterangkan di atas untuk pembolehubah dengan nama pendek seperti n, anda harus berpegang pada panjang kira-kira 8-16 aksara. Ini bukan peraturan yang ketat, hanya garis panduan.

Perbezaan kecil

Saya tidak boleh gagal untuk menyebut perbezaan halus dalam nama. Ini juga merupakan amalan buruk, kerana perbezaan ini boleh mengelirukan atau memerlukan banyak masa tambahan untuk melihatnya. Sebagai contoh, perbezaan antara InvalidDataAccessApiUsageException dan InvalidDataAccessResourceUsageException sukar untuk dikesan sepintas lalu. Kekeliruan juga sering timbul apabila menggunakan huruf kecil L dan O, kerana mereka boleh dengan mudah disalah anggap sebagai 1 dan 0. Dalam sesetengah fon perbezaannya lebih jelas, dalam sesetengahnya ia kurang.

Erti yang

Kita perlu menjadikan nama bermakna, tetapi tidak mencipta kekaburan melalui sinonim, kerana, sebagai contoh, UserData dan UserInfo sebenarnya mempunyai makna yang sama. Dalam kes ini, kita perlu menggali lebih dalam ke dalam kod untuk memahami objek tertentu yang kita perlukan. Elakkan perkataan yang tidak menyampaikan maklumat yang berguna. Sebagai contoh, dalam firstNameString, mengapa kita memerlukan perkataan String? Bolehkah ini benar-benar objek Tarikh? Sudah tentu tidak. Jadi, kami hanya menggunakan firstName. Saya juga ingin menyebut pembolehubah boolean. Sebagai contoh, ambil boolean bernama flagDeleted. Perkataan bendera tidak mempunyai makna. Adalah lebih munasabah untuk memanggilnya sebagai Dipadam.

Pemsalah maklumat

Saya juga ingin mengatakan beberapa perkataan tentang konvensyen penamaan yang salah. Katakan kita mempunyai pembolehubah bernama userActivityList, tetapi bukannya sebagai Senarai, objek ini ialah beberapa jenis bekas lain atau objek storan tersuai. Ini boleh mengelirukan pengaturcara biasa: lebih baik memanggilnya seperti userActivityGroup atau userActivities.

Cari

Salah satu kelemahan nama pendek dan ringkas ialah ia sukar dicari dalam badan besar kod — Manakah yang lebih mudah dicari: "nama" atau "NAME_FOR_DEFAULT_USER"? Pilihan kedua, sudah tentu. Kita harus mengelakkan perkataan (huruf) yang sering ditemui dalam nama, kerana ia hanya akan meningkatkan bilangan fail yang sepadan semasa carian, yang tidak baik. Saya ingin mengingatkan anda bahawa pengaturcara menghabiskan lebih banyak masa membaca kod daripada menulisnya, jadi bijaklah menamakan elemen aplikasi anda. Tetapi bagaimana jika nama yang baik tidak dijumpai? Bagaimana jika nama kaedah tidak menerangkan fungsinya dengan baik? Di sinilah komen memasuki peringkat.

Komen

Peraturan pengekodan: kuasa nama yang betul, komen baik dan buruk - 4Tiada apa-apa yang lebih baik daripada ulasan yang berkaitan, tetapi tiada apa-apa yang mengacaukan modul seperti komen kosong, lapuk atau palsu. Mereka boleh menjadi pedang bermata dua, bukan? Namun, anda tidak seharusnya menganggap komen sebagai baik, sebaliknya sebagai kejahatan yang lebih kecil. Lagipun, komen pada asasnya adalah cara untuk mengimbangi pemikiran yang tidak jelas dalam kod. Sebagai contoh, kami menggunakannya untuk menyampaikan intipati sesuatu kaedah, jika kaedah itu sendiri ternyata terlalu mengelirukan. Dalam keadaan ini, adalah lebih baik untuk memfaktorkan semula kod dengan betul daripada menulis nota deskriptif. Semakin lama ulasan, semakin buruk ulasan itu, kerana kod cenderung berkembang dan berkembang, tetapi ulasan mungkin tetap sama. Semakin banyak masa berlalu sejak ulasan dibuat, semakin dipersoalkan. Komen yang tidak tepat adalah lebih teruk daripada tiada ulasan sama sekali, kerana ia mengelirukan dan memperdaya, memberikan jangkaan palsu. Dan walaupun kita mempunyai kod yang sangat rumit, kita harus menulis semula dan bukannya mengulasnya.

Jenis komen

  • Komen undang-undang — Komen pada permulaan setiap fail sumber atas sebab undang-undang, contohnya:

    * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
    * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.

  • Komen bermaklumat — Komen yang mewakili penjelasan kod (menyediakan maklumat tambahan atau menghuraikan niat bahagian kod yang diberikan).

    Sebagai contoh:

    /*
    * Combines the user from the database with the one passed for updating
    * When a field in requestUser is empty, it is filled with old data from foundUser
    */
    private User mergeUser(User requestUser, User foundUser) {
           return new User(
           foundUser.getId(),
           requestUser.getFirstName() == null ? requestUser.getFirstName() : foundUser.getFirstName(),
           requestUser.getMiddleName() == null ? requestUser.getMiddleName() : foundUser.getMiddleName(),
           requestUser.getLastName() == null ? requestUser.getLastName() : foundUser.getLastName(),
           requestUser.getAge() == null ? requestUser.getAge() : foundUser.getAge()
           );
           }

    Dalam kes ini, anda boleh melakukannya tanpa komen, kerana nama kaedah dan parameternya, ditambah dengan fungsi yang sangat telus, menggambarkan diri mereka dengan baik.

  • Komen amaran — Komen bertujuan untuk memberi amaran kepada pembangun lain tentang akibat yang tidak diingini daripada tindakan (contohnya, memberi amaran kepada mereka tentang sebab ujian ditandakan sebagai @Abaikan):

    // Takes too long to run
    // Don't run if you don't have a lot of time
    @Ignore
    @Test
    public void someIntegrationTest() {
           ……
           }

  • TODO — Komen yang merupakan nota tentang sesuatu yang perlu dilakukan pada masa hadapan yang tetapi atas sebab tertentu tidak boleh dilakukan sekarang. Ini adalah amalan yang baik, tetapi ulasan sedemikian harus dikaji secara berkala untuk menghapuskan yang tidak berkaitan dan mengelakkan kekacauan.

    Contohnya ialah:

    // TODO: Add a check for the current user ID (when the security context is created)
    
    @Override
    public Resource downloadFile(File file) {
           return fileManager.download(file);
           }

    Di sini kami perhatikan hakikat bahawa kami perlu menambah perbandingan pengguna yang melakukan operasi muat turun (ID yang kami akan ekstrak daripada konteks keselamatan) dengan pengguna yang melakukan operasi simpan.

  • Mengukuhkan ulasan — Komen yang menekankan kepentingan keadaan yang pada pandangan pertama mungkin kelihatan tidak penting.

    Sebagai contoh, pertimbangkan sekeping kaedah yang mengisi pangkalan data ujian dengan beberapa skrip:

    Stream.of(IOUtils.resourceToString("/fill-scripts/" + x, StandardCharsets.UTF_8)
           .trim()
           .split(";"))
           .forEach(jdbcTemplate::update);
    // The trim() call is very important. It removes possible spaces at the end of the script
    // so that when we read and split into separate requests, we don't end up with empty ones

  • Komen Javadoc — Komen yang menerangkan API untuk fungsi tertentu. Mungkin terdapat ulasan yang paling berguna, kerana API yang didokumenkan adalah lebih mudah untuk digunakan. Yang berkata, mereka juga boleh ketinggalan zaman seperti mana-mana jenis ulasan lain. Jadi, jangan lupa bahawa sumbangan utama kepada dokumentasi dibuat bukan oleh komen, tetapi oleh kod yang baik.

    Berikut ialah contoh kaedah yang agak biasa untuk mengemas kini pengguna:

    /**
    * Updates the passed fields for a user based on its id.
         *
    * @param id id of the user to be updated
    * @param user user with populated fields for updating
    * @return updated user
    */
           User update(Long id, User user);

Komen buruk

  • komen bergumam — Komen yang biasanya ditulis dengan tergesa-gesa dan maknanya hanya boleh difahami oleh pembangun yang menulisnya, kerana hanya dia yang melihat situasi bernuansa yang dirujuk oleh ulasan itu.

    Pertimbangkan contoh ini:

    public void configureSomeSystem() {
           try{
           String configPath = filesLocation.concat("/").concat(CONFIGURATION_FILE);
           FileInputStream stream = new FileInputStream(configPath);
           } catch (FileNotFoundException e) {
           // If there is no configuration file, the default configuration is loaded
          }
    }

    Siapa yang memuatkan tetapan ini? Adakah mereka sudah dimuatkan? Adakah kaedah ini sepatutnya menangkap pengecualian dan memuatkan tetapan lalai? Terlalu banyak soalan timbul yang hanya boleh dijawab dengan menyelidiki penyiasatan bahagian lain sistem.

  • Komen berlebihan — Komen yang tidak membawa apa-apa beban semantik, kerana perkara yang berlaku dalam bahagian tertentu kod adalah jelas. Dalam erti kata lain, ulasan tidak lebih mudah dibaca daripada kod.

    Mari lihat contoh:

    public class JdbcConnection{
    public class JdbcConnection{
       /**
        * The logger associated with the current class
        */
       private Logger log = Logger.getLogger(JdbcConnection.class.getName());
    
       /**
        * Creates and returns a connection using the input parameters
        */
       public static Connection buildConnection(String url, String login, String password, String driver) throws Exception {
           Class.forName(driver);
           connection = DriverManager.getConnection(url, login, password);
           log.info("Created connection with db");
           return connection;
       }

    Apa gunanya komen sebegitu? Semua yang mereka jelaskan sudah jelas.

  • Komen yang tidak boleh dipercayai — Komen yang tidak benar dan hanya mengelirukan (maklumat salah). Sebagai contoh, inilah satu.

    /**
    * Helper method. Closes the connection with the scanner if isNotUsing is true
    */
    private void scanClose(Scanner scan, boolean isNotUsing) throws Exception {
       if (!isNotUsing) {
           throw new Exception("The scanner is still in use");
       } scan.close();
    }

    Apa yang salah dengan komen ini? Fakta bahawa ia terletak kepada kami sedikit, kerana sambungan ditutup jika isNotUsing adalah palsu, bukan sebaliknya, seperti yang dimaklumkan oleh ulasan itu.

  • Komen wajib — Komen yang dianggap wajib (cth ulasan Javadoc), tetapi sebenarnya kadangkala bertimbun secara berlebihan dan tidak boleh dipercayai serta tidak perlu (anda perlu memikirkan sama ada ulasan ini sebenarnya diperlukan).

  • Contoh:

    /**
    * Create a user based on the parameters
    * @param firstName first name of the created user
    * @param middleName middle name of the created user
    * @param lastName last name of the created user
    * @param age age of the created user
    * @param address address of the created user
    * @return user that was created
    */
    User createNewUser(String firstName, String middleName, String lastName, String age, String address);

    Adakah anda dapat memahami apa yang dilakukan oleh kaedah tanpa ulasan ini? Kemungkinan besar, ya, jadi komen menjadi sia-sia di sini.

  • Komen log — Komen yang kadangkala ditambahkan pada permulaan modul setiap kali ia diedit (sesuatu seperti log perubahan).

    /**
    * Records kept since January 9, 2020;
    **********************************************************************
    * 9 Jan 2020: Providing a database connection using JDBC Connection;
    * 15 Jan 2020: Adding DAO-level interfaces for working with the database;
    * 23 Jan 2020: Adding integration tests for the database;
    * 28 Jan 2020: Implementation of DAO-level interfaces;
    * 1 Feb 2020: Development of interfaces for services,
    * in accordance with the requirements specified in user stories;
    * 16 Feb 2020: Implementation of service interfaces
    * (implementation of business logic related to the work of the database);
    * 25 Feb 2020: Adding tests for services;
    * 8 Mar 2020: Celebration of International Women's Day (Terry is drunk again);
    * 21 Mar 2020: Refactoring the service layer;
    */

    Pendekatan ini pernah dibenarkan, tetapi dengan kemunculan sistem kawalan versi (contohnya, Git), ia menjadi kekacauan dan kerumitan kod yang tidak perlu.

  • Komen pengarang — Komen yang tujuannya adalah untuk menunjukkan orang yang menulis kod itu, supaya anda boleh menghubunginya dan membincangkan bagaimana, apa dan mengapa, cth:

    * @author Bender Bending

    Sekali lagi, sistem kawalan versi mengingati dengan tepat siapa yang menambah sedikit kod dan bila, jadi pendekatan ini tidak diperlukan.

  • Kod yang diulas — Kod yang diulas untuk satu sebab atau yang lain. Ini adalah salah satu tabiat yang paling teruk, kerana apa yang berlaku ialah anda mengulas sesuatu dan melupakannya, dan kemudian pembangun lain tidak mempunyai keberanian untuk memadamkannya (lagipun, bagaimana jika ia sesuatu yang berharga?).

    //    public void someMethod(SomeObject obj) {
    //    .....
    //    }

    Akibatnya, kod yang dikomentari terkumpul seperti sampah. Anda tidak boleh meninggalkan kod sedemikian. Jika anda benar-benar memerlukannya, jangan lupa tentang sistem kawalan versi.

  • Komen tidak jelas — Komen yang menerangkan sesuatu dengan cara yang terlalu rumit.

    /*
        * Start with an array large enough to store
        * all the data bytes (plus filter bytes) with a cushion, plus 300 bytes
        * for header data
        */
    this.dataBytes = new byte[(this.size * (this.deep + 1) * 2)+300];

    Komen harus menerangkan kod. Ia tidak sepatutnya memerlukan penjelasan. Jadi apa yang salah di sini? Apakah "bait penapis"? Apakah maksud "+ 1" itu? Kenapa betul-betul 300?

Jika anda sudah memutuskan untuk menulis ulasan, berikut ialah beberapa petua:
  1. Gunakan gaya yang mudah diselenggara: mengekalkan gaya yang terlalu mewah dan eksotik menjengkelkan dan memakan masa.
  2. Jangan gunakan ulasan hujung baris yang merujuk kepada satu baris: hasilnya ialah longgokan ulasan yang besar. Apatah lagi, sukar untuk memikirkan komen yang bermakna untuk setiap baris.
  3. Apabila anda mengarang ulasan, cuba jawab soalan "mengapa", bukan "bagaimana."
  4. Elakkan maklumat ringkas. Seperti yang saya katakan di atas, kita tidak memerlukan penjelasan untuk ulasan: komen itu sendiri adalah penjelasan.
  5. Anda boleh menggunakan ulasan untuk membuat nota unit dan julat nilai.
  6. Letakkan ulasan berdekatan dengan kod yang mereka huraikan.
Akhir sekali, saya masih ingin mengingatkan anda bahawa ulasan terbaik ialah tiada ulasan, sebaliknya penggunaan penamaan yang mahir sepanjang permohonan anda. Sebagai peraturan, kebanyakan masa kami akan bekerja dengan kod sedia ada, mengekalkan dan memanjangkannya. Ia adalah lebih mudah apabila kod ini mudah dibaca dan difahami, kerana kod buruk adalah halangan. Ia seperti melempar sepana dalam kerja-kerja, dan tergesa-gesa adalah teman setianya. Dan semakin banyak kod buruk yang kita ada, semakin banyak prestasi menurun. Ini bermakna kita perlu memfaktorkan semula dari semasa ke semasa. Tetapi jika dari awal anda cuba menulis kod yang tidak akan menyebabkan pembangun seterusnya mahu mencari dan membunuh anda, maka anda tidak perlu memfaktorkannya semula dengan kerap. Tetapi ia masih perlu, kerana keadaan dan keperluan produk sentiasa berubah dengan penambahan kebergantungan dan sambungan baharu. Nah, saya rasa itu sahaja untuk saya hari ini. Terima kasih kepada semua yang membaca sejauh ini :)
Komen
  • Popular
  • Baru
  • Tua
Anda mesti log masuk untuk meninggalkan ulasan
Halaman ini tidak mempunyai sebarang ulasan lagi