Koşulların bu kullanımı bir anti-kalıp mı?


14

İşteki eski sistemimizde bunu çok gördüm - böyle bir şey yapan işlevler:

bool todo = false;
if(cond1)
{
  ... // lots of code here
  if(cond2)
    todo = true;
  ... // some other code here
}

if(todo)
{
  ...
}

Başka bir deyişle, işlevin iki bölümü vardır. İlk bölüm bir tür işlem yapar (potansiyel olarak döngüler, yan etkiler vb.) Ve yol boyunca "yapılacak" bayrağını ayarlayabilir. İkinci bölüm yalnızca "yapılacak" bayrağı ayarlanmışsa yürütülür.

Bir şeyler yapmak için oldukça çirkin bir yol gibi görünüyor ve bence aslında anlamaya zaman ayırdığım vakaların çoğu, bayrağı kullanmaktan kaçınmak için yeniden düzenlenebilir. Ama bu gerçek bir anti-desen, kötü bir fikir mi yoksa tamamen kabul edilebilir mi?

İlk belirgin yeniden düzenleme, onu iki yönteme ayırmak olacaktır. Bununla birlikte, sorum daha çok yerel bir bayrak değişkeni yaratmaya (modern bir OO dilinde) ihtiyaç duyup duymayacağı, potansiyel olarak birden çok yere ayarlayıp, daha sonra bir sonraki kod bloğunun yürütülüp yürütülmeyeceğine karar vermek için kullanılmasıyla ilgili.


3
Nasıl yeniden düzenlersiniz?
Tamás Szelei

13
Todo'nun birkaç yerde belirlendiğini varsayarsak, önemsiz olmayan birkaç özel duruma göre, en ufak bir anlam ifade eden bir yeniden düzenleme düşünmek zor. Yeniden düzenleme yoksa, antipattern yoktur. Yapılacak değişkenin isimlendirilmesi dışında; "doSecurityCheck" gibi daha anlamlı olarak adlandırılmalıdır.
user281377

3
@ AmmoQ: +1; işler karmaşıksa o zaman böyle olur. Bir bayrak değişkeni, bir kararın alındığını daha açık hale getirdiği için bazı durumlarda çok daha mantıklı olabilir ve kararın nerede verildiğini bulmak için onu arayabilirsiniz.
Donal Fellows

1
@Donal Fellows: Nedeni aramak gerekirse, değişkeni bir liste haline getiririm; boş olduğu sürece "yanlış"; bayrak her nereye yerleştirilirse, listeye bir neden kodu eklenir. Bu nedenle ["blacklisted-domain","suspicious-characters","too-long"], birkaç nedenin uygulandığını gösteren bir liste ile bitebilirsiniz.
user281377

2
Bunun bir anti-desen olduğunu düşünmüyorum, ama kesinlikle bir koku
Binary Worrier

Yanıtlar:


23

Anti-desen hakkında bilmiyorum, ama bundan üç yöntem çıkarırım.

Birincisi biraz iş yapar ve boole değeri döndürür.

İkincisi, "başka bir kod" tarafından yapılan işi yapar

Üçüncüsü, döndürülen boole doğruysa yardımcı işi gerçekleştirirdi.

Çıkarılan yöntemler muhtemelen ilk yöntem doğruysa yalnızca ikincisinin (ve her zaman) çağrılmasının önemli olması durumunda özel olacaktır.

Yöntemleri iyi adlandırarak, kodu daha net hale getireceğini umuyorum.

Bunun gibi bir şey:

public void originalMethod() {
    bool furtherProcessingRequired = lotsOfCode();
    someOtherCode();
    if (furtherProcessingRequired) {
        doFurtherProcessing();
    }
    return;
}

private boolean lotsOfCode() {
    if (cond1) {
        ... // lots of code here
        if(cond2) {
            return true;
        }
    }
    return false;
}

private void someOtherCode() {
    ... // some other code here
}

private void doFurtherProcessing() {
    // Do whatever is needed
}

Açıkçası, erken iadelerin kabul edilebilir olup olmadığı konusunda bir tartışma var, ancak bu bir uygulama detayıdır (kod formatlama standardı gibi).

Mesele, kodun amacı daha net hale geliyor, ki bu iyi ...

Sorudaki yorumlardan biri, bu paternin bir kokuyu temsil ettiğini gösteriyor ve buna katılıyorum. Niyeti daha net hale getirip getiremeyeceğinizi görmek önemlidir.


2 işleve bölmek yine de bir tododeğişken gerektirir ve muhtemelen anlaşılması daha zor olur.
Pubby

Evet, ben de yaparım, ama sorum daha çok "yapılacaklar" bayrağının kullanılmasıyla ilgiliydi.
Kricket

2
Sonunda if (check_if_needed ()) do_whatever ();, orada belirgin bir bayrak yok. Bu kod çok fazla kırmak ve kod oldukça basit olsa potansiyel olarak okunabilirlik zarar düşünüyorum. Sonuçta, ne yaptığınızın ayrıntıları do_whatevertest etme şeklinizi etkileyebilir check_if_needed, böylece tüm kodu aynı ekran içinde bir arada tutmak yararlı olur. Ayrıca, bu check_if_neededbir bayrak kullanmaktan kaçınabileceğini garanti etmez - ve eğer yaparsa, bunu returnyapmak için muhtemelen birden fazla ifade kullanacak ve muhtemelen tek çıkışlı katı savunucuları üzecektir.
Steve314

3
@ Pubby8 dedi ki "bundan 2 yöntem çıkar" , sonuç 3 yöntem. Gerçek işlemeyi gerçekleştiren 2 yöntem ve iş akışını koordine eden orijinal yöntem. Bu çok daha temiz bir tasarım olurdu.
MattDavey

Bu ... // some other code hereerken iade davasında atlar
Caleth

6

Tek bir yöntemde kod bir sürü olduğu çirkinlik nedeni olduğunu düşünüyorum ve / veya değişkenler kötü (her ikisi de vardır adlandırılır kod kokuyor , kendi sağda - antipatterns daha soyut ve karmaşık şeyler IMO vardır).

@Bill'in önerdiği gibi, kodun çoğunu daha düşük düzeydeki yöntemlere çıkarırsanız, geri kalanı temiz olur (en azından bana göre). Örneğin

bool registrationNeeded = installSoftware(...);
if (registrationNeeded) {
  registerUser(...)
}

Veya bayrak denetimini ikinci yönteme gizleyerek ve benzer bir form kullanarak yerel bayraktan tamamen kurtulabilirsiniz.

calculateTaxRefund(isTaxRefundable(...), ...)

Genel olarak, yerel bir bayrak değişkenine sahip olmanın mutlaka kötü olduğunu görmüyorum. Yukarıdakilerin hangi seçeneği daha okunabilir (= benim için tercih edilir), yöntem parametrelerinin sayısına, seçilen adlara ve hangi formun kodun iç mantığı ile daha tutarlı olduğuna bağlıdır.


4

todo değişken için gerçekten kötü bir isim, ama bence bunların hepsi yanlış olabilir. Bağlam olmadan tamamen emin olmak zor.

Diyelim ki işlevin ikinci kısmı ilk kısım tarafından oluşturulan bir listeyi sıralıyor. Bu çok daha okunabilir olmalıdır:

bool requiresSorting = false;
if(cond1)
{
    ... // lots of code here
    if(cond2)
        requiresSorting = true;
    ... // some other code here
}

if(requiresSorting)
{
    ...
}

Ancak Bill'in önerisi de doğrudur. Bu yine de daha okunabilir:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

Neden sadece bir adım daha ileri gitmiyoruz: if (BuildList (list)) SortList (list);
Phil N DeBlanc

2

Durum makinesi modeli bana iyi geliyor. Buradaki anti-desenler "todo" (kötü ad) ve "çok sayıda kod" dur.


Ama bunun sadece örnekleme için olduğuna eminim.
Loren Pechtel

1
Kabul. Ne iletmeye çalışıyorum kötü kod boğulan iyi desenler kod kalitesi için suçlanmaması gerektiğini olduğunu.
ptyx

1

Gerçekten bağlıdır. Kod tarafından korunuyorsa todo(umarım bu ismi tamamen anımsatıcı olmadığı için gerçek olarak kullanmazsınız!) Kavramsal olarak temizleme kodu ise, o zaman bir anti-desen var ve C ++ 'ın RAII veya C #' s gibi bir şey kullanmalısınız usingbunun yerine işleri halletmek.

Öte yandan, eğer kavramsal olduğunu değil bir temizlik aşaması ziyade sadece ek işlem bazen gereklidir ve nerede daha önce alınması gerekir yapmaya karar, ince yazılı budur. Tek tek kod parçalarının elbette kendi işlevlerine daha iyi yansıtılıp tekrarlanmayacağını ve ayrıca bayrak değişkeninin adını kendi adına yakalayıp yakalamadığınızı, ancak bu temel kod deseninin iyi olup olmadığını düşünün. Özellikle, diğer işlevlere çok fazla şey koymaya çalışmak, olup biteni daha az netleştirebilir ve bu kesinlikle bir anti-desen olacaktır.


Açıkçası bir temizlik değil - her zaman çalışmaz. Daha önce böyle vakalara çarptım - bir şeyi işlerken bir tür önceden hesaplanmış sonucu geçersiz kılabilirsiniz. Hesaplama pahalıysa, yalnızca gerektiğinde çalıştırmak istersiniz.
Loren Pechtel

1

Buradaki cevapların birçoğu karmaşıklık kontrolünden geçmekte zorlanırdı, birkaçı> 10'a baktı.

Bence bu, baktığınız şeyin "anti-desen" kısmı. Kodunuzun döngüsel karmaşıklığını ölçen bir araç bulun - tutulma için eklentiler var. Temel olarak kodunuzun test edilmesinin ne kadar zor olduğunun bir ölçüsüdür ve kod dallarının sayısını ve seviyelerini içerir.

Olası bir çözümü tam olarak tahmin etmek gerekirse, kodunuzun düzeni beni "Görevler" de düşündürüyor, eğer bu çok fazla yerde gerçekleşiyorsa belki de gerçekten istediğiniz şey göreve yönelik bir mimaridir - her görev kendi nesnenin ortasında ve başka bir görev nesnesini örnekleyerek ve kuyruğa atayarak bir sonraki görevi sıraya atabilirsiniz. Bunların kurulumu inanılmaz derecede basit olabilir ve belirli kod türlerinin karmaşıklığını önemli ölçüde azaltırlar - ancak dediğim gibi bu karanlıkta toplam bir bıçaktır.


1

Yukarıdaki pdr örneğini kullanarak, güzel bir örnek olduğu için, bir adım daha ileri gideceğim.

O vardı:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

Bu yüzden aşağıdakilerin işe yarayacağını fark ettim:

if(BuildList(list)) 
    SortList(list)

Ama net değil.

Orijinal soruya, neden olmasın:

BuildList(list)
SortList(list)

Ve SortList'in sıralama gerektirip gerektirmediğine karar versin mi?

BuildList yönteminizin, bu şekilde gösteren bir bool döndürdüğü için sıralama hakkında bilgi sahibi gibi göründüğünü görüyorsunuz, ancak bu, bir liste oluşturmak için tasarlanmış bir yöntem için hiçbir anlam ifade etmiyor.


Ve elbette bir sonraki adım, bunun neden iki aşamalı bir süreç olduğunu sormaktır. Her yerde böyle kodu görüyorum BuildAndSortList (liste) adlı bir yönteme refactor
Ian

Bu bir cevap değil. Kodun davranışını değiştirdiniz.
D Drmmr

Pek sayılmaz. Yine, 7 yıl önce gönderdiğim bir şeye cevap verdiğime inanamıyorum, ama ne cehennem :) Tartıştığım şey SortList'in koşullu içereceğiydi. Listenin yalnızca x koşulu karşılandıysa sıralandığını iddia eden bir birim testiniz varsa, yine de geçer. Koşullu SortList'e taşıyarak, her zaman yazmak zorunda kalmazsınız (eğer bir şey varsa) SortList (...))
Ian

0

Evet, bu bir sorun gibi görünüyor çünkü bayrağı AÇIK / KAPALI olarak işaretlediğiniz tüm yerleri izlemeye devam etmeniz gerekiyor. Mantığı çıkarmak yerine mantığı içine iç içe yerleştirilmiş olarak dahil etmek daha iyidir.

Ayrıca zengin etki alanı modelleri, bu durumda sadece bir astar nesnenin içinde büyük işler yapar.


0

Bayrak yalnızca bir kez ayarlanırsa,
...
kodunu doğrudan
... // karakterinden hemen sonra buraya taşıyın
.

Aksi takdirde
... // burada çok sayıda kod
... // başka bir kod burada
...
mümkünse ayrı işlevlere kodlayın, bu nedenle bu fonksiyonun şube mantığı olan bir sorumluluğu olduğu açıktır.

Mümkün olan her yerde kodu
... // burada bir sürü kod
olarak iki veya daha fazla işleve ayırın, bazıları bir iş yapar (bu bir komuttur) ve diğerleri ya todo değerini döndüren (bir sorgu) ya da çok açık bir şekilde değiştiriyorlar (yan etkileri kullanan bir sorgu)

Kod kendisi burada devam eden anti-desen değil ... Ben gerçek şeyler (komutlar) yapmak ile karışan dallanma mantığı aradığınız anti-desen olduğundan şüpheleniyorum.


bu yazı mevcut cevapların eksik olduğunu ne ekliyor?
esoterik

@esoterik Bazen bayraklar söz konusu olduğunda küçük bir CQRS ekleme fırsatı sıklıkla gözden kaçırılır ... bir bayrağı değiştirmeye karar vermek mantığı bir sorguyu temsil ederken, iş yapmak bir komutu temsil eder. Bazen ikisini ayırmak kodu daha net hale getirebilir. Ayrıca, yukarıdaki kodda, bayrağın yalnızca bir dalda ayarlandığı için basitleştirilebileceğini belirtmeye değerdi. Bayraklar bir antipattern değildir ve isimleri aslında kodu daha etkileyici hale getirirse iyi bir şeydir. Bayrakların oluşturulduğu, ayarlandığı ve kullanıldığı yerlerde, mümkünse kodda birbirine yakın olması gerektiğini hissediyorum.
andrew pate

0

Bazen bu kalıbı uygulamam gerektiğini düşünüyorum. Bazen bir işleme devam etmeden önce birden fazla kontrol yapmak isteyebilirsiniz. Verimlilik nedeniyle, kesinlikle kontrol edilmesi gerekmedikçe belirli kontrolleri içeren hesaplamalar yapılmaz. Yani genellikle aşağıdaki gibi bir kod görürsünüz:

// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(valuesExist) {
    try {
      // Attempt insertion
      trx.commit();
    } catch (DatabaseException dbe) {
      trx.rollback();
      throw dbe;
    }
  } else {
    closeConnection(db);
    throwException();
  }
} else {
  closeConnection(db);
  throwException();
}

Doğrulamayı, işlemi gerçekleştirme sürecinden ayırarak basitleştirilebilir, böylece daha fazlasını görürsünüz:

boolean proceed = true;
// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(!valuesExist) {
    proceed = false;
  }
} else {
  proceed = false;
}

// The moment of truth
if(proceed) {
  try {
    // Attempt insertion
    trx.commit();
  } catch (DatabaseException dbe) {
    trx.rollback();
    throw dbe;
  }
} else {
  if(db.isOpen()) {
    closeConnection(db);
  }
  throwException();
}

Açıkçası, elde etmeye çalıştığınız şeye göre değişir, bu şekilde yazılmış olsa da, hem "başarı" kodunuz hem de "başarısızlık" kodunuz bir kez yazılır, bu da mantığı basitleştirir ve aynı performans seviyesini korur. Oradan, bazı programcılar tuhaf bir nedenden dolayı son derece uzun yöntemleri sevmesine rağmen, boolean başarı veya başarısızlık göstergelerini döndüren iç yöntemlerin içine tüm doğrulama seviyelerine uymak iyi bir fikir olacaktır.


Verdiğiniz örnekte, yanıtı döndüren bir işlevi shouldIDoIt (fieldsValidated, valueExist) olmasını tercih ederdim. Bunun nedeni, evet / hayır tespitinin hepsi, burada gördüğüm kodun aksine, devam etme kararının birkaç farklı bitişik olmayan noktaya dağıldığı bir kerede yapılır.
Kricket

@KelseyRider, asıl mesele buydu. Doğrulamanın yürütmeden ayrılması, programın genel mantığını (isValidated ()) doOperation ()
Neil

0

Bu bir örüntü değil . En genel yorum, bir boolean değişkeni ayarladığınız ve daha sonra değeri üzerinde dallandığınızdır. Bu normal prosedürel programlama, başka bir şey değil.

Şimdi, özel örneğiniz şu şekilde yeniden yazılabilir:

if(cond1)
{
    ... // lots of code here
    ... // some other code here
    if (cond2)
    {
        ...
    }
}

Bunu okumak daha kolay olabilir. Ya da olmayabilir. Bu, atladığınız kodun geri kalanına bağlıdır. Bu kodu daha özlü yapmaya odaklanın.


-1

Kontrol akışı için kullanılan yerel bayraklar gotogizlenmiş bir biçim olarak tanınmalıdır . Bir bayrak yalnızca bir işlev içinde kullanılırsa, işlevin iki kopyasını yazarak, birini "bayrak doğrudur" ve diğerini "bayrak yanlış" olarak etiketleyerek ve bayrağı ayarlayan her işlemi değiştirerek ortadan kaldırılabilir açık olduğunda veya ayarlandığında, işlevin iki sürümü arasında bir atlama ile temizler.

Çoğu durumda, bir bayrak kullanarak kullanılan kod, gotobunun yerine kullanılan olası herhangi bir alternatiften daha temiz olacaktır , ancak bu her zaman doğru değildir. Bazı durumlarda, gotobir kod parçasını atlamak, bunu yapmak için bayrak kullanmaktan daha temiz olabilir [ancak bazı insanlar buraya belirli bir raptor karikatürü ekleyebilirler].

Bence ana yol gösterici ilke, program mantık akışının iş mantığı tanımına mümkün olduğunca benzemesi gerektiğidir. İş mantığı gereksinimleri, garip yollarla bölünen ve birleşen durumlar açısından açıklanıyorsa, program mantığına sahip olmak benzer mantığı gizlemek için bayrakları kullanmaya çalışmaktan daha temiz olabilir. Öte yandan, iş kurallarını tanımlamanın en doğal yolu, diğer bazı eylemler yapıldıysa bir eylemin yapılması gerektiğini söylemek ise, bunu ifade etmenin en doğal yolu, gösteri yaparken ayarlanan bir bayrak kullanmak olabilir ikinci eylem ve aksi takdirde açıktır.

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.