Praktik terbaik untuk menyampaikan banyak argumen ke metode?

95

Terkadang, kami harus menulis metode yang menerima banyak argumen, misalnya:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Ketika saya menghadapi masalah seperti ini, saya sering merangkum argumen ke dalam peta.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

Ini bukan praktik yang baik, merangkum parameter ke dalam peta benar-benar membuang-buang efisiensi. Hal baiknya adalah, tanda tangan yang bersih, mudah untuk menambahkan parameter lain dengan modifikasi paling sedikit. apa praktik terbaik untuk jenis masalah ini?

Penggergaji
sumber

Jawaban:

140

Di Java Efektif , Bab 7 (Metode), Butir 40 (Metode desain tanda tangan dengan hati-hati), Bloch menulis:

Ada tiga teknik untuk mempersingkat daftar parameter yang terlalu panjang:

  • memecah metode menjadi beberapa metode, yang masing-masing hanya memerlukan sebagian dari parameter
  • membuat kelas pembantu untuk menampung sekelompok parameter (biasanya kelas anggota statis)
  • mengadaptasi pola Builder dari konstruksi objek ke pemanggilan metode.

Untuk lebih jelasnya, saya mendorong Anda untuk membeli buku itu, itu sangat berharga.

JRL
sumber
Apa itu "parameter yang terlalu panjang"? Kapan kita dapat mengatakan bahwa suatu metode memiliki terlalu banyak parameter? Apakah ada angka atau rentang tertentu?
Red M
2
@RedM Saya selalu menganggap lebih dari 3 atau 4 parameter sebagai "terlalu panjang"
jtate
2
@jtate apakah itu pilihan pribadi atau apakah Anda mengikuti dokumen resmi?
Red M
2
@RedM preferensi pribadi :)
jtate
2
Dengan edisi ketiga dari Java Efektif, ini adalah bab 8 (metode), item 51
GarethOwen
71

Menggunakan peta dengan kunci String ajaib adalah ide yang buruk. Anda kehilangan pemeriksaan waktu kompilasi, dan sangat tidak jelas apa parameter yang diperlukan. Anda perlu menulis dokumentasi yang sangat lengkap untuk menebusnya. Akankah Anda ingat dalam beberapa minggu apa itu String tanpa melihat kodenya? Bagaimana jika Anda salah ketik? Gunakan tipe yang salah? Anda tidak akan mengetahuinya sampai Anda menjalankan kode.

Sebaliknya gunakan model. Buat kelas yang akan menjadi wadah untuk semua parameter tersebut. Dengan begitu Anda menjaga keamanan tipe Java. Anda juga dapat meneruskan objek itu ke metode lain, memasukkannya ke dalam koleksi, dll.

Tentu saja jika kumpulan parameter tidak digunakan di tempat lain atau disebarkan, model khusus mungkin berlebihan. Ada keseimbangan yang harus dicapai, jadi gunakan akal sehat.

tom
sumber
24

Jika Anda memiliki banyak parameter opsional, Anda dapat membuat API yang lancar: ganti metode tunggal dengan rangkaian metode

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Menggunakan impor statis Anda dapat membuat API fasih dalam:

... .datesBetween(from(date1).to(date2)) ...
Ha.
sumber
3
Bagaimana jika setiap parameter diperlukan, bukan opsional?
emeraldhieu
1
Anda juga dapat memiliki parameter default dengan cara ini. Selain itu, pola pembangun terkait dengan antarmuka yang lancar. Ini seharusnya menjadi jawabannya, saya pikir. Selain memecah konstruktor panjang menjadi metode inisialisasi yang lebih kecil yang bersifat opsional.
Ehtesh Choudhury
13

Ini disebut "Perkenalkan Objek Parameter". Jika Anda mendapati diri Anda meneruskan daftar parameter yang sama di beberapa tempat, cukup buat kelas yang menampung semuanya.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Meskipun Anda tidak terlalu sering menyampaikan daftar parameter yang sama, pemfaktoran ulang yang mudah itu masih akan meningkatkan keterbacaan kode Anda, yang selalu bagus. Jika Anda melihat kode Anda 3 bulan kemudian, akan lebih mudah untuk memahaminya ketika Anda perlu memperbaiki bug atau menambahkan fitur.

Ini adalah filosofi umum tentu saja, dan karena Anda belum memberikan detail apa pun, saya juga tidak dapat memberikan saran yang lebih detail. :-)

dimitarvp
sumber
apakah pengumpulan sampah akan menjadi masalah?
rupinderjeet
1
Tidak jika Anda membuat objek parameter lingkup lokal dalam fungsi pemanggil, dan jika Anda tidak memutasinya. Kemungkinan besar itu akan dikumpulkan dan memorinya digunakan kembali dengan cukup cepat dalam keadaan seperti itu.
dimitarvp
Imo, Anda juga harus memiliki yang XXXParameter param = new XXXParameter();tersedia, dan kemudian menggunakan XXXParameter.setObjA(objA); dll ...
satibel
10

Pertama, saya akan mencoba memfaktorkan ulang metode ini. Jika menggunakan banyak parameter, mungkin akan terlalu panjang. Memecahnya akan meningkatkan kode dan berpotensi mengurangi jumlah parameter untuk setiap metode. Anda mungkin juga dapat melakukan refaktorisasi seluruh operasi ke kelasnya sendiri. Kedua, saya akan mencari contoh lain di mana saya menggunakan yang sama (atau superset) dari daftar parameter yang sama. Jika Anda memiliki beberapa contoh, kemungkinan itu menandakan bahwa properti ini dimiliki bersama. Dalam hal ini, buat kelas untuk menampung parameter dan menggunakannya. Terakhir, saya akan mengevaluasi apakah jumlah parameter membuatnya layak untuk dibuat objek peta untuk meningkatkan keterbacaan kode. Saya pikir ini adalah panggilan pribadi - ada kesulitan masing-masing dengan solusi ini dan di mana titik trade-off mungkin berbeda. Untuk enam parameter saya mungkin tidak akan melakukannya. Untuk 10 saya mungkin akan (jika tidak ada metode lain yang berhasil terlebih dahulu).

tvanfosson.dll
sumber
8

Ini sering menjadi masalah saat membangun objek.

Dalam kasus tersebut, gunakan pola objek pembangun , ini berfungsi dengan baik jika Anda memiliki daftar parameter yang besar dan tidak selalu membutuhkan semuanya.

Anda juga dapat menyesuaikannya dengan pemanggilan metode.

Ini juga meningkatkan banyak keterbacaan.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

Anda juga bisa meletakkan logika verifikasi ke dalam metode set .. () dan build () Builder.

Bohdan
sumber
Apa yang akan Anda rekomendasikan jika banyak bidang Anda final? Ini adalah hal utama yang membuat saya tidak dapat menulis fungsi pembantu. Saya kira saya dapat membuat bidang menjadi pribadi dan memastikan saya tidak memodifikasinya dengan benar dalam kode kelas itu, tetapi saya berharap untuk sesuatu yang lebih elegan.
ragerdl
7

Ada pola yang disebut sebagai objek Parameter .

Idealnya adalah menggunakan satu objek sebagai pengganti semua parameter. Sekarang meskipun Anda perlu menambahkan parameter nanti, Anda hanya perlu menambahkannya ke objek. Antarmuka metode tetap sama.

Padmarag
sumber
5

Anda dapat membuat kelas untuk menyimpan data tersebut. Perlu cukup bermakna, tetapi jauh lebih baik daripada menggunakan peta (OMG).

Johannes Rudolph
sumber
Saya tidak berpikir itu perlu untuk membuat kelas untuk menampung parameter metode.
Sawyer
Saya hanya akan membuat kelas jika ada beberapa contoh melewati parameter yang sama. Ini akan menandakan bahwa parameter terkait dan mungkin tetap bersama. Jika Anda membuat kelas untuk satu metode, penyembuhannya mungkin lebih buruk daripada penyakitnya.
tvanfosson
Ya - Anda dapat memindahkan parameter terkait ke DTO atau Value Object. Apakah beberapa dari beberapa parameter opsional, yaitu metode utama kelebihan beban dengan parameter tambahan ini? Dalam kasus seperti itu - saya merasa itu dapat diterima.
JoseK
Itulah yang saya maksud dengan mengatakan harus cukup bermakna.
Johannes Rudolph
4

Code Complete * menyarankan beberapa hal:

  • "Batasi jumlah parameter rutin menjadi sekitar tujuh. Tujuh adalah angka ajaib untuk pemahaman orang" (p 108).
  • "Letakkan parameter dalam urutan input-ubah-output ... Jika beberapa rutinitas menggunakan parameter serupa, letakkan parameter serupa dalam urutan yang konsisten" (p 105).
  • Letakkan variabel status atau kesalahan terakhir.
  • Seperti yang disebutkan tvanfosson , berikan hanya bagian dari variabel terstruktur (objek) yang dibutuhkan rutin. Meskipun demikian, jika Anda menggunakan sebagian besar variabel terstruktur dalam fungsi tersebut, teruskan saja seluruh struktur, tetapi ketahuilah bahwa ini mendorong penggandengan sampai tingkat tertentu.

* Edisi Pertama, saya tahu saya harus memperbarui. Selain itu, nampaknya beberapa dari saran ini mungkin telah berubah sejak edisi kedua ditulis ketika OOP mulai menjadi lebih populer.

Justin Johnson
sumber
2

Praktik yang baik adalah refactor. Bagaimana dengan benda-benda ini yang berarti mereka harus diteruskan ke metode ini? Haruskah mereka dikemas menjadi satu objek?

GaryF
sumber
ya mereka harus. Misalnya, formulir penelusuran yang besar, memiliki banyak batasan dan kebutuhan penomoran halaman yang tidak terkait. Anda perlu meneruskan currentPageNumber, searchCriteria, pageSize ...
Sawyer
2

Menggunakan Peta adalah cara sederhana untuk membersihkan tanda panggilan tetapi Anda memiliki masalah lain. Anda perlu melihat ke dalam tubuh metode untuk melihat apa yang diharapkan metode di Peta itu, apa nama kunci atau jenis nilai yang dimiliki.

Cara yang lebih bersih adalah dengan mengelompokkan semua parameter dalam objek bean tetapi itu masih tidak memperbaiki masalah sepenuhnya.

Apa yang Anda miliki di sini adalah masalah desain. Dengan lebih dari 7 parameter ke sebuah metode, Anda akan mulai memiliki masalah dalam mengingat apa yang mereka wakili dan urutan apa yang mereka miliki. Dari sini Anda akan mendapatkan banyak bug hanya dengan memanggil metode dengan urutan parameter yang salah.

Anda memerlukan desain aplikasi yang lebih baik, bukan praktik terbaik untuk mengirim banyak parameter.


sumber
1

Buat kelas kacang, dan setel semua parameter (metode penyetel) dan teruskan objek kacang ini ke metode.

Tony
sumber
1
  • Lihat kode Anda, dan lihat mengapa semua parameter itu diteruskan. Kadang-kadang dimungkinkan untuk memfaktor ulang metode itu sendiri.

  • Menggunakan peta membuat metode Anda rentan. Bagaimana jika seseorang yang menggunakan metode Anda salah mengeja nama parameter, atau memposting string di mana metode Anda mengharapkan UDT?

  • Tentukan Objek Transfer . Ini akan memberi Anda pemeriksaan jenis paling tidak; bahkan mungkin bagi Anda untuk melakukan beberapa validasi pada titik penggunaan alih-alih dalam metode Anda.

Semua orang
sumber
0

Ini sering kali merupakan indikasi bahwa kelas Anda memiliki lebih dari satu tanggung jawab (yaitu, kelas Anda melakukan JUGA banyak).

Lihat Prinsip Tanggung Jawab Tunggal

untuk keterangan lebih lanjut.

metode pembantu
sumber
0

Jika Anda memberikan terlalu banyak parameter, coba lakukan refaktorisasi metode. Mungkin ia melakukan banyak hal yang seharusnya tidak dilakukan. Jika bukan itu masalahnya, coba gantikan parameter dengan satu kelas. Dengan cara ini Anda dapat merangkum semuanya dalam satu instance kelas dan meneruskan instance tersebut dan bukan parameternya.

azamsharp.dll
sumber
0

Saya akan mengatakan tetap dengan cara Anda melakukannya sebelumnya. Jumlah parameter dalam contoh Anda tidak banyak, tetapi alternatifnya jauh lebih mengerikan.

  1. Peta - Ada hal efisiensi yang Anda sebutkan, tetapi masalah yang lebih besar di sini adalah:

    • Penelepon tidak tahu apa yang harus dikirimkan kepada Anda tanpa mengacu pada sesuatu yang
      lain ... Apakah Anda memiliki javadocs yang menyatakan dengan tepat kunci dan
      nilai apa yang digunakan? Jika Anda melakukannya (yang bagus), maka memiliki banyak parameter juga tidak menjadi masalah.
    • Menjadi sangat sulit untuk menerima tipe argumen yang berbeda. Anda dapat membatasi parameter input ke satu jenis, atau menggunakan Map <String, Object> dan memasukkan semua nilai. Kedua opsi tersebut seringkali mengerikan.
  2. Objek pembungkus - ini hanya memindahkan masalah karena Anda perlu mengisi objek pembungkus di tempat pertama - alih-alih langsung ke metode Anda, ini akan menjadi konstruktor dari objek parameter. Untuk menentukan apakah memindahkan masalah itu tepat atau tidak tergantung pada penggunaan kembali benda tersebut. Contohnya:

Tidak akan menggunakannya: Ini hanya akan digunakan sekali pada panggilan pertama, jadi banyak kode tambahan untuk menangani 1 baris ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Dapat menggunakannya: Di sini, dapat melakukan sedikit lebih banyak. Pertama, ini dapat memfaktorkan parameter untuk 3 panggilan metode. itu juga dapat melakukan 2 baris lain dalam dirinya sendiri ... sehingga menjadi variabel keadaan dalam arti ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Pola pembangun - ini adalah anti-pola menurut saya. Mekanisme penanganan kesalahan yang paling diinginkan adalah mendeteksi lebih awal, bukan nanti; tetapi dengan pola pembangun, panggilan yang hilang (programmer tidak berpikir untuk memasukkannya) parameter wajib dipindahkan dari waktu kompilasi ke waktu berjalan. Tentu saja jika programmer dengan sengaja memasukkan null atau semacamnya ke dalam slot, itu akan menjadi runtime, tetapi masih menemukan beberapa kesalahan sebelumnya adalah keuntungan yang jauh lebih besar untuk melayani programmer yang menolak untuk melihat nama parameter dari metode yang mereka panggil. Saya merasa itu hanya cocok ketika berhadapan dengan sejumlah besar parameter opsional , dan bahkan kemudian, manfaatnya paling sedikit. Saya sangat menentang "pola" pembangun.

Hal lain yang orang lupa pertimbangkan adalah peran IDE dalam semua ini. Ketika metode memiliki parameter, IDE menghasilkan sebagian besar kode untuk Anda, dan Anda memiliki garis merah yang mengingatkan Anda apa yang perlu Anda berikan / setel. Saat menggunakan opsi 3 ... Anda kehilangan ini sepenuhnya. Sekarang terserah programmer untuk melakukannya dengan benar, dan tidak ada isyarat selama pengkodean dan waktu kompilasi ... programmer harus mengujinya untuk mengetahuinya.

Selain itu, opsi 2 dan 3, jika diadopsi dengan penyebaran yang tidak perlu, memiliki implikasi negatif jangka panjang dalam hal pemeliharaan karena banyaknya kode duplikat yang dihasilkannya. Semakin banyak kode yang ada, semakin banyak yang harus dipelihara, semakin banyak waktu dan uang yang dihabiskan untuk memeliharanya.

John
sumber