Fungsi kecil vs. menjaga fungsionalitas dependen dalam fungsi yang sama

15

Saya memiliki kelas yang mengatur array node dan menghubungkannya satu sama lain dalam struktur seperti grafik. Apakah yang terbaik untuk:

  1. Jaga fungsionalitas untuk menginisialisasi dan menghubungkan node dalam satu fungsi
  2. Memiliki fungsi inisialisasi dan koneksi dalam dua fungsi yang berbeda (dan memiliki urutan tergantung pada fungsi yang harus dipanggil - meskipun perlu diingat fungsi-fungsi ini bersifat pribadi.)

Metode 1: (Buruk karena satu fungsi melakukan dua hal, TETAPI itu membuat fungsi dependen dikelompokkan bersama - node tidak boleh dihubungkan tanpa diinisialisasi terlebih dahulu.)

init() {
    setupNodes()
}

private func setupNodes() {
    // 1. Create array of nodes
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

Metode 2: (Lebih baik dalam arti mendokumentasikan diri, TETAPI connectNodes () tidak boleh dipanggil sebelum setupNodes (), jadi siapa pun yang bekerja dengan kelas internal perlu tahu tentang pesanan ini.)

init() {
    setupNodes()
}

private func setupNodes() {
    createNodes()
    connectNodes()
}

private func createNodes() {
    // 1. Create array of nodes
}

private func connectNodes() {
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

Gembira mendengar pikiran.

mcfroob
sumber
Salah satu cara untuk memecahkan ini dengan mendefinisikan objek antara yang hanya dapat digunakan untuk membuat objek akhir. Ini tidak selalu merupakan solusi yang tepat tetapi berguna jika pengguna antarmuka perlu memanipulasi keadaan perantara dalam beberapa cara.
Joel Cornett

Jawaban:

23

Masalah yang Anda hadapi disebut temporal coupling

Anda benar untuk khawatir tentang bagaimana kode ini dapat dimengerti:

private func setupNodes() {
    createNodes();
    connectNodes();
}

Saya bisa menebak apa yang terjadi di sana, tetapi beri tahu saya jika ini membuat apa yang terjadi sedikit lebih jelas:

private func setupNodes() {
    self.nodes = connectNodes( createNodes() );
}

Ini memiliki manfaat tambahan karena kurang digabungkan dengan memodifikasi variabel instan tetapi bagi saya yang dapat dibaca adalah nomor satu.

Ini membuat connectNodes() ketergantungan pada node eksplisit.

candied_orange
sumber
1
Terima kasih untuk tautannya. Karena fungsi saya bersifat pribadi dan dipanggil dari constructor - init () di Swift - Saya tidak berpikir kode saya akan seburuk contoh yang Anda tautkan (tidak mungkin bagi klien eksternal untuk membuat instance sebuah instance dengan variabel instan nol), tetapi saya memiliki bau yang serupa.
mcfroob
1
Kode yang Anda tambahkan lebih mudah dibaca, jadi saya akan refactor dengan gaya itu.
mcfroob
10

Fungsi terpisah , karena dua alasan:

1. Fungsi pribadi bersifat pribadi untuk situasi ini.

initFungsi Anda bersifat publik, dan itu antarmuka, perilaku, dan nilai kembali adalah apa yang Anda perlu khawatirkan tentang perlindungan dan perubahan. Hasil yang Anda harapkan dari metode itu akan tetap sama apa pun implementasi yang Anda gunakan.

Karena fungsi lainnya disembunyikan di balik kata kunci pribadi itu, kata kunci itu dapat diimplementasikan sesuka Anda ... jadi Anda bisa membuatnya bagus dan modular, walaupun satu bit tergantung pada yang lain dipanggil terlebih dahulu.

2. Menghubungkan node satu sama lain mungkin bukan fungsi pribadi

Bagaimana jika pada titik tertentu Anda ingin menambahkan node lain ke array? Apakah Anda menghancurkan pengaturan yang Anda miliki sekarang, dan menginisialisasi ulang sepenuhnya? Atau apakah Anda menambahkan node ke array yang ada dan kemudian jalankan connectNodeslagi?

Mungkin connectNodesdapat memiliki respons yang waras jika array node belum dibuat (melemparkan pengecualian? Mengembalikan set kosong? Anda harus memutuskan apa yang masuk akal untuk situasi Anda).

Jen
sumber
Saya berpikir dengan cara yang sama seperti 1, dan saya bisa melempar pengecualian atau sesuatu jika node tidak diinisialisasi, tetapi itu tidak terlalu intuitif. Terima kasih atas tanggapannya.
mcfroob
4

Anda juga dapat menemukan (tergantung pada seberapa kompleks masing-masing tugas ini) bahwa ini adalah jahitan yang baik untuk membagi kelas lain.

(Tidak yakin apakah Swift bekerja dengan cara ini tetapi pseudo-code :)

class YourClass {
    init(generator: NodesGenerator) {
        self.nodes = connectNodes(generator.make())
    }
    private func connectNodes() {

    }
}

class NodesGenerator {
    public func make() {
        // Return some nodes from storage or make new ones
    }
}

Ini memisahkan tanggung jawab untuk membuat dan memodifikasi node ke kelas yang terpisah: NodeGeneratorhanya peduli membuat / mengambil node, sementara YourClasshanya peduli tentang menghubungkan node yang diberikan.

Willoller
sumber
2

Selain ini menjadi tujuan tepat metode pribadi, Swift memberi Anda kemampuan untuk menggunakan fungsi-fungsi batin.

Metode internal sempurna untuk fungsi yang hanya memiliki satu situs panggilan, tetapi merasa seperti itu tidak membenarkan menjadi fungsi pribadi yang terpisah.

Misalnya, sangat umum untuk memiliki fungsi "entri" rekursif publik, yang memeriksa prasyarat, mengatur beberapa parameter, dan mendelegasikan ke fungsi rekursif pribadi yang berfungsi.

Berikut adalah contoh bagaimana hal itu terlihat dalam kasus ini:

init() {
    self.nodes = setupNodes()

    func setupNodes() {
        var nodes = createNodes()
        connect(Nodes: nodes)
    }

    private func createNodes() -> [Node]{
        // 1. Create array of nodes
    }

    func connect(Nodes: [Node]) {
        // 2. Go through array, connecting each node to its neighbors 
        //    according to some predefined constants
    }
}

Perhatikan bagaimana saya menggunakan nilai dan parameter pengembalian untuk mengedarkan data, daripada memutasikan status bersama. Ini membuat aliran data jauh lebih jelas pada pandangan pertama, tanpa perlu melompat ke implementasi.

Alexander - Pasang kembali Monica
sumber
0

Setiap fungsi yang Anda nyatakan memiliki beban untuk menambahkan dokumentasi dan membuatnya digeneralisasikan sehingga dapat digunakan oleh bagian lain dari program. Itu juga membawa beban memahami bagaimana fungsi lain dalam file dapat menggunakannya untuk seseorang yang membaca kode.

Namun jika itu tidak digunakan oleh bagian lain dari program Anda, saya tidak akan mengeksposnya sebagai fungsi terpisah.

Jika bahasa Anda mendukungnya, Anda masih dapat memiliki satu-fungsi-melakukan-satu-hal dengan menggunakan fungsi bersarang

function setupNodes ()  {
  function createNodes ()  {...} 
  function connectNodes ()  {...}
  createNodes() 
  connectNodes() 
} 

Tempat deklarasi sangat penting, dan dalam contoh di atas jelas tanpa perlu petunjuk lebih lanjut bahwa fungsi-fungsi batin dimaksudkan untuk digunakan hanya di dalam tubuh fungsi luar.

Bahkan jika Anda mendeklarasikannya sebagai fungsi pribadi, saya berasumsi mereka masih dapat dilihat oleh keseluruhan file. Jadi, Anda perlu mendeklarasikannya dekat dengan deklarasi fungsi utama, dan menambahkan beberapa dokumentasi yang menjelaskan bahwa mereka hanya akan digunakan oleh fungsi luar.

Saya tidak berpikir bahwa Anda harus melakukan satu atau yang lain secara ketat. Hal terbaik untuk dilakukan bervariasi berdasarkan kasus per kasus.

Memecahnya menjadi beberapa fungsi tentu saja menambah pemahaman mengapa ada 3 fungsi dan bagaimana mereka semua bekerja satu sama lain, tetapi jika logikanya kompleks maka overhead yang ditambahkan ini mungkin jauh lebih sedikit daripada kesederhanaan yang diperkenalkan dengan memecah logika kompleks menjadi bagian-bagian yang lebih sederhana.

Peeyush Kushwaha
sumber
Opsi menarik. Seperti yang Anda katakan, saya pikir mungkin akan sedikit membingungkan mengapa fungsi tersebut dideklarasikan seperti ini, tetapi itu akan menjaga ketergantungan fungsi tetap terkandung.
mcfroob
Untuk menjawab beberapa ketidakpastian dalam pertanyaan ini: 1) Ya, Swift mendukung fungsi dalam, dan 2) Ia memiliki dua tingkat "pribadi". privatememungkinkan akses hanya dalam tipe penutup (struct / class / enum), sedangkan fileprivatemengizinkan akses di seluruh file
Alexander - Reinstate Monica