Enhanced Four Rules untuk Desain Perangkat Lunak

Halo, Habr! Saya menyajikan kepada Anda artikel "Empat Aturan Lebih Baik untuk Desain Perangkat Lunak" oleh David Bryant Copeland. David Bryant Copeland adalah arsitek perangkat lunak dan CTO untuk Stitch Fix. Dia memelihara sebuah blog dan merupakan penulis beberapa buku .


Martin Fowler baru-baru ini mentweet dengan tautan ke posting blognya tentang empat aturan desain sederhana dari Kent Beck, yang menurut saya dapat ditingkatkan lebih lanjut (dan yang kadang-kadang dapat mengirim programmer ke jalur yang salah):


Aturan Kent dari Pemrograman Ekstrim Dijelaskan :


  • Kent berkata, "Jalankan semua tes."
  • Jangan menduplikasi logika. Cobalah untuk menghindari duplikat tersembunyi, seperti hierarki kelas paralel.
  • Semua niat penting bagi programmer harus terlihat jelas.
  • Kode harus memiliki jumlah kelas dan metode sekecil mungkin.

Menurut pengalaman saya, aturan-aturan ini tidak cukup memenuhi kebutuhan desain perangkat lunak. Empat aturan saya untuk sistem yang dirancang dengan baik mungkin:


  • itu ditutupi dengan baik oleh tes dan berhasil melewati mereka.
  • tidak memiliki abstraksi yang tidak secara langsung dibutuhkan oleh program.
  • dia memiliki perilaku yang jelas.
  • membutuhkan paling sedikit konsep.

Bagi saya, aturan ini berasal dari apa yang kami lakukan dengan perangkat lunak kami.


Jadi apa yang kita lakukan dengan perangkat lunak kita?


Kita tidak dapat berbicara tentang desain perangkat lunak tanpa terlebih dahulu berbicara tentang apa yang ingin kita lakukan dengannya.


Perangkat lunak ini ditulis untuk menyelesaikan masalah. Program berjalan dan memiliki perilaku. Perilaku ini dipelajari untuk memastikan operasi yang benar atau untuk mendeteksi kesalahan. Perangkat lunak juga sering berubah untuk memberikan perilaku baru atau berubah.


Oleh karena itu, setiap pendekatan desain perangkat lunak harus difokuskan pada memprediksi, mempelajari, dan memahami perilakunya untuk membuat mengubah perilaku ini sesederhana mungkin.


Kami memeriksa kebenaran perilaku dengan menguji, dan oleh karena itu saya setuju dengan Kent bahwa hal pertama dan yang paling penting adalah perangkat lunak yang dirancang dengan baik harus lulus tes. Saya bahkan akan melangkah lebih jauh dan bersikeras bahwa perangkat lunak harus memiliki tes (mis. Dicakup dengan baik oleh tes).


Setelah perilaku diverifikasi, tiga poin berikut pada kedua daftar terkait dengan memahami perangkat lunak kami (dan karenanya, perilaku). Daftarnya dimulai dengan duplikasi kode, yang benar-benar ada. Namun, dalam pengalaman pribadi saya, terlalu banyak berfokus pada mengurangi duplikasi kode itu mahal. Untuk menghilangkannya, perlu membuat abstraksi yang menyembunyikannya, dan abstraksi inilah yang membuat perangkat lunak sulit untuk dipahami dan diubah.


Menghilangkan duplikasi kode memerlukan abstraksi, dan abstraksi menyebabkan kompleksitas


Jangan Ulangi Diri Anda atau KERING digunakan untuk membenarkan keputusan desain yang kontroversial. Pernahkah Anda melihat kode serupa?


ZERO = BigDecimal.new(0) 

Selain itu, Anda mungkin melihat sesuatu seperti ini:


 public void call(Map payload, boolean async, int errorStrategy) { // ... } 

Jika Anda melihat metode atau fungsi dengan flag, boolean, dll., Maka ini biasanya berarti bahwa seseorang menggunakan prinsip KERING saat refactoring, tetapi kode itu tidak persis sama di kedua tempat, sehingga kode yang dihasilkan harus memiliki cukup fleksibel untuk mengakomodasi kedua perilaku.


Abstraksi umum semacam itu sulit untuk diuji dan dipahami, karena mereka harus menangani lebih banyak kasus daripada kode asli (mungkin digandakan). Dengan kata lain, abstraksi mendukung lebih banyak perilaku daripada yang diperlukan untuk fungsi normal sistem. Dengan demikian, menghilangkan duplikasi kode dapat menciptakan perilaku baru yang tidak diperlukan oleh sistem.


Oleh karena itu, sangat penting untuk menggabungkan beberapa jenis perilaku, tetapi mungkin sulit untuk memahami perilaku seperti apa yang benar-benar diduplikasi. Seringkali potongan kode terlihat serupa, tetapi ini hanya terjadi secara kebetulan.


Pertimbangkan betapa lebih mudahnya menghilangkan duplikasi kode daripada mengembalikannya lagi (misalnya, setelah membuat abstraksi yang dipikirkan dengan buruk). Oleh karena itu, kita perlu berpikir tentang meninggalkan kode duplikat, kecuali kita benar-benar yakin bahwa kita memiliki cara yang lebih baik untuk menghilangkannya.


Membuat abstraksi harus membuat kita berpikir. Jika dalam proses menghilangkan kode duplikat Anda membuat abstraksi umum yang sangat fleksibel, maka Anda mungkin salah jalan.


Ini membawa kita ke titik berikutnya - niat versus perilaku.


Niat Programmer tidak ada artinya - perilaku berarti segalanya


Kami sering memuji bahasa pemrograman, konstruksi, atau cuplikan kode karena "mengungkap maksud programmer." Tapi apa gunanya mengetahui niat jika Anda tidak bisa memprediksi perilaku? Dan jika Anda tahu perilaku, seberapa besar niatnya? Ternyata Anda perlu tahu bagaimana perilakunya seharusnya , tetapi ini tidak sama dengan "niat programmer."


Mari kita lihat contoh ini, yang mencerminkan niat programmer dengan sangat baik, tetapi tidak berperilaku sebagaimana dimaksud:


 function LastModified(props) { return ( <div> Last modified on { props.date.toLocaleDateString() } </div> ); } 

Jelas, programmer merencanakan komponen Bereaksi ini akan menampilkan tanggal dengan pesan "Terakhir diubah pada". Apakah ini berfungsi sebagaimana dimaksud? Tidak juga. Bagaimana jika tanggal ini tidak penting? Semuanya rusak. Kami tidak tahu apakah itu dikandung, atau seseorang hanya melupakannya, dan itu bahkan tidak masalah. Yang penting adalah perilaku.


Dan inilah yang harus kita ketahui jika kita ingin mengubah bagian kode ini. Bayangkan kita perlu mengubah baris menjadi "Modifikasi terakhir". Meskipun kita bisa melakukan ini, tidak jelas apa yang harus terjadi jika tanggal tidak ada. Akan lebih baik jika kita menulis komponen sedemikian rupa untuk membuat perilakunya lebih dimengerti.


 function LastModified(props) { if (!props.date) { throw "LastModified requires a date to be passed"; } return ( <div> Last modified on { props.date.toLocaleDateString() } </div> ); } 

Atau bahkan seperti ini:


 function LastModified(props) { if (props.date) { return ( <div> Last modified on { props.date.toLocaleDateString() } </div> ); } else { return <div>Never modified</div>; } } 

Dalam kedua kasus, perilaku lebih mudah dimengerti, dan niat programmer tidak penting. Misalkan kita memilih alternatif kedua (yang menangani nilai tanggal yang hilang). Ketika kita diminta untuk mengubah pesan, kita dapat melihat perilaku dan memeriksa apakah pesan "Tidak pernah dimodifikasi" benar atau apakah itu juga perlu diubah.


Dengan demikian, semakin tidak ambigu perilakunya , semakin besar peluang kita untuk berhasil mengubahnya. Dan ini berarti bahwa kita mungkin perlu menulis lebih banyak kode atau membuatnya lebih akurat, atau bahkan kadang-kadang menulis kode duplikat.


Ini juga berarti bahwa kita akan membutuhkan lebih banyak kelas, fungsi, metode, dll. Tentu saja, kita ingin menjaga jumlah mereka minimal, tetapi kita tidak boleh menggunakan angka ini sebagai metrik kita. Membuat sejumlah besar kelas atau metode menciptakan overhead konseptual , dan lebih banyak konsep muncul dalam perangkat lunak daripada unit modularitas. Oleh karena itu, kita perlu mengurangi jumlah konsep, yang, pada gilirannya, dapat menyebabkan penurunan jumlah kelas.


Biaya konseptual berkontribusi pada kebingungan dan kompleksitas


Untuk memahami apa yang sebenarnya akan dilakukan kode, Anda perlu mengetahui tidak hanya bidang subjek, tetapi juga semua konsep yang digunakan dalam kode ini (misalnya, saat mencari standar deviasi, Anda harus mengetahui tugas, penambahan, perkalian, untuk panjang loop dan panjang array). Ini menjelaskan mengapa ketika jumlah konsep dalam desain meningkat, kompleksitasnya untuk pemahaman meningkat.


Saya biasa menulis tentang pengeluaran konseptual , dan efek samping yang baik dari mengurangi jumlah konsep dalam suatu sistem adalah bahwa lebih banyak orang dapat memahami sistem ini. Ini pada gilirannya meningkatkan jumlah orang yang dapat membuat perubahan pada sistem ini. Jelas, desain perangkat lunak yang dapat diubah dengan aman oleh banyak orang lebih baik daripada yang hanya dapat diubah oleh segelintir orang. (Oleh karena itu, saya percaya bahwa pemrograman fungsional hardcore tidak akan pernah menjadi populer, karena memerlukan pemahaman yang mendalam tentang banyak konsep yang sangat abstrak.)


Mengurangi biaya konseptual secara alami akan mengurangi jumlah abstraksi dan membuat perilaku lebih mudah dipahami. Saya tidak mengatakan "tidak pernah memperkenalkan konsep baru", saya mengatakan bahwa itu memiliki harga sendiri, dan jika harga ini lebih besar daripada manfaatnya, pengenalan konsep baru harus dipertimbangkan dengan cermat.


Ketika kita menulis kode atau mendesain perangkat lunak, kita harus berhenti memikirkan keanggunan , keindahan, atau ukuran subjektif lainnya dari kode kita. Sebaliknya, kita harus selalu ingat apa yang akan kita lakukan dengan perangkat lunak.


Anda tidak menggantung kode di dinding - Anda mengubahnya


Kode bukanlah karya seni yang dapat Anda cetak dan gantung di museum. Kode sedang dieksekusi. Ini dipelajari dan didebug. Dan, yang paling penting, itu berubah . Dan seringkali. Setiap desain yang sulit untuk dikerjakan harus dipertanyakan dan ditinjau. Setiap desain yang mengurangi jumlah orang yang dapat bekerja dengannya juga harus dipertanyakan.


Kode harus bekerja, jadi harus diuji. Kode memiliki bug dan akan membutuhkan penambahan fitur baru, jadi kita perlu memahami perilakunya. Kode ini hidup lebih lama dari kemampuan programmer tertentu untuk mendukungnya, jadi kita harus mengusahakan kode yang dapat dimengerti oleh banyak orang.


Ketika Anda menulis kode atau mendesain sistem Anda, apakah Anda menyederhanakan penjelasan perilaku sistem? Apakah menjadi lebih mudah untuk memahami bagaimana dia akan berperilaku? Apakah Anda fokus untuk menyelesaikan masalah tepat di depan Anda atau pada masalah yang lebih abstrak?


Selalu berusaha untuk menjaga perilaku sederhana untuk demonstrasi, prediksi dan pemahaman, dan menjaga jumlah konsep seminimal mungkin.

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


All Articles