Sitemap

Array Pointer

Pike’s “Notes on Programming in C” transalation with anotation.

Prakata Penerjemah

Kernighan pernah bilang: “Ketika saya menulis program menggunakan C, seberapa pun besarnya, saya akan menggunakan 75, 80, atau 90% fitur bahasa yang ada. Dengan kata lain, sebagian besar bahasa C itu berguna dalam hampir segala bentuk program. Namun, ketika saya menulis menggunakan C++, mungkin saya bahkan tidak memakai 10% aspek dari bahasa tersebut, dan bahkan 90% sisanya tidak benar-benar saya pahami. Tapi C++ memberi anda banyak sekali hal yang dibutuhkan untuk menulis program besar.” 1

Saya (penerjemah) tersentuh membaca kalimat yang digaris bawahi itu. Maka ketika sedang berniat membuat ikhtisar dari buku The ANSI C Programming Language. 2nd Edition., saya memperhatikan bahwa praktek daripada maksud Kernighan di atas adalah 7 konsep yang Ia susun dalam bab bukunya: 1) Tutorial Introduction 2) Types, Operators and Expressions 3) Control Flow 4) Functions and Program Structure 5) Pointers and Arrays 6) Structures 7) Input and Output, dengan tambahan 8) The UNIX System Interface.

Dengan 8 itu saja Linux tercipta. Hal ini sangat masuk akal mengingat Dennis Ritchie merancang C untuk Unix di awal-awal tahun pembuatannya.2 (Lihat Schein hal. 133). Dengan 8 itu saja juga, produk-produk besar terlahir.

Di bawah ini adalah terjemahan dari “Notes on Programming in C”, pemahaman yang menurut saya lanjutan dari pernyataan Kernighan.

Tambahan dari penerjemah ditulis sebagai footnote (catatan kaki). Jadi, semua kata ganti ‘saya’ mulai dari mukadimah kembali kepada Rob Pike.

Mukadimah

The Elements of Programming Style karangan Kernighan dan Plauger adalah buku yang bagus dan punya pengaruh selayaknya.3 Tapi terkadang saya merasa bahwa aturan-aturan di dalamnya dipakai sebagai resep programming style, bukan sebagaimana mestinya: filosofi. Jika tertera di sana, misalnya, sebuah aturan untuk memilih nama variabel yang bermakna; bukankah artinya variabel yang bentuknya seperti (sub judul) esai itu lebih baik? BukankahMaximumValueUntilOverflow adalah nama yang lebih baik ketimbangmaxval? Saya pikir tidak.

Berikut adalah kumpulan esai pendek yang secara keseluruhan mendorong pola pikir “cara gamblang dalam memprogram”. Di sini tidak akan ada aturan. Saya tidak mengharap supaya anda sepakat dengan semua isinya, karena bentuknya opini, dan opini berubah mengikuti zaman. 4 Tapi yang ada di sini telah terkumpul dalam pikiran saya walaupun tidak di atas kertas seperti sekarang, dan atas dasar banyak pengalaman. Jadi saya berharap mereka bisa membantu anda memahami bagaimana cara merencanakan detail dari sebuah program. (Lagipula saya belum menemukan esai bagus yang menjelaskan bagaiamana merencanakan keseluruhan program, tapi mungkin sebagian dari yang tersaji disini adalah tentang hal itu.) Jika anda berpikir esai ini terlalu idiosinkratis, itu wajar; Jika anda tidak sepakat, itu wajar; tapi jika anda berpikir kenapa anda tidak sepakat, itu lebih baik. Dalam kondisi apapun, anda tidak seharusnya membuat program karena ada yang berkata begitulah seharusnya cara membuat program. Buatlah program sesuai dengan apa yang memang anda rasa sebagai ekspresi terbaik dari tujuan yang ingin anda capai. Lakukanlah itu dengan konsisten dan yakin.

Komentar anda selalu diterima.

Persoalan Tipografi (Dekorasi Penulisan)

Program itu semacam publikasi. Ia dirancang untuk bisa dibaca oleh pemrogram, pemrogram lainnya (mungkin anda sendiri di masa depan), baru mesin. Mesin tidak peduli seberapa cantik program anda — mesin senang jika proses compile tidak rewel— tapi manusia peduli, dan memang begitulah seharusnya. Bahkan kadang mereka terlalu peduli: secara mekanik pretty printer memang memproduksi pretty output, tapi terlalu menonjolkan detail program yang tidak relevan dan terlihat seperti sengaja membuat preposisi (kata depan) di dalam sebuah kalimat dengan huruf tebal.

Memegang kaidah gaya penulisan tentu saja penting dalam penyajian — indentation (menjorok beberapa spasi) mungkin adalah yang paling terkenal dan berguna — tapi ketika gaya justru mengaburkan tujuan, gaya telah mengambil alih tujuan. (Karena anda di sini bukan untuk menghias tapi menyusun algoritma). Jadi hindarilah dekorasi; persingkatlah komentar. Katakanlah tujuan anda, kemudian lanjut. 5

Penamaan Variabel

Variabel: kearifan dalam penamaan bukanlah pada panjangnya, tapi pada kegamblangannya terhadap maksud. Variabel global yang jarang dipakai mungkin patut diberi nama panjang, misal maxphysaddr. Sedangkan indeks array yang dipakai di tiap baris loop tidak perlu lebih dijelaskan, ia hanya perlu karakter i. Karena terlalu panjang untuk menulis index atau elementnumber, dan akan mengaburkan detil perhitungan (akan lebih sulit melihat apa yang sebenarnya terjadi ketika nama variabelnya panjang). Ini masih menyerempet masalah tipografi; bandingkan

for (i=0 to 100)
Array[i] = 0

dengan

for (elementnumber=0 to 100)
Array[elementnumber] = 0

Masalahnya akan lebih parah dalam contoh nyata.

Indeks itu notasi, jadi perlakukan mereka demikian.

Nama yang baik juga diperlukan dalam pointer. np akan berfungsi sebagai mnemonic dari nodepointer jika anda konsisten menggunakannya untuk mengatakan “pointer dari node”. Lebih detail tentang ini akan dibahas dalam esai berikutnya.

Seperti halnya aspek-aspek program tertulis, konsistensi sangatlah penting dalam penamaan. Jika anda memanggil maxphysaddr di satu tempat, jangan beri nama saudaranya dengan lowestaddress.

Kesimpulan terakhir tentang variabel: saya cenderung memilihminimumlength denganmaximuminformation, kemudian biarkan konteks memberikan makna sisanya. Variabel global misalnya, karena jarang punya konteks ketika digunakan, maka penamaanya harus relatif lebih mengundang. Jadi gunakan maxphysaddr (bukan MaximumPhysicalAddress) untuk variabel global, tapi np (bukan nodepointer) untuk pointer yang didefinisikan dan digunakan secara lokal. Sebenarnya ini soal rasa, tapi kadang rasa relevan terhadap kegamblangan.

Penggunaan Pointer

Kemampuan C yang memungkinkan pointer untuk menunjuk pada apapun tidaklah biasa. Pointer adalah pisau yang tajam, dan seperti halnya alat-alat demikian, yang apabila dipakai dengan baik dapat menambah produktivitas, tapi dipakai secara serampangan dapat menyebabkan kerusakan parah. Pointer punya reputasi buruk di kampus, karena dianggap terlalu berbahaya, bahkan kotor. Tapi saya berpikir pointer adalah notasi yang padat fungsi, yang artinya mereka dapat membantu kita untuk mengekspresikan diri kita secara gamblang.

Bayangkan: ketika anda punya pointer ke object, maka ia adalah menunjuk pada object itu dan bukan yang lain. Terdengar biasa, tapi lihatlah ekspresi di bawah ini:

np
node[i]

Bentuk pertama menunjuk ke suatu node, yang kedua mengevaluasi (mengambil dan melihat nilai), katakanlah, node yang sama. Tapi bentuk yang kedua adalah expression; ia tidaklah sesederhana itu. Untuk menerjemahkannya kita mesti tahu apa itu node, apa itu i, dan apakah i punya hubungan dengan lingkungan sekitarnya, yang mungkin belum diberi penegasan yang seharusnya. Expression yang terisolasi tidaklah mampu memberi tahu apakah i adalah indeks valid dari node, apalagi indeks dari elemen yang kita mau. Passing sesuatu ke subroutine juga rentan terhadap kesalahan. Karena pointer adalah satu barang; sedangkan array dan indeks adalah satu kesatuan yang musti diterima sepaket oleh subroutine yang menerimanya.

Expression yang melakukan operasi pengecekan ke sebuah object lebih rentan salah dibanding ke address dari object tersebut. Penggunaan pointer yang benar dapat mempermudah penulisan kode:

parent->link[i].type
lp->type

Jika kita ingin mendapatkan tipe element berikutnya, maka

parent->link[++i].type
(++lp)->type

i maju tapi expression sisanya tetap konstan. Dengan kata lain, hanya ada satu yang maju ketika kita menggunakan pointer.

Persoalan tipografi muncul di sini juga. Menelusuri structure (struct) menggunakan pointer dapat lebih mudah dibaca dibanding dengan expression: dibutuhkan lebih sedikit tinta dan usaha oleh compiler dan komputer. Keunggulan pointer dibanding indeks array adalah fakta bahwa penggunaan pointer sangat ditentukan oleh tipe yang sudah didefinisikan di awal (tidak bisa semena-mena), yang mana akan mempermudah error checking pada compiletime. Apalagi, ketika suatu object merupakan structure (struct), tag mereka sendiri adalah pengingat typenya, maka

np->left

cukup menggugah; jika yang sedang diindeks adalah array, maka penulisan expressionnya akan lebih panjang:

node[i].left

Lagi-lagi, dalam contoh kasus yang lebih besar karakter ekstra membuat sesuatunya menjadi lebih mengganggu.

Sebagai rumus, jika anda menemukan kode dengan banyak expression rumit yang mirip, yang dia mengambil atau melihat nilai dari suatu elemen struktur data, penggunaan pointer yang tepat dapat membuatnya jadi lebih jelas. Bayangkan jika di bawah ini

if (goleft)
p->left = p->right->left;
else
p->right = p->left->right;

menggunakan expression yang kompleks untuk p. Kadang kita butuh variabel sementara (di sini p) atau macro untuk sampai pada kalkulasi inti.

Penamaan Fungsi

Nama fungsi harus mencerminkan apa nilai return mereka. Fungsi digunakan di dalam expression, seringkali dalam parameter if. Jadi

if (checksize(x))

tidaklah terlalu membantu karena kita tidak dapat menyimpulkan nilai return dari checksize, apakah error atau nonerror jika dibandingkan dengan

if (validsize(x))

Bentuk kedua membuat poinnya lebih jelas dan mengurangi kemungkinan kesalahan di masa mendatang akibat penggunaan fungsi yang semena-mena.

Komentar

Sesuatu yang halus membutuhkan perasaaan dan rasionalitas. Saya cenderung berada di pihak yang menghilangkan komentar. Untuk beberapa alasan. Pertama, jika kodenya sudah jalan, menggunakan variabel yang baik, semuanya akan terjelaskan secara otomatis. Kedua, komentar tidaklah diperiksa oleh compiler, jadi kebenarannya tidak terjamin terutama ketika kodenya telah dimodifikasi; komentar yang menyesatkan bisa sangat membingungkan. Ketiga, masalah tipografi: komentar mengotori kode.

Get Umar Rudi’s stories in your inbox

Join Medium for free to get updates from this writer.

Tapi kadang saya pun membuat komentar. Secara khusus, saya menggunakannya untuk ‘pengantar kode di bawah’. Contoh: menjelaskan penggunaan variabel global dan types (satu-satunya yang saya beri komentar di program besar); pengantar dari fungsi yang penting atau tidak biasa; atau menandai bagian dari suatu komputasi besar.

Ada contoh komentar buruk yang terkenal

i = i+1; /* Tambahkan satu ke i */

Dan ada cara yang lebih buruk lagi

/**********************************
* *
* Tambahkan satu ke i *
* *
**********************************/
i=i+1;

Jangan tertawa sekarang, tunggu sampai anda melihatnya di dunia nyata.

Hindari tulisan lucu di dalam komentar, hindari komentar di dalam blok besar kecuali mungkin sebelum bagian vital seperti deklarasi struktur data utama (komentar untuk struktur data seringkali lebih membantu daripada untuk algoritma); pada dasarnya, hindari komentar. Jika kode anda membutuhkan komentar untuk dapat dimengerti, akan lebih baik jika ditulis ulang sehingga lebih mudah dimengerti. Dan itu membawa kita kepada…

Kompleksitas

Sebagian besar program itu rumit — atau setidaknya rumit melebihi solusi yang lebih efisien. Kenapa? Sebagian besar karena desain yang buruk, tapi saya tidak akan membahas itu di sini karena itu adalah persoalan besar. Tapi program seringkali rumit di level mikro, dan itulah yang hendak saya jelaskan di sini.

Aturan 1. Anda tidak bisa menentukan dimana sebuah program akan menghabiskan waktunya. Bottlenecks seringkali muncul di tempat yang tidak terduga, jadi jangan coba untuk menebak-nebak sampai anda dapat membuktikan memang di situlah bottleneck-nya.

Aturan 2. Ukur. Jangan utak-atik kecepatan sampai anda selesai mengukur, dan bahkan jika sudah pun jangan. Kecuali jika memang kode tersebut mengganggu bagian lainnya.

Aturan 3. Algoritma yang diperindah relatif lebih lambat ketika n-nya kecil, dan n seringkali kecil. Algoritma yang diperindah punya nilai konstanta yang besar. Sampai anda tahu bahwa n akan membesar, jangan memperindah. (Bahkan jika n terbukti besar, pakai Aturan 2 terlebih dahulu.) Binary tree, contohnya, seringkali lebih cepat dibanding splay tree untuk tugas-tugas harian.

Aturan 4. Algoritma yang diperindah itu lebih memuat bug jika dibanding yang sederhana. Dan tentunya mereka lebih sulit diimplementasi. Gunakan algoritma yang sederhana, juga data struktur yang sederhana.

Berikut adalah daftar lengkap struktur data untuk sebagian besar program praktis:

  • Array
  • Linked list
  • Hash table
  • Binary tree

Tentu anda harus bersiap untuk mengolahnya menjadi struktur data campuran. Misalnya, symbol table mungkin dapat diimplementasikan dengan hash table yang memuat linked list dari sebuah array yang memuat karakter.6

Aturan 5. Data lebih dominan. Jika anda memilih struktur data dan mengatur semuanya secara baik, algoritmanya hampir-hampir akan selalu mengalir secara intuitif (self evident). Struktur data, bukan algoritma, adalah inti dari pemrograman.7 (Lihat Brooks hal. 102)

Aturan 6. Tidak ada Aturan 6.

Memprogram Menggunakan Data

Algoritma seringkali dapat disarikan (encoded) secara singkat, efisien, dan ekspresif sebagai data dibanding, katakanlah, banyak statement if. Alasannya adalah, jika sebab bentuk kerumitan dalam suatu pekerjaan itu berasal dari kombinasi banyaknya detail-detail, maka bisa di-encode. Contoh klasik dari hal ini adalah tabel untuk parsing, dimana grammar suatu bahasa pemrograman di-encode dalam suatu bentuk yang bisa diinterpretasi oleh kode yang tetap dan relatif sederhana. Finite state machine secara spesifik patuh terhadap hukum ini, tapi sebenarnya hampir seluruh program yang memuat fungsi “parsing” (mengubah input yang abstrak menjadi urutan aksi-aksi tertentu) dapat dibangun menggunakan algoritma yang datadriven.

Mungkin aspek yang paling menggoda dari desain seperti ini adalah ketika tabelnya bahkan bisa dihasilkan oleh program lain — parser generator misalnya, dalam contoh klasik. Contoh yang lebih membumi adalah, jika suatu OS (Operating System) dijalankan oleh semacam tabel yang menghubungkan I/O request dengan device driver yang sesuai, sistemnya mungkin dapat dikonfigurasi oleh program yang melakukan pembacaan deskripsi suatu device tertentu yang terhubung dengan mesin tersebut dan mengeluarkan output table yang sesuai.

Salah satu alasan tidak lumrahnya program dengan datadriven, setidaknya bagi pemula, adalah tirani Pascal. Pascal, seperti pembuatnya, percaya pada pemisahan kode dengan data. Itu lah sebabnya (setidaknya dalam bentuk pertamanya) Pascal tidak punya kemampuan untuk melakukan inisialisasi data. Jika ditarik ke belakang lagi, maka kita akan mendapati teori Turing dan von Neumann terhadap hal ini, dimana mereka mendefinisikan prinsip mendasar dari komputer stored program.

Kode dan data itu sama, setidaknya mereka dapat menjadi demikian. Bagaimana lagi cara anda menjelaskan cara kerja compiler? (Bahasa pemrograman functional punya masalah yang sama dengan I/O.)

Pointer-to-function

Efek lain dari tirani Pascal adalah: pemula tidak menggunakan pointer-to-function. (Anda tidak bisa punya variable bernilai fungsi di Pascal). Memakai pointer-to-function untuk menyederhanakan (encode) kerumitan punya bentuk yang menarik.

Beberapa kerumitan dapat dikirim ke fungsi yang ditunjuk. Fungsinya mesti patuh kepada protokol standar terlebih dahulu, lalu sisanya akan melakukan urusan-urusan detailnya sendiri; kita cukup memperhatikan what dan mengabaikan how pada saat pemakaian fungsi itu. Intinya adalah mendistribusikan kerumitan.

Ide tentang protokol adalah tentang penyeragaman fungsi: fungsi yang mirip mesti berkelakuan mirip. Ini mempermudah dokumentasi, pemeriksaan (testing), dan pengembangan bahkan membuat program yang mampu berjalan dengan jaringan network yang terpencar (distributed) — protokolnya bisa di-encode menjadi rpc remote procedure call.

Saya berargumen bahwa penggunaan pointer-to-function yang jelas adalah jantung dari objectoriented programming. Dengan operasi yang ingin anda lakukan pada suatu data, dan tipe data yang anda harapkan untuk menyimpan operasi tersebut, cara paling mudah untuk menyatukan program adalah dengan sejumlah pointer-to-function untuk tiap tipe data. Inilah pada dasarnya yang mendefinisikan class dan method. Tentu saja bahasa OO (object oriented) memberi anda kemampuan lebih — tapi secara konsep mereka memberikan ekstra yang tidak banyak.

Mengombinasikan datadriven program dengan pointer-to-function menuntun kita kepada cara kerja yang memuaskan. Bahkan tanpa bahasa OO yang spesial, anda bisa mendapatkan 90% keuntungan tanpa melakukan pekerjaan ekstra dan dengan kuasa yang lebih atas hasilnya.

Saya tidak mampu memberi saran untuk gaya implementasi yang lebih lagi. Semua program yang telah saya tata dengan cara seperti ini telah selamat jauh lebih baik melewati banyaknya pengembangan lanjutan, ketimbang pendekatan yang tidak disiplin. Mungkin itu: disiplin akan terbayar di kemudian hari.

Include File

Aturan sederhana: include file harus tidak boleh melakukan include dari file yang sifatnya untuk penggunaan include. Include yang berkali-kali adalah sumber gangguan dari system programming. Tidak jarang kita menemukan file di-include lima kali atau lebih untuk melakukan compile satu file program C. Bentuk /usr/include/sys dalam Unix buruk dalam hal ini.

Ada #ifdef yang dapat mencegah terbacanya sebuah file berkali-kali, tapi biasanya mereka dipraktekkan dengan salah —#ifdef dimasukkan di dalam file itu sendiri, bukan pada file yang melakukan include. Hasilnya seringkali adalah ribuan baris kode untuk yang tidak perlu dalam proses analisa leksikon (lexical analyzer), yang untuk compiler yang bagus adalah fase yang mahal.

Ikutilah aturan yang sederhana.

Footnote

[1] An Interview with Brian Kernighan

[2] Edgar H. Schein. DEC is Dead.

[3] B.W. Kernighan dan P. J. Plauger

[4] Seorang insinyur bertindak menanggapi perubahan, dan perubahan terjadi dalam waktu; jadi mereka yang paham zaman bertindak dengan dinamis (luwes). Buku dihasilkan oleh kata-kata, dan kata-kata berasal dari pengetahuan; jadi mereka yang tahu tidaklah menyimpan buku.

[5] Dekorasi dan hiasan dipakai untuk membuat log (aktivitas program) yang memang butuh pembacaan lebih teliti.

[6] Secara eksplisit maupun implisit, daftar ini bisa disarikan menjadi dua: array dan pointer (hash, linked list, tree). Jadi kalau Kernighan bilang 90% program bisa disarikan dengan 7 konsep, Rob mempersingkatnya lagi menjadi hanya dua saja. Dan entah karena kesengajaan atau bukan, Rob menyampaikan menyelipkan contoh ini pada poin “Kompleksitas”, maka jika dilanjutkan lebih jauh kesimpulannya mungkin bahwa kompleksitas pemrograman bisa dilihat dari penyusunan array dan pointer.

[7] Sitasi aslinya mereferensi kepada Frederick P. Brooks. The Mythical Man-Month. Tapi tentang algoritma Niklaus Wirth pernah bilang bahwa “Program = Algoritma + Struktur Data.”

[8] https://users.ece.utexas.edu/~adnan/pike.html

@rob_pike profile on Twitter