Pendekatan fungsional untuk penanganan kesalahan di Dart

Dalam transisi ke teknologi baru, kita kehilangan alat yang biasa untuk pengembangan. Dalam beberapa kasus, kami dipaksa untuk tidak hadir karena beberapa keterbatasan teknis, tetapi jika memungkinkan, kami membawa alat tersebut bersama kami. Saat mengembangkan aplikasi android, saya mengambil contoh arsitektur bersih yang diusulkan oleh Fernando Cejas sebagai dasar. Memahami pola desain yang digunakan dalam Flutter, saya memutuskan untuk meninggalkan arsitektur ini demi BLoC. Saya dengan cepat terbiasa dengan templat ini, sangat mirip dengan MVVM yang pernah saya kerjakan sebelumnya, tetapi saya tidak mau memasang satu detail. Ketika memanggil metode repositori, saya harus menangkap pengecualian, melemparkannya ke beberapa jenis dan, menurut jenisnya, membuat keadaan yang diperlukan. Menurut pendapat saya, ini sangat mengacaukan blok dan saya porting jenis yang digunakan sebelumnya dalam proyek-proyek android berdasarkan Fernando.


Baik berasal dari bahasa pemrograman fungsional. Ini memberikan nilai dari salah satu jenis yang mungkin:


  • Kiri (jika gagal);
  • Benar (jika berhasil).

Implementasi dasar Either
/// Signature of callbacks that have no arguments and return right or left value. typedef Callback<T> = void Function(T value); /// Represents a value of one of two possible types (a disjoint union). /// Instances of [Either] are either an instance of [Left] or [Right]. /// FP Convention dictates that: /// [Left] is used for "failure". /// [Right] is used for "success". abstract class Either<L, R> { Either() { if (!isLeft && !isRight) throw Exception('The ether should be heir Left or Right.'); } /// Represents the left side of [Either] class which by convention is a "Failure". bool get isLeft => this is Left<L, R>; /// Represents the right side of [Either] class which by convention is a "Success" bool get isRight => this is Right<L, R>; L get left { if (this is Left<L, R>) return (this as Left<L, R>).value; else throw Exception('Illegal use. You should check isLeft() before calling '); } R get right { if (this is Right<L, R>) return (this as Right<L, R>).value; else throw Exception('Illegal use. You should check isRight() before calling'); } void either(Callback<L> fnL, Callback<R> fnR) { if (isLeft) { final left = this as Left<L, R>; fnL(left.value); } if (isRight) { final right = this as Right<L, R>; fnR(right.value); } } } class Left<L, R> extends Either<L, R> { final L value; Left(this.value); } class Right<L, R> extends Either<L, R> { final R value; Right(this.value); } 

Implementasinya sangat mendasar, lebih rendah dari solusi dalam bahasa lain, tetapi berupaya dengan tugasnya. Saya menggunakan tipe ini sebagai hasil dari semua metode repositori, dan penanganan pengecualian ditransfer ke lapisan data. Ini menghilangkan kebutuhan untuk mencoba / menangkap konstruksi, yang membuat kode lebih mudah dibaca.


Coba / tangkap contohnya
 class ContactBloc { final ContactRepository contactRepository; ContactBloc(this.contactRepository); @override Stream<ContactState> mapEventToState(ContactEvent event) async* { if (event is GetContactEvent) { yield LoadContactState(); try { var contact = contactRepository.getById(event.id); yield ContactIsShowingState(contact); } on NetworkConnectionException catch (e) { yield NetworkExceptionState(e); } catch (e) { yield UnknownExceptionState(e); } } } } abstract class ContactRepository { Future<Contact>getById(int id); } 

Contoh dengan salah satunya
 class ContactBloc { final ContactRepository contactRepository; ContactBloc(this.contactRepository); @override Stream<ContactState> mapEventToState(ContactEvent event) async* { if (event is GetContactEvent) { yield LoadContactState(); final either = contactRepository.getById(event.id); if (either.isRight) { final contact = either.right; yield ContactIsShowingState(contact); } else { final failure = either.left; if (failure is NetworkFailure) yield NetworkFailureState(failure); if (failure is UnknownFailure) yield UnknownFailureState(failure); } } } } abstract class ContactRepository { Future<Either<Failure, Contact>>getById(int id); } 

Mengenai keterbacaan, seseorang mungkin berdebat. Mungkin seseorang terbiasa mencoba / menangkap dan akan benar dengan caranya sendiri, sebagian besar ini adalah penyedap. Keuntungan tambahan adalah bahwa kita sendiri dapat mendefinisikan hierarki Kegagalan dan kembali di sisi kiri. Mari kita buat Kegagalan abstrak, jadikan itu biasa untuk semua fitur ServerFailure, NetworkFailure, dan beberapa fitur spesifik ContactFailure untuk fitur saat ini, dengan ahli waris. Di blok, kita akan tahu persis dari Kegagalan yang diharapkan.


Kerugian dalam mengimplementasikan Kegagalan pada Dart adalah kurangnya kelas tersegel seperti di kotlin, jika tidak akan ada jika dengan casting. Bahasanya masih muda, aktif berkembang dan saya harap waktunya akan tiba dan kita akan memiliki alat untuk menulis penangan yang lebih ringkas.


Seseorang mungkin tidak menyukai implementasi ini, mungkin merasa tidak ada gunanya, tapi saya hanya ingin memperkenalkan Anda dengan kemungkinan pendekatan fungsional untuk penanganan kesalahan di Dart, meskipun penggunaannya ternyata tidak seanggun bahasa lain.


Sumber:


Kode sumber

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


All Articles