C # Anda sudah "berfungsi", biarkan saja.

Halo, Habr! Saya mempersembahkan kepada Anda terjemahan artikel asli "C # Anda sudah berfungsi, tetapi hanya jika Anda membiarkannya" oleh Igal Tabachnik.

Beberapa hari yang lalu, saya tweet potongan kode C # yang mengimplementasikan FizzBuzz , menggunakan beberapa fitur baru di C # 8.0 . Tweet “menjadi viral”, beberapa orang mengagumi keringkasan dan fungsinya, sementara yang lain bertanya mengapa saya tidak menulisnya di F #?

Lebih dari 4 tahun telah berlalu sejak terakhir kali saya menulis dalam bahasa C #, dan fakta bahwa saya biasanya menggunakan pemrograman fungsional telah jelas mempengaruhi cara saya menulis kode hari ini. Cuplikan yang saya tulis tampaknya sangat rapi dan alami, tetapi beberapa orang telah menyatakan keprihatinan bahwa itu tidak terlihat seperti kode C #.
"Itu terlihat terlalu fungsional." Mereka menulis kepada saya.
Bergantung pada siapa Anda bertanya, "pemrograman fungsional" memiliki arti berbeda bagi orang yang berbeda. Tapi alih-alih membahas semantik, saya ingin menawarkan penjelasan mengapa implementasi FizzBuzz ini tampaknya fungsional.

Pertama, mari kita lihat apa yang dilakukan kode ini:

public static void Main(string[] args)
{
      string FizzBuzz(int x) => 
            (x % 3 == 0, x % 5 == 0) switch
            {  
                  (true, true) => "FizzBuzz", 
                  (true, _) => "Fizz", 
                  (_, true) => "Buzz", 
                  _ => x.ToString()
            };
    
      Enumerable.Range(1, 100 ) 
            .Select(FizzBuzz).ToList() 
            .ForEach(Console.WriteLine); 
}

Di sini kita membuat metode lokal, diwakili oleh ekspresi lambda, yang hasilnya dihitung menggunakan tuple.

Kebaruan di sini adalah penggunaan tuple (pasangan) untuk bekerja dengan hasil penghitungan dua ekspresi bersama (x% 3 = = 0 dan x% 5 = = 0). Ini memungkinkan Anda untuk menggunakan pencocokan pola untuk menentukan hasil akhir. Jika tidak ada opsi yang cocok, maka secara default representasi string nomor akan dikembalikan.

Namun, tidak satu pun dari banyak "fungsional" "fitur" yang digunakan dalam bagian kode ini (termasuk loop foreach gaya LINQ) membuat kode ini berfungsi dengan sendirinya. Apa yang membuatnya fungsional adalah kenyataan bahwa, dengan pengecualian mengeluarkan hasilnya ke konsol, semua metode yang digunakan dalam program ini adalah ekspresi.

Sederhananya, ekspresi adalah pertanyaan yang selalu memiliki jawaban. Dalam hal pemrograman, ekspresi adalah kombinasi dari konstanta, variabel, operator, dan fungsi yang dihitung oleh runtime untuk menghitung (“mengembalikan”) suatu nilai. Untuk mengilustrasikan perbedaan dengan pernyataan, mari kita menulis versi FizzBuzz untuk programmer C # yang lebih akrab:

public static void Main(string[] args) 
{
      foreach( int x in Enumerable.Range(1, 100)) 
      {
             FizzBuzz(x);
      }
} 

public static void FizzBuzz( int x) 
{
      if (x % 3 == 0  && x % 5 == 0) 
             Console.WriteLine("FizzBuzz"); 
      else if (x % 3 == 0 ) 
             Console.WriteLine("Fizz"); 
      else if (x % 5 == 0 ) 
             Console.WriteLine("Buzz"); 
      else
             Console.WriteLine(x);
}

Tentu saja, ini jauh dari kode "bersih", dan dapat ditingkatkan, tetapi orang tidak bisa tidak setuju bahwa ini adalah kode C # biasa. Setelah memeriksa lebih dekat metode FizzBuzz, meskipun mempertimbangkan kesederhanaannya, itu jelas menunjukkan beberapa masalah desain.

Pertama-tama, program ini melanggar prinsip pertama SOLID - prinsip tanggung jawab tunggal. Ini mencampur logika menghitung nilai output berdasarkan nomor dan mengeluarkan nilai ini ke konsol. Akibatnya, itu melanggar prinsip inversi ketergantungan (yang terakhir dari SOLID), yang terhubung erat dengan output hasil ke konsol. Akhirnya, implementasi program seperti itu membuat sulit untuk menggunakan kembali dan kode sandbox. Tentu saja, untuk program sederhana seperti ini, tidak masuk akal untuk masuk ke seluk-beluk desain, jika tidak sesuatu seperti ini dapat berubah.

Semua masalah di atas dapat diselesaikan dengan memisahkan penerimaan nilai dan output ke konsol. Bahkan tanpa menggunakan konstruksi bahasa mewah, hanya mengembalikan nilai yang dihasilkan ke kode panggilan membebaskan kita dari tanggung jawab untuk menggunakan nilai ini.

public static string FizzBuzz(int x)
{
      if (x % 3 == 0 && x % 5 == 0)
            return "FizzBuzz";
      else if (x % 3 == 0)
            return "Fizz";
      else if (x % 5 == 0)
            return "Buzz";
      else
            return x.ToString();
}

Tentu saja, ini bukan perubahan radikal, tetapi ini sudah cukup:

  1. Metode FizzBuzz sekarang merupakan ekspresi yang mengambil nilai numerik dan mengembalikan string.
  2. Tidak memiliki tanggung jawab dan efek tersembunyi lainnya, yang mengubahnya menjadi fungsi murni.
  3. Itu dapat digunakan dan diuji secara independen, tanpa ada ketergantungan dan pengaturan tambahan.
  4. Kode yang memanggil fungsi ini bebas untuk melakukan apa saja dengan hasilnya - sekarang ini bukan tanggung jawab kami.

Dan ini adalah inti dari pemrograman fungsional - program terdiri dari ekspresi yang menghasilkan nilai apa pun, dan nilai-nilai ini dikembalikan ke kode panggilan. Ekspresi ini, sebagai suatu peraturan, sepenuhnya independen, dan hasil panggilannya hanya bergantung pada data input. Di bagian paling atas, di titik masuk (atau kadang-kadang disebut "akhir dunia"), nilai-nilai yang dikembalikan oleh fungsi mengumpulkan dan berinteraksi dengan seluruh dunia. Dalam jargon berorientasi objek, ini kadang-kadang disebut "arsitektur bawang" (atau "port dan adapter") - inti bersih yang terdiri dari logika bisnis dan kulit luar imperatif yang bertanggung jawab untuk berinteraksi dengan dunia luar.

Menggunakan ekspresi alih-alih pernyataan digunakan sampai batas tertentu oleh semua bahasa pemrograman. C # berkembang dari waktu ke waktu untuk memperkenalkan fungsi yang membuatnya lebih mudah untuk bekerja dengan ekspresi: LINQ, metode berbasis ekspresi, pencocokan pola, dan banyak lagi. "Fitur" ini sering disebut "fungsional" karena ada - dalam bahasa seperti F # atau Haskell, di mana keberadaan apa pun selain ekspresi hampir mustahil.

Bahkan, gaya pemrograman ini sekarang didorong oleh tim C #. Dalam pidatonya baru-baru ini di NDC London, Bill Wagner mendesak pengembang untuk mengubah kebiasaan (keharusan) mereka dan mengadopsi metode modern:


C # (dan bahasa imperatif lainnya seperti Java) dapat digunakan secara fungsional, tetapi membutuhkan banyak usaha. Bahasa-bahasa ini membuat gaya fungsional pengecualian, bukan norma. Saya mendorong Anda untuk belajar bahasa pemrograman fungsional untuk menjadi spesialis kelas satu.

All Articles