Mengetik: Membuat Negara Tidak Valid Tidak Dapat Diekspresikan

Saya mempersembahkan kepada Anda terjemahan artikel Scott Wlaschin "Merancang dengan tipe: Membuat negara-negara ilegal tidak terwakili" .


Pada artikel ini, kita akan melihat keuntungan utama dari F # - kemampuan untuk "membuat status yang salah tidak dapat diekspresikan" menggunakan sistem tipe (frasa tersebut dipinjam dari Yaron Minsky ).


Pertimbangkan jenis Contact . Sebagai hasil dari refactoring, ia sangat menyederhanakan:


 type Contact = { Name: Name; EmailContactInfo: EmailContactInfo; PostalContactInfo: PostalContactInfo; } 

Sekarang anggaplah ada aturan bisnis yang sederhana: "Kontak harus berisi alamat email atau alamat pos." Apakah tipe kami mematuhi aturan ini?


Tidak. Ini mengikuti dari aturan bahwa kontak dapat berisi alamat email, tetapi tidak memiliki alamat surat, atau sebaliknya. Namun, dalam bentuk saat ini, tipe ini membutuhkan kedua bidang untuk diisi.


Tampaknya jawabannya jelas - jadikan alamat opsional, misalnya, seperti ini:


 type Contact = { Name: PersonalName; EmailContactInfo: EmailContactInfo option; PostalContactInfo: PostalContactInfo option; } 

Tapi sekarang tipe kami memungkinkan terlalu banyak. Dalam implementasi ini, Anda dapat membuat kontak tanpa alamat sama sekali, meskipun aturan mengharuskan setidaknya satu alamat ditentukan.


Bagaimana cara mengatasi masalah ini?


Cara membuat status yang salah tidak bisa diungkapkan


Setelah mempertimbangkan aturan logika bisnis, kita dapat menyimpulkan bahwa tiga kasus dimungkinkan:


  • hanya alamat email yang disediakan;
  • hanya alamat surat yang ditunjukkan;
  • Email dan alamat pos disediakan.

Dalam formulasi seperti itu, solusinya menjadi jelas - untuk membuat tipe-jumlah dengan konstruktor untuk setiap kasus yang mungkin.


 type ContactInfo = | EmailOnly of EmailContactInfo | PostOnly of PostalContactInfo | EmailAndPost of EmailContactInfo * PostalContactInfo type Contact = { Name: Name; ContactInfo: ContactInfo; } 

Implementasi ini sepenuhnya sesuai. Ketiga kasus dinyatakan secara eksplisit, sedangkan kasus keempat (tanpa alamat apa pun) tidak diperbolehkan.


Perhatikan "alamat email dan alamat pos". Sejauh ini saya hanya menggunakan tuple. Dalam hal ini, ini sudah cukup.


Membuat ContactInfo


Sekarang mari kita lihat bagaimana menggunakan implementasi ini sebagai contoh. Pertama, buat kontak baru:


 let contactFromEmail name emailStr = let emailOpt = EmailAddress.create emailStr //          match emailOpt with | Some email -> let emailContactInfo = {EmailAddress=email; IsEmailVerified=false} let contactInfo = EmailOnly emailContactInfo Some {Name=name; ContactInfo=contactInfo} | None -> None let name = {FirstName = "A"; MiddleInitial=None; LastName="Smith"} let contactOpt = contactFromEmail name "abc@example.com" 

Dalam contoh ini, kami membuat fungsi pembantu sederhana contactFromEmail untuk membuat kontak baru dengan mengirimkan nama dan alamat email. Namun, alamatnya mungkin salah, dan fungsinya harus menangani kedua kasus ini. Fungsi tidak dapat membuat kontak dengan alamat yang tidak valid, sehingga mengembalikan nilai jenis Contact option , bukan Kontak.


ContactInfo Ubah


Jika Anda perlu menambahkan alamat surat ke ContactInfo ada, maka Anda harus menangani tiga kemungkinan kasus:


  • jika kontak hanya memiliki alamat email, maka sekarang ia memiliki kedua alamat, jadi Anda harus mengembalikan kontak dengan konstruktor EmailAndPost ;
  • jika kontak hanya memiliki alamat surat, Anda harus mengembalikan kontak dengan konstruktor PostOnly , mengganti alamat surat dengan yang baru;
  • jika kontak tersebut memiliki kedua alamat, Anda harus mengembalikan kontak dengan konstruktor EmailAndPost , mengganti alamat surat dengan yang baru.

Fungsi tambahan untuk memperbarui alamat surat adalah sebagai berikut. Perhatikan pemrosesan eksplisit untuk setiap kasus.


 let updatePostalAddress contact newPostalAddress = let {Name=name; ContactInfo=contactInfo} = contact let newContactInfo = match contactInfo with | EmailOnly email -> EmailAndPost (email,newPostalAddress) | PostOnly _ -> //     PostOnly newPostalAddress | EmailAndPost (email,_) -> //     EmailAndPost (email,newPostalAddress) //    {Name=name; ContactInfo=newContactInfo} 

Dan di sini adalah penggunaan kode ini:


 let contact = contactOpt.Value //      option.Value  let newPostalAddress = let state = StateCode.create "CA" let zip = ZipCode.create "97210" { Address = { Address1= "123 Main"; Address2=""; City="Beverly Hills"; State=state.Value; //      option.Value  Zip=zip.Value; //      option.Value  }; IsAddressValid=false } let newContact = updatePostalAddress contact newPostalAddress 

PERINGATAN: Dalam contoh ini, saya menggunakan option.Value untuk mendapatkan konten opsi. Ini dapat diterima ketika Anda bereksperimen di konsol interaktif, tetapi ini adalah solusi yang mengerikan untuk kode kerja! Anda harus selalu menggunakan pencocokan pola dan menangani kedua konstruktor option .


Mengapa repot dengan tipe rumit ini?


Pada saat ini, Anda dapat memutuskan bahwa kami semua terlalu rumit. Saya akan menjawab dengan tiga poin.


Pertama, logika bisnis itu sendiri kompleks. Tidak ada cara mudah untuk menghindari ini. Jika kode Anda lebih sederhana dari logika bisnis, Anda tidak menangani semua kasus sebagaimana mestinya.


Kedua, jika logika diekspresikan oleh tipe, maka itu adalah dokumentasi diri. Anda dapat melihat konstruktor tipe penjumlahan di bawah ini dan segera memahami aturan bisnis. Anda tidak perlu membuang waktu menganalisis kode lain.


 type ContactInfo = | EmailOnly of EmailContactInfo | PostOnly of PostalContactInfo | EmailAndPost of EmailContactInfo * PostalContactInfo 

Akhirnya, jika logika dinyatakan berdasarkan tipe, maka setiap perubahan pada aturan logika bisnis akan memecah kode yang tidak memperhitungkan perubahan ini, dan ini biasanya baik.


Poin terakhir terungkap dalam artikel berikutnya . Mencoba untuk mengungkapkan aturan logika bisnis melalui tipe, Anda bisa sampai pada pemahaman mendalam tentang area subjek.

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


All Articles