Bagaimana saya bisa mengelola basis kode dari perangkat lunak yang sangat kompleks?

8

Saya sering membuat program untuk saya dan orang lain menggunakan berbagai bahasa pemrograman berorientasi objek. Ketika melakukannya, mereka biasanya relatif kecil (paling sedikit beberapa ribu baris). Namun, baru-baru ini, saya telah berupaya membuat proyek yang lebih besar, seperti mesin game lengkap. Ketika melakukan hal itu, saya sering tampak mengalami hambatan: kompleksitas.

Dalam proyek saya yang lebih kecil, mudah untuk mengingat peta mental tentang bagaimana setiap bagian dari program ini bekerja. Dengan melakukan ini, saya dapat sepenuhnya menyadari bagaimana perubahan apa pun akan memengaruhi program lainnya dan menghindari bug dengan sangat efektif serta melihat dengan tepat bagaimana fitur baru harus masuk ke dalam basis kode. Namun, ketika saya mencoba membuat proyek yang lebih besar, saya merasa tidak mungkin untuk menyimpan peta mental yang baik yang mengarah pada kode yang sangat berantakan dan banyak bug yang tidak diinginkan.

Selain masalah "peta mental" ini, saya merasa sulit untuk menjaga kode saya dipisahkan dari bagian lain dari dirinya sendiri. Misalnya, jika dalam permainan multipemain ada kelas yang menangani fisika gerakan pemain dan yang lain menangani jaringan, maka saya tidak melihat ada cara agar salah satu kelas ini tidak bergantung pada yang lain untuk mendapatkan data gerakan pemain ke sistem jaringan. untuk mengirimnya melalui jaringan. Kopling ini merupakan sumber signifikan dari kompleksitas yang mengganggu peta mental yang baik.

Terakhir, saya sering menemukan diri saya datang dengan satu atau lebih "manajer" kelas yang mengoordinasikan kelas lain. Misalnya, dalam sebuah game, sebuah kelas akan menangani loop tick utama dan akan memanggil metode pembaruan di kelas jaringan dan pemain. Ini bertentangan dengan filosofi dari apa yang saya temukan dalam penelitian saya bahwa setiap kelas harus dapat diuji unit dan dapat digunakan secara terpisah dari yang lain, karena setiap kelas manajer seperti itu dengan tujuan bergantung pada sebagian besar kelas lain dalam proyek. Selain itu, kelas manajer orkestrasi dari sisa program adalah sumber signifikan dari kompleksitas non-mental-mappable.

Secara keseluruhan, ini mencegah saya dari menulis perangkat lunak bebas bug berkualitas tinggi dengan ukuran yang besar. Apa yang dilakukan pengembang profesional untuk mengatasi masalah ini secara efektif? Saya terutama tertarik pada target jawaban OOP di Java dan C ++, tetapi saran semacam ini mungkin sangat umum.

Catatan:

  • Saya telah mencoba menggunakan diagram UML, tetapi itu sepertinya hanya membantu dengan masalah pertama, dan itupun hanya ketika itu berkaitan dengan struktur kelas daripada (misalnya) pemesanan pemanggilan metode berkenaan dengan apa yang diinisialisasi terlebih dahulu.
john01dav
sumber
7
"Saat memperdagangkan saham, bagaimana cara menghasilkan uang sebanyak mungkin?" - pertanyaan yang sama, pekerjaan yang berbeda.
Maks
1
Berapa banyak waktu yang Anda habiskan untuk desain dan pemodelan? Banyak masalah yang kita temui selama pengembangan berasal dari kebocoran perspektif. Jenis perspektif yang kami fokuskan selama pemodelan. Lalu nanti kita coba atasi kebocoran ini dengan kode. Kode itu juga bocor dari perspektif (abstraksi yang tepat). Proyek skala besar membutuhkan perencanaan, untuk menjernihkan gambaran global. Dari tingkat basis kode akan sulit untuk memiliki visi. " Jangan biarkan pohon menghentikanmu dari melihat hutan ".
Laiv
4
Modularisasi dan isolasi. Tapi sepertinya Anda sudah melakukan itu, tetapi gagal melakukannya. Dalam hal ini, itu benar-benar tergantung pada proyek apa itu dan teknologi apa yang digunakan.
Euforia
1
Refactoring konstan adalah bagian penting dari pengembangan perangkat lunak - menganggapnya sebagai indikasi bahwa Anda melakukan sesuatu dengan benar. Setiap sistem non-sepele selalu mengandung "tidak dikenal tidak dikenal" - yaitu hal-hal yang Anda tidak mungkin dapat memperkirakan atau memperhitungkan dalam desain asli Anda. Refactoring adalah kenyataan dari pengembangan berulang dan secara bertahap berkembang menuju solusi 'baik'. Itulah alasan mengapa pengembang cenderung lebih suka menulis kode yang mudah untuk refactor, yang sering melibatkan mengikuti Prinsip SOLID
Ben Cottrell
2
UML IMHO tidak memiliki jenis diagram yang paling berguna untuk ini: diagram aliran data. Namun, itu bukan peluru perak, modularisasi dan isolasi tidak datang secara otomatis dengan menggunakan diagram aliran data.
Doc Brown

Jawaban:

8

Saya sering menghadapi hambatan: kompleksitas.

Ada seluruh buku yang ditulis tentang hal ini. Berikut adalah kutipan dari salah satu buku paling penting yang pernah ditulis tentang pengembangan perangkat lunak, Kode Lengkap Steve McConnell :

Mengelola kompleksitas adalah topik teknis terpenting dalam pengembangan perangkat lunak. Dalam pandangan saya, sangat penting bahwa Software Teknis Utama harus mengelola kompleksitas.

Sebagai tambahan, saya akan sangat menyarankan membaca buku jika Anda memiliki minat dalam pengembangan perangkat lunak sama sekali (yang saya anggap Anda lakukan, karena Anda telah mengajukan pertanyaan ini). Paling tidak, klik tautan di atas dan baca kutipan tentang konsep desain.


Misalnya, jika dalam permainan multipemain ada kelas yang menangani fisika gerakan pemain dan yang lain menangani jaringan, maka saya tidak melihat ada cara agar salah satu kelas ini tidak bergantung pada yang lain untuk mendapatkan data gerakan pemain ke sistem jaringan. untuk mengirimnya melalui jaringan.

Dalam kasus khusus ini, saya akan menganggap PlayerMovementCalculatorkelas Anda dan NetworkSystemkelas Anda sama sekali tidak terkait satu sama lain; satu kelas bertanggung jawab untuk menghitung pergerakan pemain, dan yang lainnya bertanggung jawab untuk I / O jaringan. Mungkin bahkan dalam modul independen yang terpisah.

Namun saya tentu berharap akan ada setidaknya beberapa kabel tambahan atau lem di suatu tempat di luar modul-modul yang memediasi data dan / atau peristiwa / pesan di antara mereka. Misalnya, Anda dapat menulis PlayerNetworkMediatorkelas menggunakan Pola Mediator .

Pendekatan lain yang mungkin dilakukan adalah menghapus pasangan modul Anda menggunakan Agregator Acara .

Dalam hal pemrograman Asynchronous seperti jenis logika yang terlibat dengan soket jaringan, Anda dapat menggunakan expose Observable untuk merapikan kode yang 'mendengarkan' pemberitahuan tersebut.

Pemrograman asinkron tidak berarti multi-threaded juga; lebih lanjut tentang struktur program dan kontrol aliran (meskipun multi-threading adalah kasus penggunaan yang jelas untuk asinkron). Dapat diobservasi mungkin berguna dalam satu atau kedua modul tersebut untuk memungkinkan kelas yang tidak terkait berlangganan untuk mengubah pemberitahuan.

Sebagai contoh:

  • NetworkMessageReceivedEvent
  • PlayerPositionChangedEvent
  • PlayerDisconnectedEvent

dll.


Terakhir, saya sering menemukan diri saya datang dengan satu atau lebih "manajer" kelas yang mengoordinasikan kelas lain. Misalnya, dalam sebuah game, sebuah kelas akan menangani loop tick utama dan akan memanggil metode pembaruan di kelas jaringan dan pemain. Ini bertentangan dengan filosofi dari apa yang saya temukan dalam penelitian saya bahwa setiap kelas harus dapat diuji unit dan dapat digunakan secara terpisah dari yang lain, karena setiap kelas manajer seperti itu dengan tujuan bergantung pada sebagian besar kelas lain dalam proyek. Selain itu, kelas manajer orkestrasi dari sisa program adalah sumber signifikan dari kompleksitas non-mental-mappable.

Sementara beberapa hal ini tentu saja merupakan pengalaman; nama Managerdalam kelas sering menunjukkan aroma desain .

Saat menamai kelas, pertimbangkan fungsionalitas yang menjadi tanggung jawab kelas, dan izinkan nama kelas Anda mencerminkan apa yang dilakukannya .

Masalah dengan Manajer dalam kode, sedikit seperti masalah dengan Manajer di tempat kerja. Tujuan mereka cenderung tidak jelas dan kurang dipahami bahkan oleh mereka sendiri; sebagian besar waktu kita hanya lebih baik tanpa mereka sama sekali.

Pemrograman Berorientasi Objek terutama tentang perilaku . Kelas bukanlah entitas data, tetapi representasi dari beberapa persyaratan fungsional dalam kode Anda.

Jika Anda dapat memberi nama kelas berdasarkan persyaratan fungsional yang dipenuhi, Anda akan mengurangi peluang Anda untuk berakhir dengan semacam Objek Dewa yang membengkak , dan lebih mungkin memiliki kelas yang identitas dan tujuannya dalam program Anda jelas.

Selain itu, akan lebih jelas ketika metode dan perilaku tambahan mulai merayap ketika sebenarnya tidak termasuk, karena nama akan mulai terlihat salah - yaitu Anda akan memiliki kelas yang melakukan banyak hal yang tidak t tercermin dengan namanya

Terakhir, hindari godaan kelas menulis yang namanya terlihat seperti milik mereka dalam model hubungan entitas. Masalah dengan nama kelas seperti Player, Monster, Car, Dog, dll adalah bahwa menyiratkan apa-apa tentang perilaku mereka, dan hanya tampaknya menggambarkan koleksi data atau atribut yang berhubungan secara logis. Desain berorientasi objek bukan pemodelan data, pemodelan perilaku.

Misalnya, pertimbangkan dua cara berbeda untuk memodelkan Monsterdan Playermenghitung kerusakan:

class Monster : GameEntity {
    dealDamage(...);
}

class Player : GameEntity {
    dealDamage(...);
}

Masalahnya di sini adalah bahwa Anda mungkin berharap Playerdan Monstermemiliki sejumlah metode lain yang mungkin sama sekali tidak terkait dengan jumlah kerusakan yang mungkin dilakukan entitas ini (misalnya Gerakan); Anda berada di jalan menuju Objek Tuhan yang disebutkan di atas.

Pendekatan Object-Oriented yang lebih alami adalah mengidentifikasi nama kelas berdasarkan perilakunya, misalnya:

class MonsterDamageDealer : IDamageDealer {
    dealDamage(...) { }
}

class PlayerDamageDealer : IDamageDealer {
    dealDamage(...) { }
}

Dengan jenis desain ini, objek Anda Playerdan Monstermungkin tidak memiliki metode yang terkait dengan mereka karena objek tersebut berisi data yang dibutuhkan oleh seluruh aplikasi Anda; mereka mungkin hanya entitas data sederhana yang hidup di dalam repositori dan hanya berisi bidang / properti.

Pendekatan ini biasanya dikenal sebagai Anemic Domain Model , yang dianggap sebagai anti-pola untuk Domain-Driven-Design (DDD) , tetapi prinsip-prinsip SOLID secara alami menuntun Anda ke arah pemisahan yang bersih antara entitas data 'bersama' (mungkin dalam repositori) , dan kelas perilaku modular (lebih disukai stateless) dalam grafik objek aplikasi Anda.

SOLID dan DDD adalah dua pendekatan berbeda untuk desain OO; sementara mereka menyeberang dalam banyak cara, mereka cenderung menarik arah yang berlawanan berkaitan dengan identitas kelas dan pemisahan data dan perilaku.


Kembali ke kutipan McConnell di atas - mengelola kompleksitas adalah alasan mengapa pengembangan perangkat lunak adalah profesi yang terampil daripada tugas klerikal duniawi. Sebelum McConnell menulis bukunya, Fred Brooks menulis makalah tentang subjek yang dengan rapi merangkum jawaban untuk pertanyaan Anda - Tidak Ada Peluru Perak untuk mengelola kompleksitas.

Jadi, sementara tidak ada jawaban tunggal, Anda dapat membuat hidup lebih mudah atau lebih sulit untuk diri Anda sendiri tergantung pada cara Anda mendekatinya:

  • Ingat KISS , KERING dan YAGNI .
  • Memahami cara menerapkan Prinsip SOLID dalam Pengembangan OO / Pengembangan Perangkat Lunak
  • Juga memahami Desain Berbasis Domain bahkan jika ada tempat di mana pendekatan tersebut bertentangan dengan prinsip SOLID; SOLID dan DDD cenderung setuju satu sama lain lebih daripada mereka tidak setuju.
  • Harapkan kode Anda untuk berubah - tulis tes otomatis untuk mengetahui dampak dari perubahan itu (Anda tidak harus mengikuti TDD untuk menulis tes otomatis yang berguna - memang, beberapa tes itu mungkin tes integrasi menggunakan aplikasi konsol "throwaway" atau aplikasi test harness)
  • Yang terpenting - menjadi pragmatis. Jangan dengan patuh mengikuti pedoman apa pun; kebalikan dari kompleksitas adalah kesederhanaan, jadi jika ragu (lagi) - CIUMAN
Ben Cottrell
sumber
Jawaban yang solid. Pun intended.
ipaul
4
Saya tidak setuju bahwa DDD dan SOLID berselisih satu sama lain. Saya pikir mereka sebenarnya saling melengkapi. Bagian dari mengapa mereka mungkin tampak kontradiktif, atau mungkin ortogonal, adalah bahwa mereka menggambarkan hal-hal yang berbeda pada tingkat abstraksi yang berbeda.
RibaldEddie
1
@RibaldEddie Saya setuju mereka saling melengkapi untuk sebagian besar; masalah utama yang saya lihat adalah bahwa SOLID tampaknya secara aktif mendorong apa yang disebut model domain "Anemik" sebagai konsekuensi alami dari mengambil SRP ke kesimpulan logis dari tanggung jawab tunggal-per-kelas yang benar, sedangkan DDD menyebut bahwa pola. Atau mungkin ada sesuatu tentang deskripsi Fowler tentang Model Domain Anemik yang saya salah artikan?
Ben Cottrell
1

Saya pikir ini tentang mengelola kompleksitas, yang selalu turun ketika hal-hal di sekitar Anda menjadi terlalu kompleks untuk tetap mengelola dengan apa yang Anda miliki saat ini dan mungkin terjadi beberapa kali ketika tumbuh dari perusahaan 1 orang menjadi setengah juta orang perusahaan global .

Secara umum kompleksitas tumbuh ketika proyek Anda tumbuh. Dan secara umum pada saat itu kompleksitas menjadi terlalu banyak (independen jika itu adalah IT atau mengelola program, program atau perusahaan lengkap) Anda memerlukan lebih banyak alat atau mengganti alat dengan alat yang menangani kompleksitas dengan lebih baik yang telah dilakukan umat manusia sejak dulu. Dan alat-alat berjalan seiring dengan proses yang lebih rumit, karena seseorang perlu mengeluarkan sesuatu yang perlu dikerjakan orang lain. Dan mereka berjalan seiring dengan "orang tambahan" yang memiliki peran spesifik misalnya Arsitek Perusahaan yang tidak diperlukan dengan proyek 1 orang di rumah.

Pada saat yang sama taksonomi Anda di belakangnya harus tumbuh di belakang kompleksitas Anda sampai tiba saatnya bahwa taksonomi terlalu kompleks dan Anda mendorong ke data yang tidak terstruktur. Misalnya Anda dapat membuat daftar situs web dan mengategorikannya tetapi jika Anda perlu memiliki daftar 1 miliar situs web tidak ada cara untuk melakukan ini dalam waktu yang wajar sehingga Anda pergi ke algoritma yang lebih cerdas atau hanya mencari melalui data.

Dalam proyek kecil Anda dapat bekerja pada drive bersama. Pada proyek yang agak besar Anda menginstal .git atau .svn atau apa pun. Pada program yang kompleks dengan banyak proyek yang semuanya memiliki rilis berbeda yang semuanya live dan semua memiliki tanggal rilis yang berbeda dan semua memiliki ketergantungan pada sistem lain, Anda mungkin perlu beralih ke clearcase jika Anda bertanggung jawab atas manajemen konfigurasi semua itu alih-alih hanya versi beberapa cabang proyek.

Jadi saya pikir upaya terbaik manusia sampai sekarang adalah kombinasi alat, peran khusus, proses untuk mengelola semakin kompleksnya segala sesuatu.

Untungnya ada perusahaan yang menjual kerangka kerja bisnis, kerangka kerja proses, taksonomi, model data, dll, jadi tergantung pada industri di mana Anda dapat membeli, ketika kompleksitas telah tumbuh begitu besar sehingga semua orang mengakui bahwa tidak ada cara lain, data- model dan proses keluar dari kotak untuk misalnya perusahaan asuransi global, dari sana Anda bekerja turun untuk refactor aplikasi Anda yang ada untuk membawa semuanya sejalan lagi dengan kerangka kerja yang sudah terbukti secara umum yang sudah mencakup model-data dan proses yang terbukti.

Jika tidak ada pasar untuk industri Anda, mungkin ada peluang :)

edelwater
sumber
Sepertinya Anda pada dasarnya menyarankan beberapa kombinasi "merekrut orang" atau "membeli sesuatu yang akan membuat masalah Anda hilang ™" Saya tertarik pada kompleksitas pengelolaan sebagai pengembang individu tanpa anggaran yang mengerjakan proyek hobi (untuk saat ini).
john01dav
Saya pikir pada tingkat itu mungkin pertanyaan Anda adalah sesuatu seperti "nama 100 praktik terbaik yang diterapkan oleh arsitek aplikasi yang baik"? Karena saya pikir pada dasarnya dia mengelola kompleksitas di balik semua yang dia lakukan. ia menetapkan standar untuk kompleksitas pertempuran.
edelwater
Itu semacam apa yang saya minta, tetapi saya lebih tertarik pada filosofi umum daripada daftar.
john01dav
Ini adalah pertanyaan luas karena ada 21.000+ buku tentang praktik / pola / desain arsitektur aplikasi, dll: safaribooksonline.com/search/?query=application%20architecture
edelwater
Buku hanya bisa menunjukkan popularitas suatu pertanyaan, bukan luasnya jawaban yang baik. Jika menurut Anda diperlukan buku untuk menjawab pertanyaan ini, maka tentu saja buatlah rekomendasi.
john01dav
0

Berhenti menulis semuanya OOP dan tambahkan beberapa layanan sebagai gantinya.

Sebagai contoh, saya baru-baru ini menulis GOAP untuk sebuah game. Anda akan melihat contoh pada aksi pasangan internet mereka ke mesin gim dengan gaya OOP:

Action.Process();

Dalam kasus saya, saya perlu memproses tindakan baik di mesin maupun di luar mesin, mensimulasikan tindakan dengan cara yang kurang rinci di mana pemain tidak ada. Jadi alih-alih saya menggunakan:

Interface IActionProcessor<TAction>
{
    void ProcessAction(TAction action);
}

ini memungkinkan saya untuk memisahkan logika dan memiliki dua kelas.

ActionProcesser_InEngine.ProcessAction()


ActionProcesser_BackEnd.ProcessAction()

Ini bukan OOP, tetapi itu berarti bahwa tindakan sekarang tidak memiliki tautan ke mesin. Dengan demikian modul GOAP saya benar-benar terpisah dari sisa permainan.

Setelah selesai saya hanya menggunakan dll dan tidak memikirkan implementasi internal.

Ewan
sumber
1
Bagaimana pemroses tindakan Anda tidak berorientasi objek?
Greg Burghardt
2
Saya pikir poin Anda adalah: “Jangan mencoba untuk menghasilkan desain OOP, karena OOP hanya satu dari banyak teknik untuk mengatasi kompleksitas - sesuatu yang lain (misalnya pendekatan prosedural) mungkin bekerja lebih baik dalam kasus Anda. Tidak masalah bagaimana Anda sampai di sana, selama Anda memiliki batasan modul yang jelas yang abstrak atas detail implementasi. "
amon
1
Saya pikir masalahnya bukan pada level kode. Itu di tingkat yang lebih tinggi. Masalah dengan basis kode adalah simptom bukan penyakit. Solusinya tidak akan datang dari kode. Saya pikir itu akan datang dari desain dan pemodelannya.
Laiv
2
Jika saya membaca ini dengan membayangkan diri saya sebagai OP, saya menemukan jawaban ini kurang detail dan tidak memberikan informasi yang berguna, karena tidak menggambarkan bagaimana IActionProcessor benar-benar memberikan nilai alih-alih hanya menambahkan lapisan tipuan lainnya.
whatsisname
menggunakan layanan seperti ini lebih bersifat prosedural daripada OO. Ini memberikan pemisahan yang lebih jelas antara tanggung jawab daripada OO, yang mana OP telah temukan dapat dengan cepat menjadi terlalu berpasangan
Ewan
0

Apa yang ditanyakan seorang ibu! Saya mungkin mempermalukan diri sendiri mencoba yang ini dengan pikiran aneh saya (dan saya akan senang mendengar saran jika saya benar-benar tidak aktif). Tetapi bagi saya hal yang paling berguna yang saya pelajari belakangan ini di domain saya (yang termasuk game di masa lalu, sekarang VFX) adalah untuk menggantikan interaksi antara antarmuka abstrak dengan data sebagai mekanisme decoupling (dan akhirnya mengurangi jumlah informasi yang diperlukan antara hal-hal dan tentang satu sama lain sampai batas minimum absolut). Ini mungkin terdengar sangat gila (dan saya mungkin menggunakan segala macam istilah yang buruk).

Namun katakanlah saya memberi Anda pekerjaan yang cukup mudah dikelola. Anda memiliki file ini yang berisi data adegan dan animasi untuk disajikan. Ada dokumentasi yang mencakup format file. Satu-satunya tugas Anda adalah memuat file, membuat gambar-gambar cantik untuk animasi menggunakan path tracing, dan menampilkan hasilnya ke file gambar. Itu aplikasi skala yang cukup kecil yang mungkin tidak akan menjangkau lebih dari puluhan ribu LOC bahkan untuk perender yang cukup canggih (pasti bukan jutaan).

masukkan deskripsi gambar di sini

Anda memiliki dunia terisolasi Anda sendiri untuk penyaji ini. Itu tidak terpengaruh oleh dunia luar. Ini mengisolasi kompleksitasnya sendiri. Di luar kekhawatiran membaca file adegan ini dan mengeluarkan hasil Anda ke file gambar, Anda bisa fokus sepenuhnya hanya pada rendering. Jika ada yang salah dalam proses, Anda tahu itu ada di renderer dan tidak ada yang lain, karena tidak ada lagi yang terlibat dalam gambar ini.

Sementara itu, katakan saja Anda harus membuat penyaji Anda berfungsi dalam konteks perangkat lunak animasi besar yang sebenarnya memiliki jutaan LOC. Alih-alih hanya membaca format file yang ramping dan terdokumentasi untuk mendapatkan data yang diperlukan untuk rendering, Anda harus melalui semua jenis antarmuka abstrak untuk mengambil semua data yang perlu Anda lakukan:

masukkan deskripsi gambar di sini

Tiba-tiba penyaji Anda tidak lagi berada di dunianya sendiri yang kecil dan terisolasi. Ini terasa begitu, jauh lebih kompleks. Anda harus memahami desain keseluruhan dari keseluruhan perangkat lunak sebagai satu kesatuan organik dengan bagian yang berpotensi banyak bergerak, dan mungkin bahkan kadang-kadang harus memikirkan implementasi hal-hal seperti jerat atau kamera jika Anda menekan bottleneck atau bug di salah satu fungsi.

Fungsionalitas vs. Data yang Efisien

Dan salah satu alasannya adalah karena fungsionalitas jauh lebih kompleks daripada data statis. Ada juga banyak cara pemanggilan fungsi bisa salah dengan cara yang tidak bisa membaca data statis. Ada begitu banyak efek samping tersembunyi yang dapat terjadi ketika memanggil fungsi-fungsi tersebut meskipun secara konseptual hanya mengambil data read-only untuk rendering. Itu juga dapat memiliki banyak alasan untuk berubah. Beberapa bulan dari sekarang, Anda mungkin menemukan antarmuka mesh atau tekstur mengubah atau mencela bagian-bagian dengan cara yang mengharuskan Anda untuk menulis ulang bagian yang besar dan kuat dari penyaji Anda dan mengikuti perubahan itu, meskipun Anda mengambil data yang sama persis, meskipun input data ke renderer Anda belum berubah sama sekali (hanya fungsionalitas yang diperlukan untuk akhirnya mengakses semuanya).

Jadi, jika mungkin, saya telah menemukan bahwa data yang disederhanakan adalah mekanisme pelepasan jenis yang sangat baik yang benar-benar memungkinkan Anda menghindari keharusan untuk memikirkan keseluruhan sistem secara keseluruhan dan memungkinkan Anda hanya berkonsentrasi pada satu bagian yang sangat spesifik dari sistem untuk membuat perbaikan, tambahkan fitur baru, perbaiki hal-hal, dll. Ini mengikuti pola pikir I / O yang sangat besar untuk bagian-bagian besar yang membentuk perangkat lunak Anda. Input ini, lakukan hal Anda, output itu, dan tanpa melalui puluhan antarmuka abstrak panggilan fungsi tak berujung sepanjang jalan. Dan itu mulai menyerupai, sampai taraf tertentu, pemrograman fungsional.

Jadi ini hanya satu strategi dan mungkin tidak berlaku untuk semua orang. Dan tentu saja jika Anda terbang sendiri, Anda masih harus mempertahankan semuanya (termasuk format data itu sendiri), tetapi perbedaannya adalah ketika Anda duduk untuk melakukan perbaikan pada renderer itu, Anda dapat benar-benar hanya fokus pada renderer sebagian besar dan tidak ada yang lain. Ia menjadi sangat terisolasi di dunianya sendiri yang kecil - sekitar terisolasi seperti halnya dengan data yang diperlukan untuk input yang disederhanakan.

Dan saya menggunakan contoh format file tetapi tidak harus berupa file yang menyediakan data streamline yang menarik untuk input. Itu bisa berupa basis data dalam memori. Dalam kasus saya ini adalah sistem entitas-komponen dengan komponen yang menyimpan data yang menarik. Namun saya telah menemukan prinsip dasar decoupling terhadap data yang disederhanakan (namun Anda melakukannya) sehingga jauh lebih sedikit pada kapasitas mental saya daripada sistem sebelumnya yang saya kerjakan yang berputar di sekitar abstraksi dan banyak dan banyak dan banyak interaksi yang terjadi di antara semua ini antarmuka abstrak yang membuatnya tidak mungkin hanya duduk dengan satu hal dan hanya memikirkan hal itu dan sedikit hal lainnya. Otak saya memenuhi hampir semua jenis sistem sebelumnya dan ingin meledak karena ada begitu banyak interaksi yang terjadi di antara begitu banyak hal,

Decoupling

Jika Anda ingin meminimalkan berapa banyak basis kode yang lebih besar membebani otak Anda, maka buatlah itu jadi setiap bagian yang kuat dari perangkat lunak (keseluruhan sistem rendering, seluruh sistem fisika, dll) hidup di dunia yang paling terpencil mungkin. Minimalkan jumlah komunikasi dan interaksi yang berjalan ke minimum minimum melalui data yang paling efisien. Anda bahkan mungkin menerima beberapa redundansi (beberapa pekerjaan yang berlebihan untuk prosesor, atau bahkan untuk diri Anda sendiri) jika pertukaran adalah sistem yang jauh lebih terisolasi yang tidak harus berbicara dengan banyak hal lain sebelum dapat melakukan pekerjaannya.

Dan ketika Anda mulai melakukan itu, rasanya seperti Anda memelihara selusin aplikasi skala kecil alih-alih satu aplikasi raksasa. Dan saya menemukan itu jauh lebih menyenangkan juga. Anda dapat duduk dan hanya bekerja pada satu sistem sesuka hati Anda tanpa melibatkan diri dengan dunia luar. Itu hanya menjadi menginput data yang tepat dan mengeluarkan data yang tepat di akhir ke suatu tempat di mana sistem lain dapat melakukannya (pada titik mana beberapa sistem lain mungkin memasukkan itu dan melakukan hal itu, tetapi Anda tidak perlu peduli tentang itu ketika bekerja pada sistem Anda). Tentu saja Anda masih harus berpikir tentang bagaimana semuanya terintegrasi dalam antarmuka pengguna, misalnya (saya masih harus memikirkan desain semuanya sebagai keseluruhan untuk GUI), tetapi setidaknya tidak ketika Anda duduk dan bekerja pada sistem yang ada atau memutuskan untuk menambahkan yang baru.

Mungkin saya sedang menggambarkan sesuatu yang jelas bagi orang-orang yang selalu mengetahui metode rekayasa terbaru. Saya tidak tahu Tapi itu tidak jelas bagiku. Saya ingin mendekati desain perangkat lunak di sekitar objek yang saling berinteraksi dan fungsi dipanggil untuk perangkat lunak skala besar. Dan buku-buku yang saya baca awalnya pada desain perangkat lunak skala besar yang berfokus pada desain antarmuka di atas hal-hal seperti implementasi dan data (mantra pada waktu itu adalah bahwa implementasi tidak terlalu penting, hanya antarmuka, karena yang pertama dapat dengan mudah diganti atau diganti ). Pada awalnya saya tidak berpikir secara intuitif untuk memikirkan interaksi perangkat lunak sebagai pendahuluan untuk hanya memasukkan dan mengeluarkan data antara subsistem besar yang nyaris tidak berbicara satu sama lain kecuali melalui data yang disederhanakan ini. Namun ketika saya mulai mengalihkan fokus saya untuk mendesain konsep itu, itu membuat segalanya jadi lebih mudah. Saya bisa menambahkan lebih banyak kode tanpa otak saya meledak. Rasanya seperti saya sedang membangun pusat perbelanjaan bukannya menara yang bisa runtuh jika saya menambahkan terlalu banyak atau jika ada patah di bagian mana pun.

Implementasi Kompleks vs. Interaksi Kompleks

Ini adalah satu lagi yang harus saya sebutkan karena saya menghabiskan sebagian besar bagian awal karir saya mencari implementasi yang paling sederhana. Jadi saya menguraikan hal-hal menjadi potongan-potongan paling muda dan paling sederhana, berpikir saya meningkatkan rawatan.

Kalau dipikir-pikir, saya gagal menyadari bahwa saya menukar satu jenis kompleksitas dengan yang lain. Dalam mengurangi semuanya menjadi potongan-potongan yang paling sederhana, interaksi yang terjadi di antara potongan-potongan kecil berubah menjadi web interaksi yang paling kompleks dengan pemanggilan fungsi yang kadang-kadang masuk hingga 30 level ke dalam callstack. Dan tentu saja, jika Anda melihat salah satu fungsi, itu sangat, sangat sederhana dan mudah untuk mengetahui apa fungsinya. Tetapi Anda tidak mendapatkan banyak informasi yang bermanfaat pada saat itu karena setiap fungsi hanya melakukan sedikit. Anda kemudian harus menelusuri semua jenis fungsi dan melompati segala macam lingkaran untuk benar-benar mengetahui apa yang mereka semua lakukan dengan cara yang dapat membuat otak Anda ingin meledak lebih dari satu lebih besar,

Itu bukan untuk menyarankan benda dewa atau semacamnya. Tapi mungkin kita tidak perlu memotong objek mesh kita menjadi benda terkecil seperti objek vertex, objek tepi, objek wajah. Mungkin kita bisa menyimpannya di "mesh" dengan implementasi yang lebih kompleks di belakangnya dengan imbalan interaksi kode yang lebih sedikit secara radikal. Saya dapat menangani implementasi yang cukup kompleks di sana-sini. Saya tidak dapat menangani trilyun interaksi dengan efek samping yang terjadi pada siapa-tahu-di mana dan dalam urutan apa.

Setidaknya saya menemukan bahwa, jauh lebih sedikit pajak pada otak, karena interaksi itulah yang membuat otak saya terluka dalam basis kode yang besar. Tidak ada satu hal khusus.

Generalitas vs. Spesifisitas

Mungkin terkait dengan hal di atas, saya dulu suka generalisasi dan penggunaan kembali kode, dan dulu berpikir tantangan terbesar merancang antarmuka yang baik adalah memenuhi berbagai kebutuhan terluas karena antarmuka akan digunakan oleh segala macam hal yang berbeda dengan kebutuhan yang berbeda. Dan ketika Anda melakukan itu, Anda pasti harus memikirkan seratus hal sekaligus, karena Anda mencoba menyeimbangkan kebutuhan seratus hal sekaligus.

Menggeneralisasi banyak hal membutuhkan banyak waktu. Lihat saja perpustakaan standar yang menyertai bahasa kita. Pustaka standar C ++ berisi fungsionalitas yang sangat sedikit, namun itu membutuhkan tim orang untuk memelihara dan menyesuaikan dengan seluruh komite orang yang berdebat dan membuat proposal tentang desainnya. Itu karena sedikit fungsionalitas sedang mencoba untuk menangani kebutuhan seluruh dunia.

Mungkin kita tidak perlu mengambil banyak hal sejauh ini. Mungkin tidak apa-apa untuk hanya memiliki indeks spasial yang hanya digunakan untuk deteksi tabrakan antara jerat diindeks dan tidak ada yang lain. Mungkin kita bisa menggunakan yang lain untuk jenis permukaan lainnya, dan yang lain untuk rendering. Dulu saya terlalu fokus untuk menghilangkan redudansi semacam ini, tetapi sebagian alasannya adalah karena saya berurusan dengan struktur data yang sangat tidak efisien yang diterapkan oleh banyak orang. Tentu saja jika Anda memiliki satu octree yang membutuhkan 1 gigabyte untuk mesh segitiga 300k belaka, Anda tidak ingin memiliki satu lagi dalam memori.

Tetapi mengapa oktaf begitu tidak efisien? Saya dapat membuat octrees yang hanya membutuhkan 4 byte per node dan membutuhkan kurang dari satu megabyte untuk melakukan hal yang sama seperti versi gigabyte sambil membangun di sebagian kecil dari waktu dan melakukan permintaan pencarian yang lebih cepat. Pada titik itu, beberapa redundansi benar-benar dapat diterima.

Efisiensi

Jadi ini hanya relevan dengan bidang-bidang yang kritis-kinerja tetapi semakin baik Anda mendapatkan hal-hal seperti efisiensi memori, semakin banyak Anda mampu membuang sedikit lebih banyak (mungkin menerima redundansi sedikit lebih dalam pertukaran untuk mengurangi generalisasi atau decoupling) yang mendukung produktivitas . Dan itu membantu untuk menjadi cukup baik dan nyaman dengan profiler Anda dan belajar tentang arsitektur komputer dan hirarki memori, karena Anda dapat membuat lebih banyak pengorbanan untuk efisiensi dengan imbalan produktivitas karena kode Anda sudah sangat efisien dan mampu menjadi sedikit kurang efisien bahkan di area kritis sambil tetap mengungguli kompetisi. Saya telah menemukan bahwa peningkatan dalam bidang ini juga memungkinkan saya untuk pergi dengan implementasi yang lebih sederhana dan lebih sederhana,

Keandalan

Ini agak jelas tetapi mungkin juga menyebutkannya. Hal-hal yang paling andal Anda membutuhkan overhead intelektual minimum. Anda tidak perlu terlalu memikirkan mereka. Mereka hanya bekerja. Sebagai hasilnya, semakin besar Anda menumbuhkan daftar suku cadang ultra andal yang juga "stabil" (tidak perlu diubah) melalui pengujian menyeluruh, semakin sedikit yang harus Anda pikirkan.

Spesifik

Jadi semua itu di atas mencakup beberapa hal umum yang telah membantu saya, tetapi mari kita beralih ke aspek yang lebih spesifik untuk area Anda:

Dalam proyek saya yang lebih kecil, mudah untuk mengingat peta mental tentang bagaimana setiap bagian dari program ini bekerja. Dengan melakukan ini, saya dapat sepenuhnya menyadari bagaimana perubahan apa pun akan memengaruhi program lainnya dan menghindari bug dengan sangat efektif serta melihat dengan tepat bagaimana fitur baru harus masuk ke dalam basis kode. Namun, ketika saya mencoba membuat proyek yang lebih besar, saya merasa tidak mungkin untuk menyimpan peta mental yang baik yang mengarah pada kode yang sangat berantakan dan banyak bug yang tidak diinginkan.

Bagi saya ini cenderung terkait dengan efek samping yang kompleks dan aliran kontrol yang kompleks. Itu adalah pandangan tingkat yang agak rendah tentang hal-hal tetapi semua antarmuka yang tampak terbaik dan semua decoupling dari beton ke abstrak tidak dapat membuatnya lebih mudah untuk berpikir tentang efek samping kompleks yang terjadi dalam aliran kontrol yang kompleks.

Sederhanakan / kurangi efek samping dan / atau sederhanakan aliran kontrol, idealnya keduanya. dan umumnya Anda akan merasa jauh lebih mudah untuk berpikir tentang apa yang dilakukan sistem yang jauh lebih besar, dan juga apa yang akan terjadi sebagai tanggapan terhadap perubahan Anda.

Selain masalah "peta mental" ini, saya merasa sulit untuk menjaga kode saya dipisahkan dari bagian lain dari dirinya sendiri. Misalnya, jika dalam permainan multipemain ada kelas yang menangani fisika gerakan pemain dan yang lain menangani jaringan, maka saya tidak melihat ada cara agar salah satu kelas ini tidak bergantung pada yang lain untuk mendapatkan data gerakan pemain ke sistem jaringan. untuk mengirimnya melalui jaringan. Kopling ini merupakan sumber signifikan dari kompleksitas yang mengganggu peta mental yang baik.

Secara konseptual Anda harus memiliki beberapa kopling. Ketika orang berbicara tentang decoupling, mereka biasanya berarti mengganti satu jenis dengan jenis lain, jenis yang lebih diinginkan (biasanya menuju abstraksi). Bagi saya, mengingat domain saya, cara otak saya bekerja, dll. Jenis yang paling diinginkan untuk mengurangi persyaratan "peta mental" menjadi minimum adalah bahwa data efisien yang dibahas di atas. Satu kotak hitam memuntahkan data yang dimasukkan ke dalam kotak hitam lain, dan keduanya sama-sama tidak menyadari keberadaan satu sama lain. Mereka hanya mengetahui tempat sentral di mana data disimpan (mis: sistem file pusat atau basis data pusat) tempat mereka mengambil input, melakukan sesuatu, dan mengeluarkan output baru yang kemudian dimasukkan oleh kotak hitam lain.

Jika Anda melakukannya dengan cara ini, sistem fisika akan bergantung pada database pusat dan sistem jaringan akan bergantung pada database pusat, tetapi mereka tidak akan tahu apa-apa tentang satu sama lain. Mereka bahkan tidak perlu tahu bahwa ada satu sama lain. Mereka bahkan tidak perlu tahu bahwa antarmuka abstrak untuk satu sama lain ada.

Terakhir, saya sering menemukan diri saya datang dengan satu atau lebih "manajer" kelas yang mengoordinasikan kelas lain. Misalnya, dalam sebuah game, sebuah kelas akan menangani loop tick utama dan akan memanggil metode pembaruan di kelas jaringan dan pemain. Ini bertentangan dengan filosofi dari apa yang saya temukan dalam penelitian saya bahwa setiap kelas harus dapat diuji unit dan dapat digunakan secara terpisah dari yang lain, karena setiap kelas manajer seperti itu dengan tujuan bergantung pada sebagian besar kelas lain dalam proyek. Selain itu, kelas manajer orkestrasi dari sisa program adalah sumber signifikan dari kompleksitas non-mental-mappable.

Anda cenderung membutuhkan sesuatu untuk mengatur semua sistem dalam gim Anda. Central mungkin setidaknya kurang kompleks dan lebih mudah dikelola daripada seperti sistem fisika yang menerapkan sistem rendering setelah selesai. Tapi di sini kita pasti membutuhkan beberapa fungsi yang dipanggil, dan lebih disukai mereka abstrak.

Jadi, Anda dapat membuat antarmuka abstrak untuk sistem dengan updatefungsi abstrak . Kemudian dapat mendaftar sendiri dengan mesin pusat dan sistem jaringan Anda dapat mengatakan, "Hei, saya adalah sistem dan ini adalah fungsi pembaruan saya. Silakan hubungi saya dari waktu ke waktu." Dan kemudian mesin Anda dapat loop melalui semua sistem seperti itu dan memperbaruinya tanpa panggilan fungsi hard-coding ke sistem tertentu.

Itu memungkinkan sistem Anda untuk hidup lebih seperti dunia terisolasi mereka sendiri. Mesin permainan tidak perlu tahu tentang mereka secara khusus (dengan cara yang nyata) lagi. Dan kemudian sistem fisika Anda mungkin memiliki fungsi pembaruan yang disebut, pada saat itu ia memasukkan data yang dibutuhkan dari database pusat untuk gerakan segalanya, menerapkan fisika, lalu mengeluarkan kembali gerakan yang dihasilkan.

Setelah itu sistem jaringan Anda mungkin memiliki fungsi pembaruan yang disebut, pada saat itu ia memasukkan data yang dibutuhkan dari basis data pusat dan output, katakanlah, socket data ke klien. Sekali lagi tujuannya seperti yang saya lihat adalah mengisolasi setiap sistem sebanyak mungkin sehingga ia dapat hidup di dunia kecilnya sendiri dengan sedikit pengetahuan tentang dunia luar. Itu pada dasarnya jenis pendekatan yang diadopsi dalam ECS yang populer di kalangan mesin game.

ECS

Saya kira saya harus membahas ECS sedikit karena banyak pemikiran saya di atas berputar di sekitar ECS dan mencoba merasionalisasi mengapa pendekatan berorientasi data ini untuk decoupling telah membuat pemeliharaan jauh lebih mudah daripada sistem berorientasi objek dan sistem berbasis COM yang telah saya pertahankan. di masa lalu meskipun telah melanggar hampir semua yang saya anggap sakral yang saya pelajari tentang SE. Juga mungkin masuk akal bagi Anda jika Anda ingin membangun gim berskala lebih besar. Jadi ECS bekerja seperti ini:

masukkan deskripsi gambar di sini

Dan seperti pada diagram di atas, MovementSystemmungkin updatefungsinya disebut. Pada titik ini mungkin permintaan database pusat untuk PosAndVelocitykomponen sebagai data untuk dimasukkan (komponen hanya data, tidak ada fungsi). Maka mungkin akan mengulanginya, memodifikasi posisi / kecepatan, dan secara efektif menampilkan hasil baru. Kemudian RenderingSystemmungkin memiliki fungsi pembaruan yang disebut, di mana ia meminta database untuk PosAndVelocitydan Spritekomponen, dan menampilkan gambar ke layar berdasarkan data itu.

Semua sistem sama sekali tidak menyadari keberadaan satu sama lain, dan mereka bahkan tidak perlu memahami apa a Caradalah. Mereka hanya perlu mengetahui komponen spesifik dari minat masing-masing sistem yang menyusun data yang diperlukan untuk mewakili satu. Setiap sistem seperti kotak hitam. Input data dan output data dengan pengetahuan minimal tentang dunia luar, dan dunia luar juga memiliki pengetahuan minimal tentang itu. Mungkin ada beberapa peristiwa yang mendorong dari satu sistem dan muncul dari yang lain sehingga, katakanlah, tabrakan dua entitas dalam sistem fisika dapat menyebabkan audio untuk melihat peristiwa tabrakan yang menyebabkannya memainkan suara, tetapi sistem masih dapat dilupakan. tentang satu sama lain. Dan saya telah menemukan sistem seperti itu jauh lebih mudah untuk dipikirkan. Mereka tidak membuat otak saya ingin meledak bahkan jika Anda memiliki lusinan sistem, karena masing-masing sangat terisolasi. Kamu tidak Anda tidak perlu memikirkan kompleksitas segalanya secara keseluruhan ketika Anda memperbesar dan mengerjakan yang diberikan. Dan karena itu, juga sangat mudah untuk memprediksi hasil perubahan Anda.


sumber