Mem-porting utilitas baris perintah dari Go / Rust ke D

Beberapa hari yang lalu, pada reddit dalam "pemrograman", Paulo Henrique Cuchi berbagi pengalamannya dalam mengembangkan utilitas baris perintah di Rust and Go ( diterjemahkan ke dalam Habré ). Utilitas yang dimaksud adalah klien untuk proyek hewan peliharaannya Hashtrack. Hashtrack menyediakan API GraphQL yang dengannya pelanggan dapat melacak hashtag Twitter tertentu dan mendapatkan daftar tweet yang relevan secara real time. Diminta oleh komentar , saya memutuskan untuk menulis port di D untuk mendemonstrasikan bagaimana D dapat digunakan untuk tujuan serupa. Saya akan mencoba untuk mempertahankan struktur yang sama yang dia gunakan dalam posting blognya.



Sumber di



Video GitHub di klik



Bagaimana saya sampai di D



Alasan utamanya adalah bahwa postingan blog asli membandingkan bahasa yang diketik secara statis seperti Go dan Rust, dan membuat referensi yang menghormati Nim dan Crystal, tetapi tidak menyebutkan D, yang juga termasuk dalam kategori ini. Jadi saya pikir itu akan membuat perbandingan menjadi menarik.



Saya juga menyukai D sebagai bahasa dan telah menyebutkannya di berbagai posting blog lainnya.



Lingkungan lokal



Manual berisi informasi lengkap tentang cara mengunduh dan menginstal kompiler referensi, DMD. Pengguna Windows bisa mendapatkan penginstal, sedangkan pengguna macOS bisa menggunakan homebrew. Di Ubuntu, saya baru saja menambahkan repositori apt dan mengikuti instalasi normal. Dengan ini, Anda tidak hanya mendapatkan DMD, tetapi juga dub, manajer paket.



Saya menginstal Rust sehingga saya bisa mendapatkan gambaran tentang betapa mudahnya untuk memulai. Saya terkejut betapa mudahnya itu. Saya hanya perlu menjalankan penginstal interaktif , yang menangani sisanya. Saya perlu menambahkan ~ / .cargo / bin ke jalur. Anda hanya perlu memulai ulang konsol agar perubahan diterapkan.



Didukung oleh editor



Saya menulis Hashtrack di Vim tanpa banyak kesulitan, tapi itu mungkin karena saya tahu apa yang terjadi di perpustakaan standar. Saya selalu membuka dokumentasi, karena kadang-kadang saya menggunakan simbol yang tidak saya impor dari paket yang benar, atau saya memanggil fungsi dengan argumen yang salah. Perhatikan bahwa untuk pustaka standar, Anda cukup menulis "import std;" dan memiliki segalanya yang Anda inginkan. Namun, untuk perpustakaan pihak ketiga, Anda sendirian.



Saya ingin tahu tentang status toolkit, jadi saya mencari plugin untuk IDE favorit saya, Intellij IDEA. Saya menemukan inidan memasangnya. Saya juga menginstal DCD dan DScanner dengan mengkloning repositori masing-masing dan membangunnya, lalu mengonfigurasi plugin IDEA agar mengarah ke jalur yang benar. Hubungi penulis posting blog ini untuk klarifikasi.



Saya mengalami beberapa masalah pada awalnya, tetapi masalah tersebut diperbaiki setelah memperbarui IDE dan plugin. Salah satu masalah yang saya hadapi adalah dia tidak dapat mengenali paket saya sendiri dan terus menandainya sebagai "mungkin belum ditentukan". Kemudian saya menemukan bahwa agar mereka dikenali, saya harus meletakkan "module package_module_name;" di bagian atas file.



Saya rasa masih ada bug yang .length tidak dikenali, setidaknya di mesin saya. Saya telah membuka masalah di Github, Anda dapat mengikutinya di sinijika Anda penasaran.



Jika Anda menggunakan Windows, saya pernah mendengar hal-hal baik tentang VisualD .



Manajemen paket



Dub adalah manajer paket de facto di D. Ia mendownload dan menginstal dependensi dari code.dlang.org . Untuk proyek ini, saya membutuhkan klien HTTP karena saya tidak ingin menggunakan cURL. Saya berakhir dengan dua dependensi, permintaan dan ketergantungannya, cachetools, yang tidak memiliki dependensinya sendiri. Namun, untuk beberapa alasan, dia memilih dua belas dependensi lagi:







Saya pikir Dub menggunakannya secara internal, tapi saya tidak yakin tentang itu.



Rust telah memuat banyak peti ( Kira-kira: 228 ), tetapi itu mungkin karena versi Rust memiliki lebih banyak fitur daripada milik saya. Misalnya, dia mengunduh rpassword , alat yang menyembunyikan karakter kata sandi saat mereka mengetiknya ke terminal, mirip dengan fungsi getpass Python.Ini adalah salah satu dari banyak hal yang tidak saya miliki di kode. Saya telah menambahkan dukungan getpass untuk Linux, berkat rekomendasi ini . Saya juga menambahkan pemformatan teks di terminal, berkat urutan escape yang saya salin dari sumber Go asli.



Perpustakaan



Memiliki sedikit pemahaman tentang graphql, saya tidak tahu harus mulai dari mana. Pencarian untuk "graphql" di code.dlang.org membawa saya ke perpustakaan yang sesuai, yang diberi nama " graphqld ". Namun, setelah mempelajarinya, menurut saya plugin ini lebih terlihat seperti plugin vibe.d daripada klien sungguhan, jika ada.



Setelah memeriksa permintaan jaringan di Firefox, saya menyadari bahwa untuk proyek ini saya hanya dapat mensimulasikan permintaan dan transformasi graphql yang akan saya kirim menggunakan klien HTTP. Responsnya hanyalah objek JSON yang dapat saya parse menggunakan alat yang disediakan oleh paket std.json. Dengan pemikiran ini, saya mulai mencari klien HTTP dan menyelesaikan permintaan , yang merupakan klien HTTP yang mudah digunakan, tetapi yang lebih penting, telah mencapai tingkat kematangan tertentu.



Saya menyalin permintaan keluar dari network analyzer dan menempelkannya ke dalam file .graphql terpisah, yang kemudian saya impor dan kirim dengan variabel yang sesuai. Sebagian besar fungsionalitas dimasukkan ke dalam struktur GraphQLRequest karena saya ingin memasukkan berbagai titik akhir dan konfigurasi ke dalamnya sesuai kebutuhan untuk proyek:



Sumber
struct GraphQLRequest
{
    string operationName;
    string query;
    JSONValue variables;
    Config configuration;

    JSONValue toJson()
    {
        return JSONValue([
            "operationName": JSONValue(operationName),
            "variables": variables,
            "query": JSONValue(query),
        ]);
    }

    string toString()
    {
        return toJson().toPrettyString();
    }

    Response send()
    {
        auto request = Request();
        request.addHeaders(["Authorization": configuration.get("token", "")]);
        return request.post(
            configuration.get("endpoint"),
            toString(),
            "application/json"
        );
    }
}




Berikut adalah cuplikan pertukaran paket. Kode berikut menangani otentikasi:
struct Session
{
    Config configuration;

    void login(string username, string password)
    {
        auto request = createSession(username, password);
        auto response = request.send();
        response.throwOnFailure();
        string token = response.jsonBody
            ["data"].object
            ["createSession"].object
            ["token"].str;
        configuration.put("token", token);
    }

    GraphQLRequest createSession(string username, string password)
    {
        enum query = import("createSession.graphql").lineSplitter().join("\n");
        auto variables = SessionPayload(username, password).toJson();
        return GraphQLRequest("createSession", query, variables, configuration);
    }
}

struct SessionPayload
{
    string email;
    string password;

    //todo : make this a template mixin or something
    JSONValue toJson()
    {
        return JSONValue([
            "email": JSONValue(email),
            "password": JSONValue(password)
        ]);
    }

    string toString()
    {
        return toJson().toPrettyString();
    }
}




Peringatan spoiler - Saya belum pernah melakukan ini sebelumnya.



Semuanya terjadi seperti ini: fungsi main () membuat struktur Config dari argumen baris perintah dan memasukkannya ke dalam struktur Sesi, yang mengimplementasikan fungsionalitas perintah login, logout, dan status. Metode createSession () membuat kueri graphQL dengan membaca kueri sebenarnya dari file .graphql yang sesuai dan meneruskan variabel bersamanya. Saya tidak ingin mencemari kode sumber saya dengan mutasi dan kueri graphQL, jadi saya memindahkannya ke file .graphql, yang kemudian saya impor pada waktu kompilasi menggunakan enum dan impor. Yang terakhir membutuhkan flag compiler untuk menunjuk ke stringImportPaths (yang defaultnya adalah view /).



Sedangkan untuk metode login (), satu-satunya tanggung jawabnya adalah mengirim permintaan HTTP dan memproses tanggapannya. Dalam kasus ini, ia menangani potensi kesalahan, meskipun tidak terlalu hati-hati. Itu kemudian menyimpan token dalam file konfigurasi, yang sebenarnya tidak lebih dari objek JSON yang bagus.



Metode throwOnFailure bukan bagian dari fungsionalitas inti pustaka kueri. Ini sebenarnya adalah fungsi pembantu yang melakukan penanganan kesalahan cepat dan kotor:



void throwOnFailure(Response response)
{
    if(!response.isSuccessful || "errors" in response.jsonBody)
    {
        string[] errors = response.errors;
        throw new RequestException(errors.join("\n"));
    }
}


Karena D mendukung UFCS , sintaks throwOnFailure (response) bisa ditulis ulang sebagai response.throwOnFailure (). Ini membuatnya mudah untuk disematkan dalam pemanggilan metode lain seperti send (). Saya mungkin telah menggunakan fungsi ini secara berlebihan selama proyek berlangsung.



Pemrosesan kesalahan



D lebih memilih pengecualian saat menangani kesalahan. Alasannya dijelaskan secara rinci di sini . Salah satu hal yang saya suka adalah bahwa kesalahan yang tidak tertangani pada akhirnya akan muncul kecuali dicolokkan secara eksplisit. Inilah sebabnya mengapa saya dapat menghindari penanganan kesalahan yang disederhanakan. Misalnya, di baris ini:



string token = response.jsonBody
    ["data"].object
    ["createSession"].object
    ["token"].str;
configuration.put("token", token);


Jika isi respons tidak berisi token atau objek apa pun yang mengarah ke sana, pengecualian akan dilemparkan, yang akan menggelembung di fungsi utama dan kemudian meledak di depan pengguna. Jika saya menggunakan Go, saya harus sangat berhati-hati dengan kesalahan di setiap langkah. Dan, sejujurnya, karena menulis if err! = Null itu menjengkelkan setiap kali fungsi dipanggil, saya akan sangat tergoda untuk mengabaikan error tersebut. Namun, pemahaman saya tentang Go masih primitif, dan saya tidak akan terkejut jika kompiler membentak Anda karena tidak melakukan apa pun dengan pengembalian kesalahan, jadi silakan mengoreksi saya jika saya salah.



Penanganan error ala karat, seperti yang dijelaskan di postingan blog asli, ternyata menarik. Saya tidak berpikir ada yang seperti ini di pustaka standar D, tetapi ada diskusi tentang penerapan ini sebagai pustaka pihak ketiga.



Websockets



Saya hanya ingin menunjukkan secara singkat bahwa saya tidak menggunakan websockets untuk mengimplementasikan perintah jam tangan. Saya mencoba menggunakan klien websocket dari Vibe.d tetapi tidak dapat bekerja dengan hashtrack backend karena terus menutup koneksi. Pada akhirnya, saya membuangnya demi round robin, meskipun tidak disukai. Klien telah bekerja sejak saya mengujinya dengan server web lain, jadi saya mungkin akan kembali ke sini di masa mendatang.



Integrasi berkelanjutan



Untuk CI, saya menyiapkan dua pekerjaan build: build cabang reguler dan rilis master untuk memastikan bahwa build artefak yang dioptimalkan diunduh.









Approx. Gambar menunjukkan waktu perakitan. Memperhatikan pemuatan dependensi. Bangun kembali tanpa ketergantungan ~ 4s



Konsumsi memori



Saya menggunakan perintah / usr / bin / time -v ./hashtrack --list untuk mengukur penggunaan memori seperti yang dijelaskan dalam posting blog asli. Saya tidak tahu apakah penggunaan memori bergantung pada hashtag yang diikuti pengguna, tetapi berikut adalah hasil dari program D yang dikompilasi dengan rilis dub build -b:

Ukuran kumpulan penduduk maksimum (kbyte): 10036

Ukuran kumpulan penduduk maksimum (kbyte): 10164

Ukuran kumpulan penduduk maksimum (kbyte): 9940

Ukuran kumpulan penduduk maksimum (kbyte): 10060

Ukuran kumpulan penduduk maksimum (kbyte): 10008


Tidak buruk. Saya menjalankan versi Go dan Rust dengan pengguna hashtrack saya dan mendapatkan hasil berikut:



Dibuat dengan go build -ldflags "-s -w":

Ukuran himpunan penduduk maksimum (kbyte): 13684

Ukuran himpunan penduduk maksimum (kbyte): 13820

Ukuran himpunan penduduk maksimum (kbyte): 13904

Ukuran himpunan penduduk maksimum (kbyte): 13796

Ukuran himpunan penduduk maksimum (kbyte): 13600

Karat dikompilasi dengan pembuatan kargo - rilis:

Ukuran kumpulan penduduk maksimum (kbyte): 9224

Ukuran kumpulan penduduk maksimum (kbyte): 9192

Ukuran kumpulan penduduk maksimum (kbyte): 9384

Ukuran kumpulan penduduk maksimum (kbyte): 9132

Ukuran kumpulan penduduk maksimum (kbyte): 9168
Pembaruan: Pengguna Reddit skocznymroczny merekomendasikan pengujian kompiler LDC dan GDC juga. Berikut adalah hasilnya:

LDC 1.22 dikompilasi oleh dub build -b release --compiler = ldc2 (setelah menambahkan keluaran warna dan getpass)

Ukuran himpunan penduduk maksimum (kbyte): 7816

Ukuran himpunan penduduk maksimum (kbyte): 7912

Ukuran himpunan penduduk maksimum (kbyte): 7804

Ukuran himpunan penduduk maksimum (kbyte): 7832

Ukuran himpunan penduduk maksimum (kbyte): 7804


D memiliki pengumpulan sampah, tetapi juga mendukung petunjuk cerdas dan, baru-baru ini, metodologi manajemen memori eksperimental yang terinspirasi oleh Rust. Saya tidak sepenuhnya yakin seberapa baik fungsi ini terintegrasi dengan pustaka standar, jadi saya memutuskan untuk membiarkan GC menangani memori untuk saya. Saya rasa hasilnya cukup bagus mengingat saya belum memikirkan tentang konsumsi memori saat menulis kode.



Ukuran binari



Rust, cargo build --release: 7.0M



D, dub build -b release: 5.7M



D, dub build -b release --compiler=ldc2: 2.4M



Go, go build: 7.1M



Go, go build -ldflags "-s -w": 5.0M


.. — , , . Windows dub build -b release 2 x64 ( 1.5M x86-mscoff) , Rust Ubuntu18 - openssl, ,





Saya pikir D adalah bahasa yang dapat diandalkan untuk menulis alat baris perintah seperti ini. Saya tidak terlalu sering pergi ke dependensi eksternal karena perpustakaan standar berisi sebagian besar dari apa yang saya butuhkan. Hal-hal seperti mengurai argumen baris perintah, menangani JSON, pengujian unit, mengirim permintaan HTTP (dengan cURL ) semuanya tersedia di pustaka standar. Jika pustaka standar kekurangan apa yang Anda butuhkan, maka paket pihak ketiga ada, tetapi saya pikir masih ada ruang untuk perbaikan di area ini. Sebaliknya, jika mentalitas NIH Anda tidak ditemukan di sini, atau jika Anda ingin dengan mudah membuat dampak sebagai pengembang open source, maka Anda pasti akan menyukai ekosistem D.



Alasan mengapa saya menggunakan D



  • Iya



All Articles