Kondisi global. Ungkapan ini menyebabkan rasa takut dan sakit di hati setiap pengembang yang memiliki ketidakberuntungan menghadapi fenomena ini. Pernahkah Anda menemui perilaku aplikasi yang tidak terduga, tidak memahami alasannya, seperti seorang kesatria yang tidak bahagia yang mencoba membunuh Hydra dengan banyak kepala? Anda berakhir dalam siklus coba-coba tanpa akhir, 90% dari waktu bertanya-tanya apa yang terjadi?
Semua ini bisa menjadi konsekuensi menjengkelkan global: variabel tersembunyi yang mengubah keadaan mereka di tempat yang tidak diketahui, karena alasan yang belum Anda ketahui.
Apakah Anda suka berkeliaran di kegelapan saat Anda mencoba mengubah aplikasi? Tentu saja saya tidak menyukainya. Untungnya, saya punya lilin untuk Anda:
- Pertama, saya akan menggambarkan apa yang paling sering kita sebut negara global. Istilah ini tidak selalu digunakan secara akurat, oleh karena itu memerlukan klarifikasi.
- Selanjutnya, kita akan mengetahui mengapa global berbahaya bagi basis kode kita.
- Kemudian saya akan menjelaskan bagaimana cara memangkas ruang lingkup global untuk mengubahnya menjadi variabel lokal.
- Akhirnya, saya akan berbicara tentang enkapsulasi dan mengapa perang salib terhadap variabel global hanyalah bagian dari masalah besar.
Saya harap artikel ini menjelaskan semua yang perlu Anda ketahui tentang negara global. Jika Anda berpikir bahwa saya telah sangat merindukan dan Anda membenci saya untuk ini dan tidak ingin melihat saya lagi, tulis tentang itu di komentar. Ini akan menyenangkan bagi saya, pembaca saya dan semua orang yang tiba-tiba muncul di halaman ini.
Apakah Anda siap, pembaca yang budiman, untuk menunggang kuda dan mengenal musuh Anda? Temukan global ini dan buat mereka merasakan baja pedang kita!
Apa itu kondisi?

Mari kita mulai dengan dasar-dasar sehingga Anda pengembang saling memahami.
Suatu negara adalah definisi dari suatu sistem atau entitas. Negara ditemukan dalam kehidupan nyata:
- Saat komputer dimatikan, statusnya dimatikan.
- Ketika secangkir teh panas, kondisinya panas.
Dalam pengembangan perangkat lunak, beberapa konstruksi (seperti variabel) mungkin memiliki status. Katakanlah string "halo" atau angka 11 tidak dianggap sebagai negara, melainkan nilai. Mereka menjadi keadaan ketika mereka dilampirkan ke variabel dan ditempatkan di memori.
<?php echo "hello";
Dua jenis negara dapat dibedakan:
Status variabel: setelah inisialisasi mereka, mereka dapat berubah selama eksekusi aplikasi Anda kapan saja.
<?php $lala = "hello";
Status yang tidak dapat diubah: tidak dapat berubah selama eksekusi. Anda menetapkan status pertama ke variabel Anda, dan nilainya tidak kemudian berubah. "Konstanta" dalam kehidupan sehari-hari disebut sebagai contoh keadaan abadi:
<?php define("GREETING", "hello");
Sekarang mari kita dengarkan percakapan hipotetis antara Denis dan Vasily, sesama pengembang Anda:
- Dan! Anda telah membuat variabel global di mana-mana! Mereka tidak dapat diubah tanpa semuanya melanggar! Aku akan membunuhmu!
- Nifiga, Vasek! Nasib global saya luar biasa! Saya memasukkan jiwa saya ke dalamnya, ini adalah karya agung! Saya mengagumi para global saya!
Paling sering, pengembang memanggil negara global, variabel global, atau global apa yang mereka sebut negara bisa berubah global. Yaitu, status yang dapat dimodifikasi dalam lingkup terbesar yang tersedia untuk Anda: di seluruh aplikasi.
Ketika suatu variabel tidak memiliki seluruh aplikasi sebagai ruang lingkup, kita berbicara tentang variabel lokal, atau lokal. Mereka ada di beberapa area visibilitas tertentu, kurang dari area seluruh aplikasi.
<?php namespace App\Ecommerce; $global = "I'm a mutable global variable!";
Anda mungkin berpikir: betapa mudahnya memiliki variabel yang dapat Anda akses dari mana saja dan mengubahnya! Saya dapat mentransfer status dari satu bagian aplikasi ke yang lain! Tidak perlu melewati mereka melalui fungsi dan menulis begitu banyak kode! Salam, keadaan global yang bisa berubah!
Jika Anda benar-benar berpikir begitu, saya sangat merekomendasikan untuk terus membaca.
Negara-negara global lebih buruk daripada wabah dan kolera?
Diagram tautan terbesar
Fakta: akan lebih mudah bagi Anda untuk membuat diagram koneksi aplikasi yang akurat jika hanya berisi lokal dengan cakupan kecil dan pasti, tanpa global.
Mengapa
Katakanlah Anda memiliki aplikasi besar dengan variabel global. Setiap kali Anda perlu mengubah sesuatu, Anda harus:
- Ingatlah bahwa negara-negara global yang bisa berubah ini ada.
- Perkirakan apakah mereka akan memengaruhi ruang lingkup yang akan Anda ubah.
Biasanya Anda tidak perlu memikirkan variabel lokal yang ada di lingkup lain, tetapi apa pun yang Anda lakukan, Anda selalu perlu menjaga tempat di otak Anda yang lelah untuk keadaan global yang bisa berubah, karena mereka dapat mempengaruhi semua ruang lingkup.
Selain itu, status global Anda yang dapat berubah dapat berubah di mana saja di aplikasi. Orang biasanya bertanya-tanya seperti apa keadaan mereka saat ini. Ini berarti bahwa Anda dipaksa untuk mencari di seluruh aplikasi, mencoba menghitung nilai-nilai global dalam ruang lingkup yang dapat dimodifikasi.
Bukan itu saja. Jika Anda perlu mengubah keadaan global, maka Anda tidak akan membayangkan ruang lingkup apa yang akan terpengaruh. Apakah ini akan menyebabkan perilaku yang tidak terduga dari kelas, metode, atau fungsi lain? Sukses dalam pencarian.
Singkatnya, Anda menggabungkan semua kelas, metode, dan fungsi yang menggunakan status global yang sama. Jangan lupa:
ketergantungan sangat meningkatkan kompleksitas . Apakah itu membuatmu takut? Seharusnya begitu. Area visibilitas khusus yang kecil sangat berguna: Anda tidak perlu mengingat seluruh aplikasi, cukup mengingat hanya area yang Anda gunakan.
Orang tidak segera melacak sejumlah besar informasi. Ketika kami mencoba melakukan ini, kami dengan cepat menghabiskan pasokan kemampuan kognitif, menjadi sulit bagi kami untuk berkonsentrasi, dan kami mulai membuat bug dan hal-hal bodoh. Inilah sebabnya mengapa sangat tidak menyenangkan untuk bertindak dalam lingkup global aplikasi Anda.
Tabrakan Nama Global
Ada kesulitan menggunakan perpustakaan pihak ketiga. Bayangkan Anda ingin menggunakan pustaka yang sangat keren yang secara acak mewarnai setiap karakter dengan efek flicker. Impian setiap pengembang! Jika perpustakaan ini juga menggunakan global yang memiliki nama yang sama dengan Anda, maka Anda akan menikmati tabrakan nama. Aplikasi Anda akan macet dan Anda akan menebak alasannya, mungkin untuk waktu yang lama:
- Pertama, Anda perlu mengetahui bahwa perpustakaan Anda menggunakan variabel global.
- Kedua, Anda perlu menghitung variabel mana yang digunakan selama eksekusi - milik Anda atau perpustakaan? Tidak sesederhana itu, namanya sama!
- Ketiga, karena Anda tidak dapat mengubah perpustakaan sendiri, Anda harus mengubah nama variabel global yang dapat berubah. Jika digunakan di seluruh aplikasi, Anda akan menangis.
Pada setiap tahap Anda akan merobek rambut Anda dari amarah dan keputusasaan. Sebentar lagi Anda tidak lagi membutuhkan sisir. Skenario ini tidak mungkin menggoda Anda. Mungkin seseorang akan ingat bahwa pustaka JavaScript Mootools, Underscore, dan jQuery selalu berselisih satu sama lain jika mereka tidak ditempatkan di ruang lingkup yang lebih kecil. Oh, dan objek
$
global terkenal di jQuery!
Pengujian akan berubah menjadi mimpi buruk
Jika saya belum meyakinkan Anda, mari kita lihat situasi dari sudut pandang pengujian unit: bagaimana Anda menulis tes di hadapan variabel global? Karena tes dapat mengubah global, Anda tidak tahu tes apa yang sedang dilakukan. Anda perlu mengisolasi tes satu sama lain, dan negara-negara global mengikatnya bersama.
Pernahkah Anda memilikinya sehingga dalam tes isolasi berfungsi dengan baik, dan ketika Anda menjalankan seluruh paket, akankah mereka gagal? Tidak Dan saya memilikinya. Setiap kali saya mengingat ini, saya menderita.
Masalah konkurensi
Status global variabel dapat menyebabkan banyak masalah jika Anda membutuhkan konkurensi. Ketika Anda mengubah keadaan global dalam beberapa utas eksekusi, maka jungkir balik dalam kondisi yang kuat
dalam lomba .
Jika Anda adalah pengembang PHP, maka ini tidak mengganggu Anda, kecuali jika Anda menggunakan perpustakaan yang memungkinkan Anda untuk membuat paralelisme. Namun, ketika Anda
mempelajari bahasa baru di mana paralelisme mudah diimplementasikan, saya harap Anda mengingat prosa saya.
Menghindari keadaan global yang bisa berubah

Walaupun negara-negara yang bisa berubah global dapat menyebabkan banyak masalah, mereka terkadang sulit untuk dihindari.
Ambil REST API: titik akhir menerima semacam permintaan HTTP dengan parameter dan mengirim respons. Parameter HTTP yang dikirim ke server ini dapat diminta di berbagai tingkat aplikasi Anda. Sangat menggoda untuk membuat parameter ini mendunia saat menerima permintaan HTTP, memodifikasinya sebelum mengirim respons. Tambahkan paralelisme di atas setiap permintaan, dan resep bencana siap.
Negara yang bisa berubah global juga dapat secara langsung didukung dalam implementasi bahasa. Misalnya, dalam PHP ada
superglobals .
Jika global datang dari suatu tempat, lalu bagaimana cara menghadapinya? Bagaimana cara menolak aplikasi Denis, rekan pengembang Anda yang menciptakan global sedapat mungkin, karena dia belum membaca apa pun tentang pengembangan selama 20 tahun terakhir?
Argumen fungsi
Cara termudah untuk menghindari global adalah dengan mengirimkan variabel menggunakan argumen fungsi. Ambil contoh sederhana:
<?php namespace App; use Router\HttpRequest; use App\Product\ProductData; use App\Exceptions; class ProductController { public function createAction(HttpRequest $httpReq) { $productData = $httpReq->get("productData"); if (!$this->productModel->validateProduct($productData)) { return ValidationException(sprintf("The product %d is not valid", $productData["id"])); } $product = $this->productModel->createProduct($productData); } } class Product { public function createProduct(array $productData): Product { $productData["name"] = "SuperProduct".$productData["name"];
Seperti yang Anda lihat,
$productData
dari controller, melalui permintaan HTTP, melewati level yang berbeda:
- Pengontrol menerima permintaan HTTP.
- Parameter diteruskan ke model.
- Parameter diteruskan ke DAO .
- Parameter disimpan dalam basis data aplikasi.
Kita bisa membuat array parameter ini global ketika kita mengambilnya dari permintaan HTTP. Tampaknya sangat sederhana: tidak perlu mentransfer data ke 4 fungsi yang berbeda. Namun, meneruskan parameter sebagai argumen ke fungsi:
- Jelas akan menunjukkan bahwa fungsi-fungsi ini menggunakan
$productData
.
- Ini jelas akan menunjukkan fungsi mana yang menggunakan parameter mana. Dapat dilihat bahwa untuk
ProductDao::find
dari $productData
, hanya $id
diperlukan, dan bukan segalanya.
Global membuat kode kurang dapat dipahami dan menghubungkan metode satu sama lain, yang merupakan harga yang sangat tinggi karena hampir tidak ada keuntungan.
Anda sudah mendengar protes Denis: "Dan jika suatu fungsi memiliki tiga atau lebih argumen? Jika Anda perlu menambahkan lebih banyak lagi, maka kompleksitas fungsi akan meningkat! Dan bagaimana dengan variabel, objek, dan konstruksi lain yang diperlukan di mana-mana? Apakah Anda akan meneruskannya ke setiap fungsi dalam aplikasi? "
Pertanyaan itu adil, pembaca yang budiman. Sebagai
pengembang yang baik , Anda harus menjelaskan kepada Denis, menggunakan keterampilan komunikasi Anda, inilah yang:
βDenis, jika fungsi Anda memiliki terlalu banyak argumen, maka fungsi itu sendiri mungkin menjadi masalah. Mereka mungkin melakukan terlalu banyak, bertanggung jawab atas terlalu banyak hal. Anda tidak berpikir untuk membaginya menjadi fungsi yang lebih kecil? " .
Merasa seperti seorang pembicara di Acropolis of Athens, Anda melanjutkan:
βJika Anda membutuhkan variabel di banyak bidang visibilitas, maka ini merupakan masalah, dan segera kami akan membicarakannya. Tetapi jika Anda benar-benar membutuhkannya, lalu apa yang salah dengan meneruskannya melalui argumen fungsi? Ya, Anda harus mengetiknya di keyboard, tetapi kami adalah pengembang, tugas kami untuk menulis kode. "Ini mungkin tampak lebih rumit ketika Anda memiliki lebih banyak argumen (mungkin memang demikian), tetapi saya ulangi, kelebihannya melebihi kerugiannya: lebih baik kodenya sejelas mungkin, dan tidak menggunakan status global yang bisa berubah yang tersembunyi.
Objek konteks
Objek kontekstual adalah objek yang berisi data yang ditentukan oleh beberapa konteks. Biasanya, data ini disimpan sebagai pasangan kunci, seperti array asosiatif dalam PHP. Objek seperti itu tidak memiliki perilaku, hanya data, mirip
dengan objek nilai .
Objek konteks dapat menggantikan keadaan global yang dapat berubah. Kembali ke contoh kode sebelumnya. Alih-alih mengirimkan data dari permintaan melalui level, kita dapat menggunakan objek yang merangkum data ini.
Konteksnya adalah kueri itu sendiri: kueri lain - konteks lain - kumpulan data lain. Kemudian objek konteks akan diteruskan ke metode apa pun yang membutuhkan data ini.
Anda berkata: "Luar biasa dan semua itu, tapi apa manfaatnya?"
- Data diringkas dalam suatu objek. Paling sering, tugas Anda adalah membuat data tidak berubah, yaitu, sehingga Anda tidak dapat mengubah status - nilai data dalam objek setelah inisialisasi.
- Jelas, konteks membutuhkan data objek konteks, karena ditransfer ke semua fungsi (atau metode) yang membutuhkan data ini.
- Ini memecahkan masalah konkurensi: jika setiap permintaan memiliki objek konteksnya sendiri, Anda dapat dengan aman menulis atau membacanya di utas eksekusi mereka sendiri.
Tetapi segala sesuatu dalam pengembangan memiliki harga. Objek konteks dapat berbahaya:
- Melihat argumen fungsi, Anda tidak akan tahu data apa yang ada di objek konteks.
- Anda dapat meletakkan apa pun di objek konteks. Berhati-hatilah untuk tidak memasukkan terlalu banyak, misalnya, seluruh sesi pengguna, atau bahkan sebagian besar data aplikasi Anda. Dan kemudian ini bisa terjadi:
$context->getSession()->getUser()->getProfil()->getUsername()
. Langgar hukum Demeter , dan kutukanmu akan menjadi kompleksitas yang gila.
- Semakin besar objek konteks, semakin sulit untuk menemukan data apa dan di mana ruang lingkup yang digunakannya.
Secara umum, saya akan menghindari menggunakan objek kontekstual sejauh mungkin. Mereka dapat menyebabkan banyak keraguan. Kekekalan data merupakan nilai tambah yang besar, tetapi kita tidak boleh melupakan kekurangannya. Jika Anda menggunakan objek konteks, pastikan itu cukup kecil dan berikan ke lingkup yang kecil dan ditentukan dengan cermat.
Jika sebelum menjalankan program Anda tidak tahu berapa banyak status yang akan diteruskan ke fungsi Anda (misalnya, parameter dari permintaan HTTP), maka objek konteks dapat bermanfaat. Oleh karena itu, beberapa dari mereka menggunakannya, ingat, misalnya, objek
Request
di Symfony.
Injeksi Ketergantungan
Alternatif lain yang bagus untuk keadaan global yang bisa berubah adalah dengan menyematkan langsung data yang Anda butuhkan ke objek saat Anda membuatnya. Ini adalah definisi injeksi ketergantungan: seperangkat teknik untuk menanamkan objek dalam komponen Anda (kelas).
Mengapa tepatnya injeksi ketergantungan?
Tujuannya adalah membatasi penggunaan variabel, objek, atau konstruksi lainnya, dan menempatkannya dalam ruang lingkup terbatas. Jika Anda memiliki dependensi yang tertanam, dan karena itu hanya dapat bertindak dalam lingkup objek, maka akan lebih mudah bagi Anda untuk mengetahui dalam konteks mana mereka digunakan dan mengapa. Tidak ada penderitaan dan siksaan!
Ketergantungan injeksi membagi siklus hidup aplikasi menjadi dua fase penting:
- Membuat objek aplikasi dan mengimplementasikan dependensinya.
- Gunakan objek untuk mencapai tujuan Anda.
Pendekatan ini membuat kode lebih jelas, Anda tidak perlu membuat instance segalanya di tempat acak, atau, lebih buruk lagi, menggunakan objek global di mana-mana.
Banyak kerangka kerja menggunakan injeksi ketergantungan, kadang-kadang dalam skema yang cukup kompleks, dengan file konfigurasi dan Dependency Injection Container (DIC). Tetapi sama sekali tidak perlu mempersulit hal-hal. Anda cukup membuat dependensi di satu tingkat dan menerapkannya di tingkat yang lebih rendah. Misalnya, di dunia Go, saya tidak tahu siapa pun yang akan menggunakan DIC. Anda cukup membuat dependensi di file utama dengan kode (main.go), dan kemudian mentransfernya ke tingkat berikutnya. Anda juga dapat instantiate segala sesuatu dalam paket yang berbeda untuk menunjukkan dengan jelas bahwa "fase injeksi ketergantungan" harus dilakukan hanya pada tingkat spesifik ini. Di Go, lingkup paket bisa membuat segalanya lebih mudah daripada di PHP, di mana DIC digunakan secara luas dalam setiap kerangka kerja yang saya tahu, termasuk Symfony dan Laravel.
Implementasi melalui konstruktor atau setter
Ada dua cara untuk menyuntikkan dependensi: melalui konstruktor atau setter. Saya menyarankan, jika mungkin, untuk tetap berpegang pada metode pertama:
- Jika Anda perlu tahu apa dependensi kelas, yang harus Anda lakukan adalah menemukan konstruktor. Tidak perlu mencari metode yang tersebar di seluruh kelas.
- Mengatur dependensi selama instalasi akan memberi Anda kepercayaan diri dalam keamanan menggunakan objek.
Mari kita bicara sedikit tentang poin terakhir: ini disebut "menegakkan invarian". Dengan membuat instance objek dan mengimplementasikan dependensinya, Anda tahu bahwa apa pun yang dibutuhkan objek Anda, ia dikonfigurasikan dengan benar. Dan jika Anda menggunakan setter, bagaimana Anda tahu bahwa dependensi Anda sudah ditetapkan pada saat menggunakan objek? Anda dapat pergi ke tumpukan dan mencoba mencari tahu apakah setter dipanggil, tapi saya yakin Anda tidak ingin melakukan ini.
Pelanggaran enkapsulasi
Bagaimanapun, satu-satunya perbedaan antara negara-negara lokal dan global adalah cakupannya. Mereka terbatas untuk negara bagian setempat, dan untuk global, seluruh aplikasi tersedia. Namun, Anda mungkin mengalami masalah khusus untuk negara-negara global jika Anda menggunakan negara bagian. Mengapa
Apakah Anda mengatakan enkapsulasi?
Menggunakan status global pada akhirnya akan memecah enkapsulasi, sama seperti Anda dapat memecahkannya dengan status lokal.
Mari kita mulai dari awal. Apa yang
Wikipedia katakan kepada kami tentang definisi enkapsulasi? Mekanisme bahasa untuk membatasi akses langsung ke beberapa komponen objek. Pembatasan akses? Mengapa
Seperti yang kita lihat di atas, jauh lebih mudah untuk berpikir dalam lingkup lokal daripada di global. Negara yang bisa berubah global, menurut definisi, tersedia di mana-mana, dan ini bertentangan dengan enkapsulasi! Tidak ada batasan akses untuk Anda.
Tumbuh ruang lingkup dan kebocoran negara

Mari kita bayangkan sebuah negara dalam lingkup kecilnya sendiri. Sayangnya, seiring berjalannya waktu, aplikasi tumbuh, keadaan lokal ini dilewatkan sebagai argumen untuk fungsi di seluruh aplikasi. Sekarang lokal Anda digunakan dalam banyak lingkup, dan di dalamnya semuanya akses langsung ke lokal tersebut diotorisasi. Sekarang sulit bagi Anda untuk menghitung keadaan tepat lokal tanpa melihat ke semua area visibilitas di mana itu ada dan di mana ia dapat diubah. Semua ini telah kita lihat dengan keadaan global yang bisa berubah.
Ambil contoh:
Model Domain Anemik dapat meningkatkan cakupan model yang bisa berubah-ubah. Faktanya, Model Domain Anemik membagi data dan perilaku objek domain Anda menjadi dua kelompok: model (objek dengan data saja) dan layanan (objek hanya dengan perilaku). Paling sering, model ini akan digunakan di semua layanan. Oleh karena itu, ada kemungkinan bahwa beberapa model akan terus meningkatkan cakupannya. Anda tidak akan mengerti model mana yang digunakan dalam konteks apa, keadaan mereka akan berubah, dan semua masalah yang sama akan menimpa Anda.
Saya ingin menyampaikan ide penting: jika Anda menghindari keadaan global yang tidak bisa berubah, ini tidak berarti Anda bisa bersantai, memegang koktail di satu tangan, dan menekan tombol di tangan yang lain, menikmati hidup dan kode legendaris Anda. -,
, -, , .
, . , .
? - . , , -.
, - , , . , , β . , : , . .
.
.
Product
, , :
class Product { public function createProduct(array $productData): Product { $productData["name"] = "SuperProduct".$productData["name"];
$productData
. , , , .
, . , - ? , . .
, , . .
:
class Product { public function createProduct(array $productData): Product {
, ,
$productData
. , .
$productData
, , HTTP-.
, : Β«, Β».
?

. , .
?
. , .
,
ShipmentDelay
, , , . , -,
ShipmentDelay
, , , . ? ,
DRY .
, , . : , , . , , , . , , .
?
, (, ), , , . , , , , .
. :
. , β . , .
, , .
, , . , . , , . , !