6 poin oleh GN⁺ 2024-02-26 | 1 komentar | Bagikan ke WhatsApp
  • Saat kode produsen dan konsumen saling bertukar data, jika salah satunya dibalik menjadi bentuk callee, struktur algoritme yang semula terlihat mudah tenggelam dalam transisi status
  • Coroutine ala Knuth adalah model di mana dua rutin menyimpan posisi eksekusinya dan saling menyerahkan kontrol, tetapi dalam struktur pemanggilan berbasis stack milik C, hal ini sulit diimplementasikan secara langsung dengan cara yang portabel
  • Trik inti artikel ini adalah memanfaatkan sintaks C yang memungkinkan case ditempatkan di subblok switch, serta makro __LINE__, untuk membuat mesin status implisit yang masuk kembali ke posisi setelah return
  • Dengan makro crBegin, crReturn, dan crFinish, struktur loop asli dekompresor dan parser dapat dipertahankan, tetapi variabel lokal yang harus dipertahankan harus berupa static, dan crReturn tidak boleh ditempatkan di dalam switch eksplisit atau pada baris yang sama
  • Dalam kode nyata, karena batasan reentrancy dan multithreading, diperlukan versi perbaikan yang meneruskan struktur konteks; coroutine.h menyediakan makro scr yang sederhana dan makro ccr yang reentrant

Masalah struktur saat menghubungkan produsen dan konsumen

  • Dalam program besar, sering kali satu kode membuat data dan kode lain mengonsumsinya; pada titik ini, menentukan mana yang menjadi caller dan mana yang menjadi callee membuat desain menjadi sulit
  • Contohnya terdiri dari dua rutin kecil
    • Kode dekompresi run-length membaca input dengan getchar() dan mengeluarkan karakter satu per satu dengan emit()
    • Kode parser membaca karakter dengan getchar(); rangkaian alfabet diproses sebagai WORD, sedangkan karakter lain sebagai PUNCT
  • Jika dilihat terpisah, kedua rutin ini alami, tetapi agar keluaran emit() dari dekompresor langsung menjadi input getchar() bagi parser, diperlukan struktur untuk menghubungkan keduanya
  • Ini juga bisa diselesaikan dengan pipe antara dua proses atau dua thread
    • emit() milik dekompresor menulis ke pipe, dan getchar() milik parser membaca dari sisi sebaliknya
    • Cara ini sederhana dan kuat, tetapi berat dan kurang portabel, sehingga sering kali orang tidak ingin membagi pekerjaan sederhana ke dalam thread

Hilangnya keterbacaan akibat penulisan ulang fungsi

  • Solusi tradisional adalah menulis ulang salah satu ujung kanal komunikasi menjadi bentuk fungsi yang dapat dipanggil
  • Jika dekompresor diubah menjadi fungsi yang mengembalikan satu karakter pada setiap pemanggilan, parser yang ada dapat memanggil decompressor() alih-alih getchar()
  • Sebaliknya, jika parser diubah menjadi fungsi yang dipanggil setiap kali menerima satu karakter, kode dekompresi yang ada cukup memanggil parser() alih-alih emit()
  • Tidak perlu mengubah keduanya; mengubah satu sisi saja sudah cukup untuk menghubungkannya, tetapi kode yang ditulis ulang menjadi jauh lebih sulit dibaca dibanding aslinya
    • Pada dekompresor dan parser asli, alur algoritme tampak alami di dalam loop
    • Bentuk yang ditulis ulang bergantung pada variabel status static dan transisi status switch, sehingga format kompresi atau tata bahasa parser sulit dibaca dari kode
  • Tujuannya adalah menghubungkan keduanya tanpa membalik dan menulis ulang salah satu sisi seperti mesin status eksplisit

Coroutine ala Knuth dan keterbatasan C

  • Solusi coroutine Donald Knuth membuang pembedaan caller dan callee, lalu memperlakukan dua proses sebagai entitas setara yang bekerja sama
  • Prinsip pemanggilan model ini berbeda dari pemanggilan fungsi biasa
    • Posisi eksekusi saat ini disimpan di lokasi terpisah, bukan di stack
    • Eksekusi melompat ke posisi terakhir yang disimpan oleh rutin lain
    • Saat dekompresor memancarkan karakter, ia menyimpan program counter-nya sendiri dan berpindah ke posisi tersimpan milik parser
    • Saat parser membutuhkan karakter berikutnya, ia menyimpan program counter-nya sendiri dan berpindah ke posisi tersimpan milik dekompresor
  • Kontrol bolak-balik di antara dua rutin sebanyak yang diperlukan
  • Cara ini bagus secara teoretis, tetapi dalam praktiknya hanya mungkin dilakukan di bahasa assembly
  • Bahasa tingkat tinggi seperti C bergantung pada struktur berbasis stack, sehingga dalam perpindahan kontrol antar fungsi, salah satu harus menjadi caller dan yang lain callee
  • Dalam kode C yang portabel, pendekatan coroutine murni sama kurang praktisnya dengan solusi pipe Unix

Meniru “return and continue” di C

  • Perilaku yang dibutuhkan di C adalah return and continue, yaitu fungsi callee melakukan return, lalu pada pemanggilan berikutnya melanjutkan eksekusi tepat setelah return tersebut
  • Misalnya, idealnya fungsi berbentuk for (i = 0; i < 10; i++) return i; akan mengembalikan 0 sampai 9 secara berurutan saat dipanggil 10 kali
  • Implementasi pertama menggunakan variabel status dan goto
    • Label ditempatkan di awal fungsi dan setelah setiap return
    • Variabel state yang dipertahankan antar pemanggilan menunjuk ke label tempat melanjutkan berikutnya
    • Pada awal fungsi, switch(state) berpindah ke label yang sesuai
    • Tepat sebelum return, label tujuan untuk pemanggilan berikutnya disimpan ke state
  • Cara ini berfungsi, tetapi pengelolaan label bersifat manual sehingga membebani pemeliharaan
    • Setiap kali menambahkan return, harus membuat label baru dan menambahkannya juga ke switch awal
    • Saat menghapus return, label yang bersesuaian juga harus dihapus
    • Konsistensi antara badan fungsi dan daftar switch harus terus dijaga

Mesin status yang disembunyikan dengan Duff’s device

  • Duff’s device yang terkenal di C memanfaatkan sintaks yang memungkinkan pernyataan case yang berpasangan dengan switch ditempatkan juga di dalam subbloknya
  • Jika sifat ini diterapkan pada trik coroutine, alih-alih switch memilih goto mana yang akan dieksekusi, switch itu sendiri bertindak seperti lompatan masuk kembali
  • Bentuk dasarnya adalah sebagai berikut
    • static int state menyimpan titik lanjut berikutnya
    • Di awal fungsi, masuk melalui switch(state) { case 0: ... }
    • Tepat sebelum return, nilai case berikutnya disimpan ke state
    • Label case tersebut ditempatkan tepat setelah return
  • Jika dibungkus dengan makro, ini menjadi antarmuka yang tampak seperti coroutine
    • crBegin: menyembunyikan static int state=0; switch(state) { case 0:
    • crReturn: menyimpan state, mengembalikan nilai, lalu menempatkan label case di posisi yang sama
    • crFinish: menutup blok yang terbuka
  • crReturn dibungkus dengan do ... while(0), sehingga tidak menimbulkan masalah sintaks meski digunakan tanpa kurung kurawal di antara if dan else
  • Pada awalnya nomor status harus diberikan langsung seperti crReturn(1, i), tetapi dengan memakai makro ANSI C __LINE__, nomor baris sumber saat ini dapat digunakan sebagai nilai status
  • Setelah perbaikan ini, cukup menulis crReturn(x), tetapi ada aturan tambahan: jangan menaruh dua crReturn pada satu baris

Aturan penggunaan makro dan contoh

  • Coroutine berbasis makro mengandaikan beberapa aturan
    • Bungkus badan fungsi dengan crBegin dan crFinish
    • Variabel lokal yang harus dipertahankan melewati crReturn dideklarasikan sebagai static
    • Jangan pernah memasukkan crReturn ke dalam pernyataan switch eksplisit
    • Pada implementasi berbasis __LINE__, jangan menaruh dua crReturn pada baris yang sama
  • Contoh dekompresor mempertahankan struktur loop asli, hanya menggunakan crReturn(c) alih-alih emit(c) saat memancarkan karakter
  • Contoh parser kembali ke caller dengan crReturn() saat membutuhkan karakter baru, lalu pada pemanggilan berikutnya melanjutkan eksekusi dalam keadaan karakter baru telah diterima di parameter c
  • Ada sedikit perubahan struktur pada parser
    • Karena karakter pertama sudah berada di c saat fungsi dimasuki, crReturn yang setara dengan getchar() di awal loop asli dipindahkan ke akhir loop
    • Jika diinginkan, dapat juga ditetapkan bahwa parser memerlukan pemanggilan inisialisasi
  • Tidak perlu mengubah kedua rutin menjadi makro coroutine; satu sisi saja bisa diubah sementara sisi lain tetap menjadi caller
  • Hasilnya, penggabungan ANSI C, preprocessor, dan sintaks switch yang jarang dipakai memungkinkan pengiriman data antara produsen dan konsumen tanpa menulis ulang menjadi mesin status eksplisit

Konflik antara standar coding dan kejelasan algoritme

  • Teknik ini sangat melanggar standar coding umum
    • Kurung kurawal yang tidak berpasangan berada di dalam makro
    • Menggunakan case di dalam subblok
    • crReturn menyembunyikan switch, return, dan case di dalam satu makro
  • Makro yang menyembunyikan struktur sintaks dapat dianggap merusak kejelasan menurut standar coding
  • Namun fungsi yang ditulis ulang sebagai mesin status eksplisit juga terdiri dari blok-blok kecil case STATE dan transisi status, sehingga secara visual tidak jauh berbeda dari fungsi yang menderetkan blok label goto
  • Semakin panjang fungsinya, semakin besar kerusakan yang ditimbulkan penulisan ulang mesin status terhadap struktur algoritme asli
  • Teknik ini adalah kompromi yang menyembunyikan sebagian struktur sintaks demi menampilkan struktur algoritme dengan lebih baik

Versi perbaikan yang reentrant dan kode yang disediakan

  • Implementasi mainan yang sederhana bergantung pada variabel static, sehingga tidak reentrant dan juga tidak cocok untuk multithreading
  • Dalam aplikasi nyata, fungsi yang sama harus dapat dipanggil dari beberapa konteks, dan tiap konteks harus dapat melanjutkan eksekusi dari setelah return terakhirnya
  • Cara perbaikannya adalah meneruskan pointer struktur konteks sebagai parameter tambahan
    • Status lokal dan variabel status coroutine semuanya diletakkan sebagai anggota struktur
    • Variabel seperti penghitung loop juga harus diakses seperti ctx->i, bukan i
    • Kode menjadi sedikit lebih jelek, tetapi masalah reentrancy hilang sementara struktur keseluruhan rutin tetap dipertahankan
  • Pengguna C++ dapat membuat coroutine sebagai anggota kelas dan menaruh status yang setara dengan variabel lokal di dalam kelas, sehingga scope dapat ditangani lebih alami
  • coroutine.h yang disediakan mengimplementasikan trik coroutine ini sebagai sekumpulan makro yang telah didefinisikan
    • Makro berprefiks scr adalah bentuk sederhana yang memakai variabel static
    • Makro berprefiks ccr adalah bentuk lanjutan yang reentrant
    • Dokumentasi detail disertakan dalam komentar di dalam file header
  • Visual C++ 6 tidak menyukai trik ini karena pada pengaturan debug default “Program Database for Edit and Continue”, makro __LINE__ diperlakukan secara aneh
    • Untuk mengompilasi program yang memakai coroutine di VC++ 6, Edit and Continue harus dimatikan
    • Di pengaturan proyek, pada tab “C/C++”, kategori “General”, pengaturan “Debug info”, pilih opsi selain “Program Database for Edit and Continue”
  • File header tersedia dengan lisensi MIT

Referensi terkait dan penggunaan nyata

  • The Art of Computer Programming, Volume 1, Section 1.4.2 karya Donald Knuth membahas coroutine dalam bentuk murninya
  • Diskusi Tom Duff tentang Duff’s device memuat hal yang mengisyaratkan bahwa ia mungkin pernah memikirkan trik coroutine serupa secara independen, dan dalam pembaruan 2005-03-07 Tom Duff mengonfirmasinya lewat komentar blog
  • Kode protokol SSH milik PuTTY menggunakan trik coroutine ini dalam praktik
  • Kasus PuTTY berada pada tingkat C hacking yang kuat dan jarang terlihat dalam kode produksi serius

1 komentar

 
GN⁺ 2024-02-26
Opini Hacker News
  • Saat mencoba mengurangi kompleksitas API di proyek C, saya beberapa kali kembali ke halaman ini, dan menurut saya penjelasan tentang alur kontrol-nya sangat bagus
    Ini juga membantu saya berpikir lebih jelas tentang penyimpanan status di dalam/di luar stack serta perbedaan keterbacaan antarpendekatan
    Kesimpulan saya sekarang adalah sebaiknya pengguna library-lah yang menentukan apakah akan memakai coroutine C. Misalnya, Mongoose(https://github.com/cesanta/mongoose) menangani asinkron lewat event callback; untuk library seperti ini, jauh lebih nyaman membungkusnya dengan primitive thread/task masing-masing sistem daripada mencoba mem-port-nya ke coroutine C lintas platform yang legendaris, atau lebih buruk lagi, ke std::thread

  • Coroutine adalah konsep yang benar-benar keren, dan khususnya video-video coroutine C++ dari CppCon yang terutama dipresentasikan orang-orang Microsoft juga menyenangkan untuk ditonton. Hook “abstraksi berbiaya negatif” juga cukup bagus
    Beberapa tahun lalu teman-teman di Meta mulai memakai coroutine C++, tetapi mereka mengatakan kepada saya bahwa akhirnya itu adalah kesalahan besar. Mereka harus berhadapan dengan bug implementasi compiler, dan pelacakannya pasti cukup berantakan. Di Google, kami menunggu orang-orang hebat yang mengintegrasikannya dengan benar ke google3/ memberi tahu bahwa sekarang sudah boleh dipakai
    Tulisan ini menjelaskan goto terstruktur berbasis macro sebagai strategi implementasi coroutine C melalui Duff's device [1]. Intinya adalah pernyataan case bisa diletakkan hampir di mana saja di dalam blok switch; caranya membungkus seluruh fungsi dengan switch, menyimpan posisi terakhir coroutine return dalam variabel static, lalu memberi label setiap coReturn sebagai case
    Tulisan Sustrik tentang coroutine C juga mungkin menarik [2]
    [1] https://en.wikipedia.org/wiki/Duff%27s_device
    [2] https://250bpm.com/blog:48/index.html

    • Setelah berpindah dari google3 ke fbcode dalam beberapa tahun terakhir, adanya kode asinkron seperti co_yield, co_return, dan co_await yang tersebar di mana-mana dalam kode C++ punya kelebihan dan kekurangan
      Kelebihannya dibanding cara internal google3 adalah, saat membaca kode, sifat asinkron tiap bagiannya terlihat jelas. Sebagian programmer Google hidup tanpa mengetahui model threading di atas tingkat branch, lalu belakangan membuat bug serius
      Kekurangannya lebih sederhana. Karena banyak kode “mungkin saja asinkron”, seiring waktu seluruhnya menjadi asinkron hanya karena programmer menulis dalam mode itu. Memilih antara spinlock dan mutex yang yield seharusnya ditentukan oleh ukuran critical section dan kondisi threading saat itu, tetapi ketika mengejar keterbacaan dan konsistensi, seluruh proyek mudah condong ke satu sisi
      Saya ingin tahu lebih banyak tentang implementasi bahasa threading yang tidak menjadikan salah satu sisi sebagai default, dan yang mengoptimalkan eksekusi berikutnya berdasarkan profil eksekusi sebelumnya tanpa perubahan kode atau bug
    • Sebagai alternatif, bisa memakai fitur labels as values milik GCC. Anda bisa mengambil alamat label dan melompat ke sana nanti. Pada 2005 saya menyumbangkan kode yang sekarang masuk ke lc-addrlabels.h
      Saya juga memakai fitur label lokal GCC sehingga sepenuhnya menghindari penggunaan __LINE__, dan bahkan bisa menaruh beberapa coReturn dalam satu baris
    • Mungkin benar bahwa Duff menyadari case statement bisa dipakai hampir di mana saja di dalam blok switch, tetapi fitur itu sendiri hampir pasti adalah fitur yang disengaja
      Seperti juga disebutkan di bagian bawah tulisan, Duff juga menyadari bahwa coroutine bisa dibuat di atasnya, tetapi ia menganggap gagasan itu “menjijikkan”
      Jika switch di C dianggap seperti pattern matching yang kurang ekspresif, “fallthrough” mudah terlihat seperti bug, padahal bukan. Itu satu keluarga dengan computed goto seperti di Fortran, tetapi lebih nyaman karena nilainya tidak harus berurutan dan label tidak harus semuanya didaftarkan di bagian atas. Setelah menuliskannya, rasanya malah lebih mirip computed COMEFROM
    • Ah, preprocessor C memang terus memberi hadiah seiring berjalannya waktu :-(
  • Pernyataan “bahasa tingkat tinggi yang banyak dipakai tidak mendukung coroutine” mungkin benar pada tahun 2000, tetapi sekarang banyak bahasa seperti C++20, Lua, Python, Ruby dan lainnya mendukungnya

    • Python dibuat pada 1991, jadi rasanya keyword yield juga sudah ada saat itu atau tidak lama setelahnya
      Usulan perbaikan di akhir tulisan, yaitu “menambahkan pointer struct konteks sebagai argumen fungsi tambahan, dan mendeklarasikan semua status lokal serta variabel status coroutine sebagai elemen struct itu”, terlihat seperti implementasi closure. Sepertinya pihak yang dipanggil dijadikan lambda dan menggunakan variabel/konteks/status eksternal untuk menentukan apa yang dilakukan atau dengan nilai apa; saya penasaran apakah pemahaman ini benar
    • Sebagai referensi, Simula67 memiliki coroutine. Bukan yang pertama, tetapi seingat saya itu bahasa besar pertama yang mendukung coroutine
  • Metode switch memang tidak sangat jarang, tetapi biasanya ada pointer status yang diteruskan ke fungsi inisialisasi dan fungsi coroutine
    Saya sering memakai metode ini di proyek embedded; satu coroutine menangani akselerasi/deselerasi motor, sementara coroutine lain hanya memberi tahu arah mana yang harus ditempuh, dan seterusnya. Saya juga pernah memakainya di pustaka jaringan[1]. Di pustaka standar juga ada fungsi coroutine seperti strtok()[2]
    Untuk membuatnya tetap bisa dikelola, tidak perlu sampai memperkenalkan neraka makro, tetapi saya belum pernah merasa senang membaca alur switch/case
    [1]: https://github.com/REONTeam/libmobile/blob/master/relay.c#L3...
    [2]: https://manpages.debian.org/bookworm/manpages-dev/strtok.3.e...

  • Ada juga Simon Tatham's Portable Puzzle Collection dari penulis yang sama
    https://www.chiark.greenend.org.uk/~sgtatham/puzzles/

  • Jika ini terlihat seperti ilmu hitam C, tulisan dari penulis yang sama tentang membuat struktur kontrol arbitrer dengan makro juga layak dibaca: https://www.chiark.greenend.org.uk/~sgtatham/mp/

    • Skema prefiks garis bawah pun sering kali masih rentan terhadap penyembunyian nama. Untuk menghindarinya, nama harus di-mangle dengan cara yang cukup buruk rupa, dan berbeda dari makro yang mirip ekspresi/pernyataan, pada makro blok luar hal ini tidak bisa dihindari bahkan dengan peretasan makro higienis GNU/C23
  • Coroutine memang menarik, tetapi dalam kode nyata penggunaan thread sungguhan juga perlu dipertimbangkan. Prosesor modern punya banyak core, tetapi coroutine sering kali hanya memakai satu core
    Ini juga masalah nyata. Hingga belum lama ini, qemu yang banyak memakai coroutine mengirim sebagian besar I/O block device ke satu thread, dan ini menimbulkan masalah performa. Kevin Wolf dan orang lain memperbaikinya selama bertahun-tahun sehingga qemu modern memakai beberapa thread untuk I/O, dan pekerjaan ini dijadwalkan masuk ke RHEL 9.4

    • Satu-satunya keterkaitan antara thread dan coroutine hanyalah bahwa beberapa runtime bahasa single-thread hanya menyediakan coroutine, sehingga kadang coroutine dipakai di tempat thread sebenarnya merupakan pilihan yang lebih baik
      Coroutine adalah cara menstrukturkan eksekusi single-thread, dan berguna dengan sendirinya. Pola produsen-konsumen pada contoh di artikel adalah contoh yang bagus; menghubungkan stream ke parser bukanlah algoritma paralel, jadi thread tidak berguna untuk menuliskannya
      Memakai paradigma single-thread untuk pekerjaan yang bisa dijalankan paralel tentu saja tidak efisien, tetapi coroutine bukanlah paralelisme versi orang miskin, melainkan struktur kontrol yang bermakna secara independen. Coroutine juga bisa digabungkan secara produktif dengan thread, seperti event loop di server web yang, bersama dispatcher, merangkai coroutine di antara berbagai event blocking; jika runtime memparalelkan dengan menjalankan satu thread per core, koordinasi antar-thread bisa diperkecil menjadi sekadar memeriksa kedalaman tiap antrean kerja dan meneruskan permintaan ke sisi yang lebih lengang
    • Bahwa coroutine biasanya hanya memakai satu core umumnya adalah perilaku yang diinginkan. Jika itu pekerjaan paralel yang saling terpisah, secara alami mereka akan menangani data yang berbeda
      Gagasan coroutine dipakai ketika ada suatu pekerjaan lokal dan data sinkron, dan lebih mudah mengekspresikannya dalam bentuk terbalik: fungsi melakukan loop atas sesuatu lalu “mendorong” hasilnya ke konsumen abstrak di tempat lain, alih-alih paradigma fungsional tempat pemanggil “menarik” loop dalamnya
    • Ada titik yang cukup bagus untuk mencampur thread dan coroutine: setiap thread memiliki instans scheduler coroutine, dan membuat satu thread per core
      Setelah itu, coroutine hampir tidak pernah dipindahkan antar-scheduler, dan data juga hampir tidak pernah dibagi antar-coroutine dari scheduler yang berbeda
      Coroutine memungkinkan gaya pemrograman konkuren yang nyaman tanpa perlu lock sama sekali melalui cooperative scheduling. Biasanya latensi penjadwalan menjadi lebih besar, tetapi throughput bisa menjadi cukup tinggi karena overhead operasi atomik/lock hilang dan timer tidak terus-menerus memutus eksekusi untuk preemptive scheduling
    • Saran “pertimbangkan thread sungguhan” umumnya kurang baik. Misalnya, jika yang ingin dilakukan hanyalah traversal node tree pada koleksi yang tidak rata, saya tidak tahu mengapa harus menjalankan thread terpisah
    • Coroutine ringan dan sinkronisasinya sangat mudah. Cocok untuk komputasi inkremental unit kecil seperti iterator atau tokenizer. Mungkin yang terpikir adalah green thread
  • Versi C++ dari pendekatan ini: https://www.codeproject.com/Tips/29524/Generators-in-C
    Saya juga memakainya di Sciter saya sebagai jaga-jaga, dan ini bekerja cukup baik serta praktis

  • Cara untuk mencapai ini secara modular dan aman mungkin adalah effect handler. Mirip dengan yield di Python, tetapi bisa mengembalikan nilai, dan cakupannya tidak terbatas pada pemanggilan fungsi melainkan seperti exception. Jika belum familier, tulisan ini menjadi motivasi yang bagus
    Setiap fungsi yang ditulis dalam gaya langsung dapat melakukan “effect” ketika kontrol harus pergi ke tempat lain. Di sini, c=getchar() dan emit(c) adalah kasus seperti itu
    Lalu kontrol berpindah ke effect handler, dan dalam kasus ini mungkin pemanggil kedua fungsi yang menentukan apa yang dilakukan berikutnya. Misalnya, ketika decompressor mengeluarkan karakter, karakter itu diteruskan ke kode parser dan dilanjutkan; setelah berjalan sampai parser mengatakan membutuhkan lebih banyak, decompressor dilanjutkan lagi
    Effect dapat diimplementasikan secara efisien, terutama jika continuation dibatasi agar hanya bisa dipanggil sekali. OCaml adalah salah satu contohnya. Ini memungkinkan kode gaya langsung sekaligus keamanan tipe/memori, dan juga sangat berguna dalam lingkungan konkuren
    Contohnya ada di sini: https://effekt-lang.org/docs/casestudies/lexer

  • Sama sekali tidak setuju dengan bagian yang mengatakan “trik ini tentu saja melanggar semua standar coding… saya akan berargumen bahwa standar coding-nya yang salah”
    Bukan standar coding yang keliru karena menolak kode ini; kode ini hanyalah trik yang lucu. Rekayasa perangkat lunak berskala besar adalah soal menghilangkan kejutan dan membuat kode yang tetap bisa dibaca oleh orang kurang tidur yang dipanggil untuk debugging pukul 3 pagi. Kita tidak bisa berharap programmer selalu mengingat empat aturan dasar itu
    Saya juga sulit menerima klaim bahwa menyembunyikan elemen penting seperti switch, return, dan case di dalam macro “obfuscation” membuat struktur sintaksis menjadi kabur tetapi menonjolkan struktur algoritmik. Program yang baik harus membuat struktur sintaksis dan struktur algoritmik sama-sama jelas, dan pendekatan ini tidak memenuhi itu. Menurut saya, cara Rust membuat mesin status implisit dalam fungsi async seharusnya menjadi model di sini

    • Sikap yang terus-menerus “menurunkan standar sampai bodoh” demi penyebut umum terendah adalah penyebab sebagian besar kualitas perangkat lunak saat ini, atau ketiadaannya. Jika menghindari pengetahuan dan pendidikan, pada akhirnya kita akan membayar harganya
    • Setelah beralih dari C ke C++, terlihat ada perbedaan besar antara kedua komunitas tentang apa yang dianggap kode yang mudah dibaca, yaitu apa yang diharapkan bisa dipahami oleh pembaca di masa depan
      Di dunia C, operator kondisional ternary pun dianggap terlalu merangsang, dan C99 diperlakukan seperti barang baru. Di dunia C++, satu-satunya alasan untuk melarang template metaprogramming adalah karena standar yang dipakai memungkinkan hal yang sama dilakukan dengan constexpr