O-notasi dalam desain perangkat lunak

Ketika berhadapan dengan SOLID, saya sering menemukan fakta bahwa tidak mengikuti prinsip-prinsip ini dapat menyebabkan masalah. Masalah diketahui, tetapi tidak diformalkan dengan baik. Artikel ini ditulis dengan tujuan memformalkan situasi khas yang muncul dalam proses penulisan kode, solusi yang mungkin dan konsekuensi yang timbul dari ini. Kita akan berbicara tentang mengapa kita menghadapi kode buruk dan bagaimana masalah tumbuh dengan pertumbuhan program. Sayangnya, dalam kebanyakan kasus penilaian datang ke opsi "banyak" dan "satu", yang mengisyaratkan kegagalan notasi-O, tetapi bahkan analisis semacam itu akan membantu untuk lebih memahami kode mana yang benar-benar berbahaya untuk pengembangan lebih lanjut dari sistem dan kode mana yang toleran.

Definisi


Kami mengatakan bahwa perubahan dalam program memerlukan "O" dari tindakan f (n) jika programmer perlu membuat tidak lebih dari f (n) secara logis memisahkan perubahan dalam program untuk mengimplementasikan perubahan yang akurat ke faktor konstan, di mana n berarti ukuran program.

Metrik


Pertimbangkan beberapa fitur desain Robert Martin dan evaluasi mereka dalam hal notasi-O.
Sebuah desain sulit jika perubahan tunggal menyebabkan kaskade perubahan lain dalam modul dependen. Semakin banyak modul yang harus Anda ubah, semakin kaku desainnya.
Perbedaan yang signifikan adalah perubahan O (1) dan O (n). Yaitu desain kami memungkinkan sejumlah perubahan konstan, atau saat program tumbuh, jumlah perubahan akan meningkat. Selanjutnya, kita harus mempertimbangkan perubahan itu sendiri - mereka juga mungkin berubah menjadi kaku dengan beberapa perilaku asimptotik. Dengan demikian, kekakuan dapat menjadi kompleks hingga O (nm). Parameter m akan disebut kedalaman kekakuan. Bahkan perkiraan kasar dari kekakuan dalam desain yang bahkan memungkinkan kekakuan O (n) terlalu rumit untuk seseorang, karena setiap perubahan harus diperiksa untuk perubahan yang lebih dalam.
Kerapuhan adalah properti dari suatu program yang akan rusak di banyak tempat ketika satu perubahan dilakukan. Seringkali masalah baru muncul di bagian yang tidak memiliki koneksi konseptual dengan yang telah diubah.
Kami tidak akan mempertimbangkan pertanyaan tentang koneksi logis dari modul di mana perubahan terjadi. Oleh karena itu, dari sudut pandang Notasi, tidak ada perbedaan antara kerapuhan dan kekakuan, dan argumen yang berlaku untuk kekakuan berlaku untuk kerapuhan.
Sebuah desain lembam jika mengandung bagian yang mungkin berguna dalam sistem lain, tetapi upaya dan risiko yang terkait dengan mencoba memisahkan bagian-bagian ini dari sistem asli terlalu besar.
Risiko dan upaya dalam definisi ini dapat diartikan sebagai jumlah perubahan yang terjadi dalam modul ketika mencoba untuk abstrak itu dari sistem asli sebagai tidak konstan ketika ukuran modul bertambah. Namun, seperti yang diperlihatkan oleh praktik, itu masih layak untuk diabstraksikan, karena hal ini menertibkan modul itu sendiri dan memungkinkannya untuk ditransfer ke proyek lain. Sangat sering, setelah kebutuhan pertama untuk mentransfer modul ke proyek lain, yang serupa lainnya muncul.

Viskositas
Menghadapi kebutuhan untuk melakukan perubahan, pengembang biasanya menemukan beberapa cara untuk melakukan ini. Beberapa mempertahankan desain, yang lain tidak (yaitu, mereka pada dasarnya adalah "hack"). Jika pendekatan pelestarian desain lebih sulit diimplementasikan daripada retasan, maka viskositas desainnya tinggi. Memecahkan masalah itu salah mudah, tetapi benar itu sulit. Kami ingin merancang program kami sehingga mudah untuk membuat perubahan yang menjaga desain.
Viskositas berikut dapat disebut kepicikan dalam hal notasi O. Ya, pada awalnya pengembang benar-benar memiliki kesempatan untuk membuat perubahan dalam O (1) alih-alih O (n) (karena kekakuan atau kerapuhan), tetapi sering kali perubahan seperti itu menyebabkan kekakuan dan kerapuhan yang lebih besar, yaitu. meningkatkan kedalaman kekakuan. Jika Anda mengabaikan "lonceng" seperti itu, maka perubahan selanjutnya mungkin tidak lagi dapat diselesaikan dengan "peretasan" dan Anda harus membuat perubahan dalam kondisi kekakuan (mungkin lebih dari sebelum "bel") atau membawa sistem ke dalam kondisi baik. Artinya, ketika viskositas terdeteksi, lebih baik segera menulis ulang sistem dengan benar.
Itu terjadi seperti ini: Ivan perlu menulis beberapa kode yang menggulung kaki kecilnya. Memanjat ke berbagai bagian program, di mana, ketika ia mencurigai, bokryad telah dihisap lebih dari sekali, ia menemukan fragmen yang cocok. Ini menyalinnya, menempel ke modulnya dan membuat perubahan yang diperlukan.

Tetapi Ivan tidak tahu bahwa kode yang dia ekstrak dengan mouse menempatkan Peter di sana, mengambilnya dari modul yang ditulis oleh Sveta. Sveta adalah yang pertama merokok di pinggir jalan, tetapi dia tahu bahwa proses ini sangat mirip dengan merokok marmut. Dia menemukan suatu tempat kode yang karmyachit karmaglot, disalin ke modulnya dan dimodifikasi. Ketika kode yang sama muncul dalam bentuk yang sedikit berbeda berulang-ulang, pengembang kehilangan ide abstraksi.
Dalam situasi ini, menjadi jelas bahwa ketika ada kebutuhan untuk mengubah dasar tindakan yang digali, perubahan ini harus dilakukan di n tempat. Mengingat kemungkinan modifikasi unik di setiap tempel salin, ini juga dapat menghasilkan perubahan yang tidak terkait secara logis. Dalam hal ini, ada peluang untuk melupakan perubahan di tempat lain, karena tidak ada perlindungan pada tahap kompilasi. Jadi ini juga bisa berubah menjadi O (n) pengujian iterasi.

Aplikasi Tentang Notasi SOLID


SRP (Prinsip Tanggung Jawab Tunggal). Entitas perangkat lunak hanya memiliki satu alasan untuk perubahan (tanggung jawab). Dengan kata lain, misalnya, kelas tidak boleh mengikuti logika dan pemetaan bisnis, karena ketika mengubah satu tanggung jawab, kita harus memastikan bahwa kita tidak merusak tanggung jawab lain. Artinya, ketidakkonsistenan dengan prinsip SRP menghasilkan kekakuan dan kerapuhan. Mengikuti prinsip ini juga membantu menyingkirkan inersia dan mentransfer modul dari satu program ke program lain dengan jumlah dependensi yang berpotensi lebih kecil.

Perilaku asimptotik dari perubahan pada dasarnya tetap sama dengan tanpa mengikuti prinsip, tetapi faktor konstan menurun secara signifikan. Dalam kedua kasus, kita harus memeriksa seluruh konten kelas dan, jika mengubah antarmuka entitas, tempat di mana mereka berinteraksi dengan entitas ini. Hanya mengikuti SRP membantu mengurangi antarmuka dan kemungkinan perubahannya, serta jumlah implementasi internal, yang setelah perubahan dapat berubah menjadi salah. Alasan yang sama, terpotong untuk diskusi antarmuka, adalah valid untuk ISP (Interface Segregation Principle).

OCP (Prinsip Buka Tutup). Entitas perangkat lunak (kelas, modul, fungsi, dll.) Harus terbuka untuk ekspansi dan ditutup untuk modifikasi. Ini harus dipahami sedemikian rupa sehingga kita harus dapat mengubah perilaku modul tanpa mempengaruhi kode sumbernya. Biasanya, ini dicapai melalui warisan dan polimorfisme. Karena melanggar prinsip LSP adalah pelanggaran OCP, dan DIP adalah sarana untuk mempertahankan OCP, berikut ini dapat diterapkan untuk LSP dan DIP. Ketidakpatuhan dengan prinsip keterbukaan-kedekatan memaksa untuk membuat perubahan di semua entitas yang tidak tertutup terkait perubahan ini.

Situasi yang cukup sepele adalah, misalnya, keberadaan rantai ifs yang menentukan jenis variabel di antara daftar kelas anak. Struktur seperti itu dapat muncul dalam program lebih dari sekali. Dan setiap kali Anda menambahkan kelas anak baru, Anda harus membuat perubahan yang sesuai di setiap rantai tersebut. Situasi serupa dapat terjadi tidak hanya dengan kelas anak, tetapi juga dengan pertimbangan tidak semua kasus tertentu yang mungkin [Ini merujuk pada kasus tidak pada saat penulisan, tetapi secara umum. Kasus mungkin muncul kemudian].

Sekarang pertimbangkan situasi ketika kita membuat perubahan dari tipe yang sama, yang, karena perbedaan dengan prinsip keterbukaan-kedekatan, membutuhkan operasi n dari kita. Kemudian, jika kita membiarkan semuanya apa adanya, mendukung arsitektur untuk mempertimbangkan kasus-kasus khusus, dan tidak menggeneralisasi, kita akan mendapatkan kompleksitas keseluruhan untuk semua perubahan O (mn). Jika kami menutup semua tempat sehubungan dengan perubahan ini, maka perubahan selanjutnya akan mengambil O (1) alih-alih O (m). Dengan demikian, keseluruhan kompleksitas dikurangi menjadi O (m + n). Ini berarti memulai OCP tidak pernah terlambat.

Martin mengatakan tentang situasi ini bahwa Anda seharusnya tidak menebak (jika Anda tidak tahu pasti, tentu saja) bagaimana menutup dari perubahan pertama, tetapi setelah perubahan pertama itu layak ditutup, karena perubahan pertama adalah penanda bahwa sistem tidak akan selalu tetap dalam keadaan saat ini. Ini logis, karena kita melakukan tindakan O (1 * n) karena tidak tertutup, dan kemudian tindakan O (m) untuk menutup diri dari perubahan selanjutnya. Secara total, kami mendapatkan kompleksitas keseluruhan O (n + m), tetapi pada saat yang sama kami melakukan semua tindakan tepat ketika mereka menjadi perlu dan tidak melakukan apa pun di muka, tidak tahu apakah itu akan diperlukan.

Pola dan Notasi-O


Satu lagi analogi dapat ditarik antara notasi-O dalam teori komputasi dan notasi-O dalam desain. Ini terdiri dari fakta bahwa kita mengurangi jumlah perhitungan menggunakan algoritma dan struktur data, seperti pohon pencarian dan tumpukan, yang memecahkan masalah tipikal lebih cepat daripada solusi "langsung", dan jumlah operasi programmer dengan desain yang baik, di mana ia dapat menggunakan juga solusi tipikal yang baik disebut pola desain. Anda dapat mengevaluasi efek pola dalam konteks prinsip-prinsip SOLID dan, sebagai hasilnya, dalam konteks notasi-O.

Sebagai contoh, template Mediator menghilangkan kemungkinan memecahkan sesuatu dalam program ketika mengubah logika interaksi antara dua objek, karena itu sepenuhnya merangkum dan menjamin kompleksitas konstan dari perubahan seperti itu.

Template Adaptor memungkinkan kami untuk menggunakan (baca menambahkan) entitas dengan antarmuka yang berbeda, yang akan kami gunakan untuk tujuan bersama. Menggunakan templat ini, Anda dapat menanamkan objek baru dengan antarmuka yang tidak kompatibel ke dalam sistem untuk jumlah operasi yang tidak tergantung pada ukuran sistem.

Karena struktur data dapat mendukung beberapa operasi dengan asimptotik yang baik, dan beberapa dengan yang buruk, maka pola berperilaku fleksibel terhadap beberapa perubahan dan kaku terhadap yang lain.

Batas yang masuk akal


Ketika berhadapan dengan notasi-O, bekerja pada masalah optimisasi, kita harus ingat bahwa tidak selalu algoritma dengan asimptotik terbaik paling cocok untuk menyelesaikan masalah. Harus dipahami bahwa menyortir dengan gelembung untuk array 3 elemen akan bekerja lebih cepat daripada piramida, meskipun fakta bahwa penyortiran piramidal memiliki asimtotik yang lebih baik. Untuk nilai kecil n, faktor konstan memainkan peran penting, yang disembunyikan notasi-O. O-notasi dalam desain bekerja dengan cara yang sama. Untuk proyek-proyek kecil, tidak masuk akal untuk memagari banyak template, karena biaya pelaksanaannya melebihi jumlah perubahan yang harus dibuat dari "desain yang buruk".

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


All Articles