Tentang C ++ dan Pemrograman Berorientasi Objek

Halo, Habr!



Kami ingin menarik perhatian Anda ke sebuah artikel, yang penulisnya tidak menyetujui pendekatan berorientasi objek murni saat bekerja dengan bahasa C ++. Kami meminta Anda untuk mengevaluasi, jika memungkinkan, tidak hanya argumentasi penulis, tetapi juga logika dan gaya.



Akhir - akhir ini ada banyak tulisan tentang C ++ dan ke mana arah bahasanya dan seberapa banyak dari apa yang disebut "C ++ modern" bukanlah pilihan bagi pengembang game.



Sementara saya sepenuhnya berbagi sudut pandang ini, saya cenderung melihat evolusi C ++ sebagai hasil dari ide-ide yang tertanam di mana sebagian besar pengembang dipandu oleh. Dalam artikel ini, saya akan mencoba mengatur beberapa ide ini bersama dengan pikiran saya sendiri - dan mungkin saya akan mendapatkan sesuatu yang ramping.



Pemrograman berorientasi objek (OOP) sebagai alat



Meskipun C ++ dideskripsikan sebagai bahasa pemrograman multi-paradigma , dalam praktiknya sebagian besar pemrogram menggunakan C ++ murni sebagai bahasa berorientasi objek (pemrograman generik digunakan untuk "melengkapi" OOP).



OOP seharusnya menjadi alat, salah satu dari banyak paradigma yang dapat digunakan programmer untuk menyelesaikan masalah dalam kode. Namun, menurut pengalaman saya, OOP diterima oleh sebagian besar profesional sebagai standar emas untuk pengembangan perangkat lunak. Pada dasarnya mengembangkan solusi dimulai dengan menentukan objek apa yang kita butuhkan. Solusi untuk masalah tertentu dimulai setelah kode didistribusikan di antara objek. Dengan transisi ke pemikiran berorientasi objek semacam ini, OOP berubah dari alat menjadi kotak alat secara keseluruhan.



Tentang entropi sebagai kekuatan rahasia yang mendorong pengembangan perangkat lunak



Saya suka memikirkan solusi OOP sebagai konstelasi: ini adalah sekelompok objek dengan garis yang ditarik secara acak di antara mereka. Solusi semacam itu juga dapat dianggap sebagai grafik di mana objek adalah node, dan hubungan di antara mereka adalah tepi, tetapi fenomena grup / cluster, yang disampaikan oleh metafora konstelasi, lebih dekat dengan saya (dibandingkan dengan itu, grafik terlalu abstrak).



Tapi saya tidak suka cara "konstelasi objek" seperti itu disusun. Dalam pemahaman saya, setiap konstelasi tersebut tidak lebih dari sebuah snapshot dari gambar yang telah terbentuk di kepala programmer dan merefleksikan seperti apa ruang solusi pada saat tertentu. Bahkan dengan mempertimbangkan semua janji yang diberikan dalam desain berorientasi objek tentang ekstensibilitas, dapat digunakan kembali, enkapsulasi, dll ... masa depan tidak dapat diprediksi, jadi dalam setiap kasus kami dapat menawarkan solusi untuk masalah yang kita hadapi sekarang.



Kita harus didorong bahwa kita “hanya” menyelesaikan masalah yang ada di hadapan kita, tetapi menurut pengalaman saya, seorang programmer yang menggunakan prinsip-prinsip desain dalam semangat OOP menciptakan solusi, sementara terkendala oleh asumsi bahwa masalah itu sendiri tidak akan berubah secara signifikan dan Oleh karena itu, solusinya dapat dianggap permanen. Maksud saya, dari sini, orang mulai berbicara tentang solusi dalam hal objek yang membentuk konstelasi tersebut, dan bukan dalam hal data dan algoritme; masalahnya sendiri diabstraksikan.

Namun demikian, program ini tunduk pada entropi tidak kurang dari sistem lain dan, oleh karena itu, kita semua tahu bahwa kodenya akan berubah. Apalagi dengan cara yang tidak bisa diprediksi. Tetapi bagi saya dalam kasus ini, sangat jelas bahwa kode akan menurun dalam hal apa pun, meluncur ke kekacauan dan kekacauan, jika Anda tidak melawannya secara sadar.



Saya telah melihat ini nyata dalam banyak cara berbeda dalam solusi OOP:



  • Tingkat menengah baru muncul dalam hierarki, padahal awalnya tidak dimaksudkan untuk diperkenalkan.
  • Fungsi virtual baru ditambahkan dengan implementasi kosong di sebagian besar hierarki.
  • Salah satu objek di konstelasi membutuhkan lebih banyak pemrosesan daripada yang direncanakan, yang menyebabkan koneksi antara objek lain mulai tergelincir.
  • , , , .
  • .…


Ini semua adalah contoh ekstensibilitas yang tidak terorganisir dengan benar. Selain itu, hasilnya selalu sama, bisa dalam beberapa bulan, atau mungkin beberapa tahun. Dengan bantuan refactoring, mereka mencoba untuk menghilangkan pelanggaran prinsip desain OOP, dibuat ketika objek baru ditambahkan ke konstelasi, dan mereka ditambahkan karena perumusan ulang masalah itu sendiri. Terkadang refactoring membantu. Untuk sementara. Entropi stabil, dan pemrogram tidak punya waktu untuk merefaktor setiap konstelasi OOP untuk mengatasinya, jadi setiap proyek secara teratur menemukan dirinya dalam situasi yang sama, yang namanya chaos.



Dalam siklus hidup setiap proyek OOP, cepat atau lambat akan tiba suatu titik yang setelah itu tidak mungkin untuk dipertahankan. Biasanya, pada titik ini, salah satu dari dua tindakan harus diambil:



  • « »: - . , , , , , .
  • : -, , , .


Harap diperhatikan: opsi dengan kotak hitam masih memerlukan penulisan ulang jika pengembangan fitur baru harus dilanjutkan dan / atau kebutuhan untuk menghilangkan bug tetap ada.



Situasi dengan menulis ulang solusi membawa kita kembali ke fenomena snapshot dari ruang solusi yang tersedia pada momen tertentu. Jadi apa yang berubah antara OOP Design # 1 dan situasi saat ini? Pada dasarnya, itu saja. Masalahnya telah berubah, oleh karena itu, diperlukan solusi yang berbeda.



Saat kami menulis solusi, mengikuti prinsip desain OOP, kami mengabstraksi masalah, dan segera setelah berubah, solusi kami berantakan seperti rumah kartu.

Saya pikir pada saat inilah kami mulai memikirkan apa yang salah, kami mencoba untuk pergi ke arah lain dan memperbarui strategi untuk menyelesaikan masalah berdasarkan hasil postmortem (pembekalan). Namun, setiap kali saya menemukan skenario "waktu untuk menulis ulang", tidak ada yang berubah: prinsip OOP digunakan lagi, sesuai dengan penerapan snapshot baru, sesuai dengan keadaan ruang masalah saat ini. Seluruh siklus berulang.



Kemudahan penghapusan kode sebagai prinsip desain



Dalam sistem apa pun yang dibangun berdasarkan prinsip OOP, objek-objek dalam "konstelasi" -lah yang mendapat perhatian utama. Tetapi saya percaya bahwa hubungan antar objek sama pentingnya, jika tidak lebih, daripada objek itu sendiri.



Saya lebih suka solusi sederhana di mana grafik ketergantungan kode terdiri dari jumlah minimum node dan edge. Semakin sederhana solusinya, semakin mudah bukan hanya untuk mengubahnya, tetapi juga untuk menghapusnya. Saya juga menemukan bahwa semakin mudah untuk menghapus kode, semakin cepat Anda dapat memfokuskan kembali solusi dan menyesuaikannya dengan kondisi masalah yang berubah. Pada saat yang sama, kode menjadi lebih tahan terhadap entropi, karena dibutuhkan lebih sedikit upaya untuk menjaganya agar tidak tergelincir ke dalam kekacauan.



Tentang kinerja menurut definisi



Tetapi salah satu pertimbangan utama untuk menghindari desain OOP adalah kinerja. Semakin banyak kode yang perlu Anda jalankan, semakin buruk performanya.



Juga tidak mungkin untuk tidak mencatat bahwa fitur OOP, menurut definisi, tidak bersinar dengan kinerja. Saya telah menerapkan hierarki OOP sederhana dengan antarmuka dan dua kelas turunan yang menimpa panggilan fungsi virtual murni tunggal di Compiler Explorer .



Kode dalam contoh ini akan mencetak "Hello, World!" Atau tidak, bergantung pada jumlah argumen yang diteruskan ke program. Alih-alih memprogram secara langsung semua yang baru saja saya jelaskan, salah satu pola desain OOP standar, warisan, akan digunakan untuk menyelesaikan masalah ini dalam kode.



Dalam hal ini, yang paling mencolok adalah berapa banyak kompiler kode yang dihasilkan, bahkan setelah pengoptimalan. Kemudian, melihat lebih dekat, Anda dapat melihat betapa mahal dan pada saat yang sama tidak berguna pemeliharaan tersebut: ketika sebuah program dilewatkan sejumlah argumen bukan nol, kode masih mengalokasikan memori (panggilan new), memuat alamat vtablekedua objek, memuat alamat fungsi Work()untuk ImplBdan melompat ke sana, sehingga kemudian segera kembali, karena tidak ada yang bisa dilakukan di sana. Akhirnya, ini dipanggil deleteuntuk membebaskan memori yang dialokasikan.



Tidak satu pun dari operasi ini yang diperlukan sama sekali, tetapi prosesor melakukan semuanya dengan benar.



Jadi, jika salah satu tujuan utama produk Anda adalah pencapaian kinerja tinggi (aneh jika sebaliknya), maka dalam kode Anda harus menghindari operasi mahal yang tidak perlu, lebih memilih yang sederhana, yang mudah dinilai, dan menggunakan konstruksi yang membantu mencapai tujuan ini.



Ambil Unity sebagai contoh . Sebagai bagian dari praktik terbaru mereka, kinerja adalah ketepatan menggunakan C #, bahasa berorientasi objek, karena bahasa ini sudah digunakan di mesin itu sendiri. Namun, mereka menetap pada subset C # , terlebih lagi, pada subset yang tidak terikat secara kaku dengan OOP, dan atas dasar itu mereka membuat konstruksi yang dipertajam untuk kinerja tinggi.



Mengingat tugas pemrogram adalah menyelesaikan masalah menggunakan komputer, tidak terbayangkan bahwa bisnis kami mencurahkan begitu sedikit perhatian untuk menulis kode yang sebenarnya membuat prosesor melakukan pekerjaan yang sangat ahli dalam hal prosesor.



Tentang melawan stereotip



Dalam artikel Angelo Pesce " Komplikasi Berlebihan Adalah Akar Semua Kejahatan, " penulis mencapai sasaran (lihat bagian terakhir: Orang) dengan mengakui bahwa sebagian besar masalah perangkat lunak sebenarnya adalah faktor manusia.



Orang-orang di tim perlu berinteraksi dan mengembangkan pemahaman bersama tentang apa tujuan keseluruhan itu dan apa jalan untuk mencapainya. Jika terjadi ketidaksepakatan dalam tim, misalnya tentang jalan menuju tujuan, maka untuk kemajuan selanjutnya perlu dikembangkan konsensus. Hal ini biasanya tidak sulit jika perbedaan pendapatnya kecil, tetapi akan lebih sulit untuk ditoleransi jika pilihannya berbeda secara fundamental, katakan "OOP atau bukan OOP".

Mengubah pikiran Anda tidaklah mudah. Meragukan sudut pandang Anda, menyadari betapa salahnya Anda dan menyesuaikan arah Anda itu sulit dan menyakitkan. Tetapi jauh lebih sulit untuk mengubah pikiran orang lain!



Saya memiliki banyak percakapan dengan orang yang berbeda tentang OOP dan masalah yang melekat padanya, dan meskipun saya percaya bahwa saya selalu dapat menjelaskan mengapa saya berpikir seperti ini dan bukan sebaliknya, saya tidak berpikir bahwa saya berhasil membuat siapa pun menjauh dari OOP.



Benar, selama bertahun-tahun bekerja, saya telah mengidentifikasi tiga argumen utama untuk diri saya sendiri, karena itu orang tidak siap memberi kesempatan kepada pihak lain:



  • « ». « ». « » . , , ( , - ). « …».
  • « , , , ». «» , , . , « ».
  • "Semua orang tahu OOP, sangat nyaman untuk berbicara dengan orang-orang dalam bahasa yang sama, memiliki pengetahuan umum." Ini adalah kesalahan logis yang disebut "argumen kepada rakyat", artinya, jika hampir semua programmer menggunakan prinsip-prinsip OOP, maka gagasan ini tidak mungkin tidak tepat.


Saya sepenuhnya sadar bahwa mengungkapkan kesalahan logika dalam argumentasi tidak cukup untuk menghilangkan prasangka mereka. Namun, saya percaya bahwa melihat kekurangan dalam penilaian Anda sendiri, Anda bisa sampai ke dasar kebenaran dan menemukan alasan yang dalam mengapa Anda menolak ide yang tidak biasa.



All Articles