Melewati variabel anggota sebagai parameter metode

33

Dalam sebuah proyek, saya telah menemukan kode seperti ini:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

Bagaimana saya meyakinkan rekan tim saya bahwa ini adalah kode yang buruk?

Saya percaya ini adalah komplikasi yang tidak perlu. Mengapa meneruskan variabel anggota sebagai parameter metode jika Anda sudah memiliki akses ke sana? Ini juga merupakan pelanggaran enkapsulasi.

Apakah Anda melihat ada masalah lain dengan kode ini?

tika
sumber
21
Apa yang membuatmu berpikir itu buruk?
yannis
@Yannis Rizos, Anda pikir itu bagus? Setidaknya ini adalah komplikasi yang tidak perlu. Mengapa meneruskan variabel sebagai parameter metode, jika Anda sudah memiliki akses ke sana? Ini juga merupakan pelanggaran enkapsulasi.
tika
2
Poin yang valid, harap edit pertanyaan untuk memasukkan mereka. Kami tidak dapat membantu Anda meyakinkan teman satu tim Anda, tetapi kami dapat membantu Anda mengevaluasi kodenya, itulah pertanyaan Anda seharusnya.
yannis
8
Saya lebih suka memiliki metode yang dapat meringkas variabel yang berbeda dari metode yang melakukan konstanta 2 + 2. Parameter pada metode ini untuk reusability.
Dante
Satu hal yang menurut saya penting di sini adalah tipe parameter itu. Jika itu tipe referensi saya tidak melihat keuntungan tetapi jika itu tipe nilai saya pikir itu masuk akal karena jika Anda modifie tipe variabel kompiler akan memperingatkan Anda tentang tempat Anda memecahkan kode.
Rémi

Jawaban:

3

Saya pikir ini adalah topik yang valid, tetapi alasan Anda mendapat tanggapan yang beragam adalah karena cara pertanyaan itu dibentuk. Secara pribadi, saya memiliki pengalaman yang sama dengan tim saya di mana melewati anggota sebagai argumen tidak perlu dan berbelit-belit kode. Kami memiliki kelas yang bekerja dengan sekumpulan anggota tetapi beberapa fungsi mengakses anggota secara langsung dan fungsi lainnya akan memodifikasi anggota yang sama melalui parameter (yaitu menggunakan nama yang sama sekali berbeda) dan sama sekali tidak ada alasan teknis untuk melakukan itu. Secara teknis, maksud saya contoh yang diberikan Kate.

Saya akan merekomendasikan untuk mengambil langkah mundur dan alih-alih berfokus tepat pada kelulusan anggota sebagai parameter, mulailah diskusi dengan tim Anda tentang kejelasan dan keterbacaan. Entah lebih formal, atau hanya di lorong, membahas apa yang membuat beberapa segmen kode lebih mudah dibaca dan segmen kode lainnya lebih sulit. Kemudian identifikasi ukuran kualitas atau atribut kode bersih yang ingin Anda perjuangkan. Lagi pula, bahkan ketika mengerjakan proyek-proyek lapangan hijau, kami menghabiskan lebih dari 90% waktu membaca dan segera setelah kode ditulis (misalkan 10-15 menit kemudian) ia masuk ke pemeliharaan di mana keterbacaan bahkan lebih penting.

Jadi untuk contoh spesifik Anda di sini, argumen yang akan saya gunakan adalah bahwa lebih sedikit kode selalu lebih mudah dibaca daripada lebih banyak kode. Fungsi yang memiliki 3 parameter lebih sulit bagi otak untuk diproses daripada fungsi yang tidak memiliki atau 1 parameter. Jika ada nama variabel lain, otak harus melacak hal lain saat membaca kode. Jadi untuk mengingat "int m_value" dan kemudian "int localValue" dan ingat bahwa yang satu benar-benar berarti yang lain selalu lebih mahal untuk otak Anda daripada hanya bekerja dengan "m_value".

Untuk lebih banyak amunisi dan ide, saya akan merekomendasikan mengambil salinan Kode Paman Bob .

DXM
sumber
Tunduk setelah melihat pengaruh buku yang direferensikan.
Frank Hileman
Meskipun sebagian kecil dari saya sangat sedih bahwa 5 tahun setelah menulis jawaban, Anda hanya mengambil 2 poin internet dari saya, ada bagian kecil dari diri saya yang ingin tahu jika Anda dapat memberikan beberapa referensi yang akan mengindikasikan (mungkin buruk) pengaruh yang dimiliki buku yang saya rujuk. Itu akan tampak adil dan hampir sepadan dengan poin-poin itu
DXM
referensi adalah diri saya sendiri, melihat secara pribadi pengaruhnya terhadap pengembang lain. Namun, semua motivasi di balik buku-buku semacam itu bagus, dengan menetapkan bahwa semua kode harus mengikuti pedoman non-standar (yaitu pedoman yang benar-benar memenuhi tujuan), buku-buku semacam itu tampaknya menimbulkan hilangnya pemikiran kritis, yang oleh sebagian orang ditafsirkan sebagai seperti kultus .
Frank Hileman
mengenai tantangan pemrosesan otak, pertimbangkan konsep beban kognitif
jxramos
30

Saya bisa memikirkan pembenaran untuk meneruskan bidang anggota sebagai parameter dalam metode (pribadi): itu membuat eksplisit apa yang tergantung pada metode Anda.

Seperti yang Anda katakan, semua bidang anggota adalah parameter implisit dari metode Anda, karena seluruh objek. Namun, apakah objek lengkap benar-benar diperlukan untuk menghitung hasilnya? Jika SomeMethodmerupakan metode internal yang hanya bergantung _someField, bukankah lebih bersih untuk menjadikan ketergantungan ini eksplisit? Bahkan, membuat dependensi ini secara eksplisit juga mengisyaratkan bahwa Anda sebenarnya dapat memperbaiki kode ini dari kelas Anda! (Catatan saya berasumsi kita tidak berbicara tentang getter atau setter di sini, tetapi kode yang benar-benar menghitung sesuatu)

Saya tidak akan membuat argumen yang sama untuk metode publik, karena penelepon tidak tahu atau peduli tentang bagian mana dari objek yang relevan untuk menghitung hasilnya ...

Andres F.
sumber
2
Apa yang akan menjadi ketergantungan tersirat yang tersisa? Saya berasumsi dari contoh Anda bahwa _someFieldhanya parameter yang diperlukan untuk menghitung hasilnya, dan kami baru saja membuatnya eksplisit. (Catatan: ini penting. Kami tidak menambahkan ketergantungan, kami hanya membuatnya eksplisit!)
Andres F.
11
-1 Jika tidak ada ketergantungan implisit ke anggota instance, maka itu harus menjadi anggota statis yang mengambil nilai sebagai parameter. Dalam hal ini, meskipun Anda telah menemukan alasan, saya tidak berpikir itu adalah pembenaran yang valid. Itu kode yang buruk.
Steven Evers
2
@SnOrfus Saya benar-benar menyarankan refactoring metode sepenuhnya keluar dari kelas
Andres F.
5
+1. untuk "... bukankah lebih bersih untuk membuat ketergantungan ini eksplisit?" Benar-benar aneh. Siapa di sini yang akan mengatakan "variabel global baik sebagai aturan umum"? Itu sangat COBOL-68. Dengarkan aku sekarang dan percayalah padaku nanti. Dalam kode non-sepele kami, kadang-kadang, saya akan menolak untuk secara eksplisit menyampaikan di mana variabel kelas-global digunakan. Kami telah mengacaukan anjing dalam banyak kasus dengan a) penggunaan sembarang mundur dari bidang pribadi dan itu milik umum b) mengaburkan transformasi ladang, dengan "menyembunyikan dependensi". Sekarang kalikan ini dengan rantai pewarisan 3-5 dalam.
radarbob
2
@Tarion Saya tidak setuju dengan Paman Bob tentang ini. Kapan pun mungkin, metode harus seperti fungsi dan hanya bergantung pada dependensi eksplisit. (Saat memanggil metode publik di OOP, salah satu dari dependensi ini adalah this(atau self), tetapi dibuat eksplisit oleh panggilan itu sendiri, obj.method(x)). Ketergantungan implisit lainnya adalah keadaan objek; ini biasanya membuat kode lebih sulit untuk dipahami. Kapan pun memungkinkan - dan sesuai alasan - buat dependensi eksplisit & fungsional-gaya. Dalam kasus metode pribadi, jika mungkin, secara eksplisit berikan setiap parameter yang mereka butuhkan. Dan ya, itu membantu untuk memperbaiki mereka.
Andres F.
28

Saya melihat satu alasan kuat untuk meneruskan variabel anggota sebagai argumen fungsi ke metode pribadi - kemurnian fungsi. Variabel anggota secara efektif merupakan keadaan global dari sudut pandang, terlebih lagi, keadaan global yang dapat berubah jika anggota tersebut berubah selama eksekusi metode. Dengan mengganti referensi variabel anggota dengan parameter metode, kita dapat secara efektif membuat fungsi murni. Fungsi murni tidak bergantung pada keadaan eksternal dan tidak memiliki efek samping, selalu mengembalikan hasil yang sama dengan serangkaian parameter input yang sama - sehingga memudahkan pengujian dan pemeliharaan di masa mendatang.

Tentu tidak mudah atau praktis untuk memiliki semua metode Anda sebagai metode murni dalam bahasa OOP. Tapi saya percaya Anda mendapatkan banyak dalam hal kejelasan kode dengan memiliki metode yang menangani logika kompleks murni dan menggunakan variabel tidak berubah, sambil menjaga penanganan "global" keadaan tidak murni dipisahkan dalam metode khusus.

Melewati variabel anggota ke fungsi publik dari objek yang sama ketika memanggil fungsi secara eksternal akan tetapi merupakan bau kode utama menurut saya.

scrwtp
sumber
5
Jawaban Supurb. Meskipun ideal, banyak kelas dalam perangkat lunak saat ini lebih besar dan lebih buruk daripada seluruh program ketika fase "Global is Evil" diciptakan. Variabel kelas, untuk semua tujuan praktis, global dalam lingkup instance kelas. Memiliki sejumlah besar pekerjaan yang dilakukan dalam fungsi murni membuat kode jauh lebih dapat diuji dan kuat.
mattnz
@ mattnz apakah ada cara Anda bisa menyediakan tautan ke daftar pustaka Hwat atau buku-bukunya tentang pemrograman ideal? Saya telah menjelajahi internet dan tidak dapat menemukan apa pun pada dirinya. Google terus berusaha mengoreksinya menjadi "apa".
Buttle Butkus
8

Jika fungsi ini dipanggil beberapa kali, kadang-kadang melewati variabel anggota itu dan kadang-kadang melewati sesuatu yang lain, maka tidak masalah. Misalnya, saya tidak akan menganggap ini buruk sama sekali:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

di mana newStartDatebeberapa variabel lokal dan m_StartDatemerupakan variabel anggota.

Namun jika fungsi hanya dipanggil dengan variabel anggota yang diteruskan ke sana, itu aneh. Fungsi anggota bekerja pada variabel anggota setiap saat. Mereka mungkin melakukan ini (tergantung pada bahasa tempat Anda bekerja) untuk mendapatkan salinan variabel anggota - jika itu masalahnya dan Anda tidak dapat melihat itu, kode mungkin lebih baik jika membuat seluruh proses eksplisit.

Kate Gregory
sumber
3
Tidak masalah bahwa metode ini dipanggil dengan parameter selain variabel anggota. Yang penting adalah itu bisa disebut seperti itu. Anda tidak selalu tahu bagaimana suatu metode pada akhirnya akan dipanggil saat Anda membuatnya.
Caleb
Mungkin Anda sebaiknya mengganti kondisi "jika" dengan "needHandleIncreaseChages (newStartDate)" dan kemudian argumen Anda tidak berlaku lagi.
Tarion
2

Yang tidak disentuh orang adalah SomeMethod dilindungi virtual. Berarti kelas turunan dapat menggunakannya DAN mengimplementasikan kembali fungsinya. Kelas turunan tidak akan memiliki akses ke variabel pribadi dan karenanya tidak bisa menyediakan implementasi kustom SomeMethod yang tergantung pada variabel pribadi. Alih-alih mengambil ketergantungan pada variabel pribadi, deklarasi mengharuskan penelepon untuk meneruskannya.

Michael Brown
sumber
Apa yang Anda lewatkan adalah bahwa variabel anggota pribadi ini memiliki aksesor publik.
tika
Jadi Anda mengatakan metode virtual yang dilindungi harus mengambil ketergantungan pada accessor publik dari variabel pribadi? Dan Anda memiliki masalah dengan kode saat ini?
Michael Brown
Aksesor publik dibuat untuk digunakan. Periode.
tika
1

Kerangka kerja GUI biasanya memiliki semacam kelas 'Tampilan' yang mewakili hal-hal yang digambar di layar, dan kelas itu biasanya menyediakan metode seperti invalidateRect(Rect r)untuk menandai bagian dari area gambarnya yang perlu digambar ulang. Klien dapat memanggil metode itu untuk meminta pembaruan ke bagian tampilan. Tetapi suatu tampilan mungkin juga memanggil metode sendiri, seperti:

invalidateRect(m_frame);

menyebabkan menggambar ulang seluruh area. Misalnya, mungkin melakukan ini ketika pertama kali ditambahkan ke hierarki tampilan.

Tidak ada yang salah dengan melakukan ini - frame view adalah persegi panjang yang valid, dan view sendiri tahu bahwa ia ingin menggambar ulang dirinya sendiri. Kelas tampilan dapat menyediakan metode terpisah yang tidak menggunakan parameter dan menggunakan bingkai tampilan sebagai gantinya:

invalidateFrame();

Tetapi mengapa menambahkan metode khusus hanya untuk ini ketika Anda dapat menggunakan yang lebih umum invalidateRect()? Atau, jika Anda memang memilih untuk menyediakan invalidateFrame(), kemungkinan besar Anda akan mengimplementasikannya dalam hal yang lebih umum invalidateRect():

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

Mengapa meneruskan variabel sebagai parameter metode, jika Anda sudah memiliki akses ke sana?

Anda harus meneruskan variabel instan sebagai parameter ke metode Anda sendiri jika metode tidak beroperasi secara khusus pada variabel instan tersebut. Pada contoh di atas, bingkai view hanyalah persegi panjang sejauh invalidateRect()metode yang digunakan.

Caleb
sumber
1

Ini masuk akal jika metode ini adalah metode utilitas. Katakan misalnya Anda harus mendapatkan nama pendek yang unik dari beberapa string teks gratis.

Anda tidak ingin membuat kode implementasi yang terpisah untuk setiap string, alih-alih meneruskan string ke metode umum masuk akal.

Namun jika metode ini selalu bekerja pada satu anggota tampaknya agak bodoh untuk meneruskannya sebagai parameter.

James Anderson
sumber
1

Alasan utama untuk memiliki variabel anggota di kelas adalah untuk memungkinkan Anda mengaturnya di satu tempat, dan memiliki nilainya tersedia untuk setiap metode lain di kelas. Oleh karena itu secara umum Anda akan berharap bahwa tidak perlu melewatkan variabel anggota ke dalam metode kelas.

Namun saya dapat memikirkan beberapa alasan mengapa Anda mungkin ingin meneruskan variabel anggota ke metode lain di kelas. Yang pertama adalah jika Anda perlu menjamin bahwa nilai variabel anggota perlu digunakan tidak berubah ketika digunakan dengan metode yang dipanggil, bahkan jika metode itu perlu mengubah nilai variabel anggota aktual di beberapa titik dalam proses. Alasan kedua terkait dengan yang pertama di mana Anda mungkin ingin menjamin ketetapan nilai dalam lingkup rantai metode - ketika menerapkan sintaks yang lancar misalnya.

Dengan semua ini dalam pikiran, saya tidak akan mengatakan bahwa kode itu "buruk" jika Anda melewatkan variabel anggota ke salah satu metode kelasnya. Namun saya akan menyarankan bahwa itu umumnya tidak ideal, karena mungkin mendorong banyak duplikasi kode di mana parameter ditugaskan ke variabel lokal misalnya, dan parameter tambahan menambahkan "noise" dalam kode di mana itu tidak diperlukan. Jika Anda seorang penggemar buku Kode Bersih, Anda akan tahu bahwa itu menyebutkan bahwa Anda harus menjaga jumlah parameter metode ke minimum barest, dan hanya jika tidak ada cara lain yang lebih masuk akal untuk metode untuk mengakses parameter .

S.Robins
sumber
1

Beberapa hal yang datang kepada saya ketika memikirkan hal ini:

  1. Secara umum, tanda tangan metode dengan parameter lebih sedikit lebih mudah dipahami; alasan utama metode ditemukan adalah untuk menghilangkan daftar parameter panjang dengan menggabungkannya dengan data di mana mereka beroperasi.
  2. Membuat tanda tangan metode bergantung pada variabel anggota akan membuat lebih sulit untuk mengubah variabel-variabel di masa depan, karena Anda tidak hanya perlu mengubah di dalam metode, tetapi di mana-mana metode ini dipanggil juga. Dan karena SomeMethod dalam contoh Anda dilindungi, subclass juga perlu diubah.
  3. Metode (publik atau pribadi) yang tidak bergantung pada kelas internal tidak perlu berada di kelas itu. Mereka dapat difaktorkan ke dalam metode utilitas dan sama bahagia. Mereka tidak memiliki bisnis yang menjadi bagian dari kelas itu. Jika tidak ada metode lain bergantung pada variabel itu setelah Anda memindahkan metode, maka variabel itu juga harus pergi! Kemungkinan besar variabel harus pada objek itu sendiri dengan metode itu atau metode yang beroperasi di atasnya menjadi publik dan sedang disusun oleh kelas induk.
  4. Mengirimkan data ke berbagai fungsi seperti kelas Anda adalah program prosedural dengan variabel global hanya terbang di hadapan desain OO. Itu bukan maksud dari variabel anggota dan fungsi anggota (lihat di atas), dan kedengarannya seolah-olah kelas Anda tidak terlalu kohesif. Saya pikir ini adalah bau kode yang menyarankan cara yang lebih baik untuk mengelompokkan data dan metode Anda.
Colin Hunt
sumber
0

Anda hilang jika parameter ("argumen") adalah referensi atau hanya-baca.

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

Banyak pengembang, biasanya menetapkan langsung bidang internal variabel, dalam metode konstruktor. Ada beberapa pengecualian.

Ingat bahwa "setter" dapat memiliki metode tambahan, bukan hanya penetapan, dan kadang-kadang, bahkan metode virtual, atau memanggil metode virtual.

Catatan: Saya sarankan menjaga bidang internal properti sebagai "dilindungi".

umlcat
sumber