Apakah variabel global jahat di Arduino?

24

Saya relatif baru dalam pemrograman dan banyak praktik pengkodean terbaik yang saya baca secara efektif menyatakan bahwa ada beberapa alasan bagus untuk menggunakan variabel global (atau bahwa kode terbaik tidak memiliki global sama sekali).

Saya sudah melakukan yang terbaik untuk mengingat hal ini, ketika menulis perangkat lunak untuk membuat antarmuka Arduino dengan kartu SD, berbicara dengan komputer dan menjalankan pengontrol motor.

Saat ini saya memiliki 46 global untuk sekitar 1100 baris kode "tingkat pemula," (tidak ada garis yang memiliki lebih dari satu tindakan). Apakah ini rasio yang baik atau haruskah saya melihat mengurangi lebih banyak? Juga praktik apa yang dapat saya terapkan untuk mengurangi jumlah global lebih lanjut?

Saya menanyakan hal ini di sini karena saya secara khusus memusatkan perhatian pada praktik terbaik untuk pengkodean pada produk Arduino daripada pemrograman komputer pada umumnya.

ATE-ENGE
sumber
2
Di Arduino Anda tidak dapat menghindari variabel global. Setiap deklarasi variabel di luar ruang lingkup fungsi / metode bersifat global. Jadi, jika Anda perlu berbagi nilai antar fungsi, mereka harus menjadi global, kecuali jika Anda ingin memberikan setiap nilai sebagai argumen.
16
@LookAlterno Err, tidak bisakah kamu menulis kelas di Ardunio, karena hanya C ++ dengan makro dan perpustakaan aneh? Jika demikian, tidak setiap variabel bersifat global. Dan bahkan dalam C, biasanya dianggap praktik terbaik untuk lebih memilih melewati variabel (mungkin di dalam struct) ke dalam fungsi daripada memiliki variabel global. Mungkin kurang nyaman untuk program kecil tetapi biasanya terbayar karena program menjadi lebih besar dan lebih kompleks.
Muzer
11
@LookAlterno: "Saya menghindari" dan "Anda tidak bisa" adalah hal yang sangat berbeda.
Lightness Races dengan Monica
2
Sebenarnya, beberapa programmer tertanam melarang variabel lokal dan memerlukan variabel global (atau fungsi scoped variabel statis) sebagai gantinya. Ketika programnya kecil, itu bisa membuatnya lebih mudah untuk menganalisanya sebagai mesin keadaan sederhana; karena variabel tidak pernah menimpa satu sama lain (yaitu: ketika mereka melakukan stack dan menumpuk variabel yang dialokasikan).
Rob
1
Variabel statis dan global menawarkan keuntungan mengetahui konsumsi memori pada waktu kompilasi. Dengan arduino yang memiliki memori sangat terbatas, ini bisa menjadi keuntungan. Sangat mudah bagi seorang pemula untuk menghabiskan memori yang tersedia dan mengalami kegagalan yang tidak bisa dilacak.
antipattern

Jawaban:

33

Mereka tidak jahat sendiri, tetapi mereka cenderung terlalu sering digunakan di mana tidak ada alasan yang baik untuk menggunakannya.

Ada banyak waktu ketika variabel global menguntungkan. Terutama karena pemrograman Arduino, di bawah tenda, sangat berbeda dengan pemrograman PC.

Manfaat terbesar untuk variabel global adalah alokasi statis. Terutama dengan variabel besar dan kompleks seperti instance kelas. Alokasi dinamis (penggunaan newdll) disukai karena kurangnya sumber daya.

Anda juga tidak mendapatkan pohon panggilan tunggal seperti yang Anda lakukan dalam program C normal ( main()fungsi tunggal memanggil fungsi lain) - sebagai gantinya Anda secara efektif mendapatkan dua pohon terpisah ( setup()fungsi panggilan, lalu loop()fungsi panggilan), yang berarti bahwa kadang-kadang variabel global adalah satu-satunya cara untuk mencapai tujuan Anda (yaitu, jika Anda ingin menggunakannya di keduanya setup()dan loop()).

Jadi tidak, mereka tidak jahat, dan pada Arduino mereka memiliki kegunaan yang lebih banyak dan lebih baik daripada di PC.

Majenko
sumber
Oke, Bagaimana jika itu adalah sesuatu yang hanya saya gunakan loop()(atau dalam beberapa fungsi dipanggil loop())? apakah lebih baik mengaturnya dengan cara yang berbeda daripada mendefinisikannya di awal?
ATE-ENGE
1
Dalam hal ini saya mungkin akan mendefinisikan mereka dalam loop () (mungkin seolah- staticolah saya membutuhkan mereka untuk mempertahankan nilai mereka di iterasi) dan meneruskannya melalui parameter fungsi ke rantai panggilan.
Majenko
2
Itu satu arah, atau berikan sebagai referensi: void foo(int &var) { var = 4; }dan foo(n);- nsekarang 4.
Majenko
1
Kelas dapat dialokasikan secara stack. Memang, tidak baik untuk menempatkan instance besar di stack, tapi tetap saja.
JAB
1
@ JAB Apa pun bisa dialokasikan stack.
Majenko
18

Sangat sulit untuk memberikan jawaban yang pasti tanpa melihat kode Anda yang sebenarnya.

Variabel global tidak jahat, dan mereka sering masuk akal di lingkungan tertanam di mana Anda biasanya melakukan banyak akses perangkat keras. Anda hanya memiliki empat UART, hanya satu port I2C, dll. Jadi masuk akal untuk menggunakan global untuk variabel yang terkait dengan sumber daya perangkat keras tertentu. Dan memang, perpustakaan inti Arduino melakukan itu: Serial, Serial1, dll adalah variabel global. Juga, variabel yang mewakili keadaan global dari program biasanya adalah global.

Saat ini saya memiliki 46 global untuk sekitar 1100 baris [kode]. Apakah ini rasio yang baik [...]

Bukan tentang angka. Pertanyaan yang tepat yang harus Anda tanyakan pada diri Anda adalah, untuk masing-masing global ini, apakah masuk akal untuk memilikinya dalam lingkup global.

Namun, 46 global tampaknya agak tinggi bagi saya. Jika beberapa di antaranya memiliki nilai konstan, kualifikasi sebagai const: kompiler biasanya akan mengoptimalkan penyimpanannya. Jika salah satu dari variabel tersebut hanya digunakan di dalam satu fungsi, buatlah lokal. Jika Anda ingin nilainya tetap ada di antara panggilan ke fungsi, kualifikasi sebagai static. Anda juga bisa mengurangi jumlah global "terlihat" dengan mengelompokkan variabel bersama di dalam suatu kelas, dan memiliki satu instance global dari kelas ini. Tetapi hanya melakukannya ketika masuk akal untuk menyatukan barang-barang. Membuat GlobalStuffkelas besar demi hanya memiliki satu variabel global tidak akan membantu membuat kode Anda lebih jelas.

Edgar Bonet
sumber
1
Baik! Saya tidak tahu staticapakah saya memiliki variabel yang hanya digunakan dalam satu fungsi tetapi mendapatkan nilai baru setiap kali fungsi dipanggil (seperti var=millis()) haruskah saya membuatnya static?
ATE-ENGE
3
Tidak. staticHanya untuk saat Anda perlu menyimpan nilainya. Jika hal pertama yang Anda lakukan dalam fungsi adalah mengatur nilai variabel ke waktu saat ini, tidak perlu menyimpan nilai lama. Semua yang statis lakukan dalam situasi itu adalah membuang-buang memori.
Andrew
Ini adalah jawaban yang sangat lengkap, mengungkapkan kedua poin yang mendukung, dan skeptis tentang, global. +1
underscore_d
6

Masalah utama dengan variabel global adalah pemeliharaan kode. Saat membaca sebaris kode, mudah untuk menemukan deklarasi variabel yang dilewatkan sebagai parameter atau dideklarasikan secara lokal. Tidak mudah untuk menemukan deklarasi variabel global (seringkali diperlukan dan IDE).

Ketika Anda memiliki banyak variabel global (40 sudah banyak), menjadi sulit untuk memiliki nama eksplisit yang tidak terlalu panjang. Menggunakan namespace adalah cara untuk memperjelas peran variabel global.

Cara yang buruk untuk meniru ruang nama di C adalah:

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

Pada prosesor intel atau lengan, akses ke variabel global lebih lambat daripada variabel lain. Ini mungkin kebalikan dari arduino.

Dewan Komisaris
sumber
2
Pada AVR, global menggunakan RAM, sedangkan sebagian besar penduduk lokal non-statis dialokasikan ke register CPU, yang membuat akses mereka lebih cepat.
Edgar Bonet
1
Masalahnya sebenarnya bukan menemukan deklarasi; dapat dengan cepat memahami kode itu penting tetapi pada akhirnya sekunder dari apa yang dilakukannya - dan di sana masalah sebenarnya adalah menemukan varmint mana bagian tak dikenal dari kode Anda dapat menggunakan deklarasi global untuk melakukan hal-hal aneh dengan objek yang Anda tinggalkan di tempat terbuka bagi semua orang untuk melakukan apa pun yang mereka inginkan.
underscore_d
5

Meskipun saya tidak akan menggunakannya saat pemrograman untuk PC, untuk Arduino mereka memiliki beberapa manfaat. Sebagian besar jika sudah dikatakan:

  • Tidak ada penggunaan memori dinamis (menciptakan celah di ruang tumpukan terbatas dari Arduino)
  • Tersedia di mana-mana, jadi tidak perlu untuk melewatinya sebagai argumen (yang menghabiskan biaya ruang)

Juga, dalam beberapa kasus, terutama dari segi kinerja, sebaiknya menggunakan variabel global, alih-alih membuat elemen saat dibutuhkan:

  • Untuk mengurangi celah di ruang tumpukan
  • Mengalokasikan dan / atau membebaskan memori secara dinamis dapat membutuhkan waktu yang cukup lama
  • Variabel dapat 'digunakan kembali', seperti daftar elemen daftar global yang digunakan untuk berbagai alasan, misalnya sekali sebagai buffer untuk SD, dan kemudian sebagai buffer string sementara.
Michel Keijzers
sumber
5

Seperti halnya segala sesuatu (selain goto yang benar-benar jahat) global memiliki tempat mereka.

mis. Jika Anda memiliki port serial debug atau file log yang Anda perlukan untuk dapat menulis dari mana saja, maka seringkali masuk akal untuk membuatnya menjadi global. Demikian pula jika Anda memiliki informasi penting tentang status sistem maka menjadikannya global sering merupakan solusi termudah. Tidak ada gunanya memiliki nilai yang harus Anda berikan ke setiap fungsi tunggal dalam program.

Seperti yang dikatakan orang lain, 46 sepertinya hanya lebih dari 1000 baris kode tetapi tanpa mengetahui detail dari apa yang Anda lakukan, sulit untuk mengatakan apakah Anda menggunakannya berlebihan atau tidak.

Namun untuk setiap global, tanyakan pada diri sendiri beberapa pertanyaan penting:

Apakah namanya jelas dan spesifik sehingga saya tidak sengaja mencoba menggunakan nama yang sama di tempat lain? Jika tidak ganti nama.

Apakah ini perlu diubah? Jika tidak maka pertimbangkan untuk menjadikannya const.

Apakah ini perlu terlihat di mana-mana atau hanya global agar nilainya dipertahankan antara panggilan fungsi? Jadi pertimbangkan membuatnya lokal ke fungsi dan menggunakan kata kunci statis.

Akankah hal-hal benar-benar kacau jika ini diubah oleh sepotong kode ketika saya tidak berhati-hati? mis. jika Anda memiliki dua variabel terkait, ucapkan nama dan nomor ID, yang bersifat global (lihat catatan sebelumnya tentang penggunaan global ketika Anda membutuhkan beberapa informasi hampir di mana-mana) maka jika salah satu dari mereka diubah tanpa hal buruk lainnya dapat terjadi. Ya, Anda bisa saja berhati-hati tetapi kadang-kadang ada baiknya sedikit menegakkan hati. mis. meletakkannya dalam file .c yang berbeda dan kemudian mendefinisikan fungsi yang mengatur keduanya sekaligus dan memungkinkan Anda untuk membacanya. Anda kemudian hanya memasukkan fungsi-fungsi dalam file header terkait, dengan cara itu sisa kode Anda hanya dapat mengakses variabel melalui fungsi yang ditentukan dan sehingga tidak dapat mengacaukan semuanya.

- Perbarui - Saya baru menyadari bahwa Anda telah bertanya tentang praktik terbaik khusus Arduino daripada pengkodean umum dan ini lebih merupakan balasan kode umum. Tetapi jujur ​​tidak ada banyak perbedaan, praktik yang baik adalah praktik yang baik. The startup()dan loop()struktur Arduino berarti bahwa Anda harus menggunakan GLOBALS sedikit lebih dari platform lain dalam beberapa situasi tapi itu tidak benar-benar berubah banyak, Anda selalu berakhir bertujuan untuk yang terbaik yang dapat Anda lakukan dalam keterbatasan platform tidak peduli apa platform ini.

Andrew
sumber
Saya tidak tahu apa-apa tentang Arduino tetapi melakukan banyak pengembangan desktop dan server. Ada satu penggunaan yang dapat diterima (IMHO) untuk gotos dan itu untuk keluar dari loop bersarang, itu jauh lebih bersih dan lebih mudah dipahami daripada alternatif.
Kegigihan
Jika Anda pikir gotoitu jahat, periksa kode Linux. Bisa dibilang, mereka sama jahatnya seperti try...catchbalok ketika digunakan seperti itu.
Dmitry Grigoryev
5

Apakah mereka jahat? Mungkin. Masalah dengan global adalah bahwa mereka dapat diakses dan dimodifikasi pada suatu titik waktu dengan fungsi atau bagian kode apa pun yang dieksekusi, tanpa batasan. Ini dapat menyebabkan situasi yang, katakanlah, sulit untuk dilacak dan dijelaskan. Oleh karena itu diminimalkan jumlah global, jika mungkin membawa jumlah kembali ke nol, karena itu diinginkan.

Bisakah mereka dihindari? Hampir selalu ya. Masalah dengan Arduino adalah, bahwa mereka memaksa Anda ke dalam dua pendekatan fungsi di mana mereka menganggap Anda setup()dan Anda loop(). Dalam kasus khusus ini Anda tidak memiliki akses ke ruang lingkup fungsi pemanggil dari kedua fungsi ini (mungkin main()). Jika Anda melakukannya, Anda akan dapat menyingkirkan diri Anda dari semua global dan menggunakan penduduk lokal sebagai gantinya.

Bayangkan yang berikut ini:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

Ini mungkin lebih atau kurang apa fungsi utama dari sebuah penampilan Program Arduino seperti. Variabel yang Anda butuhkan di kedua setup()dan loop()fungsi akan kemudian lebih dinyatakan dalam lingkup main()fungsi daripada lingkup global. Mereka kemudian dapat diakses oleh dua fungsi lain dengan cara melewati mereka sebagai argumen (menggunakan pointer jika diperlukan).

Sebagai contoh:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

Perhatikan bahwa dalam hal ini Anda juga perlu mengubah tanda tangan dari kedua fungsi tersebut.

Karena ini mungkin tidak layak atau tidak diinginkan, saya benar-benar melihat hanya satu cara untuk menghapus sebagian besar global dari program Arduino tanpa memodifikasi struktur program yang dipaksakan.

Jika saya ingat dengan benar, Anda dapat menggunakan C ++ saat pemrograman untuk Arduino, dan bukan C. Jika Anda belum terbiasa dengan OOP (Pemrograman Berorientasi Objek) atau C ++, mungkin perlu beberapa waktu untuk membiasakan diri dan beberapa bacaan.

Proposal saya akan membuat kelas Program dan membuat contoh tunggal global kelas ini. Kelas harus dianggap sebagai cetak biru untuk objek.

Pertimbangkan contoh program berikut:

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voa, kami telah menyingkirkan hampir semua global. Fungsi di mana Anda akan mulai menambahkan logika aplikasi Anda akan menjadi Program::setup()dan Program::loop()fungsi. Fungsi-fungsi ini memiliki akses ke variabel anggota contoh spesifik myFirstSampleVariabledan mySecondSampleVariablesedangkan fungsi tradisional setup()dan loop()tidak memiliki akses karena variabel-variabel ini telah ditandai sebagai kelas privat. Konsep ini disebut enkapsulasi data atau penyembunyian data.

Mengajari Anda OOP dan / atau C ++ sedikit keluar dari ruang lingkup jawaban untuk pertanyaan ini jadi saya akan berhenti di sini.

Untuk meringkas: global harus dihindari dan hampir selalu mungkin untuk secara drastis mengurangi jumlah global. Juga saat Anda pemrograman untuk Arduino.

Yang terpenting saya harap jawaban saya agak berguna bagi Anda :)

Arjen
sumber
Anda dapat menentukan main Anda sendiri () di sketsa jika Anda mau. Inilah yang terlihat seperti stok: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234
Singleton secara efektif bersifat global, hanya berpakaian dengan cara yang membingungkan. Ini memiliki kelemahan yang sama.
patstew
@patstew Apakah Anda keberatan menjelaskan kepada saya bagaimana perasaan Anda dengan kerugian yang sama? Menurut pendapat saya itu tidak karena Anda dapat menggunakan enkapsulasi data untuk keuntungan Anda.
Arjen
@ per1234 Terima kasih! Saya jelas bukan ahli Arduino, tapi saya kira saran pertama saya bisa bekerja juga.
Arjen
2
Yah, itu masih keadaan global yang dapat diakses di mana saja dalam program ini, Anda hanya mengaksesnya melalui Program::instance().setup()bukan globalProgram.setup(). Menempatkan variabel global terkait ke dalam satu kelas / struct / namespace dapat bermanfaat, terutama jika mereka hanya dibutuhkan oleh beberapa fungsi terkait, tetapi pola tunggal tidak menambahkan apa-apa. Dengan kata lain static Program p;memiliki penyimpanan global dan static Program& instance()memiliki akses global, yang jumlahnya sama dengan sederhana Program globalProgram;.
patstew
4

Variabel global tidak pernah jahat . Aturan umum yang menentang mereka hanyalah penopang agar Anda bertahan cukup lama untuk mendapatkan pengalaman membuat keputusan yang lebih baik.

Apa variabel global, adalah asumsi yang melekat bahwa hanya ada satu hal (tidak masalah jika kita berbicara tentang array global atau peta yang mungkin mengandung banyak hal, yang masih mengandung asumsi bahwa hanya ada satu daftar atau pemetaan seperti itu, dan bukan beberapa yang independen).

Jadi sebelum Anda menggunakan global, Anda ingin bertanya pada diri sendiri: apakah mungkin saya ingin menggunakan lebih dari satu hal ini? Jika ternyata benar, Anda harus memodifikasi kode untuk membatalkan globalisasi hal itu, dan Anda mungkin akan mengetahui sepanjang bagian bahwa kode Anda bergantung pada asumsi keunikan, sehingga Anda Harus memperbaikinya juga, dan prosesnya menjadi membosankan dan rawan kesalahan. "Jangan gunakan global" diajarkan karena biasanya itu adalah biaya yang cukup kecil untuk menghindari global sejak awal, dan itu menghindari potensi harus membayar biaya besar di kemudian hari.

Tetapi asumsi penyederhanaan yang memungkinkan global juga membuat kode Anda lebih kecil, lebih cepat, dan menggunakan lebih sedikit memori, karena tidak harus menyampaikan gagasan tentang hal mana yang digunakan, tidak harus melakukan tipuan, tidak harus pertimbangkan kemungkinan hal yang diinginkan mungkin tidak ada, dll. Pada embedded Anda lebih cenderung dibatasi untuk ukuran kode dan / atau waktu CPU dan / atau memori daripada Anda di PC, jadi penghematan ini bisa berarti. Dan banyak aplikasi tertanam juga memiliki lebih banyak kekakuan dalam persyaratan - Anda tahu bahwa chip Anda hanya memiliki salah satu periferal tertentu, pengguna tidak bisa begitu saja mencolokkan yang lain ke port USB atau sesuatu.

Alasan umum lainnya untuk menginginkan lebih dari satu dari sesuatu yang tampaknya unik adalah pengujian - menguji interaksi antara dua komponen lebih mudah ketika Anda bisa memberikan contoh pengujian beberapa komponen ke suatu fungsi, sedangkan mencoba untuk memodifikasi perilaku komponen global adalah proposisi yang lebih rumit. Tetapi pengujian di dunia yang disematkan cenderung sangat berbeda dari tempat lain, jadi ini mungkin tidak berlaku untuk Anda. Sejauh yang saya tahu, Arduino tidak memiliki budaya ujian apa pun.

Jadi, teruskan dan gunakan global ketika mereka tampak berharga. Polisi kode tidak akan datang dan menjemputmu. Hanya tahu bahwa pilihan yang salah dapat menyebabkan lebih banyak pekerjaan untuk Anda, jadi jika Anda tidak yakin ...

hobbs
sumber
0

Apakah Variabel Global Jahat di Arduino?

tidak ada yang secara inheren jahat, termasuk variabel global. Saya akan menganggapnya sebagai "kejahatan yang diperlukan" - itu bisa membuat hidup Anda lebih mudah tetapi harus didekati dengan hati-hati.

Juga praktik apa yang dapat saya terapkan untuk mengurangi jumlah global lebih lanjut?

gunakan fungsi wrapper untuk mengakses variabel global Anda. jadi Anda setidaknya mengelola dari perspektif ruang lingkup.

dannyf
sumber
3
Jika Anda menggunakan fungsi wrapper untuk mengakses variabel global, Anda mungkin juga memasukkan variabel Anda di dalam fungsi-fungsi ini.
Dmitry Grigoryev