kinerja versus usabilitas ulang

8

Bagaimana saya bisa menulis fungsi yang dapat digunakan kembali tanpa mengorbankan kinerja? Saya berulang kali menghadapi situasi di mana saya ingin menulis fungsi dengan cara yang membuatnya dapat digunakan kembali (misalnya tidak membuat asumsi tentang lingkungan data) tetapi mengetahui keseluruhan aliran program saya tahu itu bukan yang paling efisien metode. Sebagai contoh jika saya ingin menulis fungsi yang memvalidasi kode stok tetapi dapat digunakan kembali saya tidak bisa hanya berasumsi bahwa recordset terbuka. Namun, jika saya membuka dan menutup recordset setiap kali fungsi dipanggil maka kinerja hit ketika perulangan melalui ribuan baris bisa sangat besar.

Jadi untuk kinerja saya mungkin punya:

Function IsValidStockRef(strStockRef, rstStockRecords)
    rstStockRecords.Find ("stockref='" & strStockRef & "'")
    IsValidStockRef = Not rstStockRecords.EOF
End Function

Tetapi untuk dapat digunakan kembali, saya membutuhkan sesuatu seperti berikut:

Function IsValidStockRef(strStockRef)
    Dim rstStockRecords As ADODB.Recordset

    Set rstStockRecords = New ADODB.Recordset
    rstStockRecords.Open strTable, gconnADO

    rstStockRecords.Find ("stockref='" & strStockRef & "'")
    IsValidStockRef = Not rstStockRecords.EOF

    rstStockRecords.Close
    Set rstStockRecords = Nothing
End Function

Saya khawatir bahwa dampak pada kinerja membuka dan menutup recordset itu ketika dipanggil dari dalam loop lebih dari ribuan baris / catatan akan parah tetapi menggunakan metode pertama membuat fungsi tersebut kurang dapat digunakan kembali.

Apa yang harus saya lakukan?

Caltor
sumber

Jawaban:

13

Anda harus melakukan apa pun yang menghasilkan nilai bisnis yang lebih besar dalam situasi ini.

Menulis perangkat lunak selalu merupakan trade-off. Hampir tidak pernah semua tujuan yang valid (rawatan, kinerja, kejelasan, keringkasan, keamanan dll.) Sepenuhnya selaras. Jangan jatuh ke dalam perangkap orang-orang yang berpandangan pendek yang menganggap salah satu dari dimensi ini sebagai yang terpenting dan meminta Anda untuk mengorbankan segalanya untuk itu.

Alih-alih, pahamilah risiko apa dan manfaat apa yang ditawarkan masing-masing alternatif, ukurlah risiko itu dan ikuti risiko yang memaksimalkan hasilnya. (Anda tidak harus benar-benar membuat estimasi numerik, tentu saja. Ini cukup untuk mempertimbangkan faktor-faktor seperti "menggunakan kelas ini berarti mengunci kita ke dalam algoritma hash, tetapi karena kita tidak menggunakannya untuk menjaga terhadap serangan berbahaya , hanya untuk kenyamanan, yang satu ini cukup baik sehingga kita bisa mengabaikan peluang 1: 1.000.000.000 tabrakan tidak sengaja ".)

Yang paling penting adalah untuk mengingat bahwa mereka adalah kompromi; tidak ada satu prinsip pun yang membenarkan segala sesuatu untuk memuaskan, dan tidak ada keputusan, sekali diambil, perlu berdiri selamanya . Anda mungkin harus selalu merevisi ke belakang ketika keadaan berubah dengan cara yang tidak Anda perkirakan. Itu menyebalkan, tapi tidak separah membuat keputusan yang sama tanpa melihat ke belakang.

Kilian Foth
sumber
2
Sementara apa yang Anda katakan adalah benar secara umum, sesuatu harus dikatakan untuk menulis kode untuk melindungi terhadap setiap kasus yang mungkin vs memiliki prasyarat. Menurut pendapat saya yang sederhana, tidak ada yang salah dengan hanya berharap bahwa recordset sudah terbuka ketika dipanggil, diberikan dokumentasi yang cukup. Jika ada, jika ini adalah metode di perpustakaan, lakukan pemeriksaan cepat jika terbuka, dan jika tidak, berikan pengecualian. Tidak perlu "membuatnya berfungsi" dalam skenario yang memungkinkan.
Neil
6

Tak satu pun dari ini tampaknya lebih dapat digunakan kembali daripada yang lain. Mereka sepertinya berada pada level abstraksi yang berbeda . Yang pertama adalah untuk memanggil kode yang memahami sistem stok cukup dekat untuk mengetahui bahwa memvalidasi referensi stok berarti melihat melalui Recordsetdengan semacam permintaan. Yang kedua adalah untuk memanggil kode yang hanya ingin tahu apakah kode stok valid atau tidak dan tidak tertarik dengan dirinya sendiri tentang bagaimana Anda memverifikasi hal itu.

Tapi seperti kebanyakan abstraksi , ini "bocor". Dalam hal ini abstraksi bocor melalui kinerjanya - kode panggilan tidak dapat sepenuhnya mengabaikan bagaimana validasi diterapkan karena jika itu dilakukan, ia mungkin memanggil fungsi itu ribuan kali seperti yang Anda gambarkan dan secara serius menurunkan kinerja keseluruhan.

Pada akhirnya, jika Anda harus memilih antara kode yang tidak diabstraksi dengan buruk dan kinerja yang tidak dapat diterima, Anda harus memilih kode yang tidak diabstraksikan dengan buruk. Tetapi pertama-tama, Anda harus mencari solusi yang lebih baik - kompromi yang mempertahankan kinerja yang dapat diterima dan menghadirkan abstraksi yang layak (jika tidak ideal). Sayangnya saya tidak terlalu mengenal VBA, tetapi dalam bahasa OO, pikiran pertama saya adalah memberikan kode panggilan kelas dengan metode seperti:

BeginValidation()
IsValidStockRef(strStockRef)
EndValidation()

Di sini metode Begin...dan Anda End...melakukan manajemen siklus satu kali dari set rekaman, IsValidStockRefcocok dengan versi pertama Anda, tetapi menggunakan set rekaman ini yang bertanggung jawab atas kelas itu, daripada menyerahkannya. Kode panggilan kemudian akan memanggil Begin...dan End...metode di luar loop, dan metode validasi di dalam.

Catatan: Ini hanya contoh ilustrasi yang sangat kasar, dan mungkin dianggap sebagai umpan pertama di refactoring. Nama-nama mungkin dapat menggunakan tweaking, dan tergantung pada bahasanya harus ada cara yang lebih bersih atau idiomatis untuk melakukannya (C # misalnya dapat menggunakan konstruktor untuk memulai dan Dispose()mengakhiri). Idealnya kode yang hanya ingin memeriksa apakah referensi stok valid seharusnya tidak harus melakukan manajemen siklus hidup sama sekali.

Ini mewakili sedikit degradasi ke abstraksi yang kami sajikan: sekarang memanggil kode perlu tahu cukup tentang validasi untuk memahami bahwa itu adalah sesuatu yang memerlukan semacam pengaturan dan teardown. Tetapi sebagai imbalan atas kompromi yang relatif sederhana ini, kami sekarang memiliki metode yang dapat digunakan dengan mudah dengan memanggil kode, tanpa mengganggu kinerja kami.

Ben Aaronson
sumber
Downvoter: Ada alasan tertentu, karena minat?
Ben Aaronson
Saya bukan downvoter, tapi saya akan menebak. BeginValidation,, EndValidationdan IsValidStockRefmemiliki hubungan khusus satu sama lain. Pengetahuan tentang hubungan itu lebih kompleks daripada pengetahuan yang diperlukan untuk menangani secara langsung a RecordSet. Dan pengetahuan yang dibutuhkan untuk menangani RecordSetlebih luas berlaku.
Tertarik
@Cory Saya setuju sampai batas tertentu, dan tangan saya sedikit dipaksa oleh kurangnya pengetahuan tentang vba. Saya memang mencoba menunjukkan hal ini dengan kalimat berikutnya, tetapi mungkin kata-kata saya tidak jelas atau cukup kuat. Saya telah mengedit untuk mencoba membuatnya lebih jelas
Ben Aaronson
Catatan yang menarik, di C #, Anda diharapkan menggunakan usingpernyataan untuk melakukan pekerjaan ini. Dalam bahasa lain (yang tetap menggunakan pengecualian), untuk melakukan pekerjaan yang sama dengan using, Anda harus menggunakan try {} finally {}untuk menjamin pembuangan yang benar, dan bahkan terkadang tidak mungkin untuk membungkus semua kode dengan benar throw. Ini adalah masalah potensial dengan semua solusi yang disebutkan di sini, dan saya juga tidak yakin bagaimana ini harus diselesaikan dalam VBA.
Tertarik
@Cory: Dan di C ++, Anda cukup menggunakan RAII.
Deduplicator
3

Untuk waktu yang lama, saya menerapkan sistem pemeriksaan yang rumit untuk dapat menggunakan transaksi basis data. Logika transaksi berjalan sebagai berikut: membuka transaksi, melakukan operasi database, kembalikan pada kesalahan atau komit pada kesuksesan. Komplikasi datang dari apa yang terjadi ketika Anda ingin operasi tambahan dilakukan dalam transaksi yang sama. Anda harus menulis metode kedua sepenuhnya yang melakukan kedua operasi, atau Anda dapat memanggil metode asli Anda dari satu detik, membuka transaksi hanya jika salah satu belum dibuka dan melakukan / mengembalikan perubahan hanya jika Anda adalah satu untuk membuka transaksi.

Sebagai contoh:

public void method1() {
    boolean selfOpened = false;
    if(!transaction.isOpen()) {
        selfOpened = true;
        transaction.open();
    }

    try {
        performDbOperations();
        method2();

        if(selfOpened) 
            transaction.commit();
    } catch (SQLException e) {
        if(selfOpened) 
            transaction.rollback();
        throw e;
    }
}

public void method2() {
    boolean selfOpened = false;
    if(!transaction.isOpen()) { 
        selfOpened = true;
        transaction.open();
    }

    try {
        performMoreDbOperations();

        if(selfOpened) 
            transaction.commit();
    } catch (SQLException e) {
        if(selfOpened) 
            transaction.rollback();
        throw e;
    }
}

Harap dicatat, saya tidak menganjurkan kode di atas dengan cara apa pun. Ini harus menjadi contoh dari apa yang tidak boleh dilakukan!

Tampaknya konyol untuk membuat metode kedua untuk melakukan logika yang sama dengan yang pertama ditambah sesuatu yang ekstra, namun saya ingin dapat memanggil bagian API database dari program dan menutup masalah di sana. Namun, sementara ini sebagian menyelesaikan masalah saya, setiap metode yang saya tulis melibatkan penambahan logika verbose untuk memeriksa apakah suatu transaksi sudah terbuka, dan melakukan / mengembalikan perubahan jika metode saya membukanya.

Masalahnya adalah konseptual. Saya seharusnya tidak mencoba merangkul setiap skenario yang mungkin. Pendekatan yang tepat adalah untuk menempatkan logika transaksi dalam metode tunggal yang menggunakan metode kedua sebagai parameter yang akan melakukan logika database aktual. Logika itu menganggap transaksi terbuka dan bahkan tidak melakukan pemeriksaan. Metode-metode ini dapat dipanggil dalam kombinasi sehingga metode ini tidak berantakan dengan logika transaksi yang tidak perlu.

Alasan saya menyebutkan ini adalah karena kesalahan saya adalah berasumsi bahwa saya perlu membuat metode saya berfungsi dalam situasi apa pun. Dengan melakukan itu, saya tidak hanya dipanggil metode memeriksa apakah transaksi terbuka, tetapi juga orang-orang yang disebutnya. Dalam hal ini, ini bukan hit kinerja utama, tetapi jika mengatakan, saya perlu memverifikasi keberadaan catatan dalam database sebelum melanjutkan, saya akan memeriksa setiap metode yang memerlukannya ketika saya seharusnya hanya mengasumsikan selama itu penelepon harus dibuat sadar bahwa catatan itu harus ada. Jika metode ini dipanggil, ini adalah perilaku yang tidak terdefinisi dan Anda tidak perlu khawatir tentang apa yang terjadi.

Sebaliknya Anda harus memberikan banyak dokumentasi, dan menulis apa yang Anda harapkan benar sebelum panggilan dibuat ke metode Anda. Jika cukup penting, tambahkan itu sebagai komentar sebelum metode Anda sehingga tidak boleh ada kesalahan (javadoc memberikan dukungan yang bagus untuk hal semacam ini di java).

Saya harap itu membantu!

Neil
sumber
2

Anda bisa memiliki dua fungsi kelebihan beban. Dengan begitu Anda bisa menggunakan keduanya sesuai situasi.

Anda tidak bisa (saya belum pernah melihat itu terjadi) mengoptimalkan untuk semuanya, jadi Anda harus puas dengan sesuatu. Pilih yang menurut Anda lebih penting.

cauchy
sumber
Sayangnya saya melakukan banyak hal di VBA dan kelebihan bukan pilihan. Saya bisa menggunakan Optionalparameter untuk mencapai efek yang sama.
Caltor
2

2 fungsi: satu membuka recordset dan meneruskannya ke fungsi analisis data.

Yang pertama dapat dilewati jika Anda sudah memiliki recordset terbuka. Yang kedua dapat mengasumsikan bahwa itu akan melewati recordset terbuka, mengabaikan dari mana asalnya, dan memproses data.

Anda memiliki kinerja dan daya guna kembali!

gbjbaanb
sumber
Saya pikir tidak perlu membuka recordset untuk penelepon, tetapi sebaliknya saya setuju.
Neil
0

Optimasi (selain optimasi mikro) secara langsung bertentangan dengan modularitas.

Modularitas bekerja dengan mengisolasi kode dari konteks globalnya, sedangkan optimasi kinerja mengeksploitasi konteks global untuk meminimalkan apa yang harus dilakukan oleh kode. Modularitas adalah manfaat kopling rendah, sedangkan (potensi untuk) kinerja sangat tinggi adalah manfaat kopling tinggi.

Jawabannya adalah arsitektur. Pertimbangkan potongan-potongan kode yang ingin Anda gunakan kembali. Mungkin komponen perhitungan harga, atau logika validasi konfigurasi.

Maka Anda harus menulis kode yang berinteraksi dengan komponen itu untuk dapat digunakan kembali. Di dalam komponen di mana Anda tidak pernah dapat menggunakan hanya bagian dari kode Anda dapat mengoptimalkan kinerja karena Anda tahu tidak ada orang lain yang akan menggunakannya.

Caranya adalah menentukan apa komponen Anda.

tl; dr: antara komponen menulis dengan modularitas dalam pikiran, dalam komponen menulis dengan kinerja dalam pikiran.

Kereta luncur
sumber
Modularitas dan optimisasi tidak selalu bertentangan. Kompiler modern dapat menyejajarkan hampir semua hal di mana saja, jadi tidak masalah seberapa modular Anda menulis, selama kompiler dapat menjahitnya bersama menjadi “non-modular yang dapat dieksekusi”, tidak ada alasan itu tidak bisa secepat kode yang ditulis non-modular di tempat pertama. Tentu saja, tidak semua kompiler dapat melakukannya dengan sangat baik, tetapi ...
leftaroundabout
@ Leftaroundabout Yah saya maksudkan pada tingkat kode sumber, tetapi Anda sangat benar. Tidak ada alasan kompiler yang cukup pintar tidak bisa mengganti jenis gelembung Anda dengan semacam cepat!
Kereta luncur