Bir bayrak daha sonra kullanmak için bir döngü içinde bir bayrak ayarlamak için bir kod kokusu mu?


30

Bir haritayı belirli bir koşul doğru oluncaya kadar tekrar eden ve daha sonra başka şeyler yapmak için bu koşulu kullandığımda bir kod parçam var.

Örnek:

Map<BigInteger, List<String>> map = handler.getMap();

if(map != null && !map.isEmpty())
{
    for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())
    {
        fillUpList();

        if(list.size() > limit)
        {
            limitFlag = true;
            break;
        }
    }
}
else
{
    logger.info("\n>>>>> \n\t 6.1 NO entries to iterate over (for given FC and target) \n");
}

if(!limitFlag) // Continue only if limitFlag is not set
{
    // Do something
}

Bir bayrak belirleyip sonra daha çok şey yapmak için kullanmak bir kod kokusu.

Haklı mıyım Bunu nasıl kaldırabilirim?


10
Neden kod kokusu olduğunu düşünüyorsun? Bunu yaparken farklı bir yapı altında gerçekleşmeyecek ne tür spesifik problemler öngörebilirsiniz?
Ben Cottrell,

13
@ gnasher729 Merak ediyorum, bunun yerine hangi terimi kullanırsınız?
Ben Cottrell,

11
-1, örneğinizin bir anlamı yok. entryişlev döngüsü içinde hiçbir yerde kullanılmaz ve yalnızca ne listolduğunu tahmin edebiliriz . Is fillUpListdolgu gerekiyordu list? Neden parametre olarak almıyor?
Doktor Brown

13
Boşluk ve boş satır kullanımınızı tekrar gözden geçiririm.
Daniel Jour,

11
Kod kokusu gibi bir şey yoktur. "Kod kokusu", seçkin standartlarına uymayan bir kod gördüklerinde burnunu tutmak isteyen yazılım geliştiricileri tarafından icat edilen bir terimdir.
Robert Harvey

Yanıtlar:


70

Bir Boolean değerini amaçlanan amacı için kullanmanın yanlış bir tarafı yoktur: ikili bir ayrım yapmak.

Eğer bu kodu tekrar gözden geçirmem söylendiyse, muhtemelen atama + breaka return; o zaman bir değişkene bile ihtiyacınız kalmaz

if(fill_list_from_map()) {
  ...

6
Aslında kodundaki koku, daha küçük fonksiyonlara bölünmesi gereken uzun işlevdir. Önerin, gitmenin yolu.
Bernhard Hiller

2
Bu kodun birinci bölümünün faydalı işlevini tanımlayan daha iyi bir ifade, eşlenen öğelerden bir şey topladıktan sonra sınırın aşılıp aşılmayacağını bulmaktır. Ayrıca , yinelemenin fillUpList()değerini gerçekten kullanan bazı kodların (OP'nin paylaşmamaya karar verdiğini) güvenli bir şekilde olduğunu varsayabiliriz entry; Bu varsayım olmadan, döngü gövdesi döngü yinelemesinden hiçbir şey kullanmamış gibi görünür.
rwong

4
@Kilian: Sadece bir endişem var. Bu yöntem bir listeyi dolduracak ve bu liste boyutunun bir sınırı aştığını ya da alamayacağını gösteren bir Boole döndürecektir. limit aşar, vb.) Boolean'ın döndürdüğü gibi, işlev adından belli olmayan özel bir durum söz konusudur. Herhangi bir yorum ? Not: Komut sorgusu ayrıştırmasını da dikkate alabiliriz.
Siddharth Trikha,

2
@SiddharthTrikha Haklısınız ve bu çizgiyi önerdiğimde de aynı endişelerim vardı. Ancak kodun hangi listeyi doldurması gerektiği belli değil. Her zaman aynı liste ise, bayrağa ihtiyacınız yoktur, daha sonra uzunluğunu kontrol edebilirsiniz. Herhangi bir bireysel doldurmanın limiti aştığını bilmeniz gerekiyorsa, o zaman bu bilgileri bir şekilde dışa aktarmanız gerekir ve IMO komut / sorgu ayırma ilkesi bariz yolu reddetmek için yeterli bir neden değildir: dönüş yoluyla değer.
Kilian Foth

6
Bob Amca Temiz Kod'un 45. sayfasında : "İşlevler ya bir şey yapmalı ya da bir şeyi yanıtlamalı, ancak ikisini birden yapmamalı. İşleviniz bir nesnenin durumunu değiştirmeli ya da bu nesne hakkında bazı bilgiler geri vermelidir. karışıklığı."
CJ Dennis,

25

Bu mutlaka kötü değil ve bazen en iyi çözümdür. Ancak iç içe bloklarda buna benzer bayraklar koymak kodun takip edilmesini zorlaştırabilir.

Sorun, kapsamları sınırlandırmak için bloklarınız olması, ancak daha sonra kapsamlar arasında iletişim kuran ve blokların mantıksal izolasyonunu kıran bayraklarınız var. Örneğin, limitFlageğer yanlış olacak mapise nulleğer "bir şeyler yapmak" -Kod çalıştırılacaktır yüzden mapolduğunu null. Bu düşündüğünüz şey olabilir, ancak özlemesi kolay bir hata olabilir, çünkü bu bayrağın koşulları iç içe geçmiş bir kapsam içinde başka bir yerde tanımlanmıştır. Bilgiyi ve mantığı mümkün olan en dar kapsamda tutabiliyorsanız, bunu yapmaya çalışmalısınız.


2
Bloklar tamamen izole olmadığından ve daha sonra takip etmek zor olabileceğinden, bunun kod kokusu olduğunu hissetmemin nedeni buydu. Yani @Kilian 'ın cevabındaki kodu alabileceğimiz en yakın kod?
Siddharth Trikha,

1
@SiddharthTrikha: Kodun gerçekte ne yapması gerektiğini bilmediğim için söylemesi zor. Haritanın sınırdan daha büyük bir listeye sahip en az bir öğe içerip içermediğini kontrol etmek istiyorsanız, bunu tek bir anyMatch ifadesiyle yapabileceğinizi düşünüyorum.
JacquesB

2
@SiddharthTrikha: Kapsam testi, ilk testi aşağıdaki gibi bir koruma maddesine değiştirerek kolayca çözülebilir. if(map==null || map.isEmpty()) { logger.info(); return;}Ancak, yalnızca gördüğümüz kod bir fonksiyonun tüm gövdesi ise // Do somethingve harita durumunda parçanın gerekli olmaması durumunda işe yarar. boş veya boş.
Doktor Brown

14

'Kod kokusu' hakkında muhakeme yapmamayı tavsiye ederim. Bu, kendi önyargınızı rasyonelleştirmenin en tembel yolu. Zamanla bir çok önyargı geliştireceksiniz ve birçoğu mantıklı olacak, ama birçoğu aptal olacak.

Bunun yerine, bir şeyi bir başkasına tercih etmek için pratik (yani dogmatik değil) nedenleriniz olmalı ve tüm benzer sorular için aynı cevaba sahip olmanız gerektiğini düşünmekten kaçının.

"Kod kokuyor" ne zaman içindir değil düşünme. Eğer gerçekten kod hakkında düşünecekseniz, doğru yapın!

Bu durumda, çevre koduna bağlı olarak karar gerçekten iki yönde de verilebilir. Bu gerçekten, kodun ne yaptığını düşünmenin en net yolu olduğunu düşündüğünüze bağlıdır. ("temiz" kod, diğer geliştiricilere ne yaptığını açıkça bildiren ve doğru olduklarını doğrulamalarını kolaylaştıran koddur)

Çoğu zaman, insanlar aşamalara yapılandırılmış yöntemler yazacak, kod ilk önce veriler hakkında neleri bilmesi gerektiğini belirleyecek ve sonra üzerinde çalışacak. "Belirle" bölümü ve "onunla hareket et" bölümü her ikisi de biraz karmaşıksa, bunun yapılması iyi bir anlam ifade edebilir ve genellikle "bilmesi gerekenler" Boolean bayraklarındaki fazlar arasında taşınabilir. Yine de bayrağa daha iyi bir isim vermeni tercih ederim. "LargeEntryExists" gibi bir şey kodu çok daha temiz hale getirir.

Öte yandan, "// Bir Şey Yap" kodu çok basitse, ifbayrak ayarlamak yerine bloğun içine koymak daha mantıklı olabilir . Bu, efekti nedene yaklaştırır ve okuyucunun belirlediğiniz değeri koruduğundan emin olmak için okuyucunun kodun geri kalanını taraması gerekmez.


5

Evet, bir kod kokusudur (bunu yapan herkesin işaretlerini düşürür).

Benim için en önemli şey breakifadenin kullanılması. Eğer kullanmadıysanız, gerekenden daha fazla öğe üzerinde yineleme yapardınız, ancak onu kullanmak döngüden iki olası çıkış noktası verir.

Örneğinizle ilgili önemli bir sorun değil, ancak döngü içindeki koşullu veya koşullu şartların daha karmaşık hale geldiğini veya ilk listenin sıralanmasının önemli hale geldiğini ve bir hatanın koda girmesinin daha kolay olduğunu hayal edebilirsiniz.

Kod, örneğiniz kadar basit olduğunda, bir değere düşürülebilir. while döngüye veya eşdeğer bir haritaya, filtre yapısına .

Kod bayrakları ve sonları gerektirecek kadar karmaşık olduğunda, hatalara açık olacaktır.

Tüm kod kokularında olduğu gibi: Bir bayrak görürseniz, a ile değiştirmeyi deneyin while. Yapamazsanız, ekstra birim testleri ekleyin.


Benden +1. Kesinlikle bir kod kokusu ve nedenini ve bununla nasıl başa çıkacağını açıkça söylüyorsun.
David Arno,

@Ewan: SO as with all code smells: If you see a flag, try to replace it with a whileBu konuda bir örnek verebilir misiniz?
Siddharth Trikha,

2
Döngüden birden çıkış noktalarını olması konusunda sebeple daha zor bunu yapabilir, ama bu durumda bu nedenle döngü koşulu bayrak bağlıdır yapmak için üstlenmeden ederim - değiştirerek ifade edecek for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())olan for (Iterator<Map.Entry<BigInteger, List<String>>> iterator = map.entrySet().iterator(); iterator.hasNext() && !limitFlag; Map.Entry<BigInteger, List<String>> entry = iterator.next()). Bu, nispeten basit bir aradan ziyade, onu anlamakta zorlanacağım nadir bir örüntü.
James_pic

@James_pic java'm biraz paslanmış, fakat haritaları kullanıyor olsaydık, öğelerin sayısını toplamak ve limitten sonra filtrelemek için bir toplayıcı kullanırdım. Ancak, örneğin "o kadar da kötü değil" dediğim gibi, kod kokusu sizi olası bir sorun konusunda uyaran genel bir kuraldır. Her zaman uymanız gereken kutsal bir kanun değil
Ewan

1
"Kuyruk" yerine "işaret" demek istemiyor musun?
psm,

0

Sadece kontrol ettiklerinizi söyleyen limitFlag dışında bir ad kullanın. Harita yokken veya boşken neden bir şeyleri kaydediyorsunuz? limtFlag yanlış, hepsi senin umrunda. Harita boşsa döngü yeterlidir, bu yüzden kontrol etmenize gerek yoktur.


0

Zaten sahip olduğunuz bilgiyi iletmek için bir boolean değer belirlemek, bence kötü bir uygulamadır. Kolay bir alternatif yoksa o zaman muhtemelen enkapsülleme gibi daha büyük bir meselenin göstergesidir.

For döngüsü mantığını, sınıra ulaşıldığında kırılması için fillUpList yöntemine taşımalısınız. Ardından doğrudan listenin boyutunu kontrol edin.

Bu kodunuzu çiğniyorsa neden?


0

İlk önce genel durum: Bir koleksiyonun bazı öğelerinin belirli bir koşulu karşılayıp karşılamadığını kontrol etmek için bir bayrak kullanmak nadir değildir. Ancak bunu çözmek için en sık gördüğüm örnek, çekimi ekstra bir yöntemle taşımak ve doğrudan geri dönmek ( cevabında açıklanan Kilian Foth gibi ):

private <T> boolean checkCollection(Collection<T> collection)
{
    for (T element : collection)
        if (checkElement(element))
            return true;
    return false;
}

Java 8’den bu yana Stream.anyMatch(…)şunları kullanarak daha özlü bir yol var :

collection.stream().anyMatch(this::checkElement);

Sizin durumunuzda bu muhtemelen şöyle gözükecektir ( list == entry.getValue()sorunuz varsayarak ):

map.values().stream().anyMatch(list -> list.size() > limit);

Özel örneğinizdeki sorun ek çağrı fillUpList(). Cevap, bu yöntemin ne yapması gerektiğine çok bağlıdır.

Yan not: Dururken, çağrı fillUpList()yapmak pek mantıklı değil, çünkü şu anda yinelediğiniz öğeye bağlı değil. Sanırım bu, gerçek kodunuzu soru biçimine uyacak şekilde çıkarmanın bir sonucudur. Fakat tam olarak bu, yorumlanması zor ve dolayısıyla da mantıklı olması zor yapay bir örneğe götürür. Bu nedenle, Minimal, Komple ve Doğrulanabilir bir örnek sunmak çok önemlidir .

Bu yüzden asıl kodun akımı entrymetoda ilettiğini varsayıyorum .

Ancak sorulacak daha çok soru var:

  • Bu koda ulaşmadan önce haritadaki listeler boş mu? Öyleyse, neden sadece bir liste veya BigIntegeranahtarların değil, bir harita var ? Boş değilse, neden listeleri doldurmanız gerekiyor ? Listede zaten öğeler varsa, bu durumda bu bir güncelleme veya başka bir hesaplama değil mi?
  • Bir listenin sınırdan daha büyük olmasına neden olan nedir? Bu bir hata durumu mu yoksa sıkça olması bekleniyor mu? Geçersiz girişten mi kaynaklanıyor?
  • Limitten daha büyük bir listeye ulaştığınız noktaya kadar hesaplanan listelere ihtiyacınız var mı?
  • "Bir şeyler yap " kısmı ne işe yarıyor?
  • Bu bölümden sonra dolumu yeniden başlatıyor musunuz?

Bu sadece kod parçasını anlamaya çalıştığımda aklıma gelen bazı sorular. Yani, benim görüşüme göre, bu gerçek kod kokusu : Kodunuz amacı açıkça iletmiyor.

Bu da ("hepsi ya da hiçbiri" anlamına gelebilir ve sınıra ulaşmak bir hataya işaret eder) olabilir:

/**
 * Computes the list of all foo strings for each passed number.
 * 
 * @param numbers the numbers to process. Must not be {@code null}.
 * @return all foo strings for each passed number. Never {@code null}.
 * @throws InvalidArgumentException if any number produces a list that is too long.
 */
public Map<BigInteger, List<String>> computeFoos(Set<BigInteger> numbers)
        throws InvalidArgumentException
{
    if (numbers.isEmpty())
    {
        // Do you actually need to log this here?
        // The caller might know better what to do in this case...
        logger.info("Nothing to compute");
    }
    return numbers.stream().collect(Collectors.toMap(
            number -> number,
            number -> computeListForNumber(number)));
}

private List<String> computeListForNumber(BigInteger number)
        throws InvalidArgumentException
{
    // compute the list and throw an exception if the limit is exceeded.
}

Veya bu şu anlama gelebilir ("ilk soruna kadar güncelleme"):

/**
 * Refreshes all foo lists after they have become invalid because of bar.
 * 
 * @param map the numbers with all their current values.
 *            The values in this map will be modified.
 *            Must not be {@code null}.
 * @throws InvalidArgumentException if any new foo list would become too long.
 *             Some other lists may have already been updated.
 */
public void updateFoos(Map<BigInteger, List<String>> map)
        throws InvalidArgumentException
{
    map.replaceAll(this::computeUpdatedList);
}

private List<String> computeUpdatedList(
        BigInteger number, List<String> currentValues)
        throws InvalidArgumentException
{
    // compute the new list and throw an exception if the limit is exceeded.
}

Veya bu ("tüm listeleri güncelle ama çok büyük olursa orijinal listesini tut"):

/**
 * Refreshes all foo lists after they have become invalid because of bar.
 * Lists that would become too large will not be updated.
 * 
 * @param map the numbers with all their current values.
 *            The values in this map will be modified.
 *            Must not be {@code null}.
 * @return {@code true} if all updates have been successful,
 *         {@code false} if one or more elements have been skipped
 *         because the foo list size limit has been reached.
 */
public boolean updateFoos(Map<BigInteger, List<String>> map)
{
    boolean allUpdatesSuccessful = true;
    for (Entry<BigInteger, List<String>> entry : map.entrySet())
    {
        List<String> newList = computeListForNumber(entry.getKey());
        if (newList.size() > limit)
            allUpdatesSuccessful = false;
        else
            entry.setValue(newList);
    }
    return allUpdatesSuccessful;
}

private List<String> computeListForNumber(BigInteger number)
{
    // compute the new list
}

Veya aşağıdakileri bile ( computeFoos(…)ilk örnekte, ancak istisnalar olmadan):

/**
 * Processes the passed numbers. An optimized algorithm will be used if any number
 * produces a foo list of a size that justifies the additional overhead.
 * 
 * @param numbers the numbers to process. Must not be {@code null}.
 */
public void process(Collection<BigInteger> numbers)
{
    Map<BigInteger, List<String>> map = computeFoos(numbers);
    if (isLimitReached(map))
        processLarge(map);
    else
        processSmall(map);
}

private boolean isLimitReached(Map<BigInteger, List<String>> map)
{
    return map.values().stream().anyMatch(list -> list.size() > limit);
}

Ya da tamamen farklı bir şey anlamına gelebilir… ;-)

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.