apa yang seharusnya menjadi posisi logger dalam daftar parameter [ditutup]

12

Dalam kode saya, saya menyuntikkan logger ke banyak kelas saya melalui daftar parameter konstruktor mereka

Saya perhatikan bahwa saya meletakkannya secara acak: kadang-kadang itu yang pertama dalam daftar, kadang-kadang terakhir, dan kadang-kadang di antaranya

Apakah Anda punya preferensi? Intuisi saya mengatakan bahwa konsistensi sangat membantu dalam kasus ini dan preferensi pribadi saya adalah mengutamakannya sehingga akan lebih mudah diperhatikan ketika hilang dan lebih mudah dilewati ketika ada.

Ezra
sumber

Jawaban:

31

Penebang adalah apa yang kita sebut "keprihatinan lintas sektoral." Mereka menghasilkan teknik seperti Pemrograman Berorientasi Aspek; jika Anda memiliki cara untuk menghias kelas Anda dengan atribut atau melakukan beberapa kode tenun, maka itu adalah cara yang baik untuk mendapatkan kemampuan logging sambil menjaga objek dan daftar parameter Anda "murni."

Satu-satunya alasan Anda mungkin ingin memasukkan logger adalah jika Anda ingin menentukan implementasi logging yang berbeda, tetapi sebagian besar kerangka kerja logging memiliki fleksibilitas untuk memungkinkan Anda mengonfigurasinya, katakanlah, untuk target logging yang berbeda. (file log, Windows Event Manager, dll.)

Karena alasan ini, saya lebih suka menjadikan logging sebagai bagian alami dari sistem, daripada menyerahkan logger ke setiap kelas untuk tujuan logging. Jadi yang biasanya saya lakukan adalah mereferensikan namespace logging yang sesuai, dan cukup gunakan logger di kelas saya.

Jika Anda masih ingin meneruskan logger, preferensi saya adalah Anda menjadikannya parameter terakhir dalam daftar parameter (menjadikannya parameter opsional, jika mungkin). Setelah itu menjadi parameter pertama tidak masuk akal; parameter pertama harus yang paling penting, yang paling relevan dengan operasi kelas.

Robert Harvey
sumber
11
+! jaga logger dari parm konstruktor; Saya lebih suka logger statis jadi saya tahu semua orang menggunakan hal yang sama
Steven A. Lowe
1
@ StevenA.Lowe Perhatikan bahwa menggunakan penebang statis dapat menyebabkan masalah lain jika Anda tidak berhati-hati (mis. Kegagalan pesanan inisialisasi statis dalam C ++). Saya setuju bahwa memiliki logger sebagai entitas yang dapat diakses secara global memiliki daya tariknya, tetapi harus dievaluasi secara hati-hati apakah dan bagaimana desain tersebut cocok dengan keseluruhan arsitektur.
ComicSansMS
@ComicSansMS: tentu saja, ditambah masalah threading dll. Hanya preferensi pribadi - "sesederhana mungkin, tetapi tidak sederhana";)
Steven A. Lowe
Memiliki pencatat statis dapat menjadi masalah. Itu membuat injeksi ketergantungan lebih sulit (kecuali jika Anda maksudkan logger tunggal yang dipakai oleh wadah DI Anda), dan jika Anda ingin mengubah arsitektur Anda, itu mungkin menyakitkan. Saat ini, misalnya, saya menggunakan fungsi Azure yang meneruskan logger sebagai parameter untuk setiap eksekusi fungsi, jadi saya menyesal melewati logger saya melalui konstruktor.
Slothario
@ Slothario: Itulah keindahan seorang pembalak statis. Tidak diperlukan injeksi apa pun.
Robert Harvey
7

Dalam bahasa dengan fungsi yang berlebihan, saya berpendapat bahwa semakin besar kemungkinan argumen menjadi opsional, semakin jauh seharusnya. Ini menciptakan konsistensi ketika Anda membuat kelebihan di mana mereka hilang:

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

Dalam bahasa fungsional, kebalikannya lebih berguna - semakin besar kemungkinan Anda untuk memilih beberapa default, semakin jauh seharusnya. Ini membuatnya lebih mudah untuk mengkhususkan fungsi dengan hanya menerapkan argumen padanya:

addThreeToList = map (+3)

Namun seperti yang disebutkan dalam jawaban lain, Anda mungkin tidak ingin secara eksplisit melewati logger dalam daftar argumen setiap kelas dalam sistem.

Doval
sumber
3

Anda pasti bisa menghabiskan banyak waktu untuk merekayasa masalah ini.

Untuk bahasa dengan implementasi logging kanonik, cukup instantiate kanger logger langsung di setiap kelas.

Untuk bahasa tanpa implementasi kanonik, cobalah untuk menemukan kerangka fasad logging dan patuhi itu. slf4j adalah pilihan yang baik di Jawa.

Secara pribadi saya lebih suka tetap berpegang pada implementasi logging beton tunggal dan mengirim semuanya ke syslog. Semua alat analisis log yang baik mampu menggabungkan log sysout dari beberapa server aplikasi ke dalam laporan yang komprehensif.

Ketika tanda tangan fungsi menyertakan satu atau dua layanan dependensi serta beberapa argumen "nyata", saya menempatkan dependensi terakhir:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

Karena sistem saya cenderung hanya memiliki lima atau lebih sedikit layanan seperti itu, saya selalu memastikan layanan dimasukkan dalam urutan yang sama di semua tanda tangan fungsi. Urutan alfabet sama baiknya dengan yang lainnya. (Selain itu: mempertahankan pendekatan metodologis ini untuk penanganan mutex juga akan mengurangi peluang Anda mengembangkan kebuntuan.)

Jika Anda mendapati diri Anda menyuntikkan lebih dari selusin ketergantungan di seluruh aplikasi Anda, maka sistem mungkin perlu dipecah menjadi beberapa subsistem yang terpisah (berani saya katakan layanan mikro?).

Christian Willman
sumber
Tampaknya aneh bagi saya untuk menggunakan injeksi properti untuk memanggil calculFooBarSum.
an phu
2

Penebang agak merupakan kasus khusus karena mereka harus tersedia secara harfiah di mana-mana.

Jika Anda telah memutuskan bahwa Anda ingin meneruskan logger ke konstruktor setiap kelas, maka Anda harus menetapkan konvensi yang konsisten untuk bagaimana Anda melakukan itu (misalnya, selalu parameter pertama, selalu lulus dengan referensi, daftar inisialisasi konstruktor selalu dimulai dengan m_logger (theLogger), dll). Apa pun yang akan digunakan di seluruh basis kode Anda akan mendapat manfaat dari konsistensi suatu hari nanti.

Atau, Anda bisa membuat setiap kelas instantiate objek logger mereka sendiri, tanpa perlu apa-apa untuk dilewatkan. Logger mungkin perlu mengetahui beberapa hal "dengan sihir" agar bisa berfungsi, tetapi meng-hardcoding sebuah filepath dalam definisi kelas berpotensi menjadi jauh lebih mudah dirawat dan tidak terlalu membosankan daripada meneruskannya dengan benar ke ratusan kelas yang berbeda, dan bisa dibilang jauh lebih sedikit jahat daripada menggunakan variabel global untuk memotong kata kebosanan. (Harus diakui, para penebang adalah satu dari sedikit kasus penggunaan yang sah untuk variabel global)

Ixrec
sumber
1

Saya setuju dengan mereka yang menyarankan bahwa logger harus diakses secara statis daripada diteruskan ke kelas. Namun jika ada alasan kuat Anda ingin lulus dalam (kasus mungkin berbeda ingin log ke lokasi yang berbeda atau sesuatu) maka saya akan menyarankan Anda tidak lulus itu menggunakan konstruktor melainkan membuat panggilan terpisah untuk melakukannya, misalnya Class* C = new C(); C->SetLogger(logger);lebih dariClass* C = new C(logger);

Alasan memilih metode ini adalah bahwa logger bukan bagian dasarnya dari kelas tetapi lebih merupakan fitur yang disuntikkan yang digunakan untuk tujuan lain. Menempatkannya di daftar konstruktor membuatnya menjadi persyaratan kelas dan menyiratkan itu adalah bagian dari keadaan logis aktual kelas. Adalah masuk akal untuk mengharapkan, misalnya (dengan sebagian besar kelas meskipun tidak semua), bahwa jika X != Ydemikian C(X) != C(Y)tetapi tidak mungkin bahwa Anda akan menguji ketimpangan penebang jika Anda membandingkan terlalu banyak instance dari kelas yang sama.

Jack Aidley
sumber
1
Tentu saja ini memiliki kelemahan bahwa logger tidak tersedia untuk konstruktor.
Ben Voigt
1
Saya sangat suka jawaban ini. Itu membuat logging menjadi perhatian kelas yang harus Anda perhatikan secara terpisah dari hanya menggunakannya. Kemungkinannya adalah jika Anda menambahkan logger ke konstruktor sejumlah besar kelas, Anda mungkin menggunakan injeksi dependensi. Saya tidak bisa berbicara untuk semua bahasa, tetapi saya tahu di C #, beberapa implementasi DI juga akan menyuntikkan langsung ke properti (pengambil / penyetel).
jpmc26
@ BenVoigt: Itu benar, dan itu mungkin menjadi alasan pembunuh untuk tidak melakukannya dengan cara ini, tetapi, biasanya, Anda dapat melakukan pencatatan yang akan Anda lakukan di konstruktor sebagai respons terhadap pencatat yang sedang diatur.
Jack Aidley
0

Perlu disebutkan, sesuatu yang saya belum melihat jawaban lain menyentuh di sini, adalah bahwa dengan membuat logger disuntikkan melalui properti atau statis, membuatnya sulit untuk menguji unit kelas. Misalnya, jika Anda membuat logger Anda disuntikkan melalui properti, Anda sekarang harus menyuntikkan logger itu setiap kali Anda menguji metode yang menggunakan logger. Ini berarti Anda mungkin harus menetapkannya sebagai dependensi konstruktor karena kelas memang membutuhkannya.

Statis cocok untuk masalah yang sama; jika logger tidak berfungsi, maka seluruh kelas Anda gagal (jika kelas Anda menggunakan logger) - meskipun logger belum tentu 'bagian' dari tanggung jawab kelas - meskipun tidak seburuk injeksi properti karena Anda setidaknya tahu bahwa logger selalu "ada" dalam arti tertentu.

Hanya beberapa makanan untuk dipikirkan, terutama jika Anda menggunakan TDD. Menurut pendapat saya, seorang logger seharusnya tidak menjadi bagian dari bagian kelas yang dapat diuji (saat Anda menguji kelas, Anda seharusnya tidak menguji logging Anda juga).

Dan Pantry
sumber
1
hmmm ... jadi Anda ingin kelas Anda untuk melakukan logging (logging harus dalam spesifikasi) tetapi Anda tidak ingin menguji menggunakan logger. Apakah ini mungkin? Saya pikir poin Anda adalah non-go. Jelas jika alat pengujian Anda rusak Anda tidak dapat menguji - untuk merancang dengan cara yang tidak bergantung pada alat pengujian adalah sedikit over-engineering IMHO
Hogan
1
Maksud saya adalah jika saya menggunakan konstruktor dan memanggil metode pada kelas dan itu masih gagal karena saya tidak menetapkan properti, maka desainer kelas telah salah mengerti konsep di balik konstruktor. Jika logger diperlukan oleh kelas, itu harus disuntikkan di konstruktor - itulah gunanya konstruktor.
Dan Pantry
umm .. tidak juga. Jika Anda menganggap bagian sistem Logging dari "kerangka kerja" maka tidak masuk akal sebagai bagian dari konstruktor. Tetapi ini telah dinyatakan dalam jawaban lain dengan jelas.
Hogan
1
Saya berdebat menentang injeksi properti. Saya tidak perlu mengadvokasi penggunaan injeksi di konstruktor. Saya hanya mengatakan bahwa menurut pendapat saya, itu lebih baik daripada injeksi properti.
Dan Pantry
"Apakah ini mungkin?" juga, ya, tenun IL adalah hal yang ada dan disebutkan di jawaban atas ... mono-project.com/docs/tools+libraries/mono.Cecil
Dan Pantry
0

Saya terlalu malas untuk membagikan objek logger ke setiap instance kelas. Jadi, dalam kode saya, hal-hal semacam ini duduk di bidang statis atau variabel thread-lokal di bidang statis. Yang terakhir ini agak keren dan memungkinkan Anda menggunakan logger yang berbeda untuk setiap utas dan memungkinkan Anda menambahkan metode untuk menghidupkan dan mematikan pencatatan yang melakukan sesuatu yang berarti dan diharapkan dalam aplikasi multi-utas.

Atsby
sumber