Apakah ini pola yang baik: mengganti fungsi panjang dengan serangkaian lambda?

14

Saya baru-baru ini mengalami situasi berikut.

class A{
public:
    void calculate(T inputs);
}

Pertama, Amerepresentasikan objek di dunia fisik, yang merupakan argumen kuat untuk tidak memisahkan kelas. Sekarang, calculate()ternyata fungsinya cukup panjang dan rumit. Saya merasakan tiga kemungkinan struktur untuk itu:

  • tulis itu sebagai dinding teks - keuntungan - semua informasi ada di satu tempat
  • menulis privatefungsi utilitas di kelas dan menggunakannya dalam calculatetubuh - kerugian - seluruh kelas tidak tahu / tidak peduli / mengerti tentang metode-metode itu
  • tulis calculatecara berikut:

    void A::calculate(T inputs){    
        auto lambda1 = () [] {};    
        auto lambda2 = () [] {};    
        auto lambda3 = () [] {};
    
        lambda1(inputs.first_logical_chunk);
        lambda2(inputs.second_logical_chunk);
        lambda3(inputs.third_logical_chunk);
    }

Bisakah ini dianggap sebagai praktik yang baik atau buruk? Apakah pendekatan ini mengungkapkan masalah? Secara keseluruhan, haruskah saya menganggap ini sebagai pendekatan yang baik ketika saya dihadapkan pada situasi yang sama?


EDIT:

class A{
    ...
public:
    // Reconfiguration of the algorithm.
    void set_colour(double colour);
    void set_density(double density);
    void set_predelay(unsigned long microseconds);
    void set_reverb_time(double reverb_time, double room_size);
    void set_drywet(double left, double right);
    void set_room_size(double value);;

private:
    // Sub-model objects.
    ...
}

Semua metode itu:

  • dapatkan nilai
  • menghitung beberapa nilai lain, tanpa menggunakan status
  • panggil beberapa "objek sub-model" untuk mengubah statusnya.

Ternyata, kecuali set_room_size(), metode-metode itu hanya meneruskan nilai yang diminta ke sub-objek. set_room_size(), di sisi lain, melakukan beberapa layar formula yang tidak jelas dan kemudian (2) melakukan setengah layar memanggil sub-objek setter untuk menerapkan berbagai hasil yang diperoleh. Karena itu, saya telah memisahkan fungsi menjadi dua lambda dan memanggil mereka di akhir fungsi. Seandainya saya bisa membaginya menjadi potongan yang lebih logis, saya akan mengisolasi lebih banyak lambda.

Terlepas dari itu, tujuan dari pertanyaan saat ini adalah untuk menentukan apakah cara berpikir itu harus bertahan, atau apakah yang terbaik tidak menambah nilai (keterbacaan, pemeliharaan, kemampuan debug dll).

Vorac
sumber
2
Apa yang Anda pikir menggunakan lambdas akan membuat Anda bahwa panggilan fungsi tidak akan?
Blrfl
1
Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.Tentunya Amerepresentasikan data tentang suatu objek yang bisa eksis di dunia fisik. Anda dapat memiliki instance Atanpa objek nyata dan objek nyata tanpa instance A, jadi memperlakukan mereka seolah-olah satu dan yang sama tidak masuk akal.
Doval
@ Bllfl, enkapsulasi - tidak ada yang calculate()tahu tentang sub-fungsi tersebut.
Vorac
Jika semua perhitungan itu relevan dengan adil A, itu agak berlebihan.
Blrfl
1
"Pertama, Amewakili sebuah objek di dunia fisik, yang merupakan argumen kuat untuk tidak memisahkan kelas." Sayangnya, saya diberitahu hal ini ketika saya mulai pemrograman. Butuh bertahun-tahun bagiku untuk menyadari bahwa itu adalah sekelompok hoki kuda. Ini alasan mengerikan untuk mengelompokkan berbagai hal. Saya tidak bisa mengartikulasikan apa yang alasan yang baik untuk hal-hal kelompok (setidaknya untuk kepuasan saya), tapi satu yang adalah salah satu Anda harus membuang sekarang. Pada akhirnya, semua "kode yang baik" adalah berfungsi dengan baik, relatif mudah dipahami, dan relatif mudah diubah (yaitu, perubahan tidak memiliki efek samping yang aneh).
jpmc26

Jawaban:

13

Tidak, ini umumnya bukan pola yang baik

Apa yang Anda lakukan adalah memecah suatu fungsi menjadi fungsi yang lebih kecil menggunakan lambda. Namun, ada alat yang jauh lebih baik untuk memecah fungsi: fungsi.

Lambdas bekerja, seperti yang telah Anda lihat, tetapi artinya jauh lebih dari sekadar memecah fungsi menjadi bit lokal. Lambdas lakukan:

  • Penutupan. Anda dapat menggunakan variabel di lingkup luar di dalam lambda. Ini sangat kuat dan sangat rumit.
  • Penugasan kembali. Meskipun contoh Anda menyulitkan untuk melakukan tugas, kita harus selalu memperhatikan gagasan bahwa kode dapat bertukar fungsi setiap saat.
  • Fungsi kelas satu. Anda dapat meneruskan fungsi lambda ke fungsi lain, melakukan sesuatu yang dikenal sebagai "pemrograman fungsional."

Begitu Anda membawa lambdas ke dalam campuran, pengembang berikutnya untuk melihat kode harus segera memuat semua aturan secara mental dalam persiapan untuk melihat bagaimana kode Anda bekerja. Mereka tidak tahu bahwa Anda tidak akan menggunakan semua fungsi itu. Ini sangat mahal, dibandingkan dengan alternatifnya.

Ini seperti menggunakan cangkul belakang untuk berkebun. Anda tahu bahwa Anda hanya menggunakannya untuk menggali lubang kecil untuk bunga tahun ini, tetapi tetangga akan merasa gugup.

Pertimbangkan bahwa semua yang Anda lakukan adalah mengelompokkan kode sumber Anda secara visual. Kompiler tidak benar-benar peduli bahwa Anda menjilat sesuatu dengan lambdas. Bahkan, saya berharap pengoptimal untuk segera membatalkan semua yang baru saja Anda lakukan ketika dikompilasi. Anda murni melayani pembaca berikutnya (Terima kasih telah melakukan itu, bahkan jika kami tidak setuju pada metodologi! Kode dibaca jauh lebih sering daripada yang ditulis!). Yang Anda lakukan hanyalah fungsi pengelompokan.

  • Fungsi yang ditempatkan secara lokal dalam aliran kode sumber akan melakukan hal yang sama baiknya, tanpa memanggil lambda. Sekali lagi, yang penting adalah bahwa pembaca dapat membacanya.
  • Komentar di bagian atas fungsi mengatakan "kami membagi fungsi ini menjadi tiga bagian," diikuti oleh garis panjang besar di // ------------------antara setiap bagian.
  • Anda juga dapat menempatkan setiap bagian dari perhitungan ke dalam cakupannya sendiri. Ini memiliki bonus untuk segera membuktikan tanpa keraguan bahwa tidak ada pembagian variabel antara bagian-bagian.

EDIT: Dari melihat hasil edit Anda dengan kode contoh, saya condong ke arah notasi komentar yang paling bersih, dengan tanda kurung untuk menegakkan batas yang disarankan komentar. Namun, jika ada fungsi yang dapat digunakan kembali di fungsi lain, saya akan merekomendasikan menggunakan fungsi saja

void A::set_room_size(double value)
{
    {
        // Part 1: {description of part 1}
        ...
    }
    // ------------------------
    {
        // Part 2: {description of part 2}
        ...
    }
    // ------------------------
    {
        // Part 3: {description of part 3}
        ...
    }
}
Cort Ammon - Pulihkan Monica
sumber
Jadi ini bukan angka objektif, tetapi saya akan secara subyektif mengklaim bahwa kehadiran fungsi lambda hanya membuat saya membayar sekitar 10 kali lebih banyak perhatian untuk setiap baris kode, karena mereka memiliki begitu banyak potensi untuk menjadi rumit rumit, cepat, dan melakukannya pada baris kode yang tampak tidak bersalah (seperti count++)
Cort Ammon - Reinstate Monica
Poin yang bagus. Saya melihat mereka sebagai beberapa keuntungan dari pendekatan dengan lambdas - (1) kode yang berdekatan secara lokal dan dengan cakupan lokal (ini akan hilang oleh fungsi tingkat file) (2) kompiler memastikan bahwa tidak ada variabel lokal dibagi antara segmen kode. Jadi keunggulan itu dapat dipertahankan dengan memisahkan calculate()menjadi {}blok dan mendeklarasikan data bersama di calculate()ruang lingkup. Saya berpikir bahwa dengan melihat bahwa lambda tidak menangkap, pembaca tidak akan terbebani oleh kekuatan lambda.
Vorac
"Aku pikir dengan melihat lambda tidak menangkap, pembaca tidak akan terbebani oleh kekuatan lambda." Itu sebenarnya pernyataan yang adil, tetapi kontroversial, yang menyentuh jantung linguistik. Kata-kata biasanya memiliki konotasi yang melampaui denotasinya. Apakah konotasi saya lambdatidak adil, atau jika Anda dengan kasar memaksa orang untuk mengikuti denotasi yang ketat, bukanlah pertanyaan yang mudah dijawab. Bahkan, mungkin bisa sepenuhnya diterima bagi Anda untuk menggunakan lambdacara itu di perusahaan Anda, dan sama sekali tidak dapat diterima di perusahaan saya, dan tidak ada yang benar-benar salah!
Cort Ammon - Pasang kembali Monica
Pendapat saya tentang konotasi lambda berasal dari fakta bahwa saya tumbuh di C ++ 03, bukan C + 11. Saya telah menghabiskan waktu bertahun-tahun mengembangkan apresiasi untuk tempat-tempat tertentu di mana C ++ telah dirugikan oleh kurangnya lambda, seperti for_eachfungsi. Karena itu, ketika saya melihat sebuah lambdayang tidak cocok dengan salah satu kasus kesulitan yang mudah dikenali, asumsi pertama yang saya dapatkan adalah bahwa itu kemungkinan akan digunakan untuk pemrograman fungsional, karena itu tidak diperlukan sebaliknya. Bagi banyak pengembang, pemrograman fungsional adalah pola pikir yang sama sekali berbeda dari pemrograman prosedural atau OO.
Cort Ammon - Pasang kembali Monica
Terima kasih telah menjelaskan. Saya seorang programmer pemula dan sekarang senang bahwa saya bertanya - sebelum kebiasaan itu menumpuk.
Vorac
20

Saya pikir Anda membuat asumsi yang buruk:

Pertama, A mewakili objek di dunia fisik, yang merupakan argumen kuat untuk tidak memisahkan kelas.

Saya tidak setuju dengan ini. Misalnya, jika saya memiliki kelas yang mewakili mobil, saya pasti ingin membaginya, karena saya pasti ingin kelas yang lebih kecil untuk mewakili ban.

Anda harus membagi fungsi ini menjadi fungsi pribadi yang lebih kecil. Jika benar-benar tampak terpisah dari bagian lain dari kelas, maka itu mungkin merupakan tanda bahwa kelas harus dipisahkan. Tentu saja sulit untuk mengatakannya tanpa contoh eksplisit.

Saya tidak benar-benar melihat keuntungan dari menggunakan fungsi lambda dalam kasus ini, karena itu tidak benar-benar membuat kode lebih bersih. Mereka diciptakan untuk membantu pemrograman gaya fungsional, tetapi ini bukan itu.

Apa yang Anda tulis sedikit mirip dengan objek fungsi bersarang gaya Javascript. Yang lagi-lagi merupakan pertanda bahwa mereka memiliki kedekatan. Apakah Anda yakin, bahwa Anda tidak boleh membuat kelas yang terpisah untuk mereka?

Singkatnya, saya tidak berpikir bahwa ini adalah pola yang baik.

MEMPERBARUI

Jika Anda tidak melihat cara apa pun untuk merangkum fungsi ini dalam kelas yang bermakna, maka Anda bisa membuat fungsi pembantu lingkup file, yang bukan anggota kelas Anda. Bagaimanapun ini adalah C ++, desain OO tidak harus.

Gábor Angyal
sumber
Kedengarannya masuk akal, tetapi sulit bagi saya untuk membayangkan implementasinya. Kelas file-lingkup dengan metode statis? Kelas, didefinisikan di dalam A(bahkan mungkin functor dengan semua metode lain pribadi)? Kelas dideklarasikan dan didefinisikan di dalam calculate()(ini sangat mirip dengan contoh lambda saya). Sebagai klarifikasi, calculate()adalah satu dari sekumpulan metode ( calculate_1(), calculate_2()dll.) Yang semuanya sederhana, hanya saja ini adalah 2 layar formula.
Vorac
@Vorac: Mengapa calculate()jauh lebih lama daripada semua metode lainnya?
Kevin
@Vorac Sangat sulit untuk membantu tanpa melihat kode Anda. Bisakah Anda juga mempostingnya?
Gábor Angyal
@ Kevin, formula untuk semuanya diberikan dalam persyaratan. Kode diposting
Vorac
4
Saya akan memperbaiki ini 100 kali jika saya bisa. "Modeling Objects in the Real World" adalah awal dari spiral desain mati Object. Itu adalah bendera merah raksasa dalam kode apa pun.
Fred the Magic Wonder Dog