Bagaimana gaya fungsional membantu dengan mengejek dependensi?

10

Dari wawancara dengan Kent Beck dalam edisi Java Magazine terbaru:

Binstock: Mari kita bahas microservices. Tampaknya bagi saya bahwa tes-pertama pada layanan-layanan mikro akan menjadi rumit dalam arti bahwa beberapa layanan, untuk berfungsi, akan membutuhkan kehadiran sejumlah besar layanan lain. Apa kamu setuju?

Beck: Sepertinya sekelompok pertukaran yang sama tentang memiliki satu kelas besar atau banyak kelas kecil.

Binstock: Benar, kecuali saya kira, di sini Anda harus menggunakan banyak tiruan agar dapat mengatur sistem yang dengannya Anda dapat menguji layanan yang diberikan.

Beck: Saya tidak setuju. Jika gaya imperatif, Anda harus menggunakan banyak ejekan. Dalam gaya fungsional di mana ketergantungan eksternal dikumpulkan bersama-sama tinggi di rantai panggilan, maka saya tidak berpikir itu perlu. Saya pikir Anda bisa mendapatkan banyak liputan dari unit test.

Apa yang dia maksud Bagaimana gaya fungsional bisa membebaskan Anda dari mengejek ketergantungan eksternal?

Dan
sumber
1
Jika mereka membahas Jawa secara khusus, saya curiga banyak dari diskusi itu diperdebatkan. Java tidak benar-benar memiliki jenis dukungan yang diperlukan untuk meminjamkan dirinya pada jenis pemrograman fungsional yang dijelaskan. Oh, tentu, Anda dapat menggunakan kelas utilitas atau mungkin Java 8 Lambdas untuk mensimulasikannya, tapi ... blecch.
Robert Harvey

Jawaban:

8

Sebuah fungsi murni adalah salah satu yang:

  1. Akan selalu memberikan hasil yang sama dengan argumen yang sama
  2. Tidak memiliki efek samping yang dapat diamati (misalnya perubahan status)

Misalkan kita sedang menulis beberapa kode untuk menangani login pengguna, di mana kita ingin memeriksa apakah nama pengguna dan kata sandi yang diberikan benar dan mencegah pengguna masuk jika ada terlalu banyak upaya gagal. Dalam gaya imperatif, kode kita mungkin terlihat seperti ini:

bool UserLogin(string username, string password)
{
    var user = _database.FindUser(username);
    if (user == null)
    {
        return false;
    }
    if (user.FailedAttempts > 3)
    {
        return false;
    }
    // Password hashing omitted for brevity
    if (user.Password != password)
    {
        _database.RecordFailedLoginAttempt(username);
    }
    return true;
}

Cukup jelas bahwa ini bukan fungsi murni:

  1. Fungsi ini tidak akan selalu memberikan hasil yang sama untuk yang diberikan usernamedan passwordkombinasi karena hasilnya juga tergantung pada catatan pengguna yang disimpan dalam database.
  2. Fungsi ini dapat mengubah keadaan database, yaitu memiliki efek samping.

Juga perhatikan bahwa untuk menguji unit fungsi ini kita perlu mengejek dua panggilan basis data, FindUserdan RecordFailedLoginAttempt.

Jika kita memperbaiki kode ini menjadi gaya yang lebih fungsional kita mungkin berakhir dengan sesuatu yang sedikit seperti ini:

bool UserLogin(string username, string password)
{
    var user = _database.FindUser(username);
    var result = UserLoginPure(user, password);
    if (result == Result.FailedAttempt)
    {
        _database.RecordFailedLoginAttempt(username);
    }
    return result == Result.Success;
}

Result UserLoginPure(User user, string pasword)
{
    if (user == null)
    {
        return Result.UserNotFound;
    }
    if (user.FailedAttempts > 3)
    {
        return Result.LoginAttemptsExceeded;
    }
    if (user.Password != password)
    {
        return Result.FailedAttempt;        
    }
    return Result.Success;
}

Perhatikan bahwa meskipun UserLoginfungsinya masih tidak murni, UserLoginPurefungsi tersebut sekarang merupakan fungsi murni dan sebagai hasilnya logika otentikasi pengguna inti dapat diuji unit tanpa perlu mengejek setiap dependensi eksternal. Ini karena interaksi dengan basis data ditangani lebih tinggi dari tumpukan panggilan.

Justin
sumber
Apakah interpretasi Anda: imperative-style = stateerl microservices dan fungsional-style = microservieless stateless ?
k3b
@ k3b Semacam, kecuali sedikit tentang layanan mikro. Sangat sederhana gaya imperatif melibatkan manipulasi negara sedangkan gaya fungsional menggunakan fungsi murni tanpa manipulasi negara.
Justin
1
@ Justin: Saya akan mengatakan bahwa gaya fungsional dengan jelas memisahkan fungsi murni dari kode dengan efek samping, seperti yang Anda lakukan dalam contoh Anda. Dengan kata lain, kode fungsional masih dapat memiliki efek samping.
Giorgio
Pendekatan fungsional harus mengembalikan pasangan dengan hasil dan pengguna, karena pada upaya yang gagal, Result.FailedAttempt adalah hasil dengan pengguna baru dengan data yang sama seperti aslinya, kecuali ia memiliki satu lagi upaya gagal dan fungsi murni tidak menginduksi efek samping pada pengguna yang diberikan sebagai parameter.
RisingDarkness
koreksi untuk bagian terakhir dari komentar saya sebelumnya: "dan fungsi murni TIDAK menyebabkan efek samping pada pengguna yang diberikan sebagai parameter."
RisingDarkness