Mesin negara dalam layanan MVP. Kuliah Yandex

Model finite-state machine (FSM) digunakan dalam penulisan kode untuk berbagai platform, termasuk Android. Hal ini memungkinkan Anda untuk membuat kode kurang rumit, cocok dengan paradigma Model-View-Presenter (MVP) dan cocok untuk pengujian sederhana. Pengembang Vladislav Kuznetsov mengatakan kepada Droid Party bagaimana model ini membantu dalam pengembangan aplikasi Yandex.Disk.


- Pertama, mari kita bicara tentang teori. Saya pikir Anda masing-masing telah mendengar tentang MVP dan mesin negara, tetapi kami akan mengulanginya.



Mari kita bicara tentang motivasi, tentang mengapa semua ini diperlukan dan bagaimana itu dapat membantu kita. Mari kita beralih ke apa yang kita lakukan, dengan contoh nyata saya akan menunjukkan potongan kode. Dan pada akhirnya kita akan berbicara tentang pengujian, tentang bagaimana pendekatan ini membantu menguji segalanya dengan mudah.

Mesin negara dan MVP, atau yang serupa - mungkin MVI - digunakan oleh semua orang.

Ada banyak mesin negara. Berikut adalah definisi paling sederhana yang dapat diberikan kepada mereka: ini adalah semacam abstraksi matematis, disajikan dalam bentuk set terbatas negara, peristiwa, dan transisi dari keadaan saat ini ke yang baru tergantung pada acara.



Berikut adalah diagram sederhana dari beberapa programmer abstrak yang kadang-kadang tidur, kadang-kadang makan, tetapi kebanyakan menulis kode. Ini cukup untuk kita. Ada banyak jenis mesin dalam kondisi terbatas, tetapi ini cukup bagi kita.



Ruang lingkup mesin negara cukup besar. Untuk setiap item digunakan dan berhasil diterapkan.



Seperti pendekatan apa pun, MVP membagi aplikasi kita menjadi beberapa lapisan. Lihat - paling sering suatu Aktivitas atau Fragmen, tugasnya adalah untuk meneruskan beberapa tindakan kepada pengguna, untuk mengidentifikasi Presenter bahwa pengguna telah melakukan sesuatu. Kami menganggap Model sebagai penyedia data. Itu bisa seperti database, jika kita berbicara tentang arsitektur bersih, atau Interactor, apa pun bisa. Dan Presenter adalah perantara yang menghubungkan View dan model, sementara pada saat yang sama dapat mengambil dan memperbarui View dari model. Ini cukup untuk kita.

Siapa yang bisa mengatakan dalam satu kalimat apa program itu? Kode yang dapat dieksekusi? Terlalu umum, lebih detail. Algoritma? Algoritme adalah urutan tindakan.

Ini adalah kumpulan data dan semacam aliran kontrol. Tidak masalah siapa yang memanipulasi data ini: pengguna atau tidak. Ini mengikuti pemikiran bahwa setiap saat keadaan aplikasi ditentukan oleh totalitas semua datanya. Dan semakin banyak data dalam aplikasi, semakin sulit untuk mengelolanya, situasi yang lebih tidak terduga dapat muncul ketika terjadi kesalahan.



Bayangkan sebuah kelas sederhana dengan tiga bendera boolean. Untuk memastikan bahwa Anda mencakup semua skenario untuk menggabungkan flag-flag ini, Anda perlu 2³ skenario. Penting untuk mencakup delapan skenario dengan jaminan untuk mengatakan bahwa saya sedang memproses semua kombinasi flag dengan pasti. Jika Anda menambahkan bendera lain, itu meningkat secara proporsional.

Kami menghadapi masalah yang sama. Tampaknya itu adalah tugas yang sederhana, tetapi ketika kami mengembangkan dan mengerjakannya, kami mulai menyadari bahwa ada sesuatu yang salah. Saya akan berbicara tentang fitur yang kami luncurkan. Ini disebut menghapus foto lokal. Intinya adalah bahwa pengguna mengunggah beberapa data ke cloud dalam mode otomatis. Kemungkinan besar, ini adalah foto dan video yang ia ambil di ponselnya. Ternyata file-file itu tampaknya berada di cloud. Mengapa menghabiskan ruang berharga di ponsel Anda ketika Anda dapat menghapus foto-foto ini?



Desainer menggambar konsep seperti itu. Sepertinya hanya dialog, memiliki tajuk tempat jumlah ruang yang dapat kita bebaskan digambar, teks pesan dan tanda centang bahwa ada dua mode pembersihan: hapus semua foto yang diunggah pengguna, atau hanya yang lebih dari satu bulan.



Kami melihat - sepertinya tidak ada yang rumit. Dialog, dua TextViews, kotak centang, tombol. Tetapi ketika kami mulai menangani masalah ini secara terperinci - kami menyadari bahwa mendapatkan data tentang berapa banyak file yang dapat kami hapus adalah tugas jangka panjang. Karena itu, kita harus menunjukkan kepada pengguna semacam rintisan. Ini adalah kode semu, dalam kehidupan nyata terlihat berbeda, tetapi artinya sama.



Kami memeriksa beberapa keadaan, memeriksa apakah kami menghitung, dan menggambar steker "Tunggu".



Ketika perhitungan selesai, kami memiliki beberapa opsi untuk ditampilkan kepada pengguna. Misalnya, jumlah file yang dapat kita hapus adalah nol. Dalam hal ini, kami menggambar pesan kepada pengguna bahwa tidak ada yang perlu dihapus, jadi datanglah lain kali. Kemudian desainer mendatangi kami dan mengatakan bahwa kami harus membedakan antara situasi di mana pengguna telah membersihkan file atau tidak menghapus apa pun, tidak ada yang dimuat. Karenanya, muncul kondisi lain bahwa kami sedang menunggu startup dan menariknya pesan lain.



Lalu ada situasi ketika sesuatu tetap bekerja, dan misalnya, pengguna memiliki tanda centang untuk tidak menghapus file baru. Dalam hal ini, ada juga dua opsi. Baik file dapat dibersihkan, atau file tidak dapat dibersihkan, yaitu sudah menghapus semua file, jadi kami memperingatkan bahwa Anda telah menghapus semua file baru.




Ada satu syarat lagi ketika kita benar-benar dapat menghapus sesuatu. Tidak dicentang, dan ada opsi untuk menghapus sesuatu. Anda melihat kode ini dan sepertinya ada sesuatu yang salah. Saya belum mendaftarkan semuanya, kami memiliki pemeriksaan permishin, karena tidak ada yang berfungsi tanpanya, kami tidak dapat menyentuh file pada kartu, plus kami perlu memeriksa bahwa pengguna telah mengaktifkan secara otomatis, karena fitur tidak berguna tanpa pengisian otomatis, yang mana kami akan untuk membersihkan. Dan beberapa syarat lagi. Dan sial, sepertinya ini hal yang sangat sederhana, dan begitu banyak masalah yang muncul karenanya.

Dan jelas, beberapa masalah segera muncul. Pertama-tama, kode ini tidak dapat dibaca. Di sini pseudo-code tertentu digambarkan, tetapi dalam proyek nyata itu tersebar di berbagai fungsi, potongan-potongan kode, tidak begitu mudah untuk dilihat dengan mata. Dukungan untuk kode semacam itu juga cukup rumit. Terutama ketika Anda datang ke proyek baru, Anda diberitahu bahwa Anda perlu membuat fitur seperti itu, Anda menambahkan beberapa kondisi, memeriksa skenario positif, semuanya bekerja, tetapi kemudian penguji datang dan mengatakan bahwa dalam kondisi tertentu semuanya rusak. Ini terjadi karena Anda sama sekali tidak memperhitungkan skenario apa pun.

Plus, itu berlebihan dalam arti bahwa karena kita memiliki cabang besar kondisi, kita harus memeriksa semua kondisi yang tidak sesuai dengan kita sebelumnya. Mereka negatif sebelumnya, tetapi karena mereka ditulis dengan cabang seperti itu, kita harus memeriksanya. Faktanya adalah bahwa dalam contoh saya memiliki semacam bendera Boolean, tetapi dalam praktiknya, Anda mungkin memiliki panggilan ke fungsi yang masuk ke suatu tempat lebih dalam ke dalam basis data. Apa pun bisa, karena redundansi akan ada rem tambahan.

Dan hal yang paling menyedihkan adalah beberapa perilaku tak terduga yang terlewatkan selama fase pengujian, tidak ada yang terjadi di sana, dan di suatu tempat dalam produksi pengguna tidak terjadi yang terbaik, semacam kurva UI, dan yang terburuk - itu jatuh atau data hilang . Hanya saja aplikasinya tidak berperilaku konsisten.

Bagaimana cara mengatasi masalah ini? Dengan kekuatan mesin negara.



Tugas utama yang ditangani oleh mesin keadaan adalah mengambil tugas rumit yang besar dan memecahnya menjadi kondisi diskrit kecil yang lebih mudah untuk berinteraksi dengan dan mengelola. Setelah duduk, berpikir, karena kita mencoba melakukan sesuatu MVP, bagaimana cara mengikat negara kita dengan semua ini? Kami telah mendekati skema semacam itu. Siapa pun yang membaca buku GOF adalah pola keadaan klasik, hanya apa yang disebut konteks, saya menyebutnya negara-oner, dan pada kenyataannya itu adalah presenter. Presenter memiliki status ini, tahu cara mengalihkannya, dan masih dapat memberikan beberapa data ke status kami jika mereka ingin mengetahui sesuatu, misalnya ukuran file atau ingin meminta permintaan asinkron, pilih.



Tidak ada yang super duper di sini, slide berikutnya lebih penting.



Dengan ini, Anda perlu memulai pengembangan ketika Anda mulai membuat mesin negara. Anda duduk di depan komputer atau di suatu tempat di sekitar meja, dan di atas selembar kertas atau di alat khusus menggambar diagram keadaan. Tidak ada yang rumit, tetapi tahap ini memiliki banyak keuntungan. Pertama, pada tahap awal, Anda dapat segera mendeteksi beberapa inkonsistensi dalam logika bisnis. Produk Anda mungkin datang, ungkapkan keinginan mereka, semuanya baik-baik saja, tetapi ketika Anda mulai menulis kode, Anda memahami bahwa ada sesuatu yang tidak cocok bersama. Saya pikir semua orang punya situasi seperti itu. Tetapi ketika Anda membuat diagram, Anda dapat melihat pada tahap awal bahwa ada sesuatu yang tidak docking. Ini digambar cukup sederhana, ada alat khusus seperti PlantUML, di mana Anda bahkan tidak perlu dapat menggambar, Anda harus dapat menulis kode pseudocode, dan itu sendiri menghasilkan grafik.

Bagan kami terlihat seperti ini, yang menggambarkan keadaan dialog ini. Ada beberapa negara dan logika transisi di antara mereka.



Mari kita beralih ke kode. Nyatakan sendiri, tidak ada yang penting, yang utama adalah bahwa ia memiliki tiga metode: onEnter, yang ketika masuk, memanggil invalidateView terlebih dahulu. Mengapa ini dilakukan? Sehingga segera setelah kami masuk ke negara, UI diperbarui. Plus ada metode invalidateView, yang kelebihan beban kita jika kita perlu melakukan sesuatu dengan UI, dan metode onExit, di mana kita dapat melakukan sesuatu jika kita keluar dari keadaan.



Pemilik Negara. Antarmuka yang menyediakan kemampuan untuk mengklik status. Seperti yang kita ketahui, itu akan menjadi presenter masa depan. Dan ini adalah metode yang menyediakan akses tambahan ke data. Jika ada data yang digeledah antar negara bagian, kita dapat menyimpannya di presenter dan memberikannya melalui antarmuka ini. Dalam hal ini, kita dapat memberikan ukuran file yang dapat kita bersihkan, dan memberikan kesempatan untuk membuat semacam permintaan. Kami dalam keadaan, kami ingin meminta sesuatu dan melalui StateOwner kami dapat memanggil metode.

Kegunaan lain yang demikian adalah bahwa ia juga dapat mengembalikan tautan ke tampilan. Ini dilakukan agar jika Anda memiliki keadaan dan beberapa data tiba, Anda tidak ingin beralih ke keadaan baru, itu hanya berlebihan, Anda dapat langsung memperbarui tampilan, teks. Kami menggunakan ini untuk memperbarui jumlah digit yang dilihat pengguna ketika dia melihat dialog. Kami sedang mengunduh file runtime, dia melihat dialog, dan nomor diperbarui. Kami tidak pindah ke keadaan baru, kami hanya memperbarui tampilan saat ini.



Ini adalah MVP standar, semuanya harus sangat sederhana, tidak ada logika, metode sederhana yang menggambar sesuatu. Saya mematuhi konsep ini. Seharusnya tidak ada logika, setidaknya semacam tindakan. Kami mengambil beberapa Text View, mengubahnya, tidak lagi.



Presenter Ada hal yang lebih menarik. Pertama-tama, kita dapat meraba-raba data melewatinya untuk beberapa negara, kita memiliki dua variabel yang ditandai dengan penjelasan Negara. Siapa yang menggunakan Icepick sudah terbiasa dengannya. Kami tidak menulis serialisasi dengan tangan kami di Partible, kami menggunakan perpustakaan yang sudah jadi.

Berikut ini adalah keadaan awal. Itu selalu berguna untuk mengatur keadaan awal, bahkan jika itu tidak melakukan apa-apa. Kegunaannya adalah Anda tidak perlu melakukan cek nol, tetapi jika kami mengatakan itu bisa melakukan sesuatu. Misalnya, Anda perlu melakukan sesuatu sekali untuk siklus hidup aplikasi Anda, ketika kami mulai, Anda perlu menjalankan prosedur sekali, dan tidak pernah melakukannya lagi. Ketika kita keluar dari keadaan awal, kita selalu dapat melakukan sesuatu seperti ini, dan kita tidak pernah kembali ke keadaan ini. Ketik begitu diagram keadaan digambar. Meski siapa tahu siapa yang akan menggambar, mungkin Anda bisa kembali.

Saya mendukung meminimalkan pemeriksaan untuk Null dan sebagainya, jadi di sini saya menyimpan tautan ke implementasi tampilan sederhana. Kita tidak perlu menyinkronkan apa pun, hanya pada titik tertentu ketika detach terjadi, kita mengganti tampilan dengan yang kosong, dan presenter dapat beralih ke suatu tempat di negara, berpikir bahwa ada tampilan, itu memperbaruinya, tetapi sebenarnya itu berfungsi dengan implementasi kosong.

Ada beberapa metode lagi untuk menyelamatkan negara, tetapi kami ingin selamat dari pergolakan Kegiatan, dalam hal ini semua dilakukan melalui konstruktor. Semuanya sedikit lebih rumit, ini contoh yang dilebih-lebihkan.



Hal ini diperlukan untuk meneruskan saveState, jika seseorang bekerja dengan perpustakaan yang sama, semuanya sangat sepele. Anda dapat menulis dengan tangan Anda. Dan dua metode sangat penting: melampirkan, memanggil onStart, dan melepaskan, dipanggil onStop.



Apa pentingnya mereka? Awalnya, kami berencana untuk melampirkan dan melepaskan di onCreateView, onDestroyView, tetapi ini tidak cukup. Jika Anda memiliki Tampilan, teks Anda dapat diperbarui, atau fragmen dialog mungkin muncul. Dan jika Anda tidak terjebak di Stop, dan kemudian mencoba menampilkan fragmen, Anda akan mengetahui pengecualian terkenal bahwa Anda tidak dapat melakukan transaksi ketika kami masih memiliki status. Baik menggunakan negara kehilangan, atau tidak. Oleh karena itu, kami dirinci dalam onStop, sementara presenter akan terus bekerja di sana, berganti status, menangkap acara. Dan pada saat itu ketika mulai terjadi, kami akan memicu tampilan acara terlampir, dan presenter akan memperbarui UI agar sesuai dengan keadaan saat ini.




Ada metode rilis, biasanya disebut di onDestroy, Anda membuat sumber daya terlepas dan melepaskan.



Metode setState penting lainnya. Karena kami berencana mengubah UI di onEnter dan onExit, ada cek untuk utas utama. Ini menciptakan batasan bagi kami bahwa kami tidak melakukan sesuatu yang berat di sini, semua permintaan harus ke UI, atau harus tidak sinkron. Keuntungan dari tempat ini adalah bahwa di sini kita dapat memesan pintu masuk dan keluar dari keadaan, sangat berguna saat debugging, misalnya, ketika terjadi kesalahan, Anda dapat melihat bagaimana sistem mengklik dan memahami apa yang salah.



Beberapa contoh kondisi. Ada keadaan awal, itu hanya memicu perhitungan berapa banyak ruang yang Anda butuhkan untuk membebaskan pada saat tampilan menjadi tersedia. Ini akan terjadi setelah onStart. Begitu onStart terjadi, kami masuk ke keadaan baru, dan sistem mulai meminta data.





Contoh negara adalah Menghitung, kami akan menyatakan ukuran file dengan stateOwner, entah bagaimana merayapi ke dalam database, dan kemudian masih ada inValidateView, kami memperbarui UI pengguna saat ini. Dan viewAttached dipanggil jika tampilan disambungkan kembali. Jika kami berada di latar belakang, Menghitung ada di latar belakang, kami kembali ke Aktivitas kami, metode ini dipanggil dan memperbarui semua data.



Sebagai contoh acara, kami bertanya pada stateOwner berapa banyak file yang dapat dibebaskan, dan ia memanggil metode filesSizeUpdated. Di sini saya terlalu malas, mungkin untuk menulis tiga metode terpisah, seperti diperbarui, ada banyak file lama seperti cara memisahkan berbagai peristiwa. Tetapi Anda harus mengerti, sekali itu akan sulit bagi Anda, sekali itu akan jauh lebih sederhana. Tidak perlu mengalami overengineering bahwa setiap peristiwa adalah metode yang terpisah. Anda dapat bertahan dengan sederhana jika, saya tidak melihat ada yang salah dengan itu.



Saya melihat beberapa peningkatan potensial. Saya tidak suka kita dipaksa untuk menggunakan metode ini, seperti onStart, on Stop, onCreate, onSave, dan banyak lagi. Anda dapat terhubung dengan Lifecycle, tetapi tidak jelas apa yang harus dilakukan dengan saveState. Ada ide, misalnya, untuk membuat fragmen presenter. Kenapa tidak Sebuah fragmen tanpa UI yang menangkap siklus hidup, dan secara umum kita tidak membutuhkan apa-apa, semuanya akan terbang kepada kita dengan sendirinya.

Hal lain yang menarik: presenter ini diciptakan kembali setiap kali, dan jika Anda memiliki data besar yang disimpan dalam presenter, Anda pergi ke database, memegang kursor besar, maka itu tidak dapat diterima untuk meminta setiap kali Anda memutar layar. Oleh karena itu, Anda dapat men-cache presenter, seperti halnya, misalnya, ViewModule dari Architecture Components, membuat beberapa fragmen yang akan menahan cache presenter dan mengembalikannya untuk setiap tampilan.

Anda dapat menggunakan cara tabular untuk menentukan mesin negara, karena pola keadaan yang kami gunakan memiliki satu kelemahan signifikan: segera setelah Anda perlu menambahkan satu metode ke acara baru, Anda harus menambahkan implementasi ke semua keturunan. Setidaknya kosong. Atau melakukannya dalam kondisi dasar. Ini sangat tidak nyaman. Jadi cara tabular menentukan mesin keadaan digunakan di semua perpustakaan - jika Anda mencari di GitHub untuk kata FSM, Anda akan menemukan sejumlah besar perpustakaan yang memberi Anda semacam pembangun tempat Anda mengatur keadaan awal, acara dan keadaan akhir. Memperluas dan merawat mesin keadaan seperti itu jauh lebih mudah.

Hal lain yang menarik: jika Anda menggunakan pola keadaan, jika mesin negara Anda mulai tumbuh, kemungkinan besar Anda harus menangani beberapa peristiwa dengan cara yang sama sehingga kode tidak disalin, Anda membuat keadaan dasar. Semakin banyak peristiwa, semakin banyak kondisi dasar mulai muncul, hierarki tumbuh, dan terjadi kesalahan.

Seperti kita ketahui, warisan harus diganti oleh delegasi, dan mesin negara hierarkis membantu menyelesaikan masalah ini. Anda memiliki status yang tidak bergantung pada tingkat warisan - cukup bangun pohon status yang melewati pawang di atas. Anda juga dapat membaca secara terpisah, suatu hal yang sangat berguna. Di Android, misalnya, mesin status hierarkis digunakan di WatchDog Wi-Fi, yang memantau status jaringan, mereka ada di sana, tepat di sumber Android.



Terakhir tapi tidak kalah pentingnya. Bagaimana ini bisa diuji? Pertama-tama, keadaan deterministik dapat diuji. Ada keadaan terpisah, kami membuat sebuah instance, menarik metode onEnter dan melihat bahwa nilai-nilai yang sesuai dipanggil dalam tampilan. Dengan demikian, kami memvalidasi bahwa negara kami memperbarui Tampilan dengan benar. Jika Tampilan Anda tidak serius, maka kemungkinan besar Anda akan membahas sejumlah besar skenario.



Anda dapat mengunci beberapa metode dengan fungsi yang mengembalikan ukuran, memanggil acara lain setelah onEnter dan melihat bagaimana keadaan tertentu merespons peristiwa tertentu. Dalam hal ini, ketika peristiwa filesSizeUpdated terjadi dan ketika AllFilesSize lebih besar dari nol, kita harus beralih ke status CleanAllFiles baru. Dengan bantuan tata letak, kami memeriksa semua ini.



Dan yang terakhir - kita dapat menguji keseluruhan sistem. Kami membangun negara, mengirim acara ke sana dan memeriksa bagaimana sistem berperilaku. Kami memiliki tiga tahap pengujian. , UI, , , , .

, 70%. 80% . , .



, ? — . - .

. . - , , - , — , .

- , , , . , , . , , . - , , . , , . lock . - , .

— . , , , , . , - , , -, , . , . , .

Source: https://habr.com/ru/post/id418561/


All Articles