Berapa besar basis kode non-OO dikelola?


27

Saya selalu melihat abstraksi adalah fitur yang sangat berguna yang disediakan OO untuk mengelola basis kode. Tapi bagaimana basis kode non-OO besar dikelola? Atau apakah mereka hanya menjadi " Bola Besar Lumpur " pada akhirnya?

Pembaruan:
Sepertinya semua orang berpikir 'abstraksi' hanyalah modularisasi atau penyembunyian data. Tapi IMHO, itu juga berarti penggunaan 'Kelas Abstrak' atau 'Antarmuka' yang merupakan keharusan untuk injeksi ketergantungan dan dengan demikian menguji. Bagaimana basis kode non-OO mengelola ini? Dan juga, selain abstraksi, enkapsulasi juga banyak membantu untuk mengelola basis kode besar saat mendefinisikan dan membatasi hubungan antara data dan fungsi.

Dengan C, sangat mungkin untuk menulis kode pseudo-OO. Saya tidak tahu banyak tentang bahasa non-OO lainnya. Jadi, apakah ini cara untuk mengelola basis kode C yang besar?


6
Dengan cara agnostik bahasa, tolong jelaskan suatu objek. Apa itu, bagaimana ia dimodifikasi, apa yang harus diwarisi dan apa yang harus disediakannya? Kernel Linux penuh dengan struktur yang dialokasikan dengan banyak pembantu dan pointer fungsi, tetapi itu mungkin tidak akan memenuhi definisi berorientasi objek untuk sebagian besar. Namun, ini adalah salah satu contoh terbaik dari basis kode yang dipelihara dengan sangat baik. Mengapa? Karena setiap sub sistem pengelola tahu apa yang menjadi tanggung jawab mereka.
Tim Post

Dengan cara agnostik bahasa, tolong jelaskan bagaimana Anda melihat basis kode dikelola, dan apa yang OO lakukan dengan ini.
David Thornley

@Tim Post Saya tertarik dengan manajemen kode sumber kernel Linux. Bisakah Anda jelaskan sistemnya lebih lanjut? Mungkin sebagai jawaban dengan contoh?
Gulshan

7
Di masa lalu, kami menggunakan tautan terpisah untuk tiruan dan bertopik untuk pengujian unit. Ketergantungan Injeksi hanyalah salah satu teknik di antara beberapa. Kompilasi bersyarat adalah hal lain.
Macneil

Saya pikir itu adalah peregangan untuk merujuk ke basis kode besar (OO atau sebaliknya) sebagai "dikelola." Akan lebih baik untuk memiliki definisi istilah pusat yang lebih baik dalam pertanyaan Anda.
tottinge

Jawaban:


43

Anda tampaknya berpikir bahwa OOP adalah satu-satunya cara untuk mencapai abstraksi.

Meskipun OOP tentu sangat pandai dalam melakukan itu, tidak berarti satu-satunya cara. Proyek-proyek besar juga dapat dikelola dengan modularisasi tanpa kompromi (lihat saja Perl atau Python, keduanya unggul dalam hal itu, dan begitu juga bahasa fungsional seperti ML dan Haskell), dan dengan menggunakan mekanisme seperti template (dalam C ++).


27
+1 Juga, Anda dapat menulis "Big Ball of Mud" menggunakan OOP jika Anda tidak tahu apa yang Anda lakukan.
Larry Coleman

Bagaimana dengan basis kode C?
Gulshan

6
@ Gulshan: Banyak basis kode C besar adalah OOP. Hanya karena C tidak memiliki kelas tidak berarti bahwa OOP tidak dapat dicapai dengan sedikit usaha. Selain itu, C memungkinkan modularisasi yang baik menggunakan header dan idiom PIMPL. Hampir tidak senyaman atau sehebat modul dalam bahasa modern, tetapi sekali lagi cukup baik.
Konrad Rudolph

9
C memungkinkan modularisasi pada level file. Antarmuka berjalan dalam file .h, fungsi yang tersedia untuk umum dalam file .c, dan variabel dan fungsi pribadi mendapatkan staticpengubah akses terlampir.
David Thornley

1
@ Konrad: sementara saya setuju bahwa OOP bukan satu-satunya cara untuk melakukannya, saya percaya OP mungkin benar-benar C dalam pikiran, yang bukan bahasa fungsional atau dinamis. Jadi saya ragu menyebutkan Perl dan Haskell akan berguna baginya. Saya benar-benar menemukan komentar Anda lebih relevan dan berguna untuk OP ( tidak berarti bahwa OOP tidak dapat dicapai dengan sedikit usaha ); Anda dapat mempertimbangkan untuk menambahkannya sebagai jawaban terpisah dengan detail tambahan, mungkin didukung dengan cuplikan kode atau beberapa tautan. Setidaknya akan memenangkan suara saya, dan sangat mungkin OP. :)
Groo

11

Modul, fungsi (eksternal / internal), subrutin ...

seperti Konrad katakan, OOP bukan satu-satunya cara untuk mengelola basis kode besar. Sebagai soal fakta, banyak perangkat lunak ditulis sebelum itu (sebelum C ++ *).


* Dan ya, saya tahu bahwa C ++ bukan satu-satunya yang mendukung OOP, tetapi entah bagaimana saat itulah pendekatan itu mulai mengambil inersia.
Benteng


6

Secara realistis baik perubahan yang jarang terjadi (pikirkan perhitungan pensiun Jaminan Sosial) dan / atau pengetahuan yang sudah berurat berakar karena orang-orang yang memelihara sistem seperti itu telah melakukannya untuk sementara waktu (yang sinis diambil adalah keamanan pekerjaan).

Solusi yang lebih baik adalah validasi berulang, yang saya maksudkan tes otomatis (misalnya pengujian unit) dan pengujian manusia yang mengikuti langkah-langkah yang dilarang (misalnya pengujian regresi) "sebagai lawan klik-klik dan lihat apa yang pecah".

Untuk mulai bergerak menuju semacam pengujian otomatis dengan basis kode yang ada, saya sarankan membaca Michael Feather's Bekerja Efektif dengan Kode Legacy , yang merinci pendekatan untuk membawa basis kode yang ada sampai semacam kerangka pengujian berulang OO atau tidak. Ini mengarah pada jenis ide yang dijawab oleh orang lain seperti modularisasi, tetapi buku ini menjelaskan pendekatan yang tepat untuk melakukannya tanpa merusak barang-barang.


+1 untuk buku Michael Feather. Ketika Anda merasa tertekan tentang basis kode yang jelek, (ulang) -baca itu :)
Matthieu

5

Meskipun injeksi dependensi berdasarkan antarmuka atau kelas abstrak adalah cara yang sangat baik untuk melakukan pengujian, itu tidak perlu. Jangan lupa bahwa hampir semua bahasa memiliki fungsi pointer atau eval, yang dapat melakukan apa pun yang dapat Anda lakukan dengan antarmuka atau kelas abstrak (masalahnya adalah mereka dapat melakukan lebih banyak , termasuk banyak hal buruk, dan mereka tidak t sendiri menyediakan metadata). Program seperti itu sebenarnya dapat mencapai injeksi ketergantungan dengan mekanisme ini.

Saya merasa sangat teliti dengan metadata sangat membantu. Dalam bahasa OO hubungan antara bit kode didefinisikan (sampai tingkat tertentu) oleh struktur kelas, dengan cara yang cukup standar untuk memiliki hal-hal seperti API refleksi. Dalam bahasa prosedural akan sangat membantu jika Anda menciptakannya sendiri.

Saya juga menemukan pembuatan kode jauh lebih bermanfaat dalam bahasa prosedural (dibandingkan dengan bahasa berorientasi objek). Ini menjamin meta-data selaras dengan kode (karena digunakan untuk menghasilkannya) dan memberi Anda sesuatu yang mirip titik potong pemrograman berorientasi aspek - tempat Anda dapat menyuntikkan kode saat Anda membutuhkannya. Kadang-kadang itu satu-satunya cara untuk melakukan pemrograman KERING di lingkungan seperti itu yang bisa saya pikirkan.


3

Sebenarnya, seperti yang baru-baru ini Anda temukan , fungsi urutan pertama adalah semua yang Anda butuhkan untuk inversi ketergantungan.

C mendukung fungsi urutan pertama dan bahkan penutupan sampai batas tertentu . Dan makro C adalah fitur yang kuat untuk pemrograman generik, jika ditangani dengan hati-hati.

Semuanya ada di sana. SGLIB adalah contoh yang cukup baik tentang bagaimana C dapat digunakan untuk menulis kode yang sangat dapat digunakan kembali. Dan saya percaya ada banyak lagi di luar sana.


2

Bahkan tanpa abstraksi, sebagian besar program dipecah menjadi beberapa bagian. Bagian-bagian itu biasanya berhubungan dengan tugas atau kegiatan tertentu dan Anda mengerjakannya dengan cara yang sama seperti Anda bekerja pada bit paling spesifik dari program yang diabstraksi.

Dalam proyek-proyek kecil hingga menengah, ini sebenarnya lebih mudah dilakukan dengan implementasi OO murni.


2

Abstraksi, kelas abstrak, injeksi dependensi, enkapsulasi, antarmuka dan sebagainya, bukan satu-satunya cara mengendalikan basis kode besar; ini adil dan berorientasi objek.

Rahasia utamanya adalah untuk menghindari berpikir OOP saat mengkode non-OOP.

Modularitas adalah kunci dalam bahasa non-OO. Dalam C ini dicapai seperti yang David Thornley sebutkan dalam komentar:

Antarmuka berjalan dalam file .h, fungsi yang tersedia untuk umum dalam file .c, dan variabel dan fungsi pribadi mendapatkan pengubah akses statis.


1

Salah satu cara mengelola kode adalah menguraikannya ke dalam jenis kode berikut, di sepanjang garis arsitektur MVC (model-view-controller).

  • Penangan input - Kode ini berkaitan dengan perangkat input seperti mouse, keyboard, port jaringan, atau abstraksi tingkat yang lebih tinggi seperti peristiwa sistem.
  • Penangan keluaran - Kode ini berkaitan dengan penggunaan data untuk memanipulasi perangkat eksternal seperti monitor, lampu, port jaringan, dll.
  • Model - Kode ini berkaitan dengan menyatakan struktur data persisten Anda, aturan untuk memvalidasi data persisten, dan menyimpan data persisten ke disk (atau perangkat data persisten lainnya).
  • Tampilan - Kode ini berkaitan dengan pemformatan data untuk memenuhi persyaratan berbagai metode tampilan seperti browser web (HTML / CSS), GUI, baris perintah, format data protokol komunikasi (misalnya JSON, XML, ASN.1, dll).
  • Algoritma - Kode ini secara berulang mengubah set data input menjadi set data output secepat mungkin.
  • Controllers - Kode ini mengambil input melalui handler input, mem-parsing input menggunakan algoritma, dan kemudian mentransformasikan data dengan algoritma lain dengan secara opsional menggabungkan input dengan data persisten atau hanya mentransformasikan input, dan kemudian secara opsional menyimpan data yang ditransformasikan secara persisten melalui model. perangkat lunak, dan secara opsional mengubah data melalui perangkat lunak tampilan untuk merender ke perangkat output.

Metode organisasi kode ini bekerja dengan baik untuk perangkat lunak yang ditulis dalam bahasa OO atau non-OO karena pola desain yang umum sering terjadi pada masing-masing bidang. Juga, jenis batas kode ini sering kali paling longgar digabungkan, kecuali algoritma karena mereka menghubungkan bersama format data dari input ke model dan kemudian ke output.

Evolusi sistem sering kali berbentuk perangkat lunak Anda menangani lebih banyak jenis input, atau lebih banyak jenis output, tetapi model dan pandangannya sama dan pengontrol berperilaku sangat mirip. Atau suatu sistem mungkin dari waktu ke waktu perlu mendukung lebih banyak jenis keluaran yang berbeda walaupun input, model, algoritmenya sama, dan pengontrol dan pandangannya serupa. Atau suatu sistem dapat ditambah untuk menambahkan model dan algoritma baru untuk rangkaian input yang sama, output yang sama, dan tampilan yang serupa.

Salah satu cara pemrograman OO membuat organisasi kode sulit adalah karena beberapa kelas sangat terkait dengan struktur data yang persisten, dan beberapa tidak. Jika struktur data yang persisten terkait erat dengan hal-hal seperti hubungan cascading 1: N atau hubungan m: n, sangat sulit untuk menentukan batasan kelas sampai Anda telah mengode bagian penting dan bermakna dari sistem Anda sebelum Anda tahu Anda melakukannya dengan benar . Setiap kelas yang terkait dengan struktur data persisten akan sulit untuk berkembang ketika skema perubahan data persisten. Kelas yang menangani algoritma, pemformatan, dan penguraian cenderung rentan terhadap perubahan dalam skema struktur data persisten. Menggunakan jenis kode organisasi MVC lebih baik mengisolasi perubahan kode paling berantakan ke kode model.


0

Ketika bekerja dalam bahasa yang tidak memiliki struktur bawaan dan fitur organisasi (misalnya jika tidak memiliki ruang nama, paket, rakitan dll ...) atau di mana ini tidak cukup untuk menjaga basis kode dari ukuran yang terkendali, respons alami adalah untuk mengembangkan strategi kita sendiri untuk mengatur kode.

Strategi organisasi ini mungkin termasuk standar yang berkaitan dengan di mana file yang berbeda harus disimpan, hal-hal yang perlu terjadi sebelum / setelah jenis operasi tertentu, dan konvensi penamaan dan standar pengkodean lainnya, serta banyak "ini adalah bagaimana hal itu diatur - jangan macam-macam dengan itu! " ketik komentar - yang valid selama mereka menjelaskan alasannya!

Karena strategi kemungkinan besar akan berakhir disesuaikan dengan kebutuhan spesifik proyek (orang, teknologi, lingkungan dll ...) sulit untuk memberikan solusi satu ukuran untuk semua untuk mengelola basis kode yang besar.

Oleh karena itu saya percaya saran terbaik adalah merangkul strategi spesifik proyek, dan menjadikan pengelolaannya sebagai prioritas utama: mendokumentasikan struktur, mengapa demikian, proses untuk membuat perubahan, mengauditnya untuk memastikan itu dipatuhi, dan yang terpenting: ubah ketika perlu diubah.

Kita sebagian besar terbiasa dengan kelas dan metode refactoring, tetapi dengan basis kode besar dalam bahasa seperti itu itu adalah strategi pengorganisasian itu sendiri (lengkap dengan dokumentasi) yang perlu di refactored jika diperlukan.

Alasannya sama dengan untuk refactoring: Anda akan mengembangkan mental block untuk bekerja pada bagian kecil dari sistem jika Anda merasa bahwa keseluruhan organisasi itu berantakan, dan pada akhirnya akan membiarkannya memburuk (setidaknya itulah pendapat saya tentang saya t).

Peringatannya juga sama: gunakan pengujian regresi, pastikan Anda dapat dengan mudah kembali jika refactoring salah, dan rancang sedemikian untuk memfasilitasi refactoring di tempat pertama (atau Anda tidak akan melakukannya!).

Saya setuju bahwa ini jauh lebih sulit daripada refactoring kode langsung, dan lebih sulit untuk memvalidasi / menyembunyikan waktu dari manajer / klien yang mungkin tidak mengerti mengapa itu perlu dilakukan, tetapi ini juga merupakan jenis proyek yang paling rentan terhadap pembusukan perangkat lunak. disebabkan oleh desain tingkat atas yang tidak fleksibel ...


0

Jika Anda bertanya tentang pengelolaan basis kode yang besar, Anda bertanya bagaimana menjaga basis kode Anda terstruktur dengan baik pada tingkat yang relatif kasar (perpustakaan / modul / pembangunan subsistem / menggunakan ruang nama / memiliki dokumen yang tepat di tempat yang tepat dll.) Prinsip OO, terutama 'kelas abstrak' atau 'antarmuka', adalah prinsip untuk menjaga kode Anda tetap bersih secara internal, pada tingkat yang sangat terperinci. Dengan demikian, teknik untuk menjaga basis kode yang besar dapat dikelola tidak berbeda untuk kode OO atau non OO.


0

Cara penanganannya adalah Anda mengetahui batas elemen yang Anda gunakan. Misalnya, elemen-elemen berikut dalam C ++ memiliki batas yang jelas dan setiap dependensi di luar perbatasan harus dipikirkan dengan cermat:

  1. fungsi bebas
  2. fungsi anggota
  3. kelas
  4. obyek
  5. antarmuka
  6. ekspresi
  7. panggilan konstruktor / membuat objek
  8. panggilan fungsi
  9. tipe parameter templat

Menggabungkan elemen-elemen ini dan mengenali batas-batasnya, Anda dapat membuat hampir semua gaya pemrograman yang Anda inginkan dalam c ++.

Contoh dari ini adalah untuk fungsi akan menyadari bahwa itu buruk untuk memanggil fungsi lain dari suatu fungsi, karena hal itu menyebabkan ketergantungan, sebaliknya, Anda hanya harus memanggil fungsi anggota dari parameter fungsi asli.


-1

Tantangan teknis terbesar adalah masalah namespace. Tautan sebagian dapat digunakan untuk mengatasi hal ini. Pendekatan yang lebih baik adalah merancang menggunakan standar pengkodean. Kalau tidak semua simbol menjadi berantakan.


-2

Emacs adalah contoh yang bagus untuk ini:

Arsitektur Emacs

Komponen Emacs

Tes Emacs Lisp menggunakan skip-unlessdan let-binduntuk melakukan deteksi fitur dan perlengkapan pengujian:

Kadang-kadang, tidak masuk akal untuk menjalankan tes karena prasyarat yang hilang. Fitur Emacs yang diperlukan mungkin tidak dikompilasi, fungsi yang akan diuji dapat memanggil biner eksternal yang mungkin tidak tersedia pada mesin uji, sebut saja. Dalam hal ini, makro skip-unlessdapat digunakan untuk melewati tes:

 (ert-deftest test-dbus ()
   "A test that checks D-BUS functionality."
   (skip-unless (featurep 'dbusbind))
   ...)

Hasil dari menjalankan tes seharusnya tidak tergantung pada keadaan lingkungan saat ini, dan setiap tes harus meninggalkan lingkungannya dalam keadaan yang sama seperti yang ditemukannya. Terutama, tes tidak boleh bergantung pada variabel kustomisasi Emacs atau pengait, dan jika harus melakukan perubahan pada status Emacs atau status eksternal untuk Emacs (seperti sistem file), ia harus membatalkan perubahan ini sebelum kembali, terlepas dari apakah itu lulus atau gagal.

Tes tidak boleh bergantung pada lingkungan karena setiap dependensi semacam itu dapat membuat tes rapuh atau menyebabkan kegagalan yang hanya terjadi dalam keadaan tertentu dan sulit untuk mereproduksi. Tentu saja, kode yang diuji dapat memiliki pengaturan yang memengaruhi perilakunya. Dalam hal ini, yang terbaik adalah membuat pengujian let-bindsemua variabel pengaturan tersebut untuk mengatur konfigurasi khusus selama durasi pengujian. Tes ini juga dapat mengatur sejumlah konfigurasi yang berbeda dan menjalankan kode yang diuji dengan masing-masing.

Seperti SQLite. Ini dia desainnya:

  1. sqlite3_open () → Buka koneksi ke database SQLite baru atau yang sudah ada. Konstruktor untuk sqlite3.

  2. sqlite3 → Objek koneksi database. Dibuat oleh sqlite3_open () dan dihancurkan oleh sqlite3_close ().

  3. sqlite3_stmt → Objek pernyataan yang disiapkan. Dibuat oleh sqlite3_prepare () dan dihancurkan oleh sqlite3_finalize ().

  4. sqlite3_prepare () → Kompilasi teks SQL ke dalam byte-code yang akan melakukan pekerjaan query atau memperbarui database. Konstruktor untuk sqlite3_stmt.

  5. sqlite3_bind () → Simpan data aplikasi ke dalam parameter SQL asli.

  6. sqlite3_step () → Tingkatkan sqlite3_stmt ke baris hasil selanjutnya atau hingga selesai.

  7. sqlite3_column () → Nilai kolom di baris hasil saat ini untuk sqlite3_stmt.

  8. sqlite3_finalize () → Destructor untuk sqlite3_stmt.

  9. sqlite3_exec () → Fungsi wrapper yang melakukan sqlite3_prepare (), sqlite3_step (), sqlite3_column (), dan sqlite3_finalize () untuk serangkaian satu atau lebih pernyataan SQL.

  10. sqlite3_close () → Destructor untuk sqlite3.

arsitektur sqlite3

Komponen Tokenizer, Parser, dan Generator Kode digunakan untuk memproses pernyataan SQL dan mengubahnya menjadi program yang dapat dieksekusi dalam bahasa mesin virtual atau kode byte. Secara kasar, tiga lapisan teratas ini mengimplementasikan sqlite3_prepare_v2 () . Kode byte yang dihasilkan oleh tiga lapisan teratas adalah pernyataan yang disiapkan. Modul Mesin Virtual bertanggung jawab untuk menjalankan kode byte pernyataan SQL. Modul B-Tree mengatur file database menjadi beberapa penyimpanan kunci / nilai dengan kunci yang dipesan dan kinerja logaritmik. Modul Pager bertanggung jawab untuk memuat halaman file database ke dalam memori, untuk mengimplementasikan dan mengendalikan transaksi, dan untuk membuat dan memelihara file jurnal yang mencegah korupsi database setelah crash atau kegagalan daya. Antarmuka OS adalah abstraksi tipis yang menyediakan serangkaian rutinitas umum untuk mengadaptasi SQLite agar berjalan pada sistem operasi yang berbeda. Secara kasar, empat lapisan terbawah mengimplementasikan sqlite3_step () .

sqlite3 tabel virtual

Tabel virtual adalah objek yang terdaftar dengan koneksi database SQLite terbuka. Dari perspektif pernyataan SQL, objek tabel virtual terlihat seperti tabel atau tampilan lainnya. Namun di balik layar, pertanyaan dan pembaruan pada tabel virtual memanggil metode panggilan balik dari objek tabel virtual alih-alih membaca dan menulis pada file database.

Tabel virtual mungkin mewakili struktur data dalam memori. Atau mungkin mewakili tampilan data pada disk yang tidak dalam format SQLite. Atau aplikasi mungkin menghitung konten tabel virtual sesuai permintaan.

Berikut adalah beberapa kegunaan yang ada dan dipostulatkan untuk tabel virtual:

Antarmuka pencarian teks lengkap
Indeks spasial menggunakan R-Trees
Introspeksi konten disk dari file database SQLite (tabel virtual dbstat)
Baca dan / atau tulis konten file CSV (comma separated value)
Akses sistem file komputer host seolah-olah itu adalah tabel database
Mengaktifkan manipulasi data SQL dalam paket statistik seperti R

SQLite menggunakan berbagai teknik pengujian termasuk:

Tiga memanfaatkan uji dikembangkan secara independen
Cakupan uji cabang 100% dalam konfigurasi yang digunakan
Jutaan dan jutaan kasus uji
Tes kehabisan memori
Tes kesalahan I / O
Tes kerusakan dan kehilangan daya
Tes fuzz
Tes nilai batas
Tes pengoptimalan yang dinonaktifkan
Tes regresi
Tes basis data salah
Penggunaan assert () dan run-time check yang ekstensif
Analisis Valgrind
Pemeriksaan perilaku yang tidak ditentukan
Daftar periksa

Referensi

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.