Apakah logging di sebelah implementasi merupakan pelanggaran SRP?

19

Ketika memikirkan pengembangan perangkat lunak yang gesit dan semua prinsip (SRP, OCP, ...) Saya bertanya pada diri sendiri bagaimana memperlakukan logging.

Apakah logging di sebelah implementasi merupakan pelanggaran SRP?

Saya akan mengatakan yeskarena implementasinya juga harus dapat berjalan tanpa logging. Jadi bagaimana saya bisa menerapkan logging dengan cara yang lebih baik? Saya telah memeriksa beberapa pola dan sampai pada kesimpulan bahwa cara terbaik untuk tidak melanggar prinsip-prinsip dengan cara yang ditentukan pengguna, tetapi menggunakan pola apa pun yang diketahui melanggar prinsip adalah dengan menggunakan pola dekorator.

Katakanlah kita memiliki banyak komponen tanpa pelanggaran SRP dan kemudian kita ingin menambahkan logging.

  • komponen A
  • komponen B menggunakan A

Kami ingin masuk untuk A, jadi kami membuat komponen lain D dihiasi dengan A keduanya mengimplementasikan antarmuka I.

  • antarmuka I
  • komponen L (komponen pencatatan sistem)
  • komponen A mengimplementasikan I
  • komponen D mengimplementasikan I, menghias / menggunakan A, menggunakan L untuk logging
  • komponen B menggunakan I

Keuntungan: - Saya dapat menggunakan A tanpa pencatatan - pengujian A berarti saya tidak memerlukan cemoohan pencatatan - pengujian lebih mudah

Kerugian: - lebih banyak komponen dan lebih banyak tes

Saya tahu ini sepertinya pertanyaan diskusi terbuka lain, tetapi saya sebenarnya ingin tahu apakah seseorang menggunakan strategi logging yang lebih baik daripada dekorator atau pelanggaran SRP. Bagaimana dengan statis singleton logger yang merupakan default NullLogger dan jika syslog-logging diinginkan, seseorang mengubah objek implementasi saat runtime?

Aitch
sumber
Saya sudah membacanya dan jawabannya tidak memuaskan, maaf.
Aitch
@ MarkRogers terima kasih telah berbagi artikel yang menarik itu. Paman Bob mengatakan dalam 'Kode Bersih', bahwa komponen SRP yang bagus berhadapan dengan komponen lain pada tingkat abstraksi yang sama. Bagi saya penjelasan itu lebih mudah dimengerti karena konteksnya juga bisa terlalu besar. Tapi saya tidak bisa menjawab pertanyaan, karena apa konteks atau tingkat abstraksi dari seorang penebang?
Aitch
3
"bukan jawaban untuk saya" atau "jawabannya tidak memuaskan" agak meremehkan. Anda mungkin merenungkan apa yang secara spesifik tidak memuaskan (persyaratan apa yang Anda miliki yang tidak dipenuhi oleh jawaban itu? Apa yang khusus tentang pertanyaan Anda?), Lalu edit pertanyaan Anda untuk memastikan bahwa persyaratan / aspek unik ini dijelaskan dengan jelas. Tujuannya adalah membuat Anda mengedit pertanyaan Anda untuk memperbaikinya agar lebih jelas dan lebih fokus, bukan untuk meminta boilerplate yang menyatakan bahwa pertanyaan Anda berbeda / tidak boleh ditutup tanpa alasan mengapa. (Anda juga dapat mengomentari jawaban yang lain.)
DW

Jawaban:

-1

Ya itu merupakan pelanggaran terhadap SRP karena penebangan merupakan masalah lintas sektoral.

Cara yang benar adalah dengan mendelegasikan logging ke kelas logger (Intersepsi) yang tujuan utamanya adalah untuk log - patuh oleh SRP.

Lihat tautan ini untuk contoh yang baik: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

Ini adalah contoh singkat :

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

Manfaatnya termasuk

  • Anda dapat menguji ini tanpa masuk - benar pengujian unit
  • Anda dapat dengan mudah beralih log on / off - bahkan saat runtime
  • Anda dapat mengganti logging untuk bentuk logging lainnya, tanpa harus mengubah file TenantStore.
z0mbi3
sumber
Terima kasih atas tautannya. Gambar 1 pada halaman itu sebenarnya adalah apa yang saya sebut solusi favorit saya. Daftar masalah lintas bidang (logging, caching, dll.) Dan pola dekorator adalah solusi paling umum dan saya senang tidak sepenuhnya salah dengan pikiran saya, meskipun komunitas yang lebih besar ingin membatalkan abstraksi dan inline logging tersebut. .
Aitch
2
Saya tidak melihat Anda menetapkan variabel _logger di mana pun. Apakah Anda berencana menggunakan injeksi konstruktor dan hanya lupa? Jika demikian, Anda mungkin akan mendapatkan peringatan kompiler.
user2023861
27
Alih-alih TenantStore yang DIPED dengan Logger tujuan umum, yang membutuhkan kelas N + 1 (ketika Anda menambahkan LandlordStore, FooStore, BarStore, dll.) Anda memiliki TenantStoreLogger yang DIPed dengan TenantStore, FooStoreLogger yang DIPed dengan FooStore , dll ... membutuhkan kelas 2N. Sejauh yang saya tahu, tanpa manfaat. Saat Anda ingin melakukan pengujian unit no-logging, Anda harus me-rejigger kelas N, bukan hanya mengkonfigurasi NullLogger. IMO, ini adalah pendekatan yang sangat buruk.
user949300
6
Melakukan hal ini untuk setiap kelas yang membutuhkan pencatatan secara drastis meningkatkan kompleksitas basis kode Anda (kecuali beberapa kelas memiliki pencatatan yang bahkan Anda tidak akan menyebutnya sebagai masalah lintas sektoral lagi). Ini pada akhirnya membuat kode kurang dapat dipelihara hanya karena banyaknya antarmuka yang harus dipelihara, yang bertentangan dengan semua yang menjadi prinsip tanggung jawab tunggal.
jpmc26
9
Diturunkan. Anda menghapus masalah logging dari kelas Tenant, tetapi sekarang Anda TenantStoreLoggerakan berubah setiap kali TenantStoreberubah. Anda tidak akan memisahkan kekhawatiran lebih dari pada solusi awal.
Laurent LA RIZZA
61

Saya akan mengatakan Anda mengambil SRP terlalu serius. Jika kode Anda cukup rapi bahwa penebangan adalah satu-satunya "pelanggaran" SRP maka Anda melakukan lebih baik dari 99% dari semua programmer lain, dan Anda harus menepuk punggung Anda sendiri.

Maksud SRP adalah untuk menghindari kode spageti yang mengerikan di mana kode yang melakukan hal-hal yang berbeda semuanya digabungkan menjadi satu. Mencampur logging dengan kode fungsional tidak membunyikan lonceng alarm untuk saya.

grahampark
sumber
19
@ Aitch: Pilihan Anda adalah melakukan hard-wire masuk ke kelas Anda, menyerahkan pegangan ke pencatat atau tidak mencatat apa pun. Jika Anda akan menjadi sangat ketat tentang SRP dengan mengorbankan segalanya, saya sarankan untuk tidak mencatat apa pun. Apa pun yang perlu Anda ketahui tentang apa yang dilakukan perangkat lunak Anda dapat ditemukan dengan debugger. P dalam SRP singkatan dari "prinsip," bukan "hukum alam yang tidak boleh dilanggar."
Blrfl
3
@ Aitch: Anda harus dapat melacak masuk kembali ke kelas Anda ke beberapa persyaratan, jika tidak Anda melanggar YAGNI. Jika logging ada di atas meja, Anda memberikan pegangan logger yang valid seperti yang Anda inginkan untuk apa pun yang dibutuhkan kelas, lebih baik satu dari kelas yang sudah lulus pengujian. Entah itu yang menghasilkan entri log aktual atau membuangnya ke dalam ember bit adalah masalah apa yang menjadi contoh instance kelas Anda; kelas itu sendiri seharusnya tidak peduli.
Blrfl
3
@Angka Untuk menjawab pertanyaan Anda tentang pengujian unit Do you mock the logger?:, itulah yang Anda lakukan. Anda harus memiliki ILoggerantarmuka yang mendefinisikan APA yang dilakukan oleh logger. Kode yang diuji akan disuntikkan dengan kode ILoggeryang Anda tentukan. Untuk pengujian, Anda harus class TestLogger : ILogger. Hal yang hebat tentang ini adalah TestLoggerdapat mengekspos hal-hal seperti string terakhir atau kesalahan yang dicatat. Tes dapat memverifikasi bahwa kode yang diuji masuk dengan benar. Sebagai contoh, sebuah tes bisa jadi UserSignInTimeGetsLogged(), di mana tes memeriksa TestLoggerlog.
CurtisHx
5
99% tampaknya agak rendah. Anda mungkin lebih baik dari 100% dari semua programmer.
Paul Draper
2
+1 untuk kewarasan. Kita membutuhkan lebih banyak pemikiran seperti ini: kurang fokus pada kata-kata dan prinsip-prinsip abstrak dan lebih fokus pada memiliki basis kode yang dapat dipelihara .
jpmc26
15

Tidak, ini bukan pelanggaran SRP.

Pesan yang Anda kirim ke log harus berubah karena alasan yang sama dengan kode di sekitarnya.

Apa yang merupakan pelanggaran terhadap SRP menggunakan perpustakaan khusus untuk masuk langsung ke dalam kode. Jika Anda memutuskan untuk mengubah cara logging, SRP menyatakan bahwa itu tidak akan berdampak pada kode bisnis Anda.

Beberapa jenis abstrak Loggerharus dapat diakses oleh kode implementasi Anda, dan satu-satunya hal implementasi Anda harus katakan adalah "Kirim pesan ini ke log", tanpa khawatir bagaimana cara melakukannya. Memutuskan cara penebangan yang tepat (bahkan cap waktu) bukanlah tanggung jawab implementasi Anda.

Implementasi Anda juga seharusnya tidak tahu apakah logger itu mengirim pesan ke adalah NullLogger.

Itu kata.

Saya tidak akan menyapu logging sebagai masalah lintas sektoral terlalu cepat . Memancarkan log untuk melacak peristiwa spesifik yang terjadi dalam kode implementasi Anda milik kode implementasi.

Apa yang menjadi perhatian lintas sektoral, OTOH, adalah penelusuran eksekusi : penebangan masuk dan keluar di setiap metode. AOP paling baik ditempatkan untuk melakukan ini.

Laurent LA RIZZA
sumber
Katakanlah pesan logger adalah 'pengguna login xyz', yang dikirim ke logger yang memuatkan stempel waktu dll. Apakah Anda tahu apa arti 'login' untuk implementasi? Apakah memulai sesi dengan cookie atau mekanisme lain? Saya pikir ada banyak cara yang berbeda untuk mengimplementasikan login, jadi mengubah implementasi secara logis tidak ada hubungannya dengan fakta bahwa pengguna login sebagai Login-muka dihiasi dengan logger yang sama.
Aitch
Tergantung apa arti pesan "login user xyz". Jika itu menandai fakta bahwa login berhasil, pengiriman pesan ke log termasuk dalam case use login. Cara khusus untuk merepresentasikan informasi login sebagai string (OAuth, Session, LDAP, NTLM, sidik jari, roda hamster) termasuk dalam kelas spesifik yang mewakili kredensial atau strategi login. Tidak perlu untuk menghapusnya. Kasus sepcific ini bukan masalah lintas sektoral. Ini khusus untuk kasus penggunaan login.
Laurent LA RIZZA
7

Karena penebangan sering dianggap sebagai masalah lintas sektoral, saya sarankan menggunakan AOP untuk memisahkan penebangan dari implementasi.

Bergantung pada bahasa yang Anda gunakan pencegat atau kerangka kerja AOP (misalnya AspectJ di Jawa) untuk melakukan ini.

Pertanyaannya adalah apakah ini benar-benar layak untuk direpotkan. Perhatikan bahwa pemisahan ini akan menambah kerumitan proyek Anda sambil memberikan sedikit manfaat.

Oliver Weiler
sumber
2
Sebagian besar kode AOP yang saya lihat adalah tentang mencatat setiap langkah masuk dan keluar dari setiap metode. Saya hanya ingin mencatat beberapa bagian logika bisnis. Jadi mungkin hanya mungkin untuk mencatat metode yang dianotasi, tetapi AOP sama sekali hanya bisa ada dalam bahasa scripting dan lingkungan mesin virtual, kan? Dalam contoh C ++ itu tidak mungkin. Saya akui bahwa saya tidak terlalu senang dengan pendekatan AOP, tetapi mungkin tidak ada solusi yang lebih bersih.
Aitch
1
@ Aitch. "C ++ tidak mungkin." : Jika Anda mencari "aop c ++" di Google, Anda akan menemukan artikel tentang itu. "... kode AOP yang saya lihat adalah tentang mencatat setiap langkah masuk dan keluar dari setiap metode. Saya hanya ingin mencatat beberapa bagian logika bisnis." Aop memungkinkan Anda menentukan pola untuk menemukan metode untuk memodifikasi. yaitu semua metode dari namespace "my.busininess. *"
k3b
1
Logging seringkali BUKAN merupakan masalah lintas sektoral, terutama ketika Anda ingin log Anda mengandung informasi yang menarik, yaitu lebih banyak informasi daripada yang terkandung dalam jejak stack pengecualian.
Laurent LA RIZZA
5

Ini kedengarannya bagus. Anda menggambarkan dekorator logging yang cukup standar. Kamu punya:

komponen L (komponen pencatatan sistem)

Ini memiliki satu tanggung jawab: mencatat informasi yang diberikan padanya.

komponen A mengimplementasikan I

Ini memiliki satu tanggung jawab: menyediakan implementasi antarmuka I (dengan asumsi saya sesuai dengan SRP, yaitu).

Ini adalah bagian penting:

komponen D mengimplementasikan I, menghias / menggunakan A, menggunakan L untuk logging

Ketika dinyatakan seperti itu, kedengarannya rumit, tetapi lihat seperti ini: Komponen D melakukan satu hal: menyatukan A dan L.

  • Komponen D tidak masuk; itu mendelegasikan itu ke L
  • Komponen D tidak mengimplementasikan saya sendiri; itu mendelegasikan itu ke A

Satu- satunya tanggung jawab yang dimiliki komponen D adalah memastikan bahwa L diberitahu ketika A digunakan. Implementasi A dan L keduanya di tempat lain. Ini sepenuhnya sesuai dengan SRP, serta menjadi contoh rapi dari OCP dan penggunaan dekorator yang lumrah.

Peringatan penting: ketika D menggunakan komponen logging Anda L, itu harus melakukannya dengan cara yang memungkinkan Anda mengubah cara Anda login. Cara paling sederhana untuk melakukan ini adalah memiliki antarmuka IL yang diterapkan oleh L. Lalu:

  • Komponen D menggunakan IL untuk mencatat; sebuah instance dari L disediakan
  • Komponen D menggunakan I untuk menyediakan fungsionalitas; sebuah instance dari A disediakan
  • Komponen B menggunakan I; sebuah instance dari D disediakan

Dengan begitu, tidak ada yang bergantung secara langsung pada hal lain, sehingga mudah untuk menukar mereka. Ini membuatnya mudah untuk beradaptasi dengan perubahan, dan mudah untuk mengejek bagian dari sistem sehingga Anda dapat menguji unit.

anaximander
sumber
Saya sebenarnya hanya tahu C # yang memiliki dukungan delegasi asli. Itu sebabnya saya menulis D implements I. Terima kasih atas jawaban Anda.
Aitch
1

Tentu saja itu merupakan pelanggaran SRP karena Anda memiliki masalah lintas sektoral. Namun Anda dapat membuat kelas yang bertanggung jawab untuk menyusun logging dengan mengeksekusi tindakan apa pun.

contoh:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}
Paul Nikonowicz
sumber
2
Diturunkan. Penebangan memang merupakan masalah lintas sektoral. Begitu juga urutan metode panggilan dalam kode Anda. Itu bukan alasan yang cukup untuk mengklaim pelanggaran SRP. Mencatat kejadian tertentu dalam aplikasi Anda BUKAN merupakan masalah lintas sektoral. CARA pesan-pesan ini dibawa ke pengguna yang berminat memang merupakan masalah yang terpisah, dan menggambarkan ini dalam kode implementasi ADALAH pelanggaran SRP.
Laurent LA RIZZA
"panggilan metode sekuensing" atau komposisi fungsional bukanlah masalah lintas sektoral melainkan detail implementasi. Tanggung jawab fungsi yang saya buat adalah untuk menyusun pernyataan log, dengan tindakan. Saya tidak perlu menggunakan kata "dan" untuk menggambarkan apa fungsi ini.
Paul Nikonowicz
Ini bukan detail implementasi. Ini memiliki efek mendalam pada bentuk kode Anda.
Laurent LA RIZZA
Saya pikir saya melihat SRP dari perspektif "APA fungsi ini lakukan" di mana ketika Anda melihat SRP dari perspektif "BAGAIMANA fungsi ini melakukannya".
Paul Nikonowicz