Apakah "variabel harus hidup dalam lingkup sekecil mungkin" termasuk kasus "variabel tidak boleh ada jika mungkin"?

32

Menurut jawaban yang diterima pada " Dasar Pemikiran untuk memilih variabel lokal daripada variabel instan? ", Variabel harus hidup dalam ruang lingkup sekecil mungkin.
Sederhanakan masalahnya menjadi interpretasi saya, artinya kita harus memperbaiki kode semacam ini:

public class Main {
    private A a;
    private B b;

    public ABResult getResult() {
        getA();
        getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      a = SomeFactory.getA();
    }

    private getB() {
      b = SomeFactory.getB();
    }
}

menjadi sesuatu seperti ini:

public class Main {
    public ABResult getResult() {
        A a = getA();
        B b = getB();
        return ABFactory.mix(a, b);
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

tetapi menurut "semangat" dari "variabel harus hidup dalam ruang lingkup sekecil mungkin", bukankah "tidak pernah memiliki variabel" memiliki ruang lingkup yang lebih kecil daripada "memiliki variabel"? Jadi saya pikir versi di atas harus di refactored:

public class Main {
    public ABResult getResult() {
        return ABFactory.mix(getA(), getB());
    }

    private getA() {
      return SomeFactory.getA();
    }

    private getB() {
      return SomeFactory.getB();
    }
}

sehingga getResult()tidak memiliki variabel lokal sama sekali. Benarkah?

ocomfd
sumber
72
Membuat variabel eksplisit datang dengan manfaat harus menamainya. Memperkenalkan beberapa variabel dapat dengan cepat mengubah metode buram menjadi metode yang dapat dibaca.
Jared Goguen
11
Sejujurnya, contoh dalam hal nama variabel a dan b terlalu dibuat-buat untuk menunjukkan nilai variabel lokal. Untungnya, Anda mendapat jawaban yang baik.
Doc Brown
3
Di cuplikan kedua, variabel Anda tidak benar-benar variabel . Mereka adalah konstanta lokal yang efektif, karena Anda tidak memodifikasinya. Kompiler Java akan memperlakukan mereka sama jika Anda mendefinisikannya final, karena mereka berada dalam lingkup lokal dan kompiler tahu apa yang akan terjadi dengan mereka. Ini masalah gaya dan pendapat, apakah Anda benar-benar harus menggunakan finalkata kunci atau tidak.
hyde
2
@JaredGoguen Membuat variabel eksplisit datang dengan beban karena harus menamai mereka dan manfaat bisa menamainya.
Bergi
2
Benar-benar nol aturan gaya kode seperti "variabel harus hidup dalam ruang lingkup sekecil mungkin" dapat diterapkan secara universal tanpa berpikir. Dengan demikian, Anda tidak pernah ingin mendasarkan keputusan gaya kode pada alasan formulir dalam pertanyaan ini, di mana Anda mengambil pedoman sederhana dan memperluasnya ke area yang kurang jelas-jelas-tertutup ("variabel harus memiliki cakupan kecil jika mungkin" -> "variabel seharusnya tidak ada jika mungkin"). Entah ada alasan bagus yang mendukung kesimpulan Anda secara spesifik, atau kesimpulan Anda adalah ekstensi yang tidak patut; bagaimanapun Anda tidak membutuhkan pedoman asli.
Ben

Jawaban:

110

Tidak. Ada beberapa alasan mengapa:

  1. Variabel dengan nama yang bermakna dapat membuat kode lebih mudah dipahami.
  2. Memecah formula rumit menjadi langkah-langkah kecil dapat membuat kode lebih mudah dibaca.
  3. Caching.
  4. Memegang referensi ke objek sehingga dapat digunakan lebih dari satu kali.

Dan seterusnya.

Robert Harvey
sumber
22
Juga perlu disebutkan: Nilai akan disimpan dalam memori bagaimanapun, sehingga sebenarnya berakhir dengan ruang lingkup yang sama pula. Semoga menyebutkannya (untuk alasan yang disebutkan Robert di atas)!
Maybe_Factor
5
@ Maybe_Factor Pedoman ini tidak benar-benar ada karena alasan kinerja; ini tentang betapa mudahnya mempertahankan kode. Tapi itu tetap berarti bahwa penduduk lokal yang bermakna lebih baik daripada satu ekspresi besar, kecuali Anda hanya menyusun fungsi yang dirancang dengan baik dan dirancang (misalnya tidak ada alasan untuk melakukannya var taxIndex = getTaxIndex();).
Luaan
16
Semua itu adalah faktor penting. Cara saya melihat itu: singkatnya adalah suatu tujuan; kejelasan adalah hal lain; ketahanan adalah hal lain; kinerja adalah hal lain; dan seterusnya. Tak satu pun dari ini adalah tujuan absolut , dan terkadang bertentangan. Jadi tugas kita adalah mencari cara terbaik untuk menyeimbangkan tujuan itu.
gidds
8
Kompiler akan menangani 3 & 4.
Stop Harming Monica
9
Nomor 4 dapat menjadi penting untuk kebenaran. Jika nilai panggilan fungsi dapat berubah (misalnya now(),) menghapus variabel dan memanggil metode lebih dari sekali dapat mengakibatkan bug. Ini dapat menciptakan situasi yang sangat halus dan sulit untuk di-debug. Ini mungkin tampak jelas, tetapi jika Anda berada di misi buta refactoring untuk menghapus variabel, mudah untuk akhirnya memperkenalkan kekurangan.
JimmyJames
16

Setuju, variabel yang tidak perlu dan tidak meningkatkan keterbacaan kode harus dihindari. Semakin banyak variabel yang berada dalam ruang lingkup pada titik tertentu dalam kode, semakin kompleks kode itu untuk dipahami.

Saya tidak benar-benar melihat manfaat dari variabel adan bdalam contoh Anda, jadi saya akan menulis versi tanpa variabel. Di sisi lain fungsinya sangat sederhana, saya pikir itu tidak penting.

Itu menjadi lebih dari masalah, semakin lama fungsinya didapat, dan semakin banyak variabel dalam ruang lingkup.

Misalnya kalau sudah

    a=getA();
    b=getB();
    m = ABFactory.mix(a,b);

Di bagian atas fungsi yang lebih besar, Anda meningkatkan beban mental untuk memahami sisa kode dengan memperkenalkan tiga variabel daripada satu. Anda harus membaca sisa kode untuk melihat apakah aatau bdigunakan lagi. Warga yang berada dalam ruang lingkup lebih lama dari yang mereka butuhkan buruk untuk keterbacaan keseluruhan.

Tentu saja dalam kasus di mana variabel yang diperlukan (misalnya untuk menyimpan hasil sementara) atau di mana variabel tidak meningkatkan pembacaan kode, maka harus disimpan.

JacquesB
sumber
2
Tentu saja, pada suatu titik metode menjadi begitu lama sehingga jumlah penduduk setempat terlalu besar untuk dapat dipelihara dengan mudah, Anda mungkin ingin tetap memecahnya.
Luaan
4
Salah satu manfaat variabel "asing" seperti var result = getResult(...); return result;adalah Anda dapat menggunakan breakpoint returndan mempelajari apa sebenarnya resultitu.
Joker_vD
5
@ Joker_vD: Seorang debugger yang layak namanya harus dapat membiarkan Anda melakukan semua itu (lihat hasil panggilan fungsi tanpa menyimpannya dalam variabel lokal + mengatur breakpoint pada keluar fungsi).
Christian Hackl
2
@ChristianHackl Sangat sedikit debuggesr membiarkan Anda melakukannya tanpa mengatur jam tangan yang mungkin rumit yang perlu waktu untuk ditambahkan (dan jika fungsi memiliki efek samping dapat merusak segalanya). Saya akan mengambil versi dengan variabel untuk debugging setiap hari dalam seminggu.
Gabe Sechan
1
@ChristianHackl dan untuk membangun lebih jauh bahkan jika debugger tidak membiarkan Anda melakukannya secara default karena debugger dirancang dengan mengerikan, Anda bisa memodifikasi kode lokal pada mesin Anda untuk menyimpan hasilnya dan kemudian memeriksanya di debugger . Masih belum ada alasan untuk menyimpan nilai dalam variabel hanya untuk tujuan itu.
The Great Duck
11

Selain jawaban lain, saya ingin menunjukkan sesuatu yang lain. Manfaat menjaga agar ruang lingkup variabel tetap kecil tidak hanya mengurangi berapa banyak kode yang secara sintaksis memiliki akses ke variabel, tetapi juga mengurangi jumlah jalur alur kontrol yang mungkin dapat mengubah variabel (baik dengan menetapkan nilai baru atau panggilan) metode mutasi pada objek yang ada yang disimpan dalam variabel).

Variabel kelas-lingkup (contoh atau statis) memiliki jalur aliran kontrol yang secara signifikan lebih mungkin daripada variabel-lingkup-lingkup karena mereka dapat dimutasi dengan metode, yang dapat dipanggil dalam urutan apa pun, berapa kali, dan sering dengan kode di luar kelas .

Mari kita lihat getResultmetode awal Anda :

public ABResult getResult() {
    getA();
    getB();
    return ABFactory.mix(this.a, this.b);
}

Sekarang, nama-nama getAdan getBmungkin menyarankan bahwa mereka akan menugaskan this.adan this.b, kita tidak bisa tahu pasti dari hanya melihat getResult. Dengan demikian, ada kemungkinan bahwa nilai this.a- this.bnilai dan yang diteruskan ke dalam mixmetode bukannya berasal dari keadaan thisobjek dari sebelumnya getResultdipanggil, yang tidak mungkin untuk diprediksi karena klien mengontrol bagaimana dan kapan metode dipanggil.

Dalam kode yang direvisi dengan variabel lokal adan b, jelas bahwa ada tepat satu (kontrol-bebas) aliran dari penugasan masing-masing variabel untuk penggunaannya, karena variabel dinyatakan tepat sebelum mereka digunakan.

Dengan demikian, ada manfaat yang signifikan untuk memindahkan (dapat dimodifikasi) variabel dari kelas-lingkup ke lokal-cakupan (serta memindahkan (dapat dimodifikasi) variabel dari luar loop ke dalam) di mana ia menyederhanakan alasan aliran kontrol.

Di sisi lain, menghilangkan variabel seperti pada contoh terakhir Anda memiliki sedikit manfaat, karena itu tidak benar-benar mempengaruhi penalaran aliran kontrol. Anda juga kehilangan nama yang diberikan ke nilai-nilai, yang tidak terjadi ketika hanya memindahkan variabel ke cakupan dalam. Ini adalah kompromi yang harus Anda pertimbangkan, jadi menghilangkan variabel mungkin lebih baik dalam beberapa kasus, dan lebih buruk pada yang lain.

Jika Anda tidak ingin kehilangan nama variabel, tetapi masih ingin mengurangi cakupan variabel (jika digunakan di dalam fungsi yang lebih besar), Anda dapat mempertimbangkan membungkus variabel dan penggunaannya dalam pernyataan blok ( atau memindahkannya ke fungsinya sendiri ).

YawarRaza7349
sumber
2

Ini agak tergantung pada bahasa, tetapi saya akan mengatakan bahwa salah satu manfaat yang kurang jelas dari pemrograman fungsional adalah bahwa hal itu mendorong programmer dan pembaca kode untuk tidak memerlukan ini. Mempertimbangkan:

(reduce (fn [map string] (assoc map string (inc (map string 0))) 

Atau LINQ:

var query2 = mydb.MyEntity.Select(x => x.SomeProp).AsEnumerable().Where(x => x == "Prop");

Atau Node.js:

 return visionFetchLabels(storageUri)
    .then(any(isCat))
    .then(notifySlack(secrets.slackWebhook, `A cat was posted: ${storageUri}`))
    .then(logSuccess, logError)
    .then(callback)

Yang terakhir adalah rantai memanggil fungsi pada hasil fungsi sebelumnya, tanpa variabel perantara. Memperkenalkan mereka akan membuatnya menjadi kurang jelas.

Namun, perbedaan antara contoh pertama dan dua lainnya adalah urutan operasi tersirat . Ini mungkin tidak sama dengan urutan yang sebenarnya dihitung, tetapi urutan di mana pembaca harus memikirkannya. Untuk dua yang kedua ini dari kiri ke kanan. Untuk contoh Lisp / Clojure lebih seperti kanan ke kiri. Anda harus sedikit waspada dalam menulis kode yang tidak berada dalam "arah default" untuk bahasa Anda, dan ekspresi "tengah" yang menggabungkan keduanya harus dihindari.

Operator pipa F # |>berguna sebagian karena memungkinkan Anda untuk menulis hal-hal kiri-ke-kanan yang seharusnya harus kanan-ke-kiri.

pjc50
sumber
2
Saya telah menggunakan garis bawah sebagai nama variabel dalam ekspresi LINQ sederhana atau lambda. myCollection.Select(_ => _.SomeProp).Where(_ => _.Size > 4);
Graham
Tidak yakin ini menjawab pertanyaan OP sekecil apa pun ...
AC
1
Mengapa downvotes? Sepertinya jawaban yang baik untuk saya, bahkan jika itu tidak langsung menjawab pertanyaan itu masih informasi yang berguna
reggaeguitar
1

Saya akan mengatakan tidak, karena Anda harus membaca "ruang lingkup sekecil mungkin" sebagai "di antara ruang lingkup yang ada atau yang masuk akal untuk ditambahkan". Kalau tidak, itu akan menyiratkan bahwa Anda harus membuat cakupan buatan (misalnya {}blok serampangan dalam bahasa seperti C) hanya untuk memastikan bahwa ruang lingkup variabel tidak melampaui penggunaan yang dimaksudkan, dan yang umumnya akan disukai sebagai kekaburan / kekacauan kecuali sudah ada alasan yang baik untuk ruang lingkup ada secara mandiri.

R ..
sumber
2
Satu masalah dengan pelingkupan dalam banyak bahasa adalah bahwa sangat umum untuk memiliki beberapa variabel yang penggunaan terakhirnya adalah dalam perhitungan nilai awal dari variabel lain. Jika suatu bahasa memiliki konstruk secara eksplisit untuk tujuan pelingkupan, yang memungkinkan nilai-nilai yang dikomputasi menggunakan variabel-variabel dalam-lingkup untuk digunakan dalam inisialisasi yang-lingkup-luar, cakupan seperti itu bisa lebih berguna daripada cakupan-bersarang ketat yang dibutuhkan banyak bahasa.
supercat
1

Pertimbangkan fungsi ( metode ). Tidak ada pemisahan kode dalam subtugas sekecil mungkin, tidak juga potongan kode tunggal terbesar.

Ini adalah batas yang berubah-ubah, dengan membatasi tugas-tugas logis, menjadi bagian-bagian yang dapat dikonsumsi.

Hal yang sama berlaku untuk variabel . Menunjukkan struktur data yang logis, menjadi bagian yang dapat dimengerti. Atau juga cukup memberi nama (menyatakan) parameter:

boolean automatic = true;
importFile(file, automatic);

Tetapi tentu saja memiliki deklarasi di atas, dan dua ratus baris lebih lanjut penggunaan pertama saat ini diterima sebagai gaya yang buruk. Ini jelas apa "variabel harus hidup dalam ruang sekecil mungkin" bermaksud mengatakan. Seperti sangat dekat "jangan menggunakan kembali variabel."

Joop Eggen
sumber
1

Apa yang agak hilang sebagai alasan untuk TIDAK adalah debugging / readabillity. Kode harus dioptimalkan untuk itu, dan nama-nama yang jelas dan ringkas sangat membantu misalnya bayangkan 3 cara jika

if (frobnicate(x) && (get_age(x) > 2000 || calculate_duration(x) < 100 )

baris ini pendek, tetapi sudah sulit dibaca. Tambahkan beberapa parameter lagi, dan itu jika membentang beberapa baris.

can_be_frobnicated = frobnicate(x)
is_short_lived_or_ancient = get_age(x) > 2000 || calculate_duration(x) < 100
if (can_be_frobnicated || is_short_lived_or_ancient )

Saya menemukan cara ini lebih mudah untuk membaca dan mengkomunikasikan makna - jadi saya tidak punya masalah dengan variabel perantara.

Contoh lain adalah bahasa seperti R, di mana baris terakhir secara otomatis nilai pengembalian:

some_func <- function(x) {
    compute(x)
}

ini berbahaya, apakah pengembaliannya dikeluarkan atau diperlukan? ini lebih jelas:

some_func <- function(x) {
   rv <- compute(x)
   return(rv)
}

Seperti biasa, ini adalah panggilan penilaian - menghilangkan variabel perantara jika mereka tidak meningkatkan bacaan, sebaliknya menyimpan atau memperkenalkan mereka.

Poin lain bisa menjadi debuggability: Jika hasil perantara menarik, yang terbaik adalah memperkenalkan perantara, seperti dalam contoh R di atas. Seberapa sering hal ini disebut sulit untuk dibayangkan dan berhati-hati dengan apa yang Anda periksa - terlalu banyak variabel debug membingungkan - sekali lagi, panggilan penilaian.

Sauer Kristen
sumber
0

Mengacu hanya pada judul Anda: tentu saja, jika suatu variabel tidak perlu itu harus dihapus.

Tetapi "tidak perlu" tidak berarti bahwa program yang setara dapat ditulis tanpa menggunakan variabel, jika tidak kita akan diberitahu bahwa kita harus menulis semuanya dalam biner.

Jenis variabel yang tidak perlu yang paling umum adalah variabel yang tidak digunakan, semakin kecil cakupan variabel semakin mudah untuk menentukan bahwa itu tidak perlu. Apakah variabel perantara tidak perlu lebih sulit untuk ditentukan, karena itu bukan situasi biner, itu kontekstual. Sebenarnya kode sumber yang identik dalam dua metode yang berbeda dapat menghasilkan jawaban yang berbeda oleh pengguna yang sama tergantung pada pengalaman masa lalu memperbaiki masalah dalam kode sekitarnya.

Jika kode contoh Anda persis seperti yang diwakili, saya akan menyarankan menyingkirkan dua metode pribadi, tetapi akan sedikit peduli tentang apakah Anda menyimpan hasil panggilan pabrik ke variabel lokal atau hanya menggunakannya sebagai argumen untuk campuran metode.

Keterbacaan kode mengalahkan segalanya kecuali berfungsi dengan benar (termasuk kriteria kinerja yang dapat diterima, yang jarang "secepat mungkin").

jmoreno
sumber