Mengelola dan mengatur jumlah kelas yang meningkat secara besar-besaran setelah beralih ke SOLID?

50

Selama beberapa tahun terakhir, kami perlahan-lahan beralih ke kode tulisan yang semakin baik, beberapa langkah sekaligus. Kami akhirnya mulai beralih ke sesuatu yang setidaknya menyerupai SOLID, tapi kami belum cukup di sana. Sejak beralih, salah satu keluhan terbesar dari pengembang adalah bahwa mereka tidak tahan meninjau rekan dan melintasi lusinan file di mana sebelumnya setiap tugas hanya mengharuskan pengembang menyentuh 5-10 file.

Sebelum mulai beralih, arsitektur kami diatur seperti halnya (diberikan, dengan satu atau dua urutan file lebih besar):

Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI

Dari segi file, semuanya sangat linier dan kompak. Jelas ada banyak duplikasi kode, kopling ketat, dan sakit kepala, namun, semua orang bisa melewatinya dan mencari tahu. Pemula yang lengkap, orang yang belum pernah membuka Visual Studio, dapat mengetahuinya hanya dalam beberapa minggu. Kurangnya keseluruhan kompleksitas file membuatnya relatif mudah bagi pengembang pemula dan karyawan baru untuk mulai berkontribusi tanpa terlalu banyak waktu. Tapi ini cukup banyak di mana manfaat dari gaya kode keluar jendela.

Saya dengan sepenuh hati mendukung setiap upaya yang kami lakukan untuk memperbaiki basis kode kami, tetapi sangat umum untuk mendapatkan balasan dari anggota tim lainnya tentang perubahan paradigma besar-besaran seperti ini. Beberapa poin pelekatan terbesar saat ini adalah:

  • Tes Unit
  • Hitungan Kelas
  • Kompleksitas Peer Review

Unit test telah menjadi penjualan yang sangat sulit bagi tim karena mereka semua percaya mereka membuang-buang waktu dan bahwa mereka dapat menangani-menguji kode mereka lebih cepat secara keseluruhan daripada masing-masing bagian secara individual. Menggunakan tes unit sebagai dukungan untuk SOLID sebagian besar sia-sia dan sebagian besar menjadi lelucon pada saat ini.

Hitungan kelas mungkin merupakan rintangan tunggal terbesar untuk diatasi. Tugas yang digunakan untuk mengambil 5-10 file sekarang dapat mengambil 70-100! Meskipun masing-masing file ini memiliki tujuan yang berbeda, volume file yang banyak dapat sangat besar. Respons dari tim sebagian besar adalah keluhan dan garukan kepala. Sebelumnya sebuah tugas mungkin memerlukan satu atau dua repositori, satu atau dua model, lapisan logika, dan metode pengontrol.

Sekarang, untuk membangun aplikasi penyimpanan file sederhana, Anda memiliki kelas untuk memeriksa apakah file tersebut sudah ada, kelas untuk menulis metadata, kelas untuk abstrak DateTime.Nowsehingga Anda dapat menyuntikkan waktu untuk pengujian unit, antarmuka untuk setiap file yang berisi logika, file mengandung tes unit untuk setiap kelas di luar sana, dan satu atau lebih file untuk menambahkan semuanya ke wadah DI Anda.

Untuk aplikasi ukuran kecil hingga menengah, SOLID adalah penjualan yang sangat mudah. Semua orang melihat manfaat dan kemudahan perawatan. Namun, mereka tidak melihat proposisi nilai yang baik untuk SOLID pada aplikasi skala sangat besar. Jadi saya mencoba mencari cara untuk meningkatkan organisasi dan manajemen agar kita dapat melewati rasa sakit yang terus tumbuh.


Saya pikir saya akan memberikan sedikit lebih kuat dari contoh volume file berdasarkan pada tugas yang baru saja selesai. Saya diberi tugas untuk mengimplementasikan beberapa fungsi di salah satu layanan microser kami yang lebih baru untuk menerima permintaan sinkronisasi file. Ketika permintaan diterima, layanan melakukan serangkaian pencarian dan pemeriksaan, dan akhirnya menyimpan dokumen ke drive jaringan, serta 2 tabel database terpisah.

Untuk menyimpan dokumen ke drive jaringan, saya memerlukan beberapa kelas khusus:

- IBasePathProvider 
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect

- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests

- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider 
- NewGuidProviderTests

- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests

- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request) 
- PatientFileWriter
- PatientFileWriterTests

Jadi itu total 15 kelas (tidak termasuk POCO dan perancah) untuk melakukan penghematan yang cukup mudah. Jumlah ini menggelembung secara signifikan ketika saya perlu membuat POCO untuk mewakili entitas dalam beberapa sistem, membangun beberapa repo untuk berkomunikasi dengan sistem pihak ketiga yang tidak sesuai dengan ORM kami yang lain, dan membangun metode logika untuk menangani seluk-beluk operasi tertentu.

JD Davis
sumber
52
"Tugas yang digunakan untuk mengambil 5-10 file sekarang dapat mengambil 70-100!" Bagaimana sih? Ini sama sekali tidak normal. Perubahan apa yang Anda buat yang perlu mengubah banyak file ??
Euforia
43
Fakta bahwa Anda harus mengubah lebih banyak file per tugas (jauh lebih banyak!) Berarti Anda melakukan kesalahan SOLID. Intinya adalah untuk mengatur kode Anda (dari waktu ke waktu) dengan cara yang mencerminkan pola perubahan yang diamati, membuat perubahan lebih sederhana. Setiap prinsip dalam SOLID dilengkapi dengan alasan tertentu di baliknya (kapan dan mengapa itu harus diterapkan); sepertinya Anda mendapatkan diri Anda dalam situasi ini dengan menerapkan ini secara membabi buta. Hal yang sama dengan pengujian unit (TDD); jika Anda melakukannya tanpa memiliki pemahaman yang baik tentang bagaimana melakukan desain / arsitektur, Anda akan menggali diri Anda sendiri ke dalam lubang.
Filip Milovanović
60
Anda telah dengan jelas mengadopsi SOLID sebagai agama daripada alat pragmatis untuk membantu menyelesaikan pekerjaan. Jika sesuatu di SOLID membuat lebih banyak pekerjaan atau membuat hal-hal lebih sulit, jangan lakukan itu.
whatsisname
25
@ Euphoric: Masalah ini dapat terjadi dengan dua cara. Saya menduga Anda merespons kemungkinan bahwa 70-100 kelas terlalu banyak. Tetapi bukan tidak mungkin ini kebetulan merupakan proyek besar yang dijejalkan menjadi 5-10 file (Saya telah bekerja di file 20KLOC sebelumnya ...) dan 70-100 sebenarnya adalah jumlah file yang tepat.
Flater
18
Ada gangguan pemikiran yang saya sebut "penyakit objek kebahagiaan", yang merupakan keyakinan bahwa teknik OO adalah tujuan mereka sendiri, bukan hanya satu dari banyak teknik yang mungkin untuk mengurangi biaya bekerja di basis kode besar. Anda memiliki bentuk sangat maju, "penyakit SOLID happiness". SOLID bukan tujuan. Menurunkan biaya pemeliharaan basis kode adalah tujuannya. Evaluasi proposal Anda dalam konteks itu, bukan apakah itu SOLID doktriner. (Bahwa proposal Anda mungkin juga tidak benar-benar doctrinaire SOLID juga merupakan hal yang baik untuk dipertimbangkan.)
Eric Lippert

Jawaban:

104

Sekarang, untuk membangun aplikasi penyimpanan file sederhana, Anda memiliki kelas untuk memeriksa apakah file tersebut sudah ada, kelas untuk menulis metadata, kelas untuk mengabstraksi DateTime. Sekarang Anda dapat menyuntikkan waktu untuk pengujian unit, antarmuka untuk setiap file yang mengandung logika, file yang berisi tes unit untuk setiap kelas di luar sana, dan satu atau lebih file untuk menambahkan semuanya ke wadah DI Anda.

Saya pikir Anda telah salah memahami gagasan tentang satu tanggung jawab. Tanggung jawab tunggal kelas mungkin "menyimpan file". Untuk melakukan itu, maka tanggung jawab itu dapat dipecah menjadi metode yang memeriksa apakah file ada, metode yang menulis metadata dll. Setiap metode tersebut kemudian memiliki tanggung jawab tunggal, yang merupakan bagian dari tanggung jawab keseluruhan kelas.

Kelas untuk abstrak jauh DateTime.Nowkedengarannya bagus. Tapi Anda hanya perlu satu dari itu dan itu bisa dibungkus dengan fitur lingkungan lainnya menjadi satu kelas dengan tanggung jawab untuk mengabstraksi fitur lingkungan. Sekali lagi satu tanggung jawab dengan banyak sub-tanggung jawab.

Anda tidak perlu "antarmuka untuk setiap file yang mengandung logika", Anda membutuhkan antarmuka untuk kelas yang memiliki efek samping, misalnya kelas-kelas yang membaca / menulis ke file atau database; dan bahkan kemudian, mereka hanya diperlukan untuk bagian publik dari fungsi itu. Jadi misalnya di AccountRepo, Anda mungkin tidak memerlukan antarmuka apa pun, Anda mungkin hanya membutuhkan antarmuka untuk akses basis data aktual yang disuntikkan ke dalam repo itu.

Unit test telah menjadi penjualan yang sangat sulit bagi tim karena mereka semua percaya mereka membuang-buang waktu dan bahwa mereka dapat menangani-menguji kode mereka lebih cepat secara keseluruhan daripada masing-masing bagian secara individual. Menggunakan tes unit sebagai dukungan untuk SOLID sebagian besar sia-sia dan sebagian besar menjadi lelucon pada saat ini.

Ini menunjukkan bahwa Anda memiliki tes unit yang salah paham juga. "Unit" dari unit test bukan unit kode. Apakah satuan kode itu? Kelas? Sebuah metode? Variabel? Instruksi mesin tunggal? Tidak, "unit" mengacu pada unit isolasi, yaitu kode yang dapat dieksekusi secara terpisah dari bagian lain dari kode. Tes sederhana apakah tes otomatis adalah tes unit atau tidak adalah apakah Anda dapat menjalankannya secara paralel dengan semua tes unit lainnya tanpa mempengaruhi hasilnya. Ada beberapa aturan praktis lain seputar tes unit, tetapi itu adalah ukuran utama Anda.

Jadi jika bagian dari kode Anda memang dapat diuji secara keseluruhan tanpa mempengaruhi bagian lain, maka lakukan itu.

Selalu bersikap pragmatis dan ingat bahwa semuanya adalah kompromi. Semakin Anda mematuhi KERING, kode Anda yang semakin erat harus menjadi. Semakin banyak Anda memperkenalkan abstraksi, semakin mudah kodenya untuk diuji, tetapi semakin sulit untuk dipahami. Hindari ideologi dan temukan keseimbangan yang baik antara yang ideal dan menjaganya tetap sederhana. Disinilah letak sweet spot efisiensi maksimum untuk pengembangan dan pemeliharaan.

David Arno
sumber
27
Saya ingin menambahkan bahwa sakit kepala yang serupa muncul ketika orang mencoba mematuhi mantra yang terlalu sering diulang-ulang dari "metode seharusnya hanya melakukan satu hal" dan berakhir dengan banyak metode satu baris hanya karena secara teknis dapat dibuat menjadi metode .
Logarr
8
Re "Selalu pragmatis dan ingat semuanya adalah kompromi" : Murid-murid Paman Bob tidak dikenal karena hal ini (tidak peduli niat aslinya).
Peter Mortensen
13
Untuk jumlah bagian pertama, Anda biasanya memiliki kopi magang, bukan suite plug-in-perkolator, flip-switch, periksa-jika-kebutuhan gula isi ulang, kulkas terbuka, keluar-susu, dapatkan -sendok makan, get-down-cup, tuangkan kopi, tambahkan-gula, tambahkan-susu, aduk-cangkir, dan sajikan magang. ; P
Justin Time 2 Reinstate Monica
12
Penyebab utama masalah OP tampaknya adalah kesalahpahaman perbedaan antara fungsi yang harus melakukan tugas tunggal , dan kelas yang harus memiliki tanggung jawab
alephzero
6
"Aturan adalah untuk panduan orang bijak dan kepatuhan orang bodoh." - Douglas Bader
Calanus
29

Tugas yang digunakan untuk mengambil 5-10 file sekarang dapat mengambil 70-100!

Ini adalah kebalikan dari prinsip tanggung jawab tunggal (SRP). Untuk sampai ke titik itu, Anda harus membagi fungsionalitas Anda dengan cara yang sangat halus, tetapi bukan itu yang dilakukan SRP - melakukan hal yang mengabaikan gagasan utama keterpaduan .

Menurut SRP, perangkat lunak harus dibagi menjadi beberapa modul sesuai dengan alasan yang memungkinkan untuk berubah, sehingga perubahan desain tunggal dapat diterapkan hanya dalam satu modul tanpa memerlukan modifikasi di tempat lain. Satu "modul" dalam hal ini mungkin berhubungan dengan lebih dari satu kelas, tetapi jika satu perubahan mengharuskan Anda menyentuh puluhan file maka itu benar-benar beberapa perubahan atau Anda melakukan kesalahan SRP.

Bob Martin, yang awalnya merumuskan SRP, menulis posting blog beberapa tahun yang lalu untuk mencoba mengklarifikasi situasinya. Ini membahas panjang lebar apa "alasan untuk berubah" adalah untuk tujuan SRP. Ini layak dibaca secara keseluruhan, tetapi di antara hal-hal yang patut mendapat perhatian khusus adalah kata-kata alternatif dari SRP ini:

Kumpulkan hal-hal yang berubah karena alasan yang sama . Pisahkan hal-hal yang berubah karena alasan berbeda.

(penekanan milikku). SRP bukan tentang memecah hal-hal menjadi potongan-potongan sekecil mungkin. Itu bukan desain yang bagus, dan tim Anda berhak menolak. Itu membuat basis kode Anda lebih sulit untuk diperbarui dan dipelihara. Sepertinya Anda mungkin mencoba menjual tim Anda berdasarkan pertimbangan pengujian unit, tetapi itu akan menempatkan kereta di depan kuda.

Demikian pula, prinsip pemisahan antarmuka tidak boleh dianggap mutlak. Tidak ada lagi alasan untuk membagi kode Anda dengan sangat baik daripada SRP, dan umumnya selaras dengan SRP. Bahwa antarmuka berisi beberapa metode yang tidak digunakan beberapa klien bukan alasan untuk memecahnya. Anda lagi mencari kohesi.

Selain itu, saya mendesak Anda untuk tidak menggunakan prinsip terbuka-tertutup atau prinsip substitusi Liskov sebagai alasan untuk mendukung hierarki warisan yang mendalam. Tidak ada kopling ketat dari subkelas dengan superclasses, dan kopling ketat adalah masalah desain. Sebaliknya, pilih komposisi daripada warisan di mana pun masuk akal untuk melakukannya. Ini akan mengurangi kopling Anda, dan dengan demikian jumlah file yang perlu disentuh oleh perubahan tertentu, dan itu sejalan dengan inversi ketergantungan.

John Bollinger
sumber
1
Saya kira saya hanya mencoba mencari tahu di mana garisnya. Dalam tugas baru-baru ini, saya harus melakukan operasi yang cukup sederhana, tetapi itu dalam basis kode tanpa banyak perancah atau fungsi yang ada. Karena itu, semua yang perlu saya lakukan sangat sederhana, tetapi semuanya cukup unik dan sepertinya tidak cocok di kelas bersama. Dalam kasus saya, saya perlu menyimpan dokumen ke drive jaringan, dan mencatatnya ke dua tabel database terpisah. Aturan yang mengelilingi setiap langkah cukup khusus. Bahkan generasi nama file (panduan sederhana) memiliki beberapa kelas untuk membuat pengujian lebih nyaman.
JD Davis
3
Sekali lagi, @JDDavis, memilih beberapa kelas daripada satu kelas semata-mata untuk tujuan testability adalah menempatkan kereta di depan kuda, dan itu bertentangan langsung dengan SRP, yang menyerukan pengelompokan fungsi kohesif bersama. Saya tidak bisa memberi tahu Anda tentang hal-hal khusus, tetapi masalah yang memerlukan perubahan fungsional individu memodifikasi banyak file adalah masalah yang harus Anda atasi (dan berusaha untuk menghindari), bukan salah satu yang harus Anda coba benarkan.
John Bollinger
Setuju, saya menambahkan ini. Mengutip Wikipedia, "Martin mendefinisikan tanggung jawab sebagai alasan untuk berubah, dan menyimpulkan bahwa suatu kelas atau modul harus memiliki satu, dan hanya satu, alasan untuk diubah (yaitu ditulis ulang)." dan "dia baru-baru ini menyatakan" Prinsip ini adalah tentang orang. "" Sebenarnya, saya percaya ini berarti bahwa "tanggung jawab" dalam SRP mengacu pada pemangku kepentingan, bukan fungsi. Kelas harus bertanggung jawab atas perubahan yang diperlukan hanya oleh satu pemangku kepentingan (orang yang mengharuskan Anda mengubah program Anda), sehingga Anda berubah sebanyak BEBERAPA hal dalam menanggapi pemangku kepentingan yang berbeda yang menuntut perubahan.
Corrodias
12

Tugas yang digunakan untuk mengambil 5-10 file sekarang dapat mengambil 70-100!

Ini bohong. Tugas tidak pernah hanya mengambil 5-10 file.

Anda tidak menyelesaikan tugas apa pun dengan kurang dari 10 file. Mengapa? Karena Anda menggunakan C #. C # adalah bahasa tingkat tinggi. Anda menggunakan lebih dari 10 file hanya untuk membuat hello world.

Oh tentu Anda tidak memperhatikannya karena Anda tidak menulisnya. Jadi Anda tidak melihatnya. Anda mempercayai mereka.

Masalahnya bukan jumlah file. Ini karena Anda sekarang memiliki begitu banyak hal yang tidak Anda percayai.

Jadi cari tahu bagaimana membuat tes-tes itu bekerja sampai pada titik bahwa setelah mereka lulus Anda mempercayai file-file ini seperti Anda mempercayai file dalam .NET. Melakukan itu adalah titik pengujian unit. Tidak ada yang peduli dengan jumlah file. Mereka peduli pada sejumlah hal yang tidak bisa mereka percayai.

Untuk aplikasi ukuran kecil hingga menengah, SOLID adalah penjualan yang sangat mudah. Semua orang melihat manfaat dan kemudahan perawatan. Namun, mereka tidak melihat proposisi nilai yang baik untuk SOLID pada aplikasi skala sangat besar.

Perubahan sulit dilakukan pada aplikasi berskala sangat besar, apa pun yang Anda lakukan. Kebijaksanaan terbaik untuk diterapkan di sini bukan berasal dari Paman Bob. Itu berasal dari Michael Feathers dalam bukunya Bekerja Efektif dengan Legacy Code.

Jangan memulai fest menulis ulang. Kode lama mewakili pengetahuan yang sulit dimenangkan. Melempar keluar karena memiliki masalah dan tidak diungkapkan dalam paradigma baru dan lebih baik X hanya meminta seperangkat masalah baru dan tidak ada pengetahuan yang dimenangkan.

Alih-alih menemukan cara untuk membuat kode lama Anda yang tidak dapat diuji (kode lama dalam Feathers berbicara). Dalam kode metafora ini seperti kemeja. Sebagian besar digabungkan pada jahitan alami yang dapat dibatalkan untuk memisahkan kode dengan cara Anda menghapus jahitan. Lakukan ini untuk memungkinkan Anda melampirkan "lengan" uji yang memungkinkan Anda mengisolasi sisa kode. Sekarang ketika Anda membuat lengan uji Anda memiliki kepercayaan diri pada lengan karena Anda melakukan ini dengan kemeja kerja. (ow, metafora ini mulai sakit).

Gagasan ini mengalir dari asumsi bahwa, seperti di sebagian besar toko, satu-satunya persyaratan terkini ada dalam kode kerja. Ini memungkinkan Anda mengunci itu dalam tes yang memungkinkan Anda untuk membuat perubahan pada kode yang terbukti tanpa kehilangan setiap bit dari itu status yang terbukti. Sekarang dengan gelombang tes pertama ini di tempat Anda dapat mulai membuat perubahan yang membuat kode "warisan" (tidak dapat diuji) dapat diuji. Anda dapat berani karena tes jahitan mendukung Anda dengan mengatakan ini adalah apa yang selalu dilakukan dan tes baru menunjukkan bahwa kode Anda benar-benar melakukan apa yang Anda pikirkan.

Apa hubungan semua ini dengan:

Mengelola dan mengatur jumlah kelas yang meningkat secara besar-besaran setelah beralih ke SOLID?

Abstraksi.

Anda dapat membuat saya membenci basis kode apa pun dengan abstraksi yang buruk. Abstraksi yang buruk adalah sesuatu yang membuat saya mencari ke dalam. Jangan mengejutkan saya ketika saya melihat ke dalam. Cukup banyak apa yang saya harapkan.

Beri saya nama baik, tes yang dapat dibaca (contoh) yang menunjukkan cara menggunakan antarmuka, dan mengaturnya sehingga saya dapat menemukan hal-hal dan saya tidak akan peduli jika kami menggunakan 10, 100, atau 1000 file.

Anda membantu saya menemukan sesuatu dengan nama deskriptif yang bagus. Tempatkan hal-hal dengan nama baik dalam hal-hal dengan nama baik.

Jika Anda melakukan semua ini dengan benar, Anda akan mengabstraksi file ke tempat menyelesaikan tugas yang hanya bergantung pada 3 hingga 5 file lainnya. File 70-100 masih ada di sana. Tapi mereka bersembunyi di balik 3 sampai 5. Itu hanya bekerja jika Anda percaya 3 sampai 5 untuk melakukan hal itu dengan benar.

Jadi yang benar-benar Anda butuhkan adalah kosakata untuk menghasilkan nama-nama baik untuk semua hal ini dan tes yang dipercayai orang-orang sehingga mereka akan berhenti mengarungi semuanya. Tanpa itu Anda akan membuat saya gila juga.

@Delioth membuat poin bagus tentang tumbuh rasa sakit. Saat Anda terbiasa dengan hidangan yang ada di lemari di atas mesin cuci piring, dibutuhkan waktu untuk membiasakan mereka berada di atas bilah sarapan. Membuat beberapa hal lebih sulit. Membuat beberapa hal lebih mudah. Tapi itu menyebabkan semua jenis mimpi buruk jika orang tidak setuju ke mana piring pergi. Dalam basis kode besar masalahnya adalah Anda hanya bisa memindahkan beberapa hidangan sekaligus. Jadi sekarang Anda punya piring di dua tempat. Ini membingungkan. Membuatnya sulit untuk percaya bahwa masakan itu memang seharusnya. Jika Anda ingin melewati ini meskipun satu-satunya yang harus dilakukan adalah terus memindahkan piring.

Masalahnya adalah Anda benar-benar ingin tahu apakah memiliki hidangan di atas bilah sarapan sepadan sebelum melewati semua omong kosong ini. Nah untuk itu yang bisa saya rekomendasikan adalah pergi berkemah.

Saat mencoba paradigma baru untuk pertama kalinya, tempat terakhir yang harus Anda terapkan adalah basis kode yang besar. Ini berlaku untuk setiap anggota tim. Tidak seorang pun boleh mengambilnya dengan keyakinan bahwa SOLID bekerja, bahwa OOP bekerja, atau pemrograman fungsional berfungsi. Setiap anggota tim harus mendapat kesempatan untuk bermain dengan ide baru, apa pun itu, dalam proyek mainan. Ini memungkinkan mereka melihat setidaknya cara kerjanya. Itu membuat mereka melihat apa yang tidak dilakukannya dengan baik. Itu membuat mereka belajar melakukannya dengan benar sebelum membuat kekacauan besar.

Memberi orang tempat yang aman untuk bermain akan membantu mereka mengadopsi ide-ide baru dan memberi mereka kepercayaan diri bahwa hidangan benar-benar bisa bekerja di rumah baru mereka.

candied_orange
sumber
3
Mungkin perlu disebutkan bahwa beberapa rasa sakit dari pertanyaan tersebut kemungkinan hanya akan bertambah rasa sakit - sementara, ya, mereka mungkin perlu membuat 15 file untuk satu hal ini ... sekarang mereka tidak perlu menulis GUIDProvider lagi, atau BasePathProvider , atau ExtensionProvider, dll. Ini adalah jenis rintangan yang sama yang Anda dapatkan ketika Anda memulai proyek greenfield baru - banyak fitur pendukung yang sebagian besar sepele, bodoh untuk menulis, namun masih perlu ditulis. Menyebalkan untuk membangun mereka, tetapi begitu mereka ada di sana Anda tidak perlu memikirkannya .... pernah.
Delioth
@Delioth Saya sangat cenderung percaya ini adalah masalahnya. Sebelumnya, jika kami memerlukan beberapa bagian fungsionalitas (katakanlah kami hanya menginginkan URL yang disimpan di AppSettings), kami hanya memiliki satu kelas besar yang diedarkan dan digunakan. Dengan pendekatan baru, tidak ada alasan untuk membagikan keseluruhan AppSettingshanya untuk mendapatkan url atau path file.
JD Davis
1
Jangan memulai fest menulis ulang. Kode lama mewakili pengetahuan yang sulit dimenangkan. Melempar keluar karena memiliki masalah dan tidak diungkapkan dalam paradigma baru dan lebih baik X hanya meminta seperangkat masalah baru dan tidak ada pengetahuan yang dimenangkan. Ini. Benar.
Flot2011
10

Sepertinya kode Anda tidak dipisahkan dengan baik dan / atau ukuran tugas Anda terlalu besar.

Perubahan kode harus 5-10 file kecuali Anda sedang melakukan kodemod atau refactoring skala besar. Jika satu perubahan menyentuh banyak file, itu mungkin berarti perubahan Anda mengalir. Beberapa abstraksi yang lebih baik (lebih banyak tanggung jawab tunggal, pemisahan antarmuka, inversi ketergantungan) harus membantu. Ini juga mungkin Anda mungkin pergi terlalu tanggung jawab tunggal dan bisa menggunakan sedikit lebih pragmatisme - lebih pendek dan lebih tipis jenis hirarki. Itu seharusnya membuat kode lebih mudah dipahami juga karena Anda tidak harus memahami lusinan file untuk mengetahui apa yang dilakukan kode.

Ini juga bisa menjadi pertanda bahwa pekerjaan Anda terlalu besar. Alih-alih "hei, tambahkan fitur ini" (yang membutuhkan perubahan UI dan perubahan api dan perubahan akses data dan perubahan keamanan dan uji perubahan dan ...) memecahnya menjadi potongan yang lebih mudah diperbaiki. Itu menjadi lebih mudah untuk ditinjau dan lebih mudah dipahami karena mengharuskan Anda untuk membuat kontrak yang layak antara bit.

Dan tentu saja, unit test membantu semua ini. Mereka memaksa Anda untuk membuat antarmuka yang layak. Mereka memaksa Anda untuk membuat kode Anda cukup fleksibel untuk menyuntikkan bit yang diperlukan untuk menguji (jika sulit untuk menguji, itu akan sulit untuk digunakan kembali). Dan mereka mendorong orang menjauh dari hal-hal rekayasa berlebihan karena semakin Anda merekayasa semakin Anda perlu menguji.

Telastyn
sumber
2
File 5-10 hingga 70-100 file lebih dari sekadar hipotetis. Tugas terakhir saya adalah membuat beberapa fungsionalitas di salah satu layanan microser kami yang lebih baru. Layanan baru seharusnya menerima permintaan, dan menyimpan dokumen. Dalam melakukan itu, saya memerlukan kelas untuk mewakili entitas pengguna dalam 2 database dan repo yang terpisah untuk masing-masing. Repos untuk mewakili tabel lain yang perlu saya tulis. Kelas khusus untuk menangani pemeriksaan file-data dan pembuatan nama. Dan daftarnya berlanjut. Belum lagi, setiap kelas yang berisi logika diwakili oleh antarmuka sehingga bisa dipermainkan untuk unit test.
JD Davis
1
Sejauh basis kode lama kami, mereka semua sangat erat dan sangat monolitik. Dengan pendekatan SOLID, satu-satunya kopling antar kelas adalah dalam kasus POCO, yang lainnya dilewatkan melalui DI dan antarmuka.
JD Davis
3
@ JDDavis - tunggu, mengapa satu microservice bekerja secara langsung dengan banyak basis data?
Telastyn
1
Itu adalah kompromi dengan manajer dev kami. Dia lebih memilih perangkat lunak monolitik dan prosedural. Dengan demikian, layanan microser kami jauh lebih makro daripada yang seharusnya. Seiring dengan membaiknya infrastruktur kami, lambat laun hal-hal akan pindah ke layanan microser mereka sendiri. Untuk saat ini kami agak mengikuti pendekatan pencekik untuk memindahkan fungsionalitas tertentu ke layanan microser. Karena beberapa layanan memerlukan akses ke sumber daya tertentu, kami juga memindahkannya ke layanan microser mereka sendiri.
JD Davis
4

Saya ingin menguraikan beberapa hal yang telah disebutkan di sini, tetapi lebih dari perspektif di mana batas-batas objek digambar. Jika Anda mengikuti sesuatu yang mirip dengan Desain Berbasis Domain, maka objek Anda mungkin akan mewakili aspek bisnis Anda. Customerdan Order, misalnya, akan menjadi objek. Sekarang, jika saya membuat tebakan berdasarkan nama kelas yang Anda miliki sebagai titik awal, AccountLogickelas Anda memiliki kode yang akan berjalan untuk akun apa pun . Namun, dalam OO, setiap kelas dimaksudkan untuk memiliki konteks dan identitas. Anda seharusnya tidak mendapatkan Accountobjek dan kemudian meneruskannya ke AccountLogickelas dan minta kelas itu membuat perubahan pada Accountobjek. Itulah yang disebut model anemia, dan tidak mewakili OO dengan sangat baik. Sebaliknya, milikmuAccountkelas harus memiliki perilaku, seperti Account.Close()atau Account.UpdateEmail(), dan perilaku itu hanya akan memengaruhi instance akun itu.

Sekarang, BAGAIMANA perilaku ini ditangani dapat (dan dalam banyak kasus harus) dilepas ke dependensi yang diwakili oleh abstraksi (yaitu, antarmuka). Account.UpdateEmail, misalnya, mungkin ingin memperbarui basis data, atau file, atau mengirim pesan ke bus layanan, dll. Dan itu mungkin berubah di masa mendatang. Jadi Accountkelas Anda mungkin memiliki ketergantungan pada, misalnya, sebuah IEmailUpdate, yang bisa menjadi salah satu dari banyak antarmuka yang diterapkan oleh suatu AccountRepositoryobjek. Anda tidak ingin meneruskan seluruh IAccountRepositoryantarmuka ke Accountobjek karena mungkin akan melakukan terlalu banyak, seperti mencari dan menemukan akun (apa saja) lainnya, yang Anda mungkin tidak ingin Accountobjek memiliki akses, tetapi meskipun AccountRepositorymungkin mengimplementasikan keduanya IAccountRepositorydan IEmailUpdateantarmuka,Accountobjek hanya akan memiliki akses ke bagian-bagian kecil yang dibutuhkannya. Ini membantu Anda mempertahankan Prinsip Segregasi Antarmuka .

Secara realistis, seperti yang orang lain sebutkan, jika Anda berurusan dengan ledakan kelas, kemungkinan besar Anda menggunakan prinsip SOLID (dan, pada akhirnya, OO) dengan cara yang salah. SOLID seharusnya membantu Anda menyederhanakan kode Anda, bukan menyulitkannya. Tetapi butuh waktu untuk benar-benar memahami apa arti SRP. Namun, yang lebih penting adalah bagaimana cara kerja SOLID akan sangat bergantung pada domain Anda dan konteks terikat (istilah DDD lain). Tidak ada peluru perak atau satu ukuran untuk semua.

Satu hal lagi yang ingin saya tekankan kepada orang-orang yang bekerja dengan saya: sekali lagi, objek OOP harus memiliki perilaku, dan, pada kenyataannya, ditentukan oleh perilakunya, bukan datanya. Jika objek Anda tidak memiliki apa pun selain properti dan bidang, ia masih memiliki perilaku, meskipun mungkin bukan perilaku yang Anda maksudkan. Properti yang dapat ditulisi secara publik / dapat diselesaikan dengan tidak ada logika set lain menyiratkan bahwa perilaku untuk kelas yang mengandungnya adalah bahwa siapa pun di mana pun karena alasan apa pun dan kapan saja diizinkan untuk mengubah nilai properti tersebut tanpa logika bisnis atau validasi yang diperlukan di antaranya. Itu biasanya bukan perilaku yang diinginkan orang, tetapi jika Anda memiliki model anemia, itu biasanya perilaku yang diumumkan kelas Anda kepada siapa pun yang menggunakannya.

Lao
sumber
2

Jadi itu total 15 kelas (tidak termasuk POCO dan perancah) untuk melakukan penghematan yang cukup mudah.

Itu gila .... tetapi kelas-kelas ini terdengar seperti sesuatu yang saya tulis sendiri. Jadi mari kita lihat mereka. Mari kita abaikan antarmuka dan tes untuk saat ini.

  • BasePathProvider- IMHO setiap proyek non-sepele yang bekerja dengan file membutuhkannya. Jadi saya berasumsi, sudah ada hal seperti itu dan Anda dapat menggunakannya apa adanya.
  • UniqueFilenameProvider - Tentu, Anda sudah memilikinya, bukan?
  • NewGuidProvider - Kasus yang sama, kecuali Anda hanya menatap menggunakan GUID.
  • FileExtensionCombiner - Kasus yang sama.
  • PatientFileWriter - Saya kira, ini adalah kelas utama untuk tugas saat ini.

Bagi saya, ini terlihat bagus: Anda perlu menulis satu kelas baru yang membutuhkan empat kelas pembantu. Keempat kelas pembantu terdengar cukup dapat digunakan kembali, jadi saya berani bertaruh mereka sudah berada di suatu tempat di basis kode Anda. Kalau tidak, itu nasib buruk (apakah Anda benar-benar orang dalam tim Anda untuk menulis file dan menggunakan GUIDs ???) atau masalah lain.


Mengenai kelas tes, tentu saja, ketika Anda membuat kelas baru, atau memperbaruinya, itu harus diuji. Jadi menulis lima kelas berarti menulis lima kelas ujian juga. Tapi ini tidak membuat desain lebih rumit:

  • Anda tidak akan pernah menggunakan kelas tes di tempat lain karena mereka akan dieksekusi secara otomatis dan hanya itu.
  • Anda ingin melihatnya lagi, kecuali jika Anda memperbarui kelas yang sedang diuji atau kecuali Anda menggunakannya sebagai dokumentasi (idealnya, tes menunjukkan dengan jelas bagaimana kelas seharusnya digunakan).

Mengenai antarmuka, mereka hanya diperlukan saat kerangka DI Anda atau kerangka kerja pengujian Anda tidak dapat menangani kelas. Anda dapat melihatnya sebagai tol untuk alat yang tidak sempurna. Atau Anda mungkin melihatnya sebagai abstraksi berguna yang memungkinkan Anda lupa bahwa ada hal-hal yang lebih rumit - membaca sumber antarmuka membutuhkan waktu jauh lebih sedikit daripada membaca sumber implementasinya.

maaartinus
sumber
Saya bersyukur atas sudut pandang ini. Dalam kasus khusus ini, saya sedang menulis fungsionalitas ke dalam microservice yang cukup baru. Sayangnya, bahkan dalam basis kode utama kami, sementara kami memiliki beberapa di atas digunakan, tidak ada yang benar-benar dalam cara yang dapat digunakan kembali. Segala sesuatu yang perlu digunakan kembali berakhir di beberapa kelas statis atau hanya menyalin dan menempel di sekitar kode. Saya pikir saya masih berjalan agak jauh, tetapi saya setuju bahwa tidak semua perlu dibedah dan dipisahkan sepenuhnya.
JD Davis
@ JDDvis saya mencoba untuk menulis sesuatu yang berbeda dari jawaban lain (yang sebagian besar saya setuju). Setiap kali Anda menyalin & menempel sesuatu, Anda mencegah penggunaan kembali alih-alih menggeneralisasi sesuatu yang Anda buat sepotong kode lain yang tidak dapat digunakan kembali, yang akan memaksa Anda untuk menyalin & menempel lebih banyak satu hari. IMHO itu adalah dosa terbesar kedua, setelah secara membabi buta mengikuti aturan. Anda perlu menemukan sweet spot Anda, di mana aturan berikut membuat Anda lebih produktif (terutama perubahan masa depan) dan kadang-kadang mematahkannya sedikit membantu dalam kasus-kasus ketika upaya itu tidak sesuai. Itu semua relatif.
maaartinus
@ JDDavis Dan semuanya tergantung pada kualitas alat Anda. Contoh: Ada orang yang mengklaim bahwa DI itu keras dan rumit, sedangkan saya mengklaim bahwa sebagian besar gratis . +++Mengenai melanggar aturan: Ada empat kelas, saya perlu di tempat, di mana saya hanya bisa menyuntikkan mereka setelah refactoring utama membuat kode lebih jelek (setidaknya untuk mata saya), jadi saya memutuskan untuk membuatnya ke lajang (programmer yang lebih baik mungkin menemukan cara yang lebih baik, tapi saya senang dengan itu; jumlah lajang ini belum berubah sejak lama).
maaartinus
Jawaban ini mengungkapkan cukup banyak apa yang saya pikirkan ketika OP menambahkan contoh untuk pertanyaan itu. @ JDDavis Izinkan saya menambahkan bahwa Anda dapat menyimpan beberapa kode / kelas boilerplate dengan menggunakan alat fungsional untuk kasus sederhana. Penyedia GUI, misalnya - alih-alih membuat antarmuka baru kelas baru untuk ini, mengapa tidak hanya memanfaatkan Func<Guid>untuk ini, dan menyuntikkan metode anonim seperti ()=>Guid.NewGuid()ke konstruktor? Dan tidak perlu untuk menguji fungsi .Net framework ini, ini adalah sesuatu yang telah dilakukan Microsoft untuk Anda. Secara total, ini akan menghemat 4 kelas.
Doc Brown
... dan Anda harus memeriksa apakah case lain yang Anda sajikan dapat disederhanakan dengan cara yang sama (mungkin tidak semuanya).
Doc Brown
2

Bergantung pada abstraksi, membuat kelas tanggung jawab tunggal, dan menulis unit test bukanlah ilmu pasti. Sangat normal untuk mengayun terlalu jauh dalam satu arah ketika belajar, pergi ke ekstrem, dan kemudian menemukan norma yang masuk akal. Sepertinya pendulum Anda telah terayun terlalu jauh, dan bahkan mungkin macet.

Di sinilah saya curiga ini keluar jalur:

Unit test telah menjadi penjualan yang sangat sulit bagi tim karena mereka semua percaya mereka membuang-buang waktu dan bahwa mereka dapat menangani-menguji kode mereka lebih cepat secara keseluruhan daripada masing-masing bagian secara individual. Menggunakan tes unit sebagai dukungan untuk SOLID sebagian besar sia-sia dan sebagian besar menjadi lelucon pada saat ini.

Salah satu manfaat yang berasal dari sebagian besar prinsip SOLID (tentu saja bukan satu-satunya manfaat) adalah membuat tes unit penulisan untuk kode kita lebih mudah. Jika suatu kelas bergantung pada abstraksi kita bisa mengejek abstraksi. Abstraksi yang dipisahkan lebih mudah untuk diejek. Jika sebuah kelas melakukan satu hal, kemungkinan memiliki kompleksitas yang lebih rendah, yang berarti lebih mudah untuk mengetahui dan menguji semua jalur yang mungkin.

Jika tim Anda tidak menulis tes unit, dua hal terkait terjadi:

Pertama, mereka melakukan banyak pekerjaan ekstra untuk membuat semua antarmuka dan kelas ini tanpa menyadari manfaat penuh. Butuh sedikit waktu dan latihan untuk melihat bagaimana tes unit menulis membuat hidup kita lebih mudah. Ada alasan mengapa orang yang belajar menulis unit test tetap melakukannya, tetapi Anda harus bertahan cukup lama untuk menemukannya sendiri. Jika tim Anda tidak berusaha maka mereka akan merasa seperti sisa pekerjaan tambahan yang mereka lakukan tidak berguna.

Misalnya, apa yang terjadi ketika mereka perlu refactor? Jika mereka memiliki seratus kelas kecil tetapi tidak ada tes untuk memberi tahu mereka apakah perubahan mereka akan berhasil atau tidak, kelas dan antarmuka tambahan itu akan tampak seperti beban, bukan perbaikan.

Kedua, tes unit penulisan dapat membantu Anda memahami seberapa banyak abstraksi yang benar-benar dibutuhkan kode Anda. Seperti yang saya katakan, ini bukan ilmu. Kami memulai dengan buruk, membelok di semua tempat, dan menjadi lebih baik. Tes unit memiliki cara khusus untuk melengkapi SOLID. Bagaimana Anda tahu kapan Anda perlu menambahkan abstraksi atau memecah sesuatu? Dengan kata lain, bagaimana Anda tahu kapan Anda "cukup PADAT?" Seringkali jawabannya adalah ketika Anda tidak dapat menguji sesuatu.

Mungkin kode Anda akan dapat diuji tanpa membuat banyak abstraksi dan kelas kecil. Tetapi jika Anda tidak menulis tes, bagaimana Anda bisa tahu? Seberapa jauh kita melangkah? Kita bisa menjadi terobsesi dengan memecah hal-hal yang lebih kecil dan lebih kecil. Itu lubang kelinci. Kemampuan untuk menulis tes untuk kode kami membantu kami melihat ketika kami telah mencapai tujuan kami sehingga kami dapat berhenti terobsesi, melanjutkan, dan bersenang-senang menulis lebih banyak kode.

Tes unit bukanlah peluru perak yang menyelesaikan segalanya, tetapi itu adalah peluru yang sangat hebat yang membuat hidup para pengembang lebih baik. Kami tidak sempurna, dan juga bukan tes kami. Tapi tes memberi kita kepercayaan diri. Kami berharap kode kami benar dan kami terkejut ketika itu salah, bukan sebaliknya. Kami tidak sempurna dan tidak juga ujian kami. Tetapi ketika kode kami diuji, kami memiliki kepercayaan diri. Kita cenderung menggigit kuku kita ketika kode kita digunakan dan bertanya-tanya apa yang akan rusak kali ini dan apakah itu akan menjadi kesalahan kita.

Di atas semua itu, begitu kita mengerti, menulis unit test membuat pengembangan kode lebih cepat, bukan lebih lambat. Kami menghabiskan lebih sedikit waktu untuk meninjau kembali kode lama atau debugging untuk menemukan masalah yang seperti jarum di tumpukan jerami.

Bug berkurang, kita menyelesaikan lebih banyak, dan kita mengganti kecemasan dengan percaya diri. Ini bukan mode atau minyak ular. Itu nyata. Banyak pengembang akan membuktikan hal ini. Jika tim Anda belum mengalami hal ini, mereka harus mendorong kurva pembelajaran itu dan mengatasi punuk. Berikan kesempatan, sadari bahwa mereka tidak akan mendapatkan hasil secara instan. Tetapi ketika itu terjadi mereka akan senang mereka lakukan dan mereka tidak akan pernah melihat ke belakang. (Atau mereka akan menjadi paria yang terisolasi dan menulis posting blog yang marah tentang bagaimana unit test dan sebagian besar pengetahuan pemrograman terakumulasi adalah buang-buang waktu.)

Sejak beralih, salah satu keluhan terbesar dari pengembang adalah bahwa mereka tidak tahan meninjau rekan dan melintasi lusinan file di mana sebelumnya setiap tugas hanya mengharuskan pengembang menyentuh 5-10 file.

Tinjauan sejawat jauh lebih mudah ketika semua tes unit lulus dan sebagian besar ulasan itu hanya memastikan bahwa tes itu bermakna.

Scott Hannen
sumber