Panduan Gaya Google di C ++. Bagian 10

Bagian 1. Pendahuluan
...
Bagian 9. Komentar
Bagian 10. Memformat
...


Artikel ini adalah terjemahan dari bagian panduan gaya Google di C ++ ke bahasa Rusia.
Artikel asli (garpu pada github), terjemahan yang diperbarui .

Memformat


Gaya pengkodean dan pemformatan adalah arbitrer, tetapi proyek ini jauh lebih mudah dikelola jika semua orang mengikuti gaya yang sama. Meskipun seseorang mungkin tidak setuju dengan semua aturan (atau menggunakan apa yang mereka gunakan), sangat penting bahwa setiap orang mengikuti aturan yang sama agar mudah membaca dan memahami kode orang lain.
Untuk pemformatan yang benar, kami membuat file pengaturan untuk emacs .

Panjang garis


Dianjurkan untuk membatasi panjang baris kode hingga 80 karakter.
Aturan ini agak kontroversial, tetapi sebagian besar kode yang ada menganut prinsip ini, dan kami juga mendukungnya.

Bagi
penganut aturan, mereka mengatakan bahwa garis yang lebih panjang tidak diperlukan, dan terus-menerus menyesuaikan ukuran jendela itu melelahkan. Selain itu, beberapa jendela tempat dengan kode di sebelah satu sama lain dan tidak dapat secara sewenang-wenang meningkatkan lebar jendela. Pada saat yang sama, lebar 80 karakter adalah standar historis, mengapa mengubahnya? ..

Terhadap

pihak lain mengklaim bahwa garis panjang dapat meningkatkan pembacaan kode. 80 karakter adalah peninggalan mainframe 1960-an. Layar modern mungkin menunjukkan garis yang lebih panjang.

Vonis

80 karakter adalah maksimum.

String dapat melebihi batas 80 karakter jika:

  • . , URL-, 80 .
  • /, 80 . , .
  • include.
  • using

-ASCII


Karakter non-ASCII harus digunakan sesering mungkin, pengodeannya harus UTF-8.
Anda tidak harus memiliki string hardcode untuk ditampilkan kepada pengguna (bahkan bahasa Inggris), jadi karakter Non-ASCII harus jarang. Namun, dalam beberapa kasus diperbolehkan memasukkan kata-kata tersebut ke dalam kode. Misalnya, jika kode mem-parsing file data (dengan penyandian non-Inggris), dimungkinkan untuk memasukkan kata pembatas nasional dalam kode. Dalam kasus yang lebih umum, kode uji unit dapat berisi string nasional. Dalam kasus ini, pengkodean UTF-8 harus digunakan, sebagai itu dipahami oleh sebagian besar utilitas (yang mengerti tidak hanya ASCII).

Hex juga valid, terutama jika itu meningkatkan keterbacaan. Misalnya, "\ xEF \ xBB \ xBF" atau u8 "\ uFEFF"- Ruang tak terpisahkan dengan panjang nol di Unicode, dan yang seharusnya tidak ditampilkan dalam teks UTF-8 yang benar.

Gunakan awalan u8 sehingga literal seperti \ uXXXX dikodekan dalam UTF-8. Jangan menggunakannya untuk baris yang mengandung karakter non-ASCII yang sudah dikodekan dalam UTF-8 - Anda bisa mendapatkan teks yang canggung jika kompilator tidak mengenali kode sumber sebagai UTF-8.

Hindari penggunaan karakter C ++ 11 char16_t dan char32_t sejak mereka diperlukan untuk jalur non-UTF-8. Untuk alasan yang sama, jangan gunakan wchar_t (kecuali saat bekerja dengan Windows API menggunakan wchar_t ).

Spaces vs. Tabs


Gunakan hanya ruang untuk lekukan. 2 ruang untuk satu indentasi.
Kami menggunakan spasi untuk indentasi. Jangan gunakan tab dalam kode Anda - konfigurasikan editor Anda untuk menyisipkan spasi ketika Anda menekan Tab.

Deklarasi dan definisi fungsi


Cobalah untuk menempatkan jenis nilai kembali, nama fungsi dan parameternya pada satu baris (jika semuanya cocok). Pecah daftar parameter terlalu panjang menjadi garis, seperti argumen dalam panggilan fungsi.

Contoh desain fungsi yang benar:

ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
  DoSomething();
  ...
}

Jika satu baris tidak cukup:

ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
                                             Type par_name3) {
  DoSomething();
  ...
}

atau, jika parameter pertama juga tidak cocok:

ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  //  4 
    Type par_name2,
    Type par_name3) {
  DoSomething();  //  2 
  ...
}

Beberapa catatan:

  • Pilih nama yang bagus untuk opsi.
  • Anda dapat menghilangkan nama parameter jika tidak digunakan dalam definisi fungsi.
  • , , . .
  • .
  • .
  • .
  • . .
  • , , .
  • .
  • .
  • — 2 .
  • Saat mentransfer parameter ke saluran lain, indentasi 4 spasi.

Anda dapat menghilangkan nama parameter yang tidak digunakan jika ini jelas dari konteksnya:

class Foo {
 public:
  Foo(const Foo&) = delete;
  Foo& operator=(const Foo&) = delete;
};

Parameter yang tidak digunakan dengan konteks yang tidak jelas harus dikomentari dalam definisi fungsi:

class Shape {
 public:
  virtual void Rotate(double radians) = 0;
};

class Circle : public Shape {
 public:
  void Rotate(double radians) override;
};

void Circle::Rotate(double /*radians*/) {}

//   -  -     ,
//    .
void Circle::Rotate(double) {}

Cobalah untuk menggunakan atribut dan makro di awal iklan atau definisi fungsi,
hingga jenis nilai pengembalian:
ABSL_MUST_USE_RESULT bool IsOk();

Lambdas


Memformat parameter dan isi ekspresi dengan cara yang sama seperti fungsi biasa, daftar variabel yang ditangkap adalah seperti daftar normal.

Untuk menangkap variabel dengan referensi, jangan beri spasi antara ampersand (&) dan nama variabel.

int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; }

Lambda pendek dapat digunakan secara langsung sebagai argumen untuk suatu fungsi.

std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) {
               return blacklist.find(i) != blacklist.end();
             }),
             digits.end());

Angka titik mengambang


Angka titik-mengambang harus selalu dengan titik desimal dan angka di kedua sisi (bahkan dalam kasus notasi eksponensial). Pendekatan ini akan lebih mudah dibaca: semua angka floating-point akan berada dalam format yang sama, Anda tidak akan bingung dengan integer, dan karakter E e dari notasi eksponensial tidak dapat diambil untuk digit heksadesimal. Ingat bahwa angka dalam notasi eksponensial bukan bilangan bulat.

float f = 1.f;
long double ld = -.5L;
double d = 1248e6;

float f = 1.0f;
float f2 = 1;   //  
long double ld = -0.5L;
double d = 1248.0e6;

Panggilan fungsi


Baik tulis seluruh panggilan fungsi dalam satu baris, atau tempatkan argumen pada baris baru. Dan indentasi dapat berupa argumen pertama, atau 4 spasi. Cobalah untuk meminimalkan jumlah garis, letakkan beberapa argumen di setiap baris.

Format panggilan fungsi:

bool result = DoSomething(argument1, argument2, argument3);

Jika argumen tidak sesuai pada satu baris, maka kami membaginya menjadi beberapa baris dan setiap baris berikutnya selaras dengan argumen pertama. Jangan menambahkan spasi di antara tanda kurung dan argumen:

bool result = DoSomething(averyveryveryverylongargument1,
                          argument2, argument3);


Diperbolehkan menempatkan argumen pada beberapa baris dengan lekukan 4 spasi:
if (...) {
  ...
  ...
  if (...) {
    bool result = DoSomething(
        argument1, argument2,  //  4 
        argument3, argument4);
    ...
  }

Cobalah untuk menempatkan beberapa argumen per baris, mengurangi jumlah baris per panggilan fungsi (jika ini tidak mengganggu keterbacaan). Beberapa orang berpikir bahwa memformat secara ketat pada satu argumen per baris lebih mudah dibaca dan membuat mengedit argumen lebih mudah. Namun, kami fokus terutama pada pembaca kode (bukan mengedit), jadi kami menawarkan sejumlah pendekatan untuk meningkatkan keterbacaan.

Jika beberapa argumen pada baris yang sama menurunkan keterbacaan (karena kompleksitas atau kompleksitas ekspresi), cobalah membuat variabel “berbicara” untuk argumen:

int my_heuristic = scores[x] * y + bases[x];
bool result = DoSomething(my_heuristic, x, y, z);

Atau letakkan argumen kompleks pada baris terpisah dan tambahkan komentar penjelas:

bool result = DoSomething(scores[x] * y + bases[x],  //  
                          x, y, z);

Jika pemanggilan fungsi masih memiliki argumen yang diinginkan untuk ditempatkan pada jalur yang terpisah - letakkan. Suatu solusi harus didasarkan pada peningkatan pembacaan kode.

Argumen terkadang membentuk struktur. Dalam hal ini, format argumen sesuai dengan struktur yang diperlukan:

//     3x3
my_widget.Transform(x1, x2, x3,
                    y1, y2, y3,
                    z1, z2, z3);


Memformat Daftar Inisialisasi


Memformat daftar inisialisasi dengan cara yang sama dengan panggilan fungsi.

Jika daftar dalam tanda kurung mengikuti nama (misalnya, nama tipe atau variabel), format {} seolah-olah itu adalah panggilan fungsi dengan nama itu. Sekalipun tidak ada nama, anggap itu benar, hanya kosong.

//      .
return {foo, bar};
functioncall({foo, bar});
std::pair<int, int> p{foo, bar};

//     .
SomeFunction(
    {"assume a zero-length name before {"},
    some_other_function_parameter);
SomeType variable{
    some, other, values,
    {"assume a zero-length name before {"},
    SomeOtherType{
        "Very long string requiring the surrounding breaks.",
        some, other values},
    SomeOtherType{"Slightly shorter string",
                  some, other, values}};
SomeType variable{
    "This is too long to fit all in one line"};
MyType m = {  // Here, you could also break before {.
    superlongvariablename1,
    superlongvariablename2,
    {short, interior, list},
    {interiorwrappinglist,
     interiorwrappinglist2}};

Kondisi


Cobalah untuk tidak memasukkan spasi di bagian dalam kurung. Tempatkan jika dan lainnya di jalur yang berbeda.

Ada dua pendekatan untuk memformat kondisi. Satu memungkinkan ruang antara tanda kurung dan suatu kondisi, yang lainnya tidak.

Opsi yang disukai tanpa spasi. Pilihan lain juga valid, tetapi konsisten . Jika Anda memodifikasi kode yang ada, gunakan format yang sudah ada dalam kode. Jika Anda menulis kode baru, gunakan format seperti file yang terletak di direktori yang sama atau gunakan format proyek. Jika tidak yakin, jangan tambahkan spasi.

if (condition) {  //    
  ...  //  2 
} else if (...) {  // 'else'      
  ...
} else {
  ...
}

Jika menggunakan format spasi:

if ( condition ) {  //   
  ...  //  2 
} else {  // 'else'      
  ...
}

Perhatikan bahwa bagaimanapun juga harus ada jarak antara jika dan braket pembuka. Anda juga membutuhkan ruang antara braket penutup dan braket keriting (jika ada).

if(condition) {   //  -    'if'
if (condition){   //  -    {
if(condition){    //  

if (condition) {  //   -     'if'   {

Kondisi singkat dapat ditulis pada satu baris jika ini meningkatkan keterbacaan. Gunakan opsi ini hanya jika garis pendek dan kondisinya tidak mengandung bagian lain .

if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();

Jangan gunakan versi singkat jika ada bagian lain :

//  -    ,   'else'
if (x) DoThis();
else DoThat();

Biasanya kawat gigi tidak diperlukan untuk kondisi singkat, tetapi mereka dapat diterima. Kondisi rumit atau kode juga lebih baik dibaca dengan kurung kurawal. Seringkali diperlukan bahwa jika dengan tanda kurung.

if (condition)
  DoSomething();  //  2 

if (condition) {
  DoSomething();  //  2 
}

Dan jika salah satu bagian dari kondisi menggunakan kurung kurawal, berikan juga yang kedua:

//  -    'if',  'else' - 
if (condition) {
  foo;
} else
  bar;

//  -    'else',  'if' - 
if (condition)
  foo;
else {
  bar;
}


//  -     'if'   'else'
if (condition) {
  foo;
} else {
  bar;
}

Loop dan switch


Konstruk switch dapat menggunakan tanda kurung untuk blok. Jelaskan transisi non-sepele antara opsi. Kurung adalah opsional untuk loop ekspresi tunggal. Loop kosong harus menggunakan tubuh kosong di dalam tanda kurung atau melanjutkan .

The kasus blok di saklar dapat menjadi dengan kawat gigi keriting, atau tanpa mereka (pilihan Anda). Jika tanda kurung digunakan, gunakan format yang dijelaskan di bawah ini.

Disarankan untuk beralih ke bagian default di sakelar . Ini tidak perlu saat menggunakan enumerasi, dan kompilator dapat mengeluarkan peringatan jika tidak semua nilai diproses. Jika bagian default tidak boleh dijalankan, maka konfigurasikan sebagai kesalahan. Contohnya:

switch (var) {
  case 0: {  //  2 
    ...      //  4 
    break;
  }
  case 1: {
    ...
    break;
  }
  default: {
    assert(false);
  }
}

Transisi dari satu label ke label berikutnya harus ditandai dengan makro ABSL_FALLTHROUGH_INTENDED; (didefinisikan dalam absl / base / macros.h ).
Tempatkan ABSL_FALLTHROUGH_INTENDED; pada titik di mana transisi akan terjadi. Pengecualian untuk aturan ini adalah label berurutan tanpa kode, dalam hal ini tidak ada yang perlu ditandai.

switch (x) {
  case 41:  //  
  case 43:
    if (dont_be_picky) {
      //    ( )    
      ABSL_FALLTHROUGH_INTENDED;
    } else {
      CloseButNoCigar();
      break;
    }
  case 42:
    DoSomethingSpecial();
    ABSL_FALLTHROUGH_INTENDED;
  default:
    DoSomethingGeneric();
    break;
}

Kurung adalah opsional untuk loop operasi tunggal.

for (int i = 0; i < kSomeNumber; ++i)
  printf("I love you\n");

for (int i = 0; i < kSomeNumber; ++i) {
  printf("I take it back\n");
}

Lingkaran kosong harus ditata baik sebagai sepasang tanda kurung, atau sebagai melanjutkan tanpa tanda kurung. Jangan gunakan tanda titik koma tunggal.

while (condition) {
  //    false
}
for (int i = 0; i < kSomeNumber; ++i) {}  // .      -   
while (condition) continue;  //  - continue     

while (condition);  //  -     do/while

Pointer dan tautan


Sekitar '.' dan '->' jangan menaruh spasi. Operator dereferencing atau menangkap harus tanpa spasi.

Berikut ini adalah contoh pemformatan ekspresi yang tepat dengan pointer dan tautan:

x = *p;
p = &x;
x = r.y;
x = r->y;

catatan:

  • '.' dan '->' digunakan tanpa spasi.
  • Tanda * atau & operator tidak dipisahkan oleh spasi.

Saat mendeklarasikan variabel atau argumen, Anda dapat menempatkan '*' baik pada tipe dan nama:

// ,   *, &
char *c;
const std::string &str;

// ,   *, &
char* c;
const std::string& str;

Cobalah menggunakan gaya tunggal dalam file kode, saat memodifikasi file yang ada, gunakan format yang digunakan.

Itu diperbolehkan untuk mendeklarasikan beberapa variabel dalam satu ekspresi. Namun, jangan gunakan banyak deklarasi dengan pointer atau tautan - ini mungkin disalahpahami.

//  - 
int x, y;

int x, *y;  //  -      &  *
char * c;  //  -     *
const std::string & str;  //  -     &

Ekspresi logis


Jika ekspresi logis sangat panjang (melebihi nilai khas), gunakan pendekatan tunggal untuk memecah ekspresi menjadi garis-garis.

Misalnya, di sini ketika membungkus operator DAN terletak di akhir baris:

if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one) {
  ...
}

Perhatikan bahwa kode ini dibagi (sesuai dengan contoh) sehingga && dan operator DAN menyelesaikan baris. Gaya ini lebih sering digunakan dengan kode Google, meskipun lokasi operator di awal baris juga dapat diterima. Anda juga dapat menambahkan tanda kurung tambahan untuk meningkatkan keterbacaan. Perhatikan bahwa menggunakan operator dalam bentuk tanda baca (seperti && dan ~ ) lebih disukai daripada menggunakan operator dalam bentuk kata dan dan komp .

Nilai pengembalian


Jangan lampirkan pernyataan pengembalian sederhana dalam tanda kurung.

Gunakan tanda kurung sebagai balasan expr; hanya jika Anda menggunakannya dalam ekspresi bentuk x = expr; .

return result;                  //   -  
//  - .    
return (some_long_condition &&
        another_condition);

return (value);                // . ,      var = (value);
return(result);                // . return -   !

Inisialisasi Variabel dan Array


Apa yang akan digunakan: = , () atau
{} adalah pilihan Anda.

Anda dapat memilih antara opsi = ,
() dan {} . Contoh kode berikut ini benar:

int x = 3;
int x(3);
int x{3};
std::string name = "Some Name";
std::string name("Some Name");
std::string name{"Some Name"};

Hati-hati saat menggunakan daftar inisialisasi {...} untuk tipe yang memiliki konstruktor dengan std :: initializer_list .

Compiler akan lebih suka menggunakan konstruktor std :: initializer_list ketika ada daftar di kawat gigi . Perhatikan bahwa kurung kurawal kosong {} adalah kasus khusus dan konstruktor default akan dipanggil (jika tersedia). Untuk secara eksplisit menggunakan konstruktor tanpa std :: initializer_list, gunakan tanda kurung alih-alih kurung kurawal.

std::vector<int> v(100, 1);  //    
std::vector<int> v{100, 1};  //   2- : 100  1


Juga, konstruksi dengan kurung keriting melarang serangkaian transformasi tipe bilangan bulat (transformasi dengan penurunan akurasi). Dan Anda bisa mendapatkan kesalahan kompilasi.

int pi(3.14);  // : pi == 3
int pi{3.14};  //  : "" 

Arahan preprosesor


Tanda # (tanda arahan preprosesor) harus di awal baris.

Bahkan jika arahan preprocessor merujuk pada kode yang disematkan, arahan ditulis dari awal baris.

//  -    
  if (lopsided_score) {
#if DISASTER_PENDING      //  -    
    DropEverything();
# if NOTIFY               //   # - ,   
    NotifyClient();
# endif
#endif
    BackToNormal();
  }

//  -   
  if (lopsided_score) {
    #if DISASTER_PENDING  // ! "#if"     
    DropEverything();
    #endif                // !     "#endif"
    BackToNormal();
  }

Pemformatan kelas


Atur bagian dalam urutan berikut: publik , dilindungi dan pribadi . Lekukan adalah satu ruang.

Format dasar untuk kelas dijelaskan di bawah ini (dengan pengecualian komentar, lihat komentar pada deskripsi kelas):

class MyClass : public OtherClass {
 public:      //  1 
  MyClass();  //  2-  
  explicit MyClass(int var);
  ~MyClass() {}

  void SomeFunction();
  void SomeFunctionThatDoesNothing() {
  }

  void set_some_var(int var) { some_var_ = var; }
  int some_var() const { return some_var_; }

 private:
  bool SomeInternalFunction();

  int some_var_;
  int some_other_var_;
};

Catatan:

  • Nama kelas dasar ditulis pada baris yang sama dengan nama kelas yang diwarisi (tentu saja, dengan mempertimbangkan batas 80 karakter).
  • Kata kunci publik: , dilindungi ,: , dan swasta: harus menjorok satu ruang.
  • Setiap kata kunci ini harus didahului oleh baris kosong (dengan pengecualian pada penyebutan pertama). Juga, di kelas kecil, garis kosong dapat dihilangkan.
  • Jangan tambahkan baris kosong setelah kata kunci ini.
  • Bagian publik harus menjadi yang pertama, di belakangnya terlindungi dan pada akhirnya bagian pribadi .
  • Lihat Prosedur Deklarasi untuk membangun deklarasi di masing-masing bagian ini.

Daftar Inisialisasi Pembuat


Daftar inisialisasi konstruktor dapat berupa satu baris atau beberapa baris dengan 4 spasi indentasi.

Berikut ini adalah format yang benar untuk daftar inisialisasi:

//    
MyClass::MyClass(int var) : some_var_(var) {
  DoSomething();
}

//          ,
//           
MyClass::MyClass(int var)
    : some_var_(var), some_other_var_(var + 1) {
  DoSomething();
}

//     ,     
//     
MyClass::MyClass(int var)
    : some_var_(var),             //  4 
      some_other_var_(var + 1) {  //   
  DoSomething();
}

//     ,       
MyClass::MyClass(int var)
    : some_var_(var) {}

Memformat Ruang nama


Konten namespace diindentasi.

Namespace tidak menambahkan padding. Contohnya:

namespace {

void foo() {  // .   
  ...
}

}  // namespace

Jangan indent di namespace:

namespace {

  // .   ,   
  void foo() {
    ...
  }

}  // namespace

Saat mendeklarasikan ruang nama bersarang, tempatkan setiap deklarasi pada baris terpisah.

namespace foo {
namespace bar {

Kerusakan horizontal


Gunakan kerusakan mendatar yang sesuai. Jangan pernah menambahkan spasi ke akhir baris.

Prinsip-prinsip umum


void f(bool b) {  //       
  ...
int i = 0;  //       
//            .
//    ,      
int x[] = { 0 };
int x[] = {0};

//        
class Foo : public Bar {
 public:
  //  inline-  
  //     (  )
  Foo(int b) : Bar(), baz_(b) {}  //    
  void Reset() { baz_ = 0; }  //      
  ...

Menambahkan spasi pembatas dapat mengganggu penggabungan kode. Oleh karena itu: Jangan menambahkan spasi terpisah ke kode yang ada. Anda dapat menghapus spasi jika Anda telah memodifikasi baris ini. Atau lakukan itu sebagai operasi terpisah (lebih disukai tidak ada yang bekerja dengan kode ini).

Siklus dan kondisi


if (b) {          //        
} else {          //   else
}
while (test) {}   //       
switch (i) {
for (int i = 0; i < 5; ++i) {
//         .   .
//   ,  
switch ( i ) {
if ( test ) {
for ( int i = 0; i < 5; ++i ) {
//         
//          ,   
for ( ; i < 5 ; ++i) {
  ...

//           
for (auto x : counts) {
  ...
}
switch (i) {
  case 1:         //    case  
    ...
  case 2: break;  //    ,   (   )  

Operator


//     
x = 0;

//      ,
//   /   .
//          
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);

//       
x = -5;
++x;
if (x && !y)
  ...

Pola dan cetakan


//       (<  >),
//  <,  >(  
std::vector<std::string> x;
y = static_cast<char*>(x);

//        .       
std::vector<char *> x;

Kerusakan vertikal


Minimalkan pemisahan vertikal.

Ini lebih merupakan prinsip daripada aturan: jangan tambahkan baris kosong tanpa kebutuhan khusus. Secara khusus, letakkan tidak lebih dari 1-2 garis kosong di antara fungsi, jangan mulai fungsi dengan garis kosong, jangan akhiri fungsi dengan garis kosong, dan coba gunakan garis kosong lebih sedikit. Baris kosong dalam blok kode harus berfungsi seperti paragraf dalam novel: pisahkan dua ide secara visual.

Prinsip dasar: semakin banyak kode yang pas pada satu layar, semakin mudah untuk memahami dan melacak urutan eksekusi. Gunakan string kosong hanya untuk memisahkan urutan ini secara visual.

Beberapa catatan bermanfaat tentang baris kosong:

  • Baris kosong di awal atau di akhir fungsi tidak akan meningkatkan keterbacaan.
  • Baris kosong di blockchain if-else dapat meningkatkan keterbacaan.
  • Baris kosong sebelum baris komentar biasanya membantu keterbacaan kode - komentar baru biasanya melibatkan penyelesaian pemikiran lama dan awal ide baru. Dan baris kosong jelas mengisyaratkan itu.

All Articles