Bir uygulamanın yanında günlüğe kaydetme bir SRP ihlali mi?


19

Çevik yazılım geliştirme ve tüm ilkeleri (SRP, OCP, ...) düşünürken kendime günlük kaydını nasıl tedavi edeceğimi soruyorum.

Bir uygulamanın yanında günlüğe kaydetme bir SRP ihlali mi?

yesUygulamanın günlüğe kaydetmeden de çalışabilmesi gerektiği için söyleyebilirim . Peki günlüğü nasıl daha iyi uygulayabilirim? Bazı desenleri kontrol ettim ve ilkeleri kullanıcı tanımlı bir şekilde ihlal etmemenin en iyi yolunun, prensibi ihlal ettiği bilinen herhangi bir deseni kullanmak, bir dekoratör deseni kullanmak olduğu sonucuna vardım.

Diyelim ki SRP ihlali olmadan bir sürü bileşenimiz var ve daha sonra günlük eklemek istiyoruz.

  • bileşen A
  • B bileşeni A kullanır

A için günlüğe kaydetmeyi istiyoruz, bu yüzden A ile süslenmiş başka bir D bileşeni oluşturuyoruz.

  • arayüz I
  • bileşen L (sistemin kayıt bileşeni)
  • A bileşeni uygular I
  • D bileşeni I'yi uygular, A'yı dekore eder / kullanır, günlüğe kaydetme için L kullanır
  • B bileşeni bir I kullanır

Avantajları: - A'yı günlük kaydı olmadan kullanabilirim - test A Günlük kaydı alayına ihtiyacım olmadığı anlamına gelir - testler daha basittir

Dezavantaj: - daha fazla bileşen ve daha fazla test

Bunun başka bir açık tartışma sorusu olduğunu biliyorum, ama aslında birisinin bir dekoratör veya SRP ihlali yerine daha iyi kayıt stratejileri kullanıp kullanmadığını bilmek istiyorum. Varsayılan NullLogger gibi statik tekli logger ve syslog-logging isteniyorsa, çalışma zamanında uygulama nesnesini değiştirmeye ne dersiniz?



Zaten okudum ve cevap tatmin edici değil, üzgünüm.
Aitch


@MarkRogers bu ilginç makaleyi paylaştığınız için teşekkür ederiz. Bob Amca 'Temiz Kod'da, güzel bir SRP bileşeninin aynı soyutlama seviyesindeki diğer bileşenlerle uğraştığını söylüyor. Benim için açıklamanın anlaşılması daha kolay çünkü bağlam da çok büyük olabilir. Ama soruyu cevaplayamıyorum, çünkü bir kaydedicinin bağlamı veya soyutlama seviyesi nedir?
Aitch

3
"bana bir cevap değil" veya "cevap tatmin edici değil" biraz küçümseyici. Sen düşünmek olabilir neyi özellikle tatmin edici değil (bunu özellikle sorunuzun hakkında benzersiz ne Bu cevap? Tarafından karşılandı değildi var ne gereksinimi?), O zaman bu gereklilik / benzersiz yönü açıkça izah emin olmak için sorunuzu düzenleyin. Amaç, sorunuzu daha net ve daha odaklanmış hale getirmek için sorunuzu düzenlemenizi sağlamak, sorunuzun farklı olduğunu / neden gerekmeksizin kapatılmaması gerektiğini iddia eden kayık levhasını istememenizdir. (Diğer yanıta da yorum yapabilirsiniz.)
DW

Yanıtlar:


-1

Evet , günlüğe kaydetme çapraz bir sorun olduğundan SRP'nin ihlalidir .

Doğru yol, günlüğe kaydetmeyi tek amaç SRP tarafından kaydedilmek olan bir logger sınıfına (Interception) devretmektir .

İyi bir örnek için bu bağlantıya bakın: https://msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

İşte olan kısa bir örnek :

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);
    }
}

Yararları

  • Bunu giriş yapmadan test edebilirsiniz - gerçek birim testi
  • günlükte bile kolayca açıp kapatabilirsiniz
  • TenantStore dosyasını değiştirmek zorunda kalmadan günlüğe kaydetmenin diğer günlük biçimleri yerine kullanabilirsiniz.

Güzel bağlantı için teşekkürler. Bu sayfadaki Şekil 1 aslında benim en sevdiğim çözüm olarak adlandırdığım şey. Çapraz kesim endişeleri (günlük kaydı, önbellekleme, vb.) Ve bir dekoratör deseni en genel çözümdür ve düşüncelerimle tamamen yanlış olmamaktan mutluluk duyuyorum, ancak daha büyük topluluk bu soyutlamayı ve satır içi günlük kaydını bırakmak istiyor .
Aitch

2
_Logger değişkenini hiçbir yere atadığınızı görmüyorum. Yapıcı enjeksiyon kullanmayı planladınız mı ve yeni mi unuttun? Öyleyse, derleyici uyarısı alırsınız.
Kullanıcı2023861

27
TenantStore'un N + 1 sınıfları gerektiren bir genel amaçlı Logger ile DIPed olması yerine (bir LandlordStore, bir FooStore, BarStore vb. Eklediğinizde) bir TenantStoreLogger, bir FoantStore ile DIPed, bir FooStore ile DIPed vb. 2N sınıfları gerektirir. Anlayabildiğim kadarıyla, sıfır fayda için. Günlük kaydı olmayan birim sınaması yapmak istediğinizde, yalnızca bir NullLogger yapılandırmak yerine N sınıflarını yeniden yapılandırmanız gerekir. IMO, bu çok zayıf bir yaklaşım.
user949300

6
Günlük kaydı gerektiren her bir sınıf için bunu yapmak, kod tabanınızın karmaşıklığını artırır (bu kadar az sınıfın artık çapraz kesişme sorunu olarak adlandırmayacağınız günlük kaydı yoksa). Sonuçta , Tek Sorumluluk İlkesi için yaratılan her şeye aykırı olan bakımı yapılacak çok sayıda arabirim nedeniyle kodu daha az bakım yapılabilir hale getirir .
jpmc26

9
Downvoted. Günlük kaygısını Kiracı sınıfından çıkardınız, ancak şimdi TenantStoreLoggerher TenantStoredeğişiklik yaptığınızda değişeceksiniz. Endişeleri ilk çözümdekinden daha fazla ayırmıyorsunuz.
Laurent LA RIZZA

62

SRP'yi çok ciddiye aldığınızı söyleyebilirim. Eğer kodunuz SRP'nin tek "ihlali" olacak kadar düzenli ise, o zaman diğer tüm programcıların% 99'undan daha iyi bir performansa sahip olursunuz.

SRP'nin amacı, farklı şeyler yapan kodun hep birlikte karıştığı korkunç spagetti kodundan kaçınmaktır. Günlüğe kaydetmeyi işlevsel kodla karıştırmak benim için alarm zili çalmaz.


20
@Aitch: Seçimleriniz günlüğe kaydetmeyi sınıfınıza zorla bağlamak, bir günlükçüye bir tutamacı aktarmak veya hiçbir şey kaydetmemek. SRP hakkında her şey pahasına ultra katı olacaksanız, hiçbir şey kaydetmemenizi tavsiye ederim. Yazılımınızın ne yaptığı hakkında bilmeniz gerekenler, bir hata ayıklayıcı ile erişilebilir. SRP'deki P, "asla kırılmaması gereken fiziksel doğa kanunu" değil "prensibi" anlamına gelir.
Blrfl

3
@Aitch: Sınıfınızdaki günlüğe kaydetmeyi bazı gereksinimlere göre izleyebilmeniz gerekir, aksi takdirde YAGNI'yı ihlal edersiniz. Günlüğe kaydetme tablodaysa, sınıfın ihtiyaç duyduğu her şey için geçerli bir günlükçü tanıtıcısı sağlarsınız, tercihen sınamayı geçmiş bir sınıftan. Gerçek günlük girdileri üreten veya bunları bit kovasına dökerken, sınıfınızın örneğini neyin başlatan kaygısıdır; sınıfın kendisi umursamamalı.
Blrfl

3
@Aitch Birim testi ile ilgili sorunuza cevap vermek için: Do you mock the logger?HASSAS ŞEKİLDE yaptığınız şey budur. ILoggerKaydedicinin ne yaptığını tanımlayan bir arabiriminiz olmalıdır . Test edilen koda ILoggerbelirttiğiniz bir kod eklenir. Test için var class TestLogger : ILogger. Bununla ilgili en iyi şey, TestLoggerkaydedilen son dize veya hata gibi şeyleri açığa çıkarabilmesidir. Testler, test edilen kodun doğru şekilde günlüğe kaydedildiğini doğrulayabilir. Örneğin, testin günlüğe kaydedildiğini UserSignInTimeGetsLogged()kontrol ettiği bir test olabilir TestLogger.
CurtisHx

5
% 99 biraz düşük görünüyor. Muhtemelen tüm programcıların% 100'ünden daha iyisinizdir.
Paul Draper

2
Akıl sağlığı için +1. Bu tür düşünmeye daha çok ihtiyacımız var: kelimelere ve soyut ilkelere daha az , sürdürülebilir kod tabanına daha fazla odaklanın .
jpmc26

15

Hayır, SRP'nin ihlali değildir.

Günlüğe gönderdiğiniz iletiler, çevresindeki kodla aynı nedenlerle değişmelidir.

SRP'nin ihlali, doğrudan kodda oturum açmak için belirli bir kitaplık kullanmaktır. Oturum açma yöntemini değiştirmeye karar verirseniz, SRP bunun iş kodunuzu etkilememesi gerektiğini belirtir.

Bir tür özetin Loggeruygulama kodunuz tarafından erişilebilir olması gerekir ve uygulamanızın söylemesi gereken tek şey, "Bu iletiyi günlüğe gönder" dir ve nasıl yapıldığından endişe duymazsınız. Tam günlüğe kaydetme yöntemine (zaman damgası bile) karar vermek uygulamanızın sorumluluğu değildir.

Uygulamanız, mesaj gönderdiği kaydedicinin a NullLogger.

Bahsedilen.

Kesişen bir endişe olarak çok hızlı bir şekilde günlüğe kaydetmeyi fırçalamam . Uygulama kodunuzda meydana gelen belirli olayları izlemek için günlük yayan uygulama koduna aittir.

Kesişen bir sorun olan OTOH, yürütme izlemesidir : günlük tutma her yönteme girer ve çıkar. AOP bunu yapmak için en iyi yerdir.


Kaydedici mesajının, zaman damgası vb. Ekleyen bir günlükçüye gönderilen 'login user xyz' olduğunu varsayalım. 'Giriş' uygulamasının ne anlama geldiğini biliyor musunuz? Bir çerez veya başka bir mekanizma ile oturum başlatıyor mu? Bir oturum açma uygulamak için birçok farklı yol olduğunu düşünüyorum, bu nedenle uygulamayı değiştirmek bir kullanıcının oturum açtığı gerçeği ile mantıklı bir şey yoktur. Bu aynı şeyi yapan farklı bileşenleri (örneğin OAuthLogin, SessionLogin, BasicAuthorizationLogin) dekorasyon başka bir harika örnek Loginaynı logger ile dekore edilmiş bir arayüz olarak .
15'te Aitch

Bu "login user xyz" mesajının ne anlama geldiğine bağlıdır. Bir oturum açma işleminin başarılı olduğunu belirtirse, iletinin günlüğe gönderilmesi oturum açma kullanım durumuna aittir. Giriş bilgilerini bir dize (OAuth, Session, LDAP, NTLM, parmak izi, hamster tekerleği) olarak temsil etmenin özel yolu, kimlik bilgilerini veya giriş stratejisini temsil eden belirli sınıfa aittir. Sökmek için zorlayıcı bir ihtiyaç yoktur. Bu özel durum kesişen bir konu değildir. Oturum açma kullanım durumuna özgüdür.
Laurent LA RIZZA

7

Günlüğe kaydetme genellikle kesişen bir endişe olarak görüldüğünden, günlüğe kaydetmeyi uygulamadan ayırmak için AOP kullanmanızı öneririm.

Dile bağlı olarak, bunu gerçekleştirmek için bir önleyici veya bazı AOP çerçevesi (örn. Java'daki AspectJ) kullanırsınız.

Soru, bunun gerçekten güçlük çekmeye değip değmeyeceği. Bu ayrımın çok az fayda sağlarken projenizin karmaşıklığını artıracağını unutmayın.


2
Gördüğüm AOP kodunun çoğu, her yöntemin her giriş ve çıkış adımını günlüğe kaydetmeyle ilgiliydi. Sadece bazı iş mantığı bölümlerini kaydetmek istiyorum. Bu nedenle, yalnızca açıklamalı yöntemleri günlüğe kaydetmek mümkündür, ancak AOP yalnızca komut dosyası dillerinde ve sanal makine ortamlarında var olabilir, değil mi? Örneğin C ++ 'da imkansız. AOP yaklaşımlarından çok memnun olmadığımı itiraf ediyorum, ancak belki daha temiz bir çözüm yoktur.
Aitch

1
@Aitch. "C ++ imkansız." : "Aop c ++" için google'ı bu konuda makaleler bulacaksınız. “... gördüğüm AOP kodu, her yöntemin her giriş ve çıkış adımını günlüğe kaydetmeyle ilgiliydi. Sadece bazı iş mantığı bölümlerini günlüğe kaydetmek istiyorum.” Aop, değiştirilecek yöntemleri bulmak için kalıpları tanımlamanızı sağlar. ad alanından tüm yöntemleri yani "my.busininess *."
k3b

1
Günlüğe kaydetme genellikle kesişen bir sorun DEĞİLDİR, özellikle günlüğünüzün ilginç bilgiler içermesini istediğinizde, yani bir istisna yığını izinde bulunandan daha fazla bilgi.
Laurent LA RIZZA

5

Kulağa hoş geliyor. Oldukça standart bir günlük dekoratörünü tarif ediyorsunuz. Var:

bileşen L (sistemin kayıt bileşeni)

Bunun bir sorumluluğu vardır: kendisine iletilen bilgilerin günlüğe kaydedilmesi.

A bileşeni uygular I

Bunun bir sorumluluğu vardır: arayüz I'in uygulanmasını sağlamak (I'nin SRP uyumlu olduğu varsayılırsa, yani).

Bu çok önemli kısım:

D bileşeni I'yi uygular, A'yı dekore eder / kullanır, günlüğe kaydetme için L kullanır

Bu şekilde ifade edildiğinde, kulağa karmaşık geliyor, ama şu şekilde bakın: D bileşeni bir şey yapar : A ve L'yi bir araya getirmek.

  • D bileşeni günlüğe kaydetmiyor; bunu L'ye devrediyor
  • D bileşeni I'in kendisini uygulamıyor; bunu A'ya delege eder

Sadece bileşen D sahip olduğu sorumluluk emin bir kullanıldığında L bildirilir olduğunu yapmaktır. A ve L'nin her ikisi de başka yerlerdedir. Bu tamamen SRP uyumludur, yanı sıra düzgün bir OCP örneği ve oldukça yaygın bir dekoratör kullanımıdır.

Önemli bir uyarı: D senin günlüğü bileşeni L kullandığında, bu değiştirmek sağlayan bir şekilde böyle yapmalı nasıl sen günlüğü. Bunu yapmanın en basit yolu, L tarafından uygulanan bir arabirim IL'ye sahip olmaktır.

  • Bileşen D, oturum açmak için bir IL kullanır; L örneği sağlanmıştır
  • Bileşen D, işlevsellik sağlamak için bir I kullanır; A örneği sağlanır
  • B bileşeni bir I kullanır; D örneği sağlanmıştır

Bu şekilde, hiçbir şey doğrudan başka bir şeye bağlı değildir, bu da onları değiştirmeyi kolaylaştırır. Bu, üniteyi test edebilmeniz için değişikliğe adapte olmayı ve sistemin parçalarını taklit etmeyi kolaylaştırır.


Aslında sadece yerel delegasyon desteği olan C # biliyorum. Bu yüzden yazdım D implements I. Cevabınız için teşekkür ederim.
Aitch

1

Elbette, çapraz kesişme endişeniz olduğu için SRP'nin ihlali. Bununla birlikte, herhangi bir eylemin yürütülmesiyle günlük kaydını oluşturmaktan sorumlu bir sınıf oluşturabilirsiniz.

misal:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}

2
Downvoted. Günlüğe kaydetme gerçekten de kesişen bir konudur. Kodlamada sıralama yöntemi çağrıları da öyle. Bu, SRP'yi ihlal etmek için yeterli bir neden değil. Uygulamanızda belirli bir olayın gerçekleştiğinin günlüğe kaydedilmesi kesişen bir sorun DEĞİLDİR. Bu mesajların herhangi bir ilgilenen kullanıcıya taşındığı yol gerçekten ayrı bir endişe kaynağıdır ve bunu uygulama kodunda tanımlamak SRP'nin ihlalidir.
Laurent LA RIZZA

"sekanslama yöntemi çağrıları" veya fonksiyonel bileşim bir kesişen konu değil, bir uygulama detayıdır. Oluşturduğum işlevin sorumluluğu, bir eylemle birlikte bir günlük ifadesi oluşturmaktır. Bu işlevin ne yaptığını açıklamak için "ve" kelimesini kullanmama gerek yok.
Paul Nikonowicz

Bu bir uygulama detayı değil. Kodunuzun şekli üzerinde derin bir etkisi vardır.
Laurent LA RIZZA

SRP'ye "Bu işlev NE YAPAR" perspektifinden "Bu işlev NE YAPAR" perspektifinden baktığımı düşünüyorum.
Paul Nikonowicz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.