Boolean temizleyici ile nasıl arama yapabilirim? Boole Tuzağı


76

@ Benjamin-gruenbaum tarafından yapılan yorumlarda belirtildiği gibi, buna Boolean tuzağı denir:

Diyelim ki böyle bir işleve sahibim.

UpdateRow(var item, bool externalCall);

ve denetleyicimde, bu değer externalCallher zaman TRUE olacaktır. Bu işlevi çağırmanın en iyi yolu nedir? Ben genellikle yazarım

UpdateRow(item, true);

Ama kendime soruyorum, sadece 'gerçek' değerin ne anlama geldiğini göstermek için bir boolean ilan etmeli miyim? İşlevin bildirgesine bakarak bunu biliyorsunuz, ancak bunun gibi bir şey gördüyseniz çok daha hızlı ve net.

bool externalCall = true;
UpdateRow(item, externalCall);

PD: Bu soru gerçekten buraya uyup uymadığından emin değil, eğer değilse, bu konuda nereden daha fazla bilgi alabilirim?

PD2: Hiçbir dili etiketlemediğim için bunun çok genel bir sorun olduğunu düşündüm. Neyse, ben c # ile çalışıyorum ve kabul edilen cevap c # için çalışıyor


10
Bu kalıp aynı zamanda boolean trap
Benjamin Gruenbaum

11
Cebirsel veri türlerini destekleyen bir dil kullanıyorsanız, bir boole yerine yeni bir reklam öneririm. data CallType = ExternalCall | InternalCallÖrneğin haskell'de.
Filip Haglund

6
Sanırım Enums aynı amacı yerine getirecekti; Booleanlar için isimler ve biraz da güvenliğe sahip olmak.
Filip Haglund

2
Anlamını belirtmek için bir boolean ilan etmenin “açıkça belli” olduğunu kabul etmiyorum. İlk seçenekle, her zaman gerçek olacağınız açıktır. İkincisi ile kontrol etmeniz gerekir (tanımlanan değişken nerede? Değeri değişiyor mu?). Elbette, eğer bu iki satır bir araya geldiğinde sorun olmaz ... ama biri kodunu eklemek için mükemmel bir yerin tam olarak iki satır arasında olduğuna karar verebilir. Olur!
AJPerez

Bir boole ilan etmek daha temiz değil, daha net, sadece söz konusu yöntemin belgelerine bakarak bir adım atlıyor. İmzayı biliyorsanız, yeni bir sabit eklemek daha az açıktır.
insidesin

Yanıtlar:


154

Her zaman mükemmel bir çözüm yoktur, ancak aralarından seçim yapabileceğiniz birçok alternatifiniz vardır:

  • Dilinizde varsa adlandırılmış değişkenleri kullanın . Bu çok iyi çalışıyor ve özel bir dezavantajı yok. Bazı dillerde, herhangi bir argüman, örneğin updateRow(item, externalCall: true)( C # ) veya update_row(item, external_call=True)(Python) gibi adlandırılmış bir argüman olarak geçirilebilir .

    Ayrı bir değişken kullanma öneriniz, adlandırılmış argümanları simüle etmenin bir yoludur, ancak ilişkili güvenlik avantajlarına sahip değildir (bu argüman için doğru değişken adını kullandığınızın garantisi yoktur).

  • Genel arabiriminiz için daha iyi adlarla farklı işlevler kullanın . Bu, parametreyi adına koyarak, adlandırılmış parametreleri simüle etmenin başka bir yoludur.

    Bu çok okunaklı, ancak bu işlevleri yazan sizin için bir çok kazana yol açıyor. Ayrıca, birden fazla boolean argümanı olduğunda, kombinasyonsal patlama ile de başa çıkamaz. Önemli bir dezavantajı, istemcilerin bu değeri dinamik olarak ayarlayamaması, ancak doğru işlevi çağırmak için if / else kullanmaları gerektiğidir.

  • Enum kullanın . Booleanlarla ilgili problem, “doğru” ve “yanlış” olarak adlandırılmasıdır. Öyleyse, daha iyi adlara sahip bir tür tanıtın (örneğin enum CallType { INTERNAL, EXTERNAL }). Ek bir avantaj olarak, bu , programınızın tür güvenliğini arttırır (eğer diliniz farklı türler veriyorsa). Enums dezavantajı, herkes tarafından görülebilen API'nize bir tür eklemeleridir. Tamamen iç fonksiyonlar için, bu önemli değil ve numaralandırmaların önemli dezavantajları yoktur.

    Enums olmayan dillerde, bunun yerine bazen kısa dizeler kullanılır. Bu işe yarar ve ham booleanlardan bile daha iyi olabilir, ancak yazım hatalarına karşı çok hassastır. Ardından fonksiyon hemen argümanın bir dizi olası değer ile eşleştiğini iddia etmelidir.

Bu çözümlerin hiçbiri engelleyici bir performans etkisine sahip değildir. Adlandırılmış parametreler ve numaralamalar derleme zamanında (derlenmiş bir dil için) tamamen çözülebilir. Dizelerin kullanılması bir dizge karşılaştırması içerebilir, ancak bunun maliyeti küçük dize değişmezleri ve çoğu uygulama için önemsizdir.


7
Az önce "adlandırılmış argümanlar" olduğunu öğrendim. Bence bu en iyi öneri. Bu seçeneği biraz anlatabilirseniz (diğer kişilerin bu konuda çok fazla google'a ihtiyacı kalmaması için) bu cevabı kabul edeceğim. Ayrıca performansta herhangi bir ipucu varsa ... Teşekkürler
Mario Garcia

6
Kısa dizgilerle ilgili problemleri hafifletmeye yardımcı olabilecek bir şey, dizge değerleri ile sabitleri kullanmaktır. @MarioGarcia Üzerinde çalışılacak fazla bir şey yok. Burada belirtilenlerin yanı sıra, ayrıntıların çoğu belirli bir dile bağlı olacaktır. Burada bahsetmek istediğin özel bir şey var mıydı?
jpmc26

8
Bir yöntemden bahsettiğimiz ve enumun bir sınıfa girebileceği dillerde genellikle bir enum kullanıyorum. Tamamen kendi kendini belgeliyor (enum türünü bildiren tek kod satırında, bu yüzden de hafif), yöntemin çıplak bir boolean ile çağrılmasını tamamen önlüyor ve ortak API üzerindeki etki ihmal edilebilir (tanımlayıcılardan beri) sınıfa dahil edilir).
davidbak

4
Bu rolde dizeleri kullanmanın bir başka eksikliği, derlemedeki geçerliliğini, derlemelerin aksine kontrol edememenizdir.
Ruslan

32
enum kazanır. Bir parametrenin yalnızca iki olası durumdan birine sahip olması nedeniyle, bu otomatik olarak bir boolean olması gerektiği anlamına gelmez.
Pete

39

Doğru çözüm, önerdiğiniz şeyi yapmaktır, ancak bir mini cepheye paketleyin:

void updateRowExternally() {
  bool externalCall = true;
  UpdateRow(item, externalCall);
}

Okunabilirlik, mikro optimizasyondan kaynaklanıyor. Ekstra işlev çağrısı, Boolean bayrağının anlambilimine bir kez bile bakma zorunluluğunu gösterebildiğinizden daha iyi olabilir.


8
Bu enkapsülasyon kontrolörümde sadece bir kez bu fonksiyonu çağırdığımda buna değer mi?
Mario Garcia

14
Evet. Evet öyle. Yukarıda yazdığım gibi, maliyet (işlev çağrısı) faydadan daha küçüktür (geliştirici zamanından fazla miktarda tasarruf edemezsiniz çünkü hiç kimse ne yaptığına bakmak truezorunda kalmaz.) Olsa bile faydalı olacaktır. CPU zamanı çok daha ucuz olduğundan, geliştirici zamanından tasarruf edeceği kadar CPU zamanı maliyeti.
Kilian Foth

8
Ayrıca, herhangi bir yetkin optimize edici, sizin için sonunda bir şey kaybetmemeniz için bunun satırını çizebilmelidir.
firedraco

33
@KilianFoth Yanlış bir varsayım olması dışında. Bir idame gelmez ikinci parametre ne yaptığını bilmek gerekir, ve bu doğru, ve neden hemen hemen her zaman bu yapmaya çalıştığın şeyin detaylara ilgilidir. Binlerce ölüme kadar işlevselliği gizlemek, minik işlevler, korunabilirliği azaltacak, azaltamayacak. Bunu kendim gördüm, üzüldüm. İşlevlere aşırı bölümleme aslında büyük bir Tanrı İşlevinden daha kötü olabilir.
Graham

6
Bu UpdateRow(item, true /*external call*/);sözdizimine izin veren dillerde daha temiz olacağını düşünüyorum . Sadece bir yorum yazmamak için kodunuzu ekstra bir işlevle şişirmek, basit bir durum için buna değmez. Belki de bu işleve başka bir sürü hata ve / veya bazı çevreleyen + zorlu çevre kodları olsaydı, daha fazla temyiz etmeye başlardı. Ama bence hata ayıklama yapıyorsanız, ne yaptığını görmek için sarmalayıcı işlevini kontrol etmeyi bırakacağınızı ve hangi işlevlerin bir kütüphane API'sinin etrafında ince sarmalayıcı olduğunu ve hangilerinin bir mantığa sahip olduğunu hatırlamak zorunda kalacağınızı düşünüyorum.
Peter Cordes

25

UpdateRow (var item, bool externalCall);

Neden böyle bir işlevin var?

Hangi koşullar altında , farklı değerlere ayarlanmış externalCall argümanı ile çağırırsınız?

Biri harici bir istemci uygulamasından geliyorsa, diğeri aynı programın içinde ise (yani farklı kod modülleri), o zaman her durumda bir tane olmak üzere iki ayrı yönteme sahip olmanız gerektiğini düşünüyorum , muhtemelen ayrı ayrı tanımlanmış olsa bile Arabirimler.

Bununla birlikte, aramayı program dışı bir kaynaktan alınan bazı verileri temel alarak yapıyorsanız (örneğin, bir yapılandırma dosyası veya veritabanı okundu), Boole geçiş yöntemi daha mantıklı olur.


6
Katılıyorum. Ve sadece diğer okuyucular için yapılmış olan noktayı kırmak için, doğru, yanlış veya değişken olarak geçecek olan tüm arayanların analizi yapılabilir. İkincisinin hiçbiri bulunmazsa, boolean ile bir üzerinden iki ayrı yöntem (boolean olmadan) tartışırdım - bu, booleanın kullanılmayan / gereksiz karmaşıklığı ortaya koyduğu bir YAGNI argümanıdır. Bazı arayanlar değişkenler geçiyor olsa bile, sabit geçen arayanlar için kullanmak için (boolean) parametresiz sürümleri hala sağlayabilirim - bu daha basit, bu iyidir.
Erik Eidt

18

Hem okunabilirliği hem de değer güvenliğini güçlendirmek için bir dil özelliği kullanmanın ideal olduğunu kabul etmeme rağmen , pratik bir yaklaşımı da tercih edebilirsiniz : arama zamanı yorumları. Sevmek:

UpdateRow(item, true /* row is an external call */);

veya:

UpdateRow(item, true); // true means call is external

veya (haklı olarak, Frax tarafından önerildiği gibi):

UpdateRow(item, /* externalCall */true);

1
Oy kullanmamak için sebep yok. Bu tamamen geçerli bir öneri.
Suncat2000

<3 @ Suncat2000'e teşekkür edin!
piskopos

11
Bir (muhtemelen tercih edilebilir) varyantı, sadece düz argüman adını oraya koymaktır UpdateRow(item, /* externalCall */ true ). Tam cümle yorumunu ayrıştırmak çok zordur, aslında çoğunlukla gürültüdür (özellikle de argümanla çok gevşek bir şekilde bağlantılı olan ikinci değişken).
Frax

1
Bu, en uygun cevaptır. Bu tam olarak yorumların amaçlandığı şeydir.
Sentinel

9
Bunun gibi yorumların büyük bir hayranı değil. Yorumlar, yeniden yapılanma veya başka değişiklikler yaparken dokunulmazsa bayatlamaya eğilimlidir. Birden fazla bool param varsa en kısa zamanda kafa karıştırıcı hale gelir.
John V.

11
  1. Göletlerini 'adlandırabilirsin'. Aşağıda OO diline bir örnek (sınıfta ifade edilebildiği yerde UpdateRow()), ancak kavramın kendisi herhangi bir dilde uygulanabilir:

    class Table
    {
    public:
        static const bool WithExternalCall = true;
        static const bool WithoutExternalCall = false;
    

    ve çağrı sitesinde:

    UpdateRow(item, Table::WithExternalCall);
    
  2. Öğe # 1'in daha iyi olduğuna inanıyorum, ancak işlevi kullanırken kullanıcıyı yeni değişkenler kullanmaya zorlamıyor. Tip güvenliği sizin için önemliyse, bir enumtür oluşturabilir ve UpdateRow()bunu yerine aşağıdakileri kabul edebilirsiniz bool:

    UpdateRow(var item, ExternalCallAvailability ec);

  3. Fonksiyonun ismini bir şekilde boolparametrenin anlamını daha iyi yansıtacak şekilde değiştirebilirsiniz . Çok emin değil ama belki:

    UpdateRowWithExternalCall(var item, bool externalCall)


8
Fonksiyonu çağırdığınızda şimdiki # 3'ü sevmeyin, externalCall=falseadı kesinlikle bir anlam ifade etmiyor. Cevabın geri kalanı iyi.
Orbit'te Hafiflik Yarışları

Kabul. Çok emin değildim ama başka bir fonksiyon için uygun bir seçenek olabilir. Ben
simurg

@LightnessRacesinOrbit Indeed. Gitmek daha güvenli UpdateRowWithUsuallyExternalButPossiblyInternalCall.
Eric Duminil

@EricDuminil: Spot Spot
Yörüngede Hafiflik Yarışları

10

Burada henüz okumadım başka bir seçenek: Modern bir IDE kullanın.

Örneğin IntelliJ IDEA siz bir hazır böyle geçerken eğer aradığınız yönteminde değişkenlerin değişken adı basar trueya nullya username + “@company.com. Bu küçük bir yazı tipinde yapılır, böylece ekranınızda çok fazla yer kaplamaz ve gerçek koddan çok farklı görünür.

Hala her yere boolean atmanın iyi bir fikir olduğunu söylemiyorum. Yazdığınızdan çok daha sık kod okuduğunuzu belirten argüman çoğu zaman çok güçlüdür, ancak bu özel durum için işinizi desteklemek için kullandığınız teknolojiye (ve iş arkadaşlarınıza!) Büyük ölçüde bağlıdır. Bir IDE ile, örneğin vim'den çok daha az problem olur.

Test kurulumumun bir bölümünün değiştirilmiş örneği: görüntü tanımını buraya girin


5
Bu, yalnızca ekibinizdeki herkes yalnızca kodunuzu okumak için bir IDE kullandıysa geçerlidir. IDE olmayan editörlerin yaygınlığına rağmen, kod inceleme araçlarına, kaynak kontrol araçlarına, Yığın Taşması sorularına vb. Sahip olursunuz ve bu bağlamda bile kodun okunabilir kalması hayati önem taşır.
Daniel Pryden

@DanielPryden emin, bu nedenle benim 've meslektaşlarım' yorum. Şirketlerde standart bir IDE'ye sahip olmak yaygındır. Diğer araçlara gelince, bu tür araçların gelecekte de bunu ya da iradeyi desteklediğini ya da bu argümanların uygulanamayacağını (2 kişilik bir çift programlama ekibi için olduğu gibi)
Sebastiaan van den Broek'in

2
@Sebastiaan: Katıldığımı sanmıyorum. Yalnız bir geliştirici olarak bile, Git diff'te kendi kodumu düzenli olarak okudum. Git hiçbir zaman bir IDE'nin yapabileceği bağlamla uyumlu görselleştirmeyi yapamaz ve yapamaz. Kod yazmanıza yardımcı olması için iyi bir IDE kullanıyorum, ancak onsuz yazmamış olduğunuz bir kod yazmak için hiçbir zaman bir IDE kullanmamalısınız.
Daniel Pryden

1
@DanielPryden Son derece özensiz kod yazmayı savunmuyorum. Siyah beyaz bir durum değil. Geçmişte belirli bir durum için bir boolean ile bir işlev yazma lehine% 40 olacağınız ve% 60 olmamaya ve sonuç vermeyeceğiniz durumlar olabilir. Belki iyi IDE desteğiyle şimdi% 55 böyle yazıyor ve% 45 değil. Ve evet, yine de IDE dışında okumalısınız, bu yüzden mükemmel değil. Fakat yine de, bir kodu başka türlü açıklamak için başka bir yöntem veya numaralandırma eklemek gibi alternatife karşı geçerli bir takas.
Sebastiaan van den Broek

6

2 gün ve hiç kimse polimorfizmden bahsetmedi mi?

target.UpdateRow(item);

Bir öğeyi içeren bir satırı güncellemek isteyen bir müşteriyken, veritabanının adı, mikro hizmetin URL'si, iletişim kurmak için kullanılan protokol veya harici bir aramanın olup olmadığı hakkında düşünmek istemiyorum. Bunun gerçekleşmesi için kullanılması gerek. Bu detayları üzerime çekmeyi bırak. Sadece eşyamı al ve git bir yerden bir yere git.

Bunu yapın ve inşaat sorununun bir parçası haline gelir. Bu birçok yolla çözülebilir. Işte bir tane:

Target xyzTargetFactory(TargetBuilder targetBuilder) {
    return targetBuilder
        .connectionString("some connection string")
        .table("table_name")
        .external()
        .build()
    ; 
}

Eğer buna bakıyor ve "Ama benim dilimin argümanları var, buna ihtiyacım yok" diye düşünüyorsan. Güzel, harika, git onları kullan. Sadece bu saçmalığı, neyle konuştuğunu bile bilmemesi gereken arayan müşteriden uzak tutun.

Bunun anlamsal bir problemden daha fazlası olduğu belirtilmelidir. Aynı zamanda bir tasarım problemi. Bir boole geçmek ve onunla başa çıkmak için dallanma kullanmak zorunda kalırsınız. Nesne yönelimli değil. İçeri girmek için iki veya daha fazla boole var mı? Dilinizin birden fazla gönderimi olmasını ister misiniz? Yuvalarken koleksiyonların neler yapabileceğini araştırın. Doğru veri yapısı hayatınızı çok daha kolaylaştırabilir.


2

updateRowİşlevi değiştirebiliyorsanız , belki de iki işleve yeniden uygulayabilirsiniz . İşlev boolean bir parametre aldığında, şöyle gözüktüğünden şüpheleniyorum:

function updateRow(var item, bool externalCall) {
  Database.update(item);

  if (externalCall) {
    Service.call();
  }
}

Biraz kod kokusu olabilir. İşlev, externalCalldeğişkenin ne olduğuna bağlı olarak, önemli ölçüde farklı davranışlara sahip olabilir , bu durumda iki farklı sorumluluğu vardır. Yalnızca bir sorumluluğu bulunan iki işleve yeniden yerleştirmek okunabilirliği artırabilir:

function updateRowAndService(var item) {
  updateRow(item);
  Service.call();
}

function updateRow(var item) {
  Database.update(item);
}

Şimdi, bu işlevleri çağırdığınız her yerde, harici servisin aranıp aranmadığını bir bakışta anlayabilirsiniz.

Elbette bu her zaman böyle değildir. Durumsal ve zevk meselesi. Bir boolean parametresini iki işleve alan bir işlevi yeniden düzenlemek genellikle göz önünde bulundurmaya değerdir, ancak her zaman en iyi seçenek değildir.


0

UpdateRow kontrollü bir kod tabanında ise, strateji modelini düşünürdüm:

public delegate void Request(string query);    
public void UpdateRow(Item item, Request request);

İsteğin bir tür DAO'yu temsil ettiği (önemsiz durumlarda geri arama).

Gerçek durum:

UpdateRow(item, query =>  queryDatabase(query) ); // perform remote call

Yanlış dava:

UpdateRow(item, query => readLocalCache(query) ); // skip remote call

Genellikle, uç nokta uygulaması daha yüksek bir soyutlama seviyesinde yapılandırılır ve burada sadece kullanırdım. Yararlı bir serbest yan etki olarak, bu bana kodda herhangi bir değişiklik yapmadan uzaktaki verilere erişmek için başka bir yol kullanma seçeneği sunar:

UpdateRow(item, query => {
  var data = readLocalCache(query);
  if (data == null) {
    data = queryDatabase(query);
  }
  return data;
} );

Genel olarak, bu tür bir kontrolün ters çevrilmesi, veri depolama alanınız ve modeliniz arasında daha az bağlantı sağlar.

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.