Yinelenen kodu kaldırmak için karmaşıklık ekleme


24

Hepsi genel bir temel sınıftan miras kalan birkaç sınıfım var. Temel sınıf, birkaç tür nesneden oluşan bir koleksiyon içerir T.

Her alt sınıf, nesne koleksiyonundan enterpolasyonlu değerleri hesaplayabilmelidir, ancak alt sınıflar farklı türleri kullandığından, hesaplama sınıftan sınıfa küçük bir miktar değiştirir.

Şimdiye kadar kodumu sınıftan sınıfa kopyaladım / yapıştırdım ve her birinde küçük değişiklikler yaptım. Ama şimdi kopyalanan kodu kaldırmaya ve onu temel sınıfımdaki tek genel enterpolasyon yöntemiyle değiştirmeye çalışıyorum. Ancak bunun çok zor olduğu kanıtlandı ve düşündüğüm tüm çözümler çok karmaşık görünüyor.

DRY ilkesinin bu tür durumlarda geçerli olmadığını, küfür gibi göründüğünü düşünmeye başladım. Kod çoğaltmayı kaldırmaya çalışırken ne kadar karmaşıklık var?

DÜZENLE:

Bulabileceğim en iyi çözüm şunun gibi gider:

Temel Sınıf:

protected T GetInterpolated(int frame)
{
    var index = SortedFrames.BinarySearch(frame);
    if (index >= 0)
        return Data[index];

    index = ~index;

    if (index == 0)
        return Data[index];
    if (index >= Data.Count)
        return Data[Data.Count - 1];

    return GetInterpolatedItem(frame, Data[index - 1], Data[index]);
}

protected abstract T GetInterpolatedItem(int frame, T lower, T upper);

Çocuk sınıfı A:

public IGpsCoordinate GetInterpolatedCoord(int frame)
{
    ReadData();
    return GetInterpolated(frame);
}

protected override IGpsCoordinate GetInterpolatedItem(int frame, IGpsCoordinate lower, IGpsCoordinate upper)
{
    double ratio = GetInterpolationRatio(frame, lower.Frame, upper.Frame);

    var x = GetInterpolatedValue(lower.X, upper.X, ratio);
    var y = GetInterpolatedValue(lower.Y, upper.Y, ratio);
    var z = GetInterpolatedValue(lower.Z, upper.Z, ratio);

    return new GpsCoordinate(frame, x, y, z);
}

B sınıfı çocuk:

public double GetMph(int frame)
{
    ReadData();
    return GetInterpolated(frame).MilesPerHour;
}

protected override ISpeed GetInterpolatedItem(int frame, ISpeed lower, ISpeed upper)
{
    var ratio = GetInterpolationRatio(frame, lower.Frame, upper.Frame);
    var mph = GetInterpolatedValue(lower.MilesPerHour, upper.MilesPerHour, ratio);
    return new Speed(frame, mph);
}

9
DRY ve Code Reuse gibi kavramların aşırı mikro uygulaması çok daha büyük günahlara yol açıyor.
Affe

1
Bazı iyi genel cevaplar alıyorsun. Örnek bir işlevi dahil etmek için düzenleme, bu özel örnekte çok ileri götürüp götürmeyeceğinizi belirlememize yardımcı olabilir.
Karl Bielefeldt

Bu daha bir gözlem, gerçekten bir cevap değildir: kolayca faktöre edilmiş aşımı taban sınıfı ne anlatmak yapamıyorsanız yapar , bu iyi olabilir tane değil. Buna bakmanın bir başka yolu (SOLID ile tanıştığınızı sanıyorum?) 'Bu işlevselliğin olası herhangi bir tüketicisi Liskov değiştirme gerektiriyor mu?' Genelleştirilmiş bir enterpolasyon işlevselliği tüketicisi için muhtemel bir iş vakası yoksa, bir temel sınıfın değeri yoktur.
Tom W

1
İlk şey, X, Y, Z üçlüsünü bir Pozisyon tipine toplamak ve bu tipe üye veya belki statik bir yöntem olarak enterpolasyon eklemek: Pozisyon interpolatı (Başka pozisyon, oran).
kevin cline

Yanıtlar:


30

Bir şekilde, son paragraftaki bu açıklama ile kendi sorunuzu cevapladınız:

DRY ilkesinin bu tür durumlarda geçerli olmadığını, küfür gibi göründüğünü düşünmeye başladım .

Ne zaman bir problemi çözmek için gerçekten pratik olmayan bir uygulama bulursanız, o uygulamayı dini olarak kullanmaya çalışmayın ( küfür kelimesi bunun için bir tür uyarıdır). Çoğu uygulamanın kendileri ve nedenleri vardır ve tüm olası davaların% 99'unu karşılatsalar bile, farklı bir yaklaşıma ihtiyaç duyabileceğiniz% 1'inde hala var.

Spesifik olarak, DRY ile ilgili olarak , bazen bakıldığında hasta hissetmenize neden olan devasa bir canavarlıktan daha birkaç kopya, basit koddan bile daha iyi olduğunu buldum .

Olduğu söyleniyor, bu kenar durumlarda varoluş özensiz kopyala ve yapıştır kodlama veya tamamen yeniden kullanılabilir modül eksikliği için bir bahane olarak kullanılmamalıdır. Basitçe, bazı dillerde bazı problemler için hem genel hem de okunabilir bir kodun nasıl yazılacağına dair bir fikriniz yoksa, fazlalık sahibi olmak muhtemelen daha az kötüdür . Kodunu kimin tutması gerektiğini düşün. Artıklık veya şaşırtmaca ile daha kolay yaşarlar mı?

Özel bir örnek hakkında daha özel bir tavsiye. Bu hesaplamaların benzer fakat biraz farklı olduğunu söylediniz . Hesaplama formülünüzü daha küçük alt formlara bölmeyi denemek isteyebilir ve daha sonra tüm farklı hesaplamalarınızın alt hesaplamaları yapmak için bu yardımcı işlevleri çağırmasını isteyebilirsiniz. Her hesaplamanın aşırı genelleştirilmiş bir koda bağlı olduğu durumdan kaçınırdınız ve yine de bir miktar yeniden kullanım seviyesine sahip olursunuz.


10
Benzer ancak biraz farklı olan bir başka nokta da, kodda benzer görünseler bile, "iş" ile aynı olması gerektiği anlamına gelmez. Ne olduğuna bağlı olarak elbette, ancak bazen işleri ayrı tutmak iyi bir fikir çünkü aynı görünseler bile, farklı iş kararlarına / gereksinimlerine bağlı olabilirler. Bu nedenle, kodlu olarak benzer görünseler bile, onlara çok farklı hesaplamalar olarak bakmak isteyebilirsiniz. (Bir kural ya da bir şey değil, sadece bir şeylerin birleştirilmesi ya da yeniden düzenlenmesi gerekip gerekmediğine karar verirken akılda tutulması gereken bir şey :)
Svish

@Svish İlginç nokta. Asla böyle düşünmedim.
Phil

17

alt sınıflar farklı türler kullanır, hesaplama sınıftan sınıfa küçük bir bit gösterir.

Birincisi ve en önemlisi şudur: İlk olarak, hangi bölümün değiştiğini ve sınıflar arasında hangi bölümün değişmediğini açıkça belirtin. Bunu belirledikten sonra probleminiz çözülür.

Yeniden faktoringe başlamadan önce ilk egzersiz olarak bunu yapın. Bundan sonra her şey otomatik olarak yerine geçecektir.


2
Peki. Bu sadece tekrarlanan işlevin çok büyük olması sorunu olabilir.
Karl Bielefeldt

8

Bir kaç satırdan daha fazla kod satırının neredeyse tümünün bir şekilde veya başka şekilde etkisiz hale getirilebileceğine inanıyorum ve neredeyse her zaman olması gerektiği gibi.

Ancak, bu yeniden düzenleme, bazı dillerde diğerlerinden daha kolaydır. LISP, Ruby, Python, Groovy, Javascript, Lua, vb. Dillerde oldukça kolaydır. Şablonları kullanarak C ++ 'da genellikle zor değildir. C'de daha acı vericidir, burada tek alet önişlemci makroları olabilir. Genellikle Java'da acı verir ve bazen basitçe imkansızdır, örneğin birden fazla yerleşik sayısal türü işlemek için genel kod yazmaya çalışmak.

Daha etkileyici dillerde, hiçbir soru yoktur: bir çift satırdan daha fazla bir şey için refactor. Daha az anlamlı dillerle, yeniden düzenlemenin acısını tekrarlanan kodun uzunluğu ve istikrarına karşı dengelemeniz gerekir. Tekrarlanan kod uzunsa veya sık sık değişebilirse, ortaya çıkan kodun okunması biraz zor olsa bile yeniden yönlendirici olma eğilimindeyim.

Tekrarlanan kodu yalnızca kısa, kararlı ve yeniden düzenleme çok çirkin olduğunda kabul edeceğim. Java yazmıyorsam, temelde neredeyse tüm kopyaları çarpar.

Dava için belirli bir öneride bulunmak imkansızdır, çünkü kodu girmediniz, hatta hangi dili kullandığınızı da belirtmediniz.


Lua bir kısaltma değildir.
DeadMG

@DeadMG: not edildi; düzenlemek için çekinmeyin. Bu yüzden sana bütün bu itibarı verdik.
kevin cline

5

Temel sınıfın bir algoritma yapması gerektiğini söyleyin, ancak algoritma her alt sınıfa göre değişir, bu, Şablon Şablonu için mükemmel bir aday gibi görünür .

Bununla, temel sınıf algoritmayı uygular ve her alt sınıf için bir varyasyon söz konusu olduğunda, uygulayacağı alt sınıfın sorumluluğunda olan soyut bir yöntemi savunur. Yolu düşünün ve ASP.NET sayfasının örneğin Page_Load uygulamasını uygulama kodunuzu savunması.


4

Her sınıftaki "bir genel enterpolasyon yönteminiz" gibi görünüyor, çok fazla şey yapıyor ve daha küçük yöntemlerle yapılması gerekiyor.

Hesaplamanızın ne kadar karmaşık olduğuna bağlı olarak neden hesaplamanın her "parçası" böyle bir sanal yöntem olamıyor?

Public Class Fraction
{
     public virtual Decimal GetNumerator(params?)
     public virtual Decimal GetDenominator(params?)
     //Some concrete method to actually compute GetNumerator / GetDenominator
}

Hesaplamanın mantığında bu “hafif çeşitliliği” yapmanız gerektiğinde, bireysel parçaları geçersiz kılın.

(Bu, küçük yöntemleri geçersiz kılarken birçok işlevselliği nasıl ekleyebileceğinize dair çok küçük ve işe yaramaz bir örnektir)


3

Kanımca, bir anlamda, DRY'nin çok fazla alınabileceği konusunda haklısın. İki benzer kod parçasının çok farklı yönlerde gelişmesi muhtemelse, başlangıçta tekrar etmemeye çalışarak kendinize sorun yaratabilirsiniz.

Ancak, böyle küfürlü düşüncelere karşı temkinli olmakta oldukça haklısın. Yalnız bırakmaya karar vermeden önce seçeneklerinizi düşünmeyi çok deneyin.

Örneğin, bu tekrarlanan kodu, temel sınıftan ziyade bir yardımcı program sınıfına / yöntemine koymak daha iyi olur mu? Kalıtım Üzerine Tercih Edilen Kompozisyon bölümüne bakınız .


2

KURU, kırılmaz bir kural değil, izlenecek bir kılavuzdur. Bir noktada, sadece bu kodda tekrarlama olmadığını söylemek için kullandığınız her sınıfta X kalıtım seviyesine ve Y şablonlarına sahip olmamaya karar vermeniz gerekir. Sorulması gereken birkaç iyi soru, bu benzer yöntemleri çıkarmam ve bunları birer birer uygulamamın daha uzun sürmesi olacak, sonra da değişiklik yapılması gerektiğinde veya bunların değişmesi muhtemel bir potansiyel olabilecekse, bunların hepsini araştıracaktı. Çalışmam bu yöntemleri en baştan çıkarmak mı? Ek soyutlamanın bu kodun nerede ya da ne olduğunu zorlaştırdığını anlamaya başladığı noktada mıyım?

Bu sorulardan herhangi birine evet cevabı verebilirseniz, potansiyel olarak kopyalanmış bir kod bırakmak için güçlü bir durumunuz olur.


0

Kendinize "neden onu yeniden yansıtmalıyım?" Sorusunu sormalısınız. Durumunuzda "benzer fakat farklı" bir kodunuz olduğunda, bir algoritmada değişiklik yaparsanız, bu değişikliği diğer noktalara da yansıttığınızdan emin olmanız gerekir. Bu normalde felaket için bir reçetedir, her zaman bir başkası bir noktayı kaçıracak ve başka bir hatayı tanıtacak.

Bu durumda, algoritmaları tek bir deve yeniden düzenlemek çok karmaşık hale getirecek ve gelecekteki bakım için çok zorlanacaktır. Öyleyse, ortak şeyleri mantıklı bir şekilde çıkaramazsanız, basit:

// this code is similar to class x function b

Yorum yeterli olacaktır. Sorun çözüldü.


0

Üst üste binme işlevine sahip daha büyük bir yönteme mi yoksa iki küçük yönteme mi sahip olacağına karar verirken, ilk 50.000 dolarlık soru, davranışın üst üste binen kısmının değişip değişmeyeceği ve herhangi bir değişikliğin daha küçük yöntemlere eşit olarak uygulanıp uygulanmayacağıdır. İlk sorunun cevabı evet ise, ikinci sorunun cevabı hayır ise, yöntemler ayrı kalmalıdır . Her iki soruya da cevap evet ise, kodun her sürümünün senkronize kalmasını sağlamak için bir şeyler yapılmalıdır; Çoğu durumda, bunu yapmanın en kolay yolu, yalnızca bir sürüme sahip olmaktır.

Microsoft'un DRY ilkelerine aykırı göründüğü birkaç yer var. Örneğin, Microsoft, yöntemlerin bir hatanın bir istisna atması gerekip gerekmediğini belirten bir parametre kabul etmesi konusunda kesinlikle önerilmez. Bir "arıza atma istisnaları" parametresinin bir yöntemin "genel kullanım" API'sinde çirkin olduğu doğru olsa da, bu gibi parametreler bir Dene / Yap yönteminin diğer Dene / Yap yöntemlerinden oluşması gerektiğinde çok yararlı olabilir. Bir başarısızlık meydana geldiğinde bir dış yöntemin bir istisna atması gerekiyorsa, başarısız olan herhangi bir iç yöntem çağrısı dış yöntemin yayılmasına izin verebilecek bir istisna atmalıdır. Dış yöntemin bir istisna atmaması gerekiyorsa, iç yöntem de değildir. Eğer dene / yap özelliğini ayırt etmek için bir parametre kullanılıyorsa, sonra dış yöntem onu ​​iç yönteme geçirebilir. Aksi takdirde, dış yöntemin "denemek" gibi davranması gereken durumlarda "try" yöntemlerini çağırması ve "yapmak" gibi davranması gerektiğinde "gerçekleştir" yöntemlerini çağırması gerekir.

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.