Bagaimana Anda secara aman menolak dalam bahasa dengan ruang lingkup dinamis?

13

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 footerlihat 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 bsedikit 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, acetak oopsdaripada 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 NEWpernyataan, 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 xsebagai 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.)

senshin
sumber
@gnat Saya tidak melihat bagaimana pertanyaan / jawaban itu relevan dengan pertanyaan ini.
senshin
1
@gnat Apakah Anda mengatakan bahwa jawabannya adalah "gunakan proses yang berbeda dan hal-hal kelas berat lainnya"? Maksud saya, itu mungkin tidak salah, tetapi juga terlalu umum sehingga tidak terlalu berguna.
senshin
2
Jujur, saya tidak berpikir ada jawaban untuk ini selain "beralih ke bahasa di mana variabel benar-benar memiliki aturan pelingkupan" atau "gunakan anak tiri bajingan notasi Hungaria di mana setiap variabel diawali oleh file dan / atau nama metode alih-alih dari tipe atau jenis ". Masalah yang Anda gambarkan sangat mengerikan sehingga saya tidak bisa membayangkan solusi yang baik .
Ixrec
4
Setidaknya Anda tidak dapat menuduh MUMPS iklan palsu karena diberi nama setelah penyakit parah.
Carson63000

Jawaban:

4

Wow.

Saya tidak tahu MUMPS sebagai bahasa, jadi saya tidak tahu apakah komentar saya berlaku di sini. Secara umum - Anda harus refactor dari dalam ke luar. Para konsumen (pembaca) dari keadaan global (variabel global) harus direactored menjadi metode / fungsi / prosedur menggunakan parameter. Metode c akan terlihat seperti ini setelah refactoring:

function c(c_scope_x) {
   print c(c_scope_x);
}

semua penggunaan c harus ditulis ulang menjadi (yang merupakan tugas mekanis)

c(x)

ini untuk mengisolasi kode "dalam" dari negara global dengan menggunakan negara lokal. Ketika Anda selesai dengan itu, Anda harus menulis ulang b menjadi:

function b() {
   x="oops"
   print c(x);
}

tugas x = "oops" ada untuk menjaga efek sampingnya. Sekarang kita harus menganggap b sebagai pencemar negara global. Jika Anda hanya memiliki satu unsur yang berpolusi, pertimbangkan refactoring ini:

function b() {
   x="oops"
   print c(x);
   return x;
}

akhir menulis ulang setiap penggunaan b dengan x = b (). Fungsi b harus menggunakan hanya metode yang sudah dibersihkan (Anda mungkin ingin mengganti nama co menjadi jelas) saat melakukan refactoring ini. Setelah itu Anda harus refactor b agar tidak mencemari lingkungan global.

function b() {
   newvardefinition b_scoped_x="oops"
   print c_cleaned(b_scoped_x);
   return b_scoped_x;
}

ganti nama b menjadi b_cleaned. Saya kira Anda harus sedikit bermain dengan itu untuk mendapatkan accoustomed ke refactoring itu. Tentu tidak setiap metode dapat dire-refoured dengan ini tetapi Anda harus mulai dari bagian dalam. Cobalah dengan Eclipse dan java (ekstrak metode) dan "negara global" alias anggota kelas untuk mendapatkan ide.

function x() {
  fifth_to_refactor();
  {
    forth_to_refactor()
    ....
    {
      second_to_refactor();
    }
    ...
    third_to_refactor();
  }
  first_to_refactor()
}

hth.

Pertanyaan: Dengan mempertimbangkan bahaya-bahaya ini, bagaimana kita dapat dengan aman mereformasi kode dalam MUMPS atau bahasa lain dengan pelingkupan dinamis?

  • Mungkin orang lain bisa memberi petunjuk.

Pertanyaan: Bagaimana kita mengurangi risiko yang secara spesifik terkait dengan peningkatan kerapuhan kode yang tercakup secara dinamis saat melakukan refactoring?

  • Tulis sebuah program, yang melakukan refactoring aman untuk Anda.
  • Tulis program, yang mengidentifikasi calon yang aman / kandidat pertama.
pembungkus
sumber
Ah, ada satu kendala khusus MUMPS untuk mencoba mengotomatiskan proses refactoring: MUMPS tidak memiliki fungsi kelas satu, juga tidak memiliki fungsi pointer atau gagasan serupa. Yang berarti bahwa setiap basis kode MUMPS besar pasti akan memiliki banyak penggunaan eval (dalam MUMPS, disebut EXECUTE), kadang-kadang bahkan pada input pengguna yang disanitasi - yang berarti bahwa mustahil untuk secara statis menemukan dan menulis ulang semua penggunaan suatu fungsi.
senshin
Oke anggap jawaban saya tidak memadai. Video youtube saya pikir skala refactoring @ google melakukan pendekatan yang sangat unik. Mereka menggunakan dentang untuk mem-parsing AST dan kemudian menggunakan mesin pencari mereka sendiri untuk menemukan (bahkan penggunaan tersembunyi) untuk memperbaiki kode mereka. Ini bisa menjadi cara untuk menemukan setiap penggunaan. Maksud saya pendekatan parse dan pencarian pada kode gondong.
pembungkus
2

Saya kira tembakan terbaik Anda adalah untuk membawa basis kode lengkap di bawah kendali Anda, dan pastikan Anda memiliki gambaran umum tentang modul dan dependensinya.

Jadi setidaknya Anda memiliki kesempatan untuk melakukan pencarian global, dan memiliki kesempatan untuk menambahkan tes regresi untuk bagian-bagian dari sistem di mana Anda mengharapkan dampak dengan perubahan kode.

Jika Anda tidak melihat kesempatan untuk menyelesaikan yang pertama, saran terbaik saya adalah: jangan refactor modul apa pun yang digunakan kembali oleh modul lain, atau yang Anda tidak tahu bahwa orang lain bergantung padanya . Dalam basis kode apa pun dengan ukuran yang masuk akal kemungkinannya besar, Anda dapat menemukan modul yang tidak bergantung pada modul lain. Jadi, jika Anda memiliki mod A tergantung pada B, tetapi tidak sebaliknya, dan tidak ada modul lain bergantung pada A, bahkan dalam bahasa yang tercakup secara dinamis, Anda dapat membuat perubahan ke A tanpa melanggar B atau modul lainnya.

Ini memberi Anda kesempatan untuk mengganti ketergantungan A ke B dengan ketergantungan A ke B2, di mana B2 adalah versi B. B2 yang telah disanitasi dan telah ditulis ulang dengan peraturan yang Anda sebutkan di atas untuk membuat kode lebih berevolusi dan lebih mudah untuk di refactor.

Doc Brown
sumber
Ini adalah saran yang baik, meskipun saya akan menambahkan sebagai tambahan bahwa ini pada dasarnya sulit di MUMPS karena tidak ada gagasan penentu akses atau mekanisme enkapsulasi lainnya, yang berarti bahwa API yang kami tentukan dalam basis kode kami secara efektif hanya saran untuk konsumen dari kode tentang fungsi mana yang harus mereka panggil. (Tentu saja, kesulitan khusus ini tidak terkait dengan pelingkupan dinamis; Saya hanya mencatat ini sebagai tempat menarik.)
senshin
Setelah membaca artikel ini , saya yakin saya tidak iri dengan Anda untuk tugas Anda.
Doc Brown
0

Untuk menyatakan yang sudah jelas: Bagaimana cara melakukan refactoring di sini? Lanjutkan dengan sangat hati-hati.

(Seperti yang telah Anda jelaskan, mengembangkan dan memelihara basis kode yang ada harus cukup sulit, apalagi mencoba untuk memperbaikinya.)

Saya percaya saya akan berlaku surut menerapkan pendekatan berbasis tes di sini. Ini akan melibatkan penulisan serangkaian pengujian untuk memastikan fungsionalitas saat ini tetap berfungsi saat Anda mulai melakukan refactoring, pertama hanya untuk membuat pengujian lebih mudah. (Ya, saya mengharapkan masalah ayam dan telur di sini, kecuali jika kode Anda sudah cukup modular untuk diuji tanpa mengubahnya sama sekali.)

Kemudian Anda dapat melanjutkan dengan refactoring lain, memeriksa apakah Anda belum melanggar tes apa pun saat berjalan.

Akhirnya, Anda dapat mulai menulis tes yang mengharapkan fungsionalitas baru dan kemudian menulis kode untuk membuat tes tersebut berfungsi.

Mark Hurd
sumber