Mengapa bahasa pemrograman memungkinkan membayangi / menyembunyikan variabel dan fungsi?

31

Banyak bahasa pemrograman yang paling populer (seperti C ++, Java, Python dll.) Memiliki konsep menyembunyikan / membayangi variabel atau fungsi. Ketika saya menemukan bersembunyi atau membayangi mereka telah menjadi penyebab sulitnya menemukan bug dan saya belum pernah melihat kasus di mana saya merasa perlu untuk menggunakan fitur-fitur bahasa ini.

Bagi saya akan lebih baik melarang persembunyian dan bayangan.

Adakah yang tahu tentang penggunaan konsep-konsep ini dengan baik?

Pembaruan:
Saya tidak merujuk pada enkapsulasi anggota kelas (anggota pribadi / yang dilindungi).

Simon
sumber
Itu sebabnya semua nama lapangan saya mulai dengan F.
Pieter B
7
Saya pikir Eric Lippert punya artikel bagus tentang ini. Oh, tunggu, ini dia: blogs.msdn.com/b/ericlippert/archive/2008/05/21/...
Lescai Ionel
1
Tolong jelaskan pertanyaan Anda. Apakah Anda bertanya tentang menyembunyikan informasi secara umum, atau tentang kasus spesifik yang dijelaskan dalam artikel Lippert di mana kelas turunan menyembunyikan fungsi-fungsi kelas dasar?
Aaron Kurtzhals
Catatan penting: Banyak bug yang disebabkan oleh persembunyian / bayangan melibatkan mutasi (pengaturan variabel yang salah dan bertanya-tanya mengapa perubahan "tidak pernah terjadi" misalnya). Ketika bekerja terutama dengan referensi yang tidak dapat diubah, bersembunyi / membayangi menyebabkan masalah yang jauh lebih sedikit dan lebih kecil kemungkinannya untuk menyebabkan bug.
Jack

Jawaban:

26

Jika Anda melarang bersembunyi dan membayangi, apa yang Anda miliki adalah bahasa di mana semua variabel bersifat global.

Itu jelas lebih buruk daripada membiarkan variabel atau fungsi lokal yang dapat menyembunyikan variabel atau fungsi global.

Jika Anda melarang bersembunyi dan membayangi, DAN Anda mencoba untuk "melindungi" variabel global tertentu, Anda membuat situasi di mana kompiler memberi tahu programmer "Maaf, Dave, tetapi Anda tidak dapat menggunakan nama itu, itu sudah digunakan . " Pengalaman dengan COBOL menunjukkan bahwa programmer hampir segera menggunakan kata-kata kotor dalam situasi ini.

Masalah mendasar bukanlah menyembunyikan / membayangi, tetapi variabel global.

John R. Strohm
sumber
19
Kerugian lain dari melarang shadowing adalah bahwa menambahkan variabel global dapat memecahkan kode karena variabel sudah digunakan di blok lokal.
Giorgio
19
"Jika Anda melarang bersembunyi dan membayangi, apa yang Anda miliki adalah bahasa di mana semua variabel bersifat global." - tidak harus: Anda dapat memiliki variabel scoping tanpa membayangi, dan Anda menjelaskannya.
Thiago Silva
@ThiagoSilva: Dan kemudian bahasa Anda harus memiliki cara untuk memberitahu compiler bahwa modul ini IS diperbolehkan untuk mengakses bahwa modul "frammis" variabel. Apakah Anda mengizinkan seseorang untuk menyembunyikan / membayangi objek yang bahkan tidak dikenalnya ada, atau apakah Anda memberi tahu dia tentang hal itu untuk memberi tahu dia mengapa dia tidak diizinkan menggunakan nama itu?
John R. Strohm
9
@Phil, permisi karena tidak setuju dengan Anda, tetapi OP bertanya tentang "menyembunyikan / membayangi variabel atau fungsi", dan kata-kata "induk", "anak", "kelas", dan "anggota" tidak muncul di mana pun dalam pertanyaannya. Itu tampaknya membuatnya menjadi pertanyaan umum tentang ruang lingkup nama.
John R. Strohm
3
@ Dnmnmc, saya tidak berharap untuk hidup cukup lama untuk menemukan seorang whippersnapper cukup muda untuk tidak mendapatkan referensi yang jelas "2001: A Space Odyssey".
John R. Strohm
15

Adakah yang tahu tentang penggunaan konsep-konsep ini dengan baik?

Menggunakan pengidentifikasi deskriptif yang akurat selalu bermanfaat.

Saya dapat berargumen bahwa menyembunyikan variabel tidak menyebabkan banyak bug karena memiliki dua variabel dengan nama yang sama / jenis yang sama (apa yang akan Anda lakukan jika menyembunyikan variabel tidak diizinkan) cenderung menyebabkan banyak bug dan / atau sama bug yang parah. Saya tidak tahu apakah argumen itu benar , tetapi setidaknya bisa diperdebatkan.

Menggunakan semacam notasi Hongaria untuk membedakan bidang vs variabel lokal mengatasi ini, tetapi memiliki dampak sendiri pada pemeliharaan (dan kewarasan programmer).

Dan (mungkin alasan utama mengapa konsep itu diketahui sejak awal) adalah jauh lebih mudah bagi bahasa untuk menerapkan persembunyian / pembayangan daripada melarangnya. Implementasi yang lebih mudah berarti bahwa kompiler cenderung memiliki bug. Implementasi yang lebih mudah berarti bahwa kompiler membutuhkan waktu lebih sedikit untuk menulis, menyebabkan adopsi platform lebih awal dan lebih luas.

Telastyn
sumber
3
Sebenarnya, tidak, itu TIDAK lebih mudah untuk menerapkan persembunyian dan bayangan. Sebenarnya lebih mudah untuk menerapkan "semua variabel bersifat global". Anda hanya perlu satu namespace, dan Anda SELALU mengekspor nama, bukan memiliki beberapa ruang nama dan harus memutuskan untuk setiap nama apakah akan mengekspornya.
John R. Strohm
5
@ JohnR.Strohm - Tentu, tetapi segera setelah Anda memiliki segala jenis pelingkupan (baca: kelas) maka memiliki cakupan menyembunyikan lingkup yang lebih rendah ada secara gratis.
Telastyn
Pelingkupan dan kelas adalah hal yang berbeda. Dengan pengecualian BASIC, setiap bahasa yang saya programkan memiliki pelingkupan, tetapi tidak semuanya memiliki konsep kelas atau objek.
Michael Shaw
@ michaelshaw - tentu saja, saya seharusnya lebih jelas.
Telastyn
7

Hanya untuk memastikan kita berada di halaman yang sama, metode "persembunyian" adalah ketika kelas turunan mendefinisikan anggota dengan nama yang sama dengan yang ada di kelas dasar (yang, jika itu adalah metode / properti, tidak ditandai virtual / dapat diganti) ), dan ketika dipanggil dari turunan kelas turunan dalam "konteks turunan", anggota turunan digunakan, sedangkan jika dipanggil oleh turunan yang sama dalam konteks kelas dasarnya, anggota kelas dasar digunakan. Ini berbeda dengan abstraksi anggota / pengesampingan di mana anggota kelas dasar mengharapkan kelas turunan untuk menentukan pengganti, dan dari pengubah lingkup / visibilitas yang "menyembunyikan" anggota dari konsumen di luar ruang lingkup yang diinginkan.

Jawaban singkat mengapa diizinkan adalah bahwa tidak melakukannya akan memaksa pengembang untuk melanggar beberapa prinsip utama desain berorientasi objek.

Inilah jawaban yang lebih panjang; pertama, pertimbangkan struktur kelas berikut di alam semesta alternatif tempat C # tidak mengizinkan anggota bersembunyi:

public interface IFoo
{
   string MyFooString {get;}
   int FooMethod();
}

public class Foo:IFoo
{
   public string MyFooString {get{return "Foo";}}
   public int FooMethod() {//incredibly useful code here};
}

public class Bar:Foo
{
   //public new string MyFooString {get{return "Bar";}}
}

Kami ingin menghapus komentar anggota di Bar, dan dengan melakukan itu, izinkan Bar untuk memberikan MyFooString yang berbeda. Namun, kami tidak dapat melakukannya karena akan melanggar larangan realitas alternatif pada persembunyian anggota. Contoh khusus ini akan penuh dengan bug dan merupakan contoh utama mengapa Anda mungkin ingin melarangnya; misalnya, output konsol apa yang akan Anda dapatkan jika Anda melakukan yang berikut?

Bar myBar = new Bar();
Foo myFoo = myBar;
IFoo myIFoo = myFoo;

Console.WriteLine(myFoo.MyFooString);
Console.WriteLine(myBar.MyFooString);
Console.WriteLine(myIFoo.MyFooString);

Dari atas kepala saya, saya sebenarnya tidak yakin apakah Anda akan mendapatkan "Foo" atau "Bar" di baris terakhir itu. Anda pasti akan mendapatkan "Foo" untuk baris pertama dan "Bar" untuk yang kedua, meskipun ketiga variabel referensi contoh yang sama persis dengan keadaan yang sama persis.

Jadi, perancang bahasa, di alam semesta alternatif kita, mencegah kode yang jelas-jelas buruk ini dengan mencegah persembunyian properti. Sekarang, Anda sebagai pembuat kode harus benar-benar melakukan ini. Bagaimana Anda mengatasi batasan itu? Salah satu caranya adalah memberi nama properti Bar secara berbeda:

public class Bar:Foo
{
   public string MyBarString {get{return "Bar";}}       
}

Sangat legal, tetapi itu bukan perilaku yang kita inginkan. Sebuah instance dari Bar akan selalu menghasilkan "Foo" untuk properti MyFooString, ketika kami menginginkannya untuk menghasilkan "Bar". Kita tidak hanya harus tahu bahwa IFoo kita secara khusus adalah Bar, kita juga harus tahu untuk menggunakan pengakses yang berbeda.

Kami juga bisa, cukup masuk akal, melupakan hubungan orangtua-anak dan mengimplementasikan antarmuka secara langsung:

public class Bar:IFoo
{
   public string MyFooString {get{return "Bar";}}
   public int FooMethod() {...}
}

Untuk contoh sederhana ini, ini adalah jawaban yang sempurna, selama Anda hanya peduli bahwa Foo dan Bar sama-sama IFOO. Kode penggunaan beberapa contoh akan gagal dikompilasi karena Bar bukan Foo dan tidak dapat ditetapkan seperti itu. Namun, jika Foo memiliki beberapa metode yang berguna "FooMethod" yang dibutuhkan Bar, sekarang Anda tidak dapat mewarisi metode itu; Anda harus mengkloning kode di Bar, atau menjadi kreatif:

public class Bar:IFoo
{
   public string MyFooString {get{return "Bar";}}
   private readonly theFoo = new Foo();

   public int FooMethod(){return theFoo.FooMethod();}
}

Ini adalah peretasan yang jelas, dan sementara beberapa implementasi spesifikasi bahasa OO berjumlah sedikit lebih banyak dari ini, secara konseptual itu salah; jika konsumen dari Bar kebutuhan untuk mengekspos fungsi Foo, Bar harus menjadi Foo, tidak memiliki Foo.

Jelas, jika kita mengendalikan Foo, kita bisa membuatnya virtual, lalu menimpanya. Ini adalah praktik konseptual terbaik di alam semesta kita saat ini ketika seorang anggota diperkirakan akan ditimpa, dan akan bertahan di alam semesta alternatif apa pun yang tidak memungkinkan bersembunyi:

public class Foo:IFoo
{
   public virtual string MyFooString {get{return "Foo";}}
   //...
}

public class Bar:Foo
{
   public override string MyFooString {get{return "Bar";}}
}

Masalah dengan ini adalah bahwa akses anggota virtual, di bawah tenda, relatif lebih mahal untuk dilakukan, dan Anda biasanya hanya ingin melakukannya ketika Anda perlu. Kurangnya persembunyian, bagaimanapun, memaksa Anda untuk menjadi pesimis tentang anggota yang pembuat kode lain yang tidak mengontrol kode sumber Anda mungkin ingin menerapkan kembali; "praktik terbaik" untuk kelas yang tidak tersegel adalah membuat semuanya virtual kecuali Anda tidak menginginkannya secara khusus. Itu juga masih tidak memberi Anda perilaku persembunyian yang tepat; string akan selalu menjadi "Bar" jika instance adalah Bar. Kadang-kadang benar-benar bermanfaat untuk memanfaatkan lapisan data keadaan tersembunyi, berdasarkan tingkat warisan tempat Anda bekerja.

Singkatnya, membiarkan persembunyian anggota adalah kejahatan yang lebih kecil. Tidak memilikinya pada umumnya akan mengarah pada kekejaman yang lebih buruk yang dilakukan terhadap prinsip-prinsip berorientasi objek daripada membiarkannya.

KeithS
sumber
+1 untuk menjawab pertanyaan aktual. Sebuah contoh yang baik dari penggunaan dunia nyata dari persembunyian anggota adalah IEnumerabledan IEnumerable<T>antarmuka, dijelaskan dalam Eric Libbert ini posting blog pada topik.
Phil
Overriding tidak bersembunyi. Saya tidak setuju dengan @Phil bahwa ini menjawab pertanyaan.
Jan Hudec
Maksud saya adalah bahwa mengesampingkan akan menjadi pengganti untuk bersembunyi saat bersembunyi bukanlah suatu pilihan. Saya setuju, itu tidak bersembunyi, dan saya katakan banyak pada paragraf pertama. Tak satu pun dari solusi untuk skenario realitas alternatif saya yaitu tidak bersembunyi di C # bersembunyi; itulah intinya.
KeithS
Saya tidak suka penggunaan bayangan / persembunyian Anda. Kegunaan utama utama yang saya lihat adalah (1) mencari-cari di sekitar situasi di mana versi baru dari kelas dasar mencakup anggota yang bertentangan dengan kode konsumen yang dirancang di sekitar versi yang lebih lama [jelek tapi perlu]; (2) memalsukan hal-hal seperti kovarians tipe-kembali; (3) berurusan dengan kasus-kasus di mana metode kelas dasar dapat dipanggil pada subtipe tertentu tetapi tidak berguna . LSP membutuhkan yang pertama, tetapi tidak mengharuskan yang kedua jika kontrak kelas dasar menentukan bahwa beberapa metode mungkin tidak berguna dalam beberapa kondisi.
supercat
2

Jujur, Eric Lippert, pengembang utama di tim C # compiler, menjelaskannya dengan cukup baik (terima kasih Lescai Ionel untuk tautannya). .NET IEnumerabledan IEnumerable<T>antarmuka adalah contoh yang baik ketika anggota bersembunyi berguna.

Pada hari-hari awal. NET, kami tidak memiliki obat generik. Jadi IEnumerableantarmuka terlihat seperti ini:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Antarmuka inilah yang memungkinkan kami untuk mengambil foreachlebih dari sekumpulan objek, namun kami harus membuang semua objek tersebut agar dapat menggunakannya dengan benar.

Lalu datang obat generik. Ketika kami mendapat obat generik, kami juga mendapat antarmuka baru:

public interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

Sekarang kita tidak perlu membuang benda saat kita mengulanginya! Woot! Sekarang, jika persembunyian anggota tidak diizinkan, antarmuka harus terlihat seperti ini:

public interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumeratorGeneric();
}

Ini akan agak konyol, karena GetEnumerator()dan GetEnumeratorGeneric()dalam kedua kasus melakukan hal yang persis sama , tetapi mereka memiliki nilai pengembalian yang sedikit berbeda. Mereka sangat mirip, pada kenyataannya, bahwa Anda hampir selalu ingin default ke bentuk generik GetEnumerator, kecuali jika Anda bekerja dengan kode warisan yang ditulis sebelum obat generik diperkenalkan ke .NET.

Kadang-kadang anggota bersembunyi tidak memungkinkan lebih banyak ruang untuk kode jahat dan bug sulit-untuk-menemukan. Namun terkadang ini berguna, seperti ketika Anda ingin mengubah jenis pengembalian tanpa melanggar kode lawas. Itu hanya salah satu dari keputusan yang harus dibuat oleh perancang bahasa: Apakah kita merepotkan pengembang yang secara sah membutuhkan fitur ini dan meninggalkannya, atau apakah kita memasukkan fitur ini ke dalam bahasa dan menangkap kritik dari mereka yang menjadi korban penyalahgunaannya?

Phil
sumber
Sementara secara formal IEnumerable<T>.GetEnumerator()menyembunyikan itu IEnumerable.GetEnumerator(), ini hanya karena C # tidak memiliki tipe kovarian kembali ketika menimpa. Logikanya itu menimpa, sepenuhnya sejalan dengan LSP. Menyembunyikan adalah ketika Anda memiliki variabel lokal mapdalam fungsi dalam file yang melakukan using namespace std(dalam C ++).
Jan Hudec
2

Pertanyaan Anda dapat dibaca dua cara: apakah Anda bertanya tentang ruang lingkup variabel / fungsi secara umum, atau Anda mengajukan pertanyaan yang lebih spesifik tentang ruang lingkup dalam hierarki warisan. Anda tidak menyebutkan pewarisan secara khusus, tetapi Anda menyebutkan sulit menemukan bug, yang kedengarannya lebih mirip lingkup dalam konteks pewarisan daripada lingkup biasa, jadi saya akan menjawab kedua pertanyaan.

Lingkup secara umum adalah ide yang baik, karena memungkinkan kita untuk memfokuskan perhatian kita pada satu bagian tertentu (mudah-mudahan kecil) dari program. Karena itu memungkinkan nama lokal selalu menang, jika Anda hanya membaca bagian dari program yang ada dalam cakupan tertentu, maka Anda tahu persis bagian mana yang didefinisikan secara lokal dan apa yang didefinisikan di tempat lain. Entah namanya merujuk ke sesuatu yang lokal, dalam hal ini kode yang mendefinisikannya tepat di depan Anda, atau itu merujuk pada sesuatu di luar lingkup lokal. Jika tidak ada referensi non-lokal yang dapat berubah dari bawah kami (terutama variabel global, yang dapat diubah dari mana saja), maka kami dapat mengevaluasi apakah bagian dari program dalam lingkup lokal sudah benar atau tidak tanpa merujuk ke bagian mana pun dari sisa program sama sekali .

Ini mungkin kadang-kadang menyebabkan beberapa bug, tetapi lebih dari kompensasi dengan mencegah sejumlah besar bug yang mungkin terjadi. Selain membuat definisi lokal dengan nama yang sama dengan fungsi perpustakaan (jangan lakukan itu), saya tidak bisa melihat cara mudah untuk memperkenalkan bug dengan cakupan lokal, tetapi cakupan lokal adalah apa yang memungkinkan banyak bagian dari penggunaan program yang sama i sebagai penghitung indeks untuk satu lingkaran tanpa saling bertabrakan, dan membiarkan Fred menyusuri aula menulis fungsi yang menggunakan string bernama str yang tidak akan mengalahkan string Anda dengan nama yang sama.

Saya menemukan artikel yang menarik dari Bertrand Meyer yang membahas kelebihan muatan dalam konteks warisan. Dia mengemukakan perbedaan yang menarik, antara apa yang dia sebut sintaksis berlebihan (artinya ada dua hal berbeda dengan nama yang sama) dan semantik berlebihan (artinya ada dua implementasi berbeda dari ide abstrak yang sama). Overloading semantik akan baik-baik saja, karena Anda bermaksud mengimplementasikannya secara berbeda dalam subkelas; sintaksis yang berlebihan akan menjadi tabrakan nama yang tidak disengaja yang menyebabkan bug.

Perbedaan antara kelebihan beban dalam situasi pewarisan yang dimaksudkan dan yang merupakan bug adalah semantik (artinya), sehingga kompiler tidak memiliki cara untuk mengetahui apakah yang Anda lakukan benar atau salah. Dalam situasi ruang lingkup yang sederhana, jawaban yang benar selalu merupakan hal lokal, sehingga kompiler dapat mengetahui apa hal yang benar.

Saran Bertrand Meyer adalah menggunakan bahasa seperti Eiffel, yang tidak memungkinkan bentrokan nama seperti ini dan memaksa programmer untuk mengganti nama satu atau keduanya, sehingga menghindari masalah sepenuhnya. Saran saya adalah untuk menghindari penggunaan warisan sepenuhnya, juga menghindari masalah sepenuhnya. Jika Anda tidak dapat atau tidak ingin melakukan salah satu dari hal-hal itu, masih ada hal-hal yang dapat Anda lakukan untuk mengurangi kemungkinan memiliki masalah dengan warisan: ikuti LSP (Prinsip Pergantian Liskov), lebih suka komposisi daripada warisan, tetap hierarki warisan Anda dangkal, dan pertahankan kelas dalam hierarki warisan kecil. Juga, beberapa bahasa mungkin dapat mengeluarkan peringatan, bahkan jika mereka tidak akan mengeluarkan kesalahan, seperti bahasa seperti Eiffel.

Michael Shaw
sumber
2

Ini dua sen saya.

Program dapat disusun menjadi blok (fungsi, prosedur) yang merupakan unit mandiri dari logika program. Setiap blok dapat merujuk ke "hal-hal" (variabel, fungsi, prosedur) menggunakan nama / pengidentifikasi. Pemetaan ini dari nama ke benda disebut mengikat .

Nama-nama yang digunakan oleh sebuah blok terbagi dalam tiga kategori:

  1. Nama yang ditentukan secara lokal, misalnya variabel lokal, yang hanya diketahui di dalam blok.
  2. Argumen yang terikat pada nilai ketika blok dipanggil dan dapat digunakan oleh pemanggil untuk menentukan parameter input / output dari blok.
  3. Nama eksternal / binding yang didefinisikan di lingkungan di mana blok terkandung dan berada dalam ruang lingkup di dalam blok.

Pertimbangkan misalnya program C berikut

#include<stdio.h>

void print_double_int(int n)
{
  int d = n * 2;

  printf("%d\n", d);
}

int main(int argc, char *argv[])
{
  print_double_int(4);
}

Fungsi ini print_double_intmemiliki nama lokal (variabel lokal) d, dan argumen n, dan menggunakan eksternal, nama global printf, yang dalam lingkup tetapi tidak didefinisikan secara lokal.

Pemberitahuan yang printfjuga bisa disampaikan sebagai argumen:

#include<stdio.h>

void print_double_int(int n, int printf(const char *, ...))
{
  int d = n * 2;

  printf("%d\n", d);
}

int main(int argc, char *argv[])
{
  print_double_int(4, printf);
}

Biasanya, argumen digunakan untuk menentukan parameter input / output dari suatu fungsi (prosedur, blok), sedangkan nama global digunakan untuk merujuk pada hal-hal seperti fungsi perpustakaan yang "ada di lingkungan", dan oleh karena itu lebih mudah untuk menyebutkannya hanya ketika mereka dibutuhkan. Menggunakan argumen alih-alih nama global adalah ide utama injeksi ketergantungan , yang digunakan ketika dependensi harus dibuat eksplisit alih-alih diselesaikan dengan melihat konteksnya.

Penggunaan serupa lainnya dari nama yang didefinisikan secara eksternal dapat ditemukan di penutupan. Dalam hal ini nama yang didefinisikan dalam konteks leksikal suatu blok dapat digunakan di dalam blok tersebut, dan nilai yang terikat pada nama itu akan (biasanya) terus ada selama blok itu merujuk padanya.

Ambil contoh kode Scala ini:

object ClosureExample
{
  def createMultiplier(n: Int) = (m: Int) => m * n

  def main(args: Array[String])
  {
    val multiplier3 = createMultiplier(3)
    val multiplier5 = createMultiplier(5)

    // Prints 6.
    println(multiplier3(2))

    // Prints 10.
    println(multiplier5(2))
  }
}

Nilai kembalinya fungsi createMultiplieradalah closure (m: Int) => m * n, yang berisi argumen mdan nama eksternal n. Nama ndiselesaikan dengan melihat konteks di mana penutupan didefinisikan: nama terikat dengan argumen nfungsi createMultiplier. Perhatikan bahwa pengikatan ini dibuat ketika penutupan dibuat, yaitu ketika createMultiplierdipanggil. Jadi nama nterikat dengan nilai aktual dari argumen untuk doa fungsi tertentu. Bandingkan ini dengan kasus fungsi perpustakaan seperti printf, yang diselesaikan oleh linker ketika program dieksekusi dibangun.

Meringkas, mungkin berguna untuk merujuk nama eksternal di dalam blok kode lokal sehingga Anda

  • tidak perlu / ingin memberikan nama yang didefinisikan secara eksternal secara eksplisit sebagai argumen, dan
  • Anda dapat membekukan binding saat runtime saat blok dibuat, dan kemudian mengaksesnya saat blok dipanggil.

Membayangi masuk ketika Anda menganggap bahwa di blok Anda hanya tertarik pada nama yang relevan yang didefinisikan di lingkungan, misalnya dalam printffungsi yang ingin Anda gunakan. Jika kebetulan Anda ingin menggunakan nama lokal ( getc, putc, scanf, ...) yang telah digunakan di lingkungan, Anda menginginkan sederhana untuk mengabaikan (shadow) nama global. Jadi, ketika berpikir secara lokal, Anda tidak ingin mempertimbangkan konteks keseluruhan (mungkin sangat besar).

Di arah lain, ketika berpikir secara global, Anda ingin mengabaikan detail internal konteks lokal (enkapsulasi). Karena itu Anda perlu membayangi, jika tidak menambahkan nama global dapat mematahkan setiap blok lokal yang sudah menggunakan nama itu.

Intinya, jika Anda ingin blok kode merujuk ke binding yang ditentukan secara eksternal, Anda perlu membayangi untuk melindungi nama lokal dari yang global.

Giorgio
sumber