Apa yang saya pelajari dari seorang programmer terkemuka


Setahun yang lalu, saya mulai bekerja penuh waktu di Bloomberg. Dan kemudian saya memutuskan untuk menulis artikel ini. Saya berpikir bahwa saya akan penuh dengan ide-ide yang bisa saya buang di atas kertas ketika saatnya tiba. Tetapi dalam sebulan saya menyadari bahwa semuanya tidak akan sesederhana itu: Saya sudah mulai melupakan apa yang saya pelajari. Entah pengetahuan itu diperoleh dengan sangat baik sehingga pikiran saya membuat saya percaya bahwa saya selalu mengetahuinya, atau mereka hanya terbang keluar dari kepala saya. 1

Ini adalah salah satu alasan saya mulai menulis buku harian. Setiap hari, memasuki situasi yang menarik, saya menggambarkannya. Dan semua berkat fakta bahwa saya duduk di sebelah seorang programmer terkemuka. Saya bisa mengamati pekerjaannya, dan melihat betapa berbedanya itu dari apa yang akan saya lakukan. Kami banyak memprogram bersama, yang membuat pengamatan saya lebih mudah. Selain itu, tim kami tidak mengutuk "memata-matai" orang yang menulis kode. Ketika saya merasa bahwa sesuatu yang menarik sedang terjadi, saya berbalik dan melihat. Berkat naik terus-menerus, saya selalu menyadari apa yang terjadi.

Saya menghabiskan satu tahun di sebelah programmer terkemuka. Inilah yang saya pelajari.

Isi



Penulisan kode


Cara memberi nama berbagai hal dalam kode


Salah satu tugas pertama saya adalah mengerjakan React UI. Kami memiliki komponen utama yang berisi semua komponen lainnya. Saya suka menambahkan sedikit humor pada kode, dan saya ingin menyebutkan komponen utama dari GodComponent . Saatnya telah tiba untuk meninjau kode, dan saya mengerti mengapa begitu sulit untuk memberi nama.


Setiap potongan kode yang saya baptis telah memperoleh tujuan tersirat. GodComponent ? Ini adalah komponen yang mendapatkan semua sampah yang tidak ingin saya tempatkan di tempat yang tepat. Ini berisi segalanya. Beri nama LayoutComponent , dan di masa depan saya akan memutuskan bahwa komponen ini menetapkan tata letak. Itu tidak mengandung keadaan.

Pelajaran penting lain yang saya pelajari adalah bahwa jika sesuatu terlihat terlalu besar, seperti LayoutComponent dengan sekelompok logika bisnis, maka sudah waktunya untuk memperbaikinya, karena seharusnya tidak ada logika bisnis di sini. Dan dalam kasus nama GodComponent kehadiran logika bisnis tidak akan menjadi masalah.

Perlu memberi nama kluster? Menelepon mereka setelah layanan yang berjalan pada mereka akan menjadi ide bagus sampai Anda menjalankan sesuatu yang lain pada kelompok ini. Kami memberi mereka nama untuk menghormati tim kami.

Hal yang sama berlaku untuk fungsi. doEverything() adalah nama yang mengerikan dengan banyak konsekuensi. Jika fungsi melakukan semuanya, akan sangat sulit untuk menguji bagian-bagiannya masing-masing. Tidak peduli seberapa besar fungsi ini, tidak akan pernah terasa terlalu aneh bagi Anda, karena harus melakukan segalanya. Jadi ganti namanya. Bereaksi.

Nama yang bermakna memiliki kelemahan. Tiba-tiba nama itu akan terlalu bermakna dan menyembunyikan semacam nuansa? Sebagai contoh, sesi penutupan tidak menutup koneksi database ketika session.close() dipanggil dalam SQLAlchemy. Saya seharusnya membaca dokumentasi dan mencegah bug ini, lebih lanjut tentang ini di bagian sepeda .

Dari sudut pandang ini, penamaan fungsi sebagai x , y , z bukannya count() , close() , insertIntoDB() tidak memungkinkan kita untuk memasukkan makna tertentu ke dalamnya dan membuat saya memantau dengan cermat apa yang dilakukan fungsi-fungsi ini. 2

Saya tidak pernah berpikir bahwa saya akan menulis tentang prinsip penamaan lebih dari satu baris teks.

Kode yang diwarisi dan pengembang berikutnya


Pernahkah Anda melihat kode itu dan rasanya aneh bagi Anda? Mengapa Anda menulis itu? Ini tidak masuk akal.

Saya memiliki kesempatan untuk bekerja dengan basis kode yang diwarisi. Ini, Anda tahu, dengan komentar seperti "Batalkan komentar kode ketika Muhammad memahami situasinya." Apa yang kamu lakukan disini Siapa muhammad?

Saya dapat beralih peran dan berpikir tentang orang yang kemudian akan diberi kode saya, akankah itu terasa aneh baginya? Peninjauan sebagian kode Anda membantu rekan kerja untuk meninjau kode Anda. Ini membuat saya berpikir tentang konteksnya: Saya perlu mengingat konteks di mana tim saya bekerja.

Jika saya lupa kode ini, kembali lagi nanti dan tidak dapat memulihkan konteksnya, saya akan mengatakan: "Apa yang mereka lakukan? Ini bodoh ... Ah, tunggu, aku berhasil. ”

Dan di sini dokumentasi dan komentar dalam kode berperan.

Dokumentasi dan komentar dalam kode


Mereka membantu mempertahankan konteks dan menyampaikan pengetahuan. Seperti yang Lee katakan dalam Cara Membangun Perangkat Lunak yang Baik :

Nilai utama perangkat lunak tidak ada dalam kode yang dibuat, tetapi dalam pengetahuan yang diakumulasikan oleh orang-orang yang menciptakan perangkat lunak ini

Anda memiliki titik akhir API klien terbuka yang tampaknya belum pernah digunakan siapa pun. Apakah itu hanya perlu dihapus? Secara umum, ini adalah tugas teknis. Dan jika saya memberi tahu Anda bahwa di salah satu negara 10 jurnalis mengirim laporan mereka ke titik akhir ini setahun sekali? Bagaimana cara memeriksanya? Jika ini tidak disebutkan dalam dokumentasi (itu), maka tidak ada cara untuk memeriksanya. Kami belum memeriksa. Mereka menghapusnya, dan beberapa bulan kemudian datang saat yang sangat tahunan. Sepuluh wartawan tidak dapat mengirim laporan penting mereka karena titik akhir tidak ada lagi. Dan orang-orang dengan pengetahuan produk telah meninggalkan tim. Tentu saja, sekarang dalam kode ada komentar yang menjelaskan mengapa ini perlu.

Sejauh yang saya tahu, setiap tim berkelahi dengan dokumentasi. Dan dengan dokumentasi, tidak hanya dengan kode, tetapi juga oleh proses yang terkait dengannya.

Kami belum menemukan solusi yang sempurna. Secara pribadi, saya suka cara Antirez membagi komentar dalam kode menjadi berbagai jenis nilai .

Atom melakukan


Jika Anda perlu memutar kembali (dan Anda membutuhkannya. Lihat bab Pengujian ), akankah komit ini masuk akal sebagai modul tunggal?

Cara menghapus kode buruk dengan percaya diri


Saya sangat tidak menyenangkan untuk menghapus kode yang buruk atau ketinggalan jaman. Tampak bagi saya bahwa segala sesuatu yang ditulis berabad-abad yang lalu adalah suci. Saya berpikir: "Ada sesuatu di benak mereka ketika mereka menulis seperti itu." Ini adalah konfrontasi antara tradisi dan budaya di satu sisi, dan berpikir dalam gaya "prinsip utama" di sisi lain. Ini sama dengan penghapusan titik akhir tahunan. Saya belajar pelajaran khusus. 3

Saya akan mencoba menyiasati kode, dan pengembang terkemuka akan mencoba melewatinya. Hapus itu. Ekspresi if yang tidak dapat diakses? Ya, kami menghapus. Apa yang telah saya lakukan? Saya baru saja menulis fungsi saya di atasnya. Saya belum mengurangi hutang teknis. Bagaimanapun, saya hanya meningkatkan kompleksitas kode dan penerusan. Akan lebih sulit bagi orang berikutnya untuk mengumpulkan potongan-potongan gambar.

Secara empiris, saya sampai pada kesimpulan: ada kode yang tidak Anda mengerti, tetapi ada kode yang Anda pasti tidak akan pernah hubungi. Hapus kode yang tidak Anda hubungi, dan berhati-hatilah dengan kode yang tidak Anda mengerti.

Ulasan kode


Tinjauan kode adalah alat yang hebat untuk pendidikan mandiri. Ini adalah umpan balik eksternal yang menunjukkan bagaimana mereka akan menulis kode dan bagaimana Anda menulisnya. Apa bedanya? Satu cara lebih baik dari yang lain? Saya bertanya pada diri sendiri tentang ini dengan setiap ulasan: "Mengapa mereka menulis seperti itu?" Dan jika dia tidak dapat menemukan jawaban yang cocok, maka dia pergi dan bertanya.

Setelah bulan pertama, saya mulai menemukan kesalahan dalam kode rekan-rekan saya (seperti yang mereka temukan di tambang). Itu semacam kegilaan. Ulasan itu menjadi jauh lebih menarik bagi saya, itu berubah menjadi permainan yang saya lewatkan, permainan yang meningkatkan "rasa kode" saya.

Dalam pengalaman saya, Anda tidak perlu menyetujui kode sampai saya mengerti cara kerjanya.


Statistik github saya.

Pengujian


Saya jatuh cinta dengan pengujian sehingga tidak menyenangkan bagi saya untuk menulis kode dalam basis kode tanpa tes.

Jika aplikasi Anda hanya melakukan satu hal (seperti semua proyek sekolah saya), maka Anda masih dapat menguji secara manual. 4 Itulah tepatnya yang saya lakukan. Tetapi apa yang terjadi jika suatu aplikasi melakukan 100 tugas yang berbeda? Saya tidak ingin menghabiskan setengah jam pengujian, dan kadang-kadang saya lupa sesuatu. Mimpi buruk.

Tes dan otomatisasi pengujian membantu di sini.

Saya menganggap pengujian sebagai dokumentasi. Ini adalah dokumentasi ide-ide saya tentang kode. Pengujian memberi tahu saya bagaimana saya (atau orang lain sebelum saya) membayangkan kode bekerja dan di mana sesuatu yang diharapkan seharusnya salah.

Hari ini, ketika saya menulis tes, saya mencoba:

  1. Tunjukkan cara menggunakan kelas, fungsi, atau sistem tes.
  2. Tunjukkan apa, menurut saya, mungkin salah.

Akibatnya, paling sering saya menguji perilaku, tetapi tidak implementasi ( berikut adalah contoh yang saya pilih saat istirahat di Google).

Dalam paragraf 2, saya tidak menyebutkan sumber bug.

Ketika saya melihat bug, saya memastikan bahwa perbaikan memiliki tes yang sesuai (ini disebut pengujian regresi) untuk mendokumentasikan informasi tersebut. Ini adalah alasan lain mengapa ada sesuatu yang salah. 5

Tentu saja, kualitas kode saya meningkat bukan karena saya menulis tes, tetapi karena saya menulis kode. Tetapi membaca tes membantu saya lebih memahami situasi dan menulis kode yang lebih baik.

Ini adalah situasi umum dengan pengujian.

Tapi ini bukan satu-satunya jenis pengujian yang saya terapkan. Saya berbicara tentang lingkungan penyebaran. Anda mungkin memiliki tes unit yang ideal, tetapi jika Anda tidak memiliki tes sistem, hal seperti ini dapat terjadi:



Ini juga berlaku untuk kode yang telah diuji dengan baik: jika Anda tidak memiliki perpustakaan yang diperlukan di komputer Anda, maka semuanya akan macet.

  • Ada beberapa mesin yang sedang Anda kembangkan (sumber dari semua meme seperti "Ini berfungsi di komputer saya!").
  • Ada mesin yang Anda uji (mungkin bertepatan dengan yang sebelumnya).
  • Akhirnya, ada mesin yang Anda gunakan (mereka tidak harus sesuai dengan mesin yang Anda kembangkan).

Jika lingkungan pengujian dan penerapan tidak cocok dengan mesin, Anda akan mengalami masalah. Lingkungan penyebaran akan membantu menghindari hal ini.

Kami sedang melakukan pengembangan lokal di Docker di komputer kami.

Kami memiliki lingkungan pengembangan, komputer ini dilengkapi dengan satu set perpustakaan (dan alat pengembangan), dan di sini kami memasang kode tertulis. Di sini dapat diuji dengan semua sistem yang diperlukan. Kami juga memiliki lingkungan beta / pementasan yang sepenuhnya mengulangi lingkungan operasional. Akhirnya, kami memiliki lingkungan operasional - mesin yang mengeksekusi kode untuk pelanggan kami.

Idenya adalah untuk menangkap kesalahan yang belum muncul selama pengujian unit dan sistem. Misalnya, perbedaan API antara sistem yang meminta dan merespons. Saya pikir dalam kasus proyek pribadi atau perusahaan kecil, situasinya mungkin sangat berbeda. Tidak semua orang memiliki kesempatan untuk membuat infrastruktur sendiri. Namun, Anda dapat menggunakan layanan cloud seperti AWS dan Azure.

Anda dapat mengonfigurasikan masing-masing kelompok untuk pengembangan dan operasi. AWS ECS menggunakan gambar Docker untuk digunakan, sehingga proses di lingkungan yang berbeda akan relatif konsisten. Ada nuansa dalam hal integrasi antara berbagai layanan AWS. Apakah Anda memanggil titik akhir yang tepat dari lingkungan yang tepat?

Anda dapat melangkah lebih jauh: mengunduh gambar wadah alternatif untuk layanan AWS lainnya dan mengonfigurasi lingkungan lokal berbasis-Docker-Compose yang berfungsi penuh. Ini mempercepat putaran umpan balik. 6 Mungkin saya akan mendapatkan lebih banyak pengalaman ketika saya membuat dan meluncurkan proyek sampingan saya.

Pengurangan risiko


Langkah apa yang dapat Anda ambil untuk mengurangi risiko bencana? Jika kita berbicara tentang perubahan radikal baru, lalu bagaimana kita bisa memverifikasi durasi minimum downtime, jika ada yang salah? "Kami tidak perlu menggunakan sistem sepenuhnya karena semua perubahan baru ini." Apa yang benar-benar Dan mengapa saya tidak memikirkannya!

Arsitektur


Mengapa saya berbicara tentang arsitektur setelah menulis kode dan pengujian? Ini dapat diprioritaskan, tetapi jika saya tidak memprogram dan menguji dalam lingkungan yang saya gunakan, saya mungkin tidak akan berhasil membuat arsitektur yang mempertimbangkan fitur-fitur dari lingkungan ini. 7

Anda harus banyak berpikir saat membuat arsitektur.

  • Bagaimana angka akan digunakan?
  • Berapa banyak pengguna akan ada? Berapa jumlah mereka dapat meningkat (jumlah baris dalam database tergantung pada ini)?
  • Terumbu apa yang bisa bertemu?

Saya perlu mengubahnya menjadi daftar periksa yang disebut "Koleksi Klaim". Saat ini saya tidak memiliki cukup pengalaman, saya akan mencoba melakukannya tahun depan di Bloomberg. Proses ini sebagian besar bertentangan dengan Agile: berapa banyak Anda bisa mendesain arsitektur sebelum beralih ke implementasi? Ini semua tentang keseimbangan, Anda harus memilih kapan dan apa yang akan Anda lakukan. Kapan masuk akal untuk bergegas ke depan, dan kapan - untuk mundur? Tentu saja, persyaratan pengumpulan tidak sama dengan mempertimbangkan semua masalah. Saya pikir itu terbayar jika kita memasukkan proses pengembangan dalam desain. Sebagai contoh:

  • Bagaimana perkembangan lokal akan dilanjutkan?
  • Bagaimana kita akan mengemas dan menyebarkan?
  • Bagaimana kita akan melakukan pengujian end-to-end?
  • Bagaimana kita akan melakukan stress testing pada layanan baru?
  • Bagaimana kita menyimpan rahasia?
  • Integrasi CI / CD?

Kami baru-baru ini mengembangkan mesin pencarian baru untuk BNEF . Sangat menyenangkan bekerja di sana, saya mengorganisir pengembangan lokal dan menemukan tentang DPG (paket dan penyebarannya), mengumpulkan penyebaran rahasia.

Siapa yang mengira bahwa menyebarkan rahasia ke prod bisa menjadi sangat tidak penting:

  1. Mereka tidak dapat ditempatkan dalam kode, karena seseorang dapat melihatnya
  2. Untuk menyimpannya sebagai variabel lingkungan karena spec menawarkan 12 faktor aplikasi? Bukan ide yang buruk, tapi bagaimana cara menempatkannya di sana? (Pergi ke prod untuk mengisi variabel lingkungan setiap kali mobil dinyalakan sangat merepotkan)
  3. Sebarkan sebagai file? Tapi dari mana mereka datang dan bagaimana cara mengisinya?

Kami tidak ingin melakukan semuanya secara manual.

Akibatnya, kami sampai ke basis data dengan kontrol akses berbasis peran (hanya kami dan komputer kami yang dapat berkomunikasi dengan basis data). Kode kami menerima rahasia dari database saat startup. Pendekatan ini direplikasi dengan baik dalam kerangka pengembangan, pementasan dan lingkungan operasi, rahasia disimpan dalam database yang sesuai.

Sekali lagi, dengan layanan cloud seperti AWS, situasinya mungkin sangat berbeda. Anda tidak perlu menjaga rahasia. Dapatkan akun untuk peran Anda, masukkan rahasia di antarmuka, dan kode Anda akan menemukannya saat dibutuhkan. Ini sangat menyederhanakan segalanya, tetapi saya senang bahwa saya telah memperoleh pengalaman, berkat yang saya dapat menghargai kesederhanaan ini.

Kami membuat arsitektur, tidak melupakan pemeliharaan


Desain sistem sangat menginspirasi. Dan pengawalnya? Tidak terlalu banyak. Perjalanan saya melalui dunia pengawalan membawa saya ke pertanyaan: mengapa dan bagaimana sistem mengalami degradasi? Bagian pertama dari jawaban tidak terkait dengan penonaktifan semua yang sudah usang, tetapi hanya penambahan yang baru. Kecenderungan untuk menambahkan daripada menghapus (bukankah itu mengingatkan apa pun?). Bagian kedua adalah merancang dengan tujuan akhir dalam pikiran. Suatu sistem yang, dari waktu ke waktu, mulai melakukan apa yang tidak dimaksudkan untuknya, tidak akan serta-merta bekerja seperti halnya sistem yang awalnya dirancang untuk tugas yang sama. Ini adalah pendekatan gaya langkah mundur, bukan trik dan trik.

Saya tahu setidaknya tiga cara untuk mengurangi laju degradasi.

  1. Logika dan infrastruktur bisnis yang terpisah: infrastruktur biasanya menurun - beban meningkat, kerangka kerja menjadi usang, kerentanan nol hari muncul, dll.
  2. Buat proses untuk dukungan di masa depan. Terapkan pembaruan yang sama untuk bit lama dan baru. Ini akan mencegah perbedaan antara yang lama dan yang baru dan menyimpan semua kode dalam keadaan "modern".
  3. Pastikan untuk membuang segala sesuatu yang tidak perlu dan lama.

Penempatan


Apakah saya akan mengemas fitur bersama atau menyebarkannya satu per satu? Tergantung pada proses saat ini, jika Anda mengemasnya bersama, maka tunggu masalah. Tanyakan pada diri sendiri mengapa Anda ingin mengemas fitur bersama?

  • Penempatan membutuhkan banyak waktu?
  • Apakah ulasan kode tidak terlalu menyenangkan?

Apa pun alasannya, situasi ini perlu diperbaiki. Saya tahu setidaknya dua masalah yang terkait dengan pengemasan:

  1. Anda sendiri memblokir semua fitur jika salah satu dari mereka memiliki bug.
  2. Anda meningkatkan risiko masalah.

Apapun proses penyebaran yang Anda pilih, Anda selalu ingin mobil Anda menjadi seperti ternak, bukan seperti hewan peliharaan. Mereka tidak unik. Anda tahu persis apa yang dieksekusi pada setiap mesin, cara membuatnya kembali jika terjadi kematian. Anda tidak akan marah jika ada mobil mati, Anda hanya mengambil yang baru. Anda merumput mereka, bukan tumbuh.

Ketika terjadi kesalahan


Jika terjadi kesalahan - dan memang terjadi - ada aturan emas: meminimalkan dampak pada pelanggan. Dalam hal kegagalan, keinginan pertama saya adalah selalu memperbaikinya. Ini sepertinya bukan solusi yang optimal. Alih-alih memperbaiki, bahkan jika itu dapat dilakukan dalam satu baris, Anda harus memutar kembali terlebih dahulu. Kembali ke kondisi pengoperasian sebelumnya. Ini adalah cara tercepat untuk mengembalikan pelanggan ke versi yang berfungsi. Hanya kemudian saya mencari tahu apa masalahnya dan memperbaikinya.

Hal yang sama berlaku untuk mesin "rusak" di cluster Anda: matikan, tandai sebagai tidak dapat diakses, sebelum mencari tahu apa yang terjadi padanya. Saya merasa aneh betapa keinginan dan insting alami saya bertentangan dengan solusi optimal.

Saya pikir naluri ini juga menyebabkan saya memperbaiki bug lebih lama. Terkadang saya menyadari bahwa ada sesuatu yang tidak berhasil, karena kode yang saya tulis entah bagaimana salah, dan saya naik ke hutan, melihat setiap baris. Sesuatu seperti pencarian "pertama dalam". Dan ketika ternyata masalah muncul karena perubahan konfigurasi, yaitu, saya tidak memeriksanya sejak awal, situasi ini mengganggu saya. Saya membuang banyak waktu untuk mencari bug.

Sejak itu saya telah belajar untuk mencari "pertama dalam luasnya", dan karena itu sudah "pertama secara mendalam" untuk mengecualikan alasan tingkat atas. Apa sebenarnya yang bisa saya konfirmasikan dengan sumber daya saat ini?

  • Apakah mobilnya berfungsi?
  • Apakah kode terpasang dengan benar?
  • Apakah ada konfigurasi?
  • <Konfigurasi khusus kode>, seperti apakah perutean dijabarkan dengan benar?
  • Apakah versi skema sudah benar?
  • Dan kemudian saya terjun ke kode.

Kami mengira nginx tidak diinstal dengan benar, tetapi ternyata konfigurasinya dinonaktifkan

Tentu saja, saya tidak perlu melakukan ini setiap waktu. Terkadang hanya pesan kesalahan yang cukup untuk segera turun ke kode. Ketika saya tidak dapat menentukan penyebabnya, saya mencoba untuk meminimalkan jumlah perubahan dalam kode untuk menemukan alasannya. Semakin sedikit perubahan, semakin cepat saya dapat menemukan akar masalah yang sebenarnya. Selain itu, sekarang saya memiliki memo untuk bug yang menyelamatkan saya lebih dari satu jam berpikir "apa yang saya lewatkan?" Kadang-kadang saya lupa tentang pemeriksaan paling sederhana, seperti mengonfigurasi perutean, skema pencocokan, dan versi layanan, dll. Ini adalah langkah lain dalam pengembangan tumpukan teknologi yang saya gunakan, dan yang Anda dapatkan hanya dengan pengalaman adalah intuisi dalam menentukan apa yang sebenarnya tidak berfungsi.

Sepeda


Artikel ini tidak dapat lengkap tanpa cerita. , . SQLAlchemy. BNEF , . . SQLAlchemy, , Solr. - .

«MYSQL server has gone away.» . , , . , . , . , ?

, ? , , . , , __exit__() session.close() .

, , . . . . .

Session.close() MySQL- SQLAlchemy , NullPool. . , , . : StackOverflow (, !) , , SQL- . , . , , (), .

«» , 1 8. , , — .

, .

Pemantauan


, . , , . , .

, , , . , . , . « ?! , ? ».

, : , . , , . , , . , . — ? , , . . , -, , , , , .. .

. , , ? (, AWS CloudWatch Grafana). .

. , , 50 %, — . ? . , — (, ?).

. , , , , ? ? , ?

, . , . — - .

Kesimpulan


. , , , . , - !

. , !

, . , — How to Build Good Software .


. : ! , .

  1. ?
  2. ? , ? , ?
  3. . , ? ?
  4. (utils) (, , , ) , « »?
  5. ?
  6. , , - ?
  7. — API , ?
  8. ? , .
  9. , , . , , .
  10. PR: « , , 52 , , , , , ». ?
  11. . ?
  12. ?
  13. ?


  1. . , ? - ? , ?
  2. , x() , y() z(), x() , y() z() . , WYSIATI .
  3. .
  4. . , ?
  5. , - , , . , .
  6. , , Docker- AWS.
  7. .

Source: https://habr.com/ru/post/id465561/


All Articles