Bagi Anda yang memiliki nasib baik untuk tidak bekerja dalam bahasa dengan ruang lingkup yang dinamis, izinkan saya memberi Anda sedikit penyegaran tentang cara kerjanya. Bayangkan sebuah bahasa semu, yang disebut "RUBELLA", yang berperilaku seperti ini:
function foo() {
print(x); // not defined locally => uses whatever value `x` has in the calling context
y = "tetanus";
}
function bar() {
x = "measles";
foo();
print(y); // not defined locally, but set by the call to `foo()`
}
bar(); // prints "measles" followed by "tetanus"
Yaitu, variabel menyebarkan naik turun tumpukan panggilan secara bebas - semua variabel yang didefinisikan dalam foo
terlihat oleh (dan dapat ditiru oleh) pemanggilnya bar
, dan sebaliknya juga benar. Ini memiliki implikasi serius untuk refactorability kode. Bayangkan Anda memiliki kode berikut:
function a() { // defined in file A
x = "qux";
b();
}
function b() { // defined in file B
c();
}
function c() { // defined in file C
print(x);
}
Sekarang, panggilan ke a()
akan dicetak qux
. Tetapi kemudian, suatu hari, Anda memutuskan bahwa Anda perlu b
sedikit berubah . Anda tidak tahu semua konteks panggilan (beberapa di antaranya mungkin sebenarnya berada di luar basis kode Anda), tetapi itu tidak apa-apa - perubahan Anda akan sepenuhnya internal b
, bukan? Jadi Anda menulis ulang seperti ini:
function b() {
x = "oops";
c();
}
Dan Anda mungkin berpikir bahwa Anda belum mengubah apa pun, karena Anda baru saja mendefinisikan variabel lokal. Tetapi, pada kenyataannya, Anda telah rusak a
! Sekarang, a
cetak oops
daripada qux
.
Membawa ini kembali keluar dari ranah pseudo-languages, ini persis bagaimana MUMPS berperilaku, meskipun dengan sintaks yang berbeda.
Versi modern ("modern") dari MUMPS menyertakan apa yang disebut NEW
pernyataan, yang memungkinkan Anda untuk mencegah variabel dari bocor dari callee ke penelepon. Jadi, dalam contoh pertama di atas, jika kita telah melakukan NEW y = "tetanus"
di foo()
, maka print(y)
di bar()
akan mencetak apa-apa (dalam gondok, semua nama menunjuk ke string kosong kecuali secara eksplisit diatur ke sesuatu yang lain). Tetapi tidak ada yang dapat mencegah variabel bocor dari penelepon ke callee: jika kita function p() { NEW x = 3; q(); print(x); }
, untuk semua yang kita tahu, q()
bisa bermutasi x
, meskipun tidak secara eksplisit menerima x
sebagai parameter. Ini masih merupakan situasi yang buruk untuk berada dalam, tapi tidak seperti buruk seperti itu mungkin dulu.
Dengan mempertimbangkan bahaya-bahaya ini, bagaimana kita dapat dengan aman mereformasi kode dalam MUMPS atau bahasa lain dengan pelingkupan dinamis?
Ada beberapa praktik baik yang jelas untuk membuat refactoring lebih mudah, seperti tidak pernah menggunakan variabel dalam suatu fungsi selain yang Anda inisialisasi ( NEW
) sendiri atau diteruskan sebagai parameter eksplisit, dan secara eksplisit mendokumentasikan parameter apa pun yang secara implisit dilewatkan dari pemanggil fungsi. Tetapi dalam basis kode lama, ~ 10 8 -LOC, ini adalah kemewahan yang sering tidak dimiliki.
Dan, tentu saja, pada dasarnya semua praktik yang baik untuk refactoring dalam bahasa dengan ruang lingkup leksikal juga berlaku dalam bahasa dengan ruang lingkup dinamis - tes tulis, dan sebagainya. Pertanyaannya, kemudian, apakah ini: bagaimana kita mengurangi risiko yang secara spesifik terkait dengan peningkatan kerapuhan kode yang tercakup secara dinamis ketika melakukan refactoring?
(Perhatikan bahwa sementara Bagaimana Anda menavigasi dan refactor kode yang ditulis dalam bahasa yang dinamis? Memiliki judul yang mirip dengan pertanyaan ini, itu sama sekali tidak terkait.)