Apa idiom "Jalankan Sekitar"?

151

Apa idiom "Jalankan Sekitar" ini (atau serupa) yang telah saya dengar? Mengapa saya mungkin menggunakannya, dan mengapa saya tidak ingin menggunakannya?

Tom Hawtin - tackline
sumber
9
Saya tidak memperhatikan itu Anda, taktik. Kalau tidak, saya mungkin akan lebih sarkastik dalam jawaban saya;)
Jon Skeet
1
Jadi ini pada dasarnya adalah aspek yang benar? Jika tidak, bagaimana bedanya?
Lucas

Jawaban:

147

Pada dasarnya itu adalah pola di mana Anda menulis metode untuk melakukan hal-hal yang selalu diperlukan, misalnya alokasi dan pembersihan sumber daya, dan membuat penelepon menyampaikan "apa yang ingin kita lakukan dengan sumber daya". Sebagai contoh:

public interface InputStreamAction
{
    void useStream(InputStream stream) throws IOException;
}

// Somewhere else    

public void executeWithFile(String filename, InputStreamAction action)
    throws IOException
{
    InputStream stream = new FileInputStream(filename);
    try {
        action.useStream(stream);
    } finally {
        stream.close();
    }
}

// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
    public void useStream(InputStream stream) throws IOException
    {
        // Code to use the stream goes here
    }
});

// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));

// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);

Kode panggilan tidak perlu khawatir tentang sisi terbuka / bersih-bersih - itu akan ditangani oleh executeWithFile.

Ini terus terang menyakitkan di Jawa karena penutupan begitu bertele-tele, dimulai dengan Java 8 ekspresi lambda dapat diimplementasikan seperti dalam banyak bahasa lain (misalnya ekspresi C # lambda, atau Groovy), dan kasus khusus ini ditangani sejak Java 7 dengan try-with-resourcesdan AutoClosablestream.

Meskipun "mengalokasikan dan membersihkan" adalah contoh khas yang diberikan, ada banyak contoh lain yang mungkin - penanganan transaksi, pencatatan, pelaksanaan beberapa kode dengan lebih banyak keistimewaan, dll. Ini pada dasarnya sedikit seperti pola metode templat tetapi tanpa pewarisan.

Jon Skeet
sumber
4
Ini deterministik. Finalizers di Java tidak dipanggil secara deterministik. Juga seperti yang saya katakan pada paragraf terakhir, ini tidak hanya digunakan untuk alokasi dan pembersihan sumber daya. Mungkin tidak perlu membuat objek baru sama sekali. Ini umumnya "inisialisasi dan penghancuran" tetapi itu mungkin bukan alokasi sumber daya.
Jon Skeet
3
Jadi seperti di C di mana Anda memiliki fungsi yang Anda lewati di pointer fungsi untuk melakukan pekerjaan?
Paul Tomblin
3
Juga, Jon, Anda merujuk pada penutupan di Jawa - yang masih belum ada (kecuali saya melewatkannya). Apa yang Anda gambarkan adalah kelas batin anonim - yang bukan merupakan hal yang sama persis. Dukungan penutupan sejati (seperti yang telah diusulkan - lihat blog saya) akan menyederhanakan sintaksis itu.
philsquared
8
@ Phil: Saya pikir ini masalah derajat. Kelas dalam Java anonim memiliki akses ke lingkungan sekitar mereka dalam arti terbatas - jadi sementara mereka tidak "penuh" penutupan mereka "terbatas" penutupan saya akan mengatakan. Saya pasti ingin melihat penutupan yang tepat di Jawa, meskipun diperiksa (lanjutan)
273 Jon Skeet
4
Java 7 menambahkan try-with-resource, dan Java 8 menambahkan lambdas. Saya tahu ini adalah pertanyaan / jawaban lama tetapi saya ingin menunjukkan ini kepada siapa pun yang melihat pertanyaan ini lima setengah tahun kemudian. Kedua alat bahasa ini akan membantu memecahkan masalah yang ditemukan pola ini untuk diperbaiki.
45

Ungkapan Execute Around digunakan ketika Anda harus melakukan sesuatu seperti ini:

//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...

//... and so on.

Untuk menghindari pengulangan semua kode mubazir ini yang selalu dieksekusi "sekitar" tugas aktual Anda, Anda akan membuat kelas yang mengatasinya secara otomatis:

//pseudo-code:
class DoTask()
{
    do(task T)
    {
        // .. chunk of prep code
        // execute task T
        // .. chunk of cleanup code
    }
};

DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)

Idiom ini memindahkan semua kode berlebihan yang rumit ke satu tempat, dan membuat program utama Anda jauh lebih mudah dibaca (dan dapat dipelihara!)

Lihatlah posting ini untuk contoh C #, dan artikel ini untuk contoh C ++.

e.James
sumber
7

Metode Execute Around adalah tempat Anda memberikan kode arbitrer ke suatu metode, yang dapat melakukan setup dan / atau kode teardown dan menjalankan kode Anda di antaranya.

Java bukan bahasa yang saya pilih untuk melakukan ini. Ini lebih gaya untuk melewati penutupan (atau ekspresi lambda) sebagai argumen. Padahal benda bisa dibilang setara dengan penutupan .

Bagi saya, metode Execute Around adalah semacam Inversion of Control (Dependency Injection) yang dapat Anda ubah ad hoc, setiap kali Anda memanggil metode tersebut.

Tapi itu juga bisa diartikan sebagai contoh Kopling Kontrol (menceritakan metode apa yang harus dilakukan dengan argumennya, secara harfiah dalam kasus ini).

Bill Karwin
sumber
7

Saya melihat Anda memiliki tag Java di sini jadi saya akan menggunakan Java sebagai contoh meskipun polanya tidak spesifik platform.

Idenya adalah bahwa kadang-kadang Anda memiliki kode yang selalu melibatkan boilerplate yang sama sebelum Anda menjalankan kode dan setelah Anda menjalankan kode. Contoh yang baik adalah JDBC. Anda selalu mengambil koneksi dan membuat pernyataan (atau pernyataan yang disiapkan) sebelum menjalankan kueri yang sebenarnya dan memproses set hasil, dan kemudian Anda selalu melakukan pembersihan boilerplate yang sama di akhir - menutup pernyataan dan koneksi.

Gagasan dengan mengeksekusi sekitar adalah lebih baik jika Anda dapat memperhitungkan kode boilerplate. Itu menghemat beberapa mengetik, tetapi alasannya lebih dalam. Ini adalah prinsip jangan-ulangi-sendiri (KERING) di sini - Anda mengisolasi kode ke satu lokasi jadi jika ada bug atau Anda perlu mengubahnya, atau Anda hanya ingin memahaminya, semuanya ada di satu tempat.

Hal yang agak sulit dengan jenis faktor-keluar adalah bahwa Anda memiliki referensi bahwa bagian "sebelum" dan "setelah" perlu dilihat. Dalam contoh JDBC ini akan mencakup Koneksi dan Pernyataan (Disiapkan). Jadi untuk menangani itu Anda pada dasarnya "membungkus" kode target Anda dengan kode boilerplate.

Anda mungkin terbiasa dengan beberapa kasus umum di Jawa. Salah satunya adalah filter servlet. Lain adalah AOP sekitar saran. Yang ketiga adalah berbagai kelas xxxTemplate di Spring. Dalam setiap kasus Anda memiliki beberapa objek pembungkus di mana kode "menarik" Anda (katakanlah permintaan JDBC dan pemrosesan kumpulan hasil) disuntikkan. Objek wrapper melakukan bagian "sebelum", memanggil kode yang menarik dan kemudian melakukan bagian "setelah".


sumber
7

Lihat juga Code Sandwiches , yang mensurvei konstruksi ini di banyak bahasa pemrograman dan menawarkan beberapa ide riset yang menarik. Mengenai pertanyaan spesifik mengapa seseorang dapat menggunakannya, makalah di atas menawarkan beberapa contoh nyata:

Situasi seperti itu muncul setiap kali suatu program memanipulasi sumber daya bersama. API untuk kunci, soket, file, atau koneksi database mungkin memerlukan program untuk secara eksplisit menutup atau melepaskan sumber daya yang sebelumnya diperoleh. Dalam bahasa tanpa pengumpulan sampah, programmer bertanggung jawab untuk mengalokasikan memori sebelum digunakan dan melepaskannya setelah digunakan. Secara umum, berbagai tugas pemrograman memerlukan program untuk membuat perubahan, beroperasi dalam konteks perubahan itu, dan kemudian membatalkan perubahan. Kami menyebutnya sandwich kode situasi seperti itu.

Dan kemudian:

Sandwich kode muncul dalam banyak situasi pemrograman. Beberapa contoh umum berkaitan dengan perolehan dan pelepasan sumber daya yang langka, seperti kunci, deskriptor file, atau koneksi soket. Dalam kasus yang lebih umum, perubahan sementara dari status program mungkin memerlukan sandwich kode. Misalnya, program berbasis GUI untuk sementara waktu mengabaikan input pengguna, atau kernel OS dapat menonaktifkan sementara gangguan perangkat keras. Kegagalan untuk mengembalikan keadaan sebelumnya dalam kasus ini akan menyebabkan bug serius.

Makalah ini tidak mengeksplorasi mengapa tidak menggunakan idiom ini, tetapi ia menjelaskan mengapa idiom mudah salah tanpa bantuan tingkat bahasa:

Sandwich kode yang rusak muncul paling sering di hadapan pengecualian dan aliran kontrol tak terlihat yang terkait. Memang, fitur bahasa khusus untuk mengelola sandwich kode muncul terutama dalam bahasa yang mendukung pengecualian.

Namun, pengecualian bukan satu-satunya penyebab sandwich kode yang rusak. Setiap kali perubahan dibuat ke kode tubuh , jalur kontrol baru dapat muncul yang mem-bypass kode setelah . Dalam kasus yang paling sederhana, pengelola hanya perlu menambahkan returnpernyataan untuk sandwich ini tubuh untuk memperkenalkan cacat baru, yang dapat menyebabkan kesalahan diam. Ketika kode tubuh besar dan sebelum dan sesudah dipisahkan secara luas, kesalahan semacam itu bisa sulit dideteksi secara visual.

Ben Liblit
sumber
Poin bagus, azurefrag. Saya telah merevisi dan memperluas jawaban saya sehingga itu lebih merupakan jawaban yang mandiri. Terima kasih telah menyarankan ini.
Ben Liblit
4

Saya akan mencoba menjelaskan, seperti yang saya lakukan pada anak berusia empat tahun:

Contoh 1

Santa datang ke kota. Elf-nya mengkode apa pun yang mereka inginkan di belakangnya, dan kecuali mereka mengubah beberapa hal akan menjadi sedikit berulang:

  1. Dapatkan kertas kado
  2. Dapatkan Super Nintendo .
  3. Bungkus itu.

Atau ini:

  1. Dapatkan kertas kado
  2. Dapatkan Barbie Doll .
  3. Bungkus itu.

.... ad mual satu juta kali dengan sejuta hadiah berbeda: perhatikan bahwa satu-satunya hal yang berbeda adalah langkah 2. Jika langkah kedua adalah satu-satunya hal yang berbeda, maka mengapa Santa menduplikasi kode, yaitu mengapa ia menggandakan langkah 1 dan 3 satu juta kali? Sejuta hadiah berarti bahwa ia mengulangi langkah 1 dan 3 juta kali dengan sia-sia.

Menjalankan sekitar membantu untuk memecahkan masalah itu. dan membantu menghilangkan kode. Langkah 1 dan 3 pada dasarnya konstan, memungkinkan langkah 2 menjadi satu-satunya bagian yang berubah.

Contoh # 2

Jika Anda masih belum mendapatkannya, berikut adalah contoh lain: pikirkan sandwhich: roti di luar selalu sama, tetapi apa yang ada di dalam berubah tergantung pada jenis sand yang Anda pilih (.eg ham, keju, selai, selai kacang dll). Roti selalu ada di luar dan Anda tidak perlu mengulanginya satu miliar kali untuk setiap jenis pasir yang Anda buat.

Sekarang jika Anda membaca penjelasan di atas, mungkin Anda akan merasa lebih mudah untuk mengerti. Saya harap penjelasan ini membantu Anda.

BKSpurgeon
sumber
+ untuk imajinasi: D
Pak. Hedgehog
3

Ini mengingatkan saya pada pola desain strategi . Perhatikan bahwa tautan yang saya tunjuk menyertakan kode Java untuk polanya.

Jelas seseorang dapat melakukan "Jalankan Sekitar" dengan membuat kode inisialisasi dan pembersihan dan hanya meneruskan strategi, yang kemudian akan selalu dibungkus dengan kode inisialisasi dan pembersihan.

Seperti halnya teknik yang digunakan untuk mengurangi pengulangan kode, Anda tidak boleh menggunakannya sampai Anda memiliki setidaknya 2 kasus di mana Anda membutuhkannya, bahkan mungkin 3 (a la the YAGNI principle). Ingatlah bahwa pengulangan kode yang dihapus mengurangi pemeliharaan (lebih sedikit salinan kode berarti lebih sedikit waktu yang dihabiskan untuk memperbaiki salinan di setiap salinan), tetapi juga meningkatkan pemeliharaan (lebih banyak kode total). Jadi, biaya dari trik ini adalah Anda menambahkan lebih banyak kode.

Jenis teknik ini berguna untuk lebih dari sekedar inisialisasi dan pembersihan. Ini juga baik untuk ketika Anda ingin membuatnya lebih mudah untuk memanggil fungsi Anda (misalnya Anda bisa menggunakannya dalam wizard sehingga tombol "berikutnya" dan "sebelumnya" tidak perlu pernyataan kasus raksasa untuk memutuskan apa yang harus dilakukan untuk pergi ke halaman berikutnya / sebelumnya.

Brian
sumber
0

Jika Anda ingin idiom asyik, ini dia:

//-- the target class
class Resource { 
    def open () { // sensitive operation }
    def close () { // sensitive operation }
    //-- target method
    def doWork() { println "working";} }

//-- the execute around code
def static use (closure) {
    def res = new Resource();
    try { 
        res.open();
        closure(res)
    } finally {
        res.close();
    }
}

//-- using the code
Resource.use { res -> res.doWork(); }
Florin
sumber
Jika buka saya gagal (katakanlah mendapatkan kunci reentrant) tutup disebut (katakanlah melepaskan kunci reentrant meskipun pencocokan terbuka gagal).
Tom Hawtin - tackline