Bloktaki ek satır - Temiz Koddaki ek parametre


33

bağlam

In Temiz Kod , sayfa 35, diyor

Bu, if ifadelerindeki blokların, başka ifadelerin, while ifadelerinin vb. Bir satır uzunluğunda olması gerektiği anlamına gelir. Muhtemelen bu hat bir işlev çağrısı olmalıdır. Bu sadece çevreleyen işlevi küçük tutmakla kalmaz, aynı zamanda belgesel değeri de ekler çünkü blok içinde çağırılan işlev güzel bir tanımlayıcı ada sahip olabilir.

Tamamen hemfikir, bu çok mantıklı.

Daha sonra, sayfa 40'ta fonksiyon argümanları hakkında yazıyor

Bir fonksiyon için ideal argüman sayısı sıfırdır (niladik). Daha sonra bir (monadik) geliyor, ardından iki (dyadic) yakından takip ediyor. Mümkünse üç argümandan (üçlü) kaçınılmalıdır. Üçten fazla (polyadik) çok özel bir gerekçe gerektirir - ve daha sonra kullanılmamalıdır. Argümanlar zor. Çok fazla kavramsal güç alıyorlar.

Tamamen hemfikir, bu çok mantıklı.

Konu

Ancak, sık sık kendimi başka bir listeden bir liste oluştururken buluyorum ve iki kötülükten biriyle yaşamak zorunda kalacağım.

Ya blokta iki satır kullandım , biri şeyi yaratmak için, biri sonucu eklemek için:

    public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
    {
        List<Flurp> flurps = new List<Flurp>();
        foreach (BadaBoom badaBoom in badaBooms)
        {
            Flurp flurp = CreateFlurp(badaBoom);
            flurps.Add(flurp);
        }
        return flurps;
    }

Veya listenin fonksiyonuna bir şeyin argümanını ekleyerek, "bir argümanı daha kötü" yapar.

    public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
    {
        List<Flurp> flurps = new List<Flurp>();
        foreach (BadaBoom badaBoom in badaBooms)
        {
            CreateFlurpInList(badaBoom, flurps);
        }
        return flurps;
    }

Soru

Bunlardan birini genel olarak tercih eden, göremediğim (dis-) avantajlar var mı? Veya bazı durumlarda böyle avantajlar var mı; Bu durumda karar verirken nelere dikkat etmeliyim?


58
Neyin var flurps.Add(CreateFlurp(badaBoom));?
cmaster

47
Hayır, bu sadece tek bir ifade. Sadece basit bir şekilde iç içe geçmiş bir ifade (tek bir iç içe düzey). Ve basit f(g(x)), stil rehberine aykırıysa, stil rehberinizi düzeltemem. Demek istediğim, sen de sqrt(x*x + y*y)dört çizgiye bölünmüyorsun, değil mi? Ve bu, iki (!) İç yuvalama düzeyindeki (gasp!) Üç (!) Yuvalanmış alt ifadelerdir. Amacınız tek operatör ifadeleri değil okunabilirlik olmalıdır . Eğer daha sonra istersen, senin için mükemmel bir dile sahibim: Assembler.
cmaster

6
@cmaster x86 düzeneğinde bile kesinlikle tek işleç ifadesi yoktur. Bellek adresleme modları birçok karmaşık işlemi içerir ve aritmetik olarak kullanılabilir - aslında, yalnızca x86 movkomutlarını ve jmp toStartsonunda bir tane kullanarak bir Turing tamamlandı bilgisayarı yapabilirsiniz . Birisi aslında tam olarak yapan bir derleyici yaptı: D
Luaan

5
@Luaan PPC'deki rezil rlwimitalimattan bahsetmemek . (Bu, Sol Maske Döndürme Anında Maske Ekini Döndürme anlamına gelir.) Bu komut, beş işlenenden (iki yazmaç ve üç anlık değer) daha azını almadı ve aşağıdaki işlemleri gerçekleştirdi: Bir yazmaç içeriği hemen kaydırma ile döndürüldü, bir maske yapıldı. diğer iki acil işlenen tarafından kontrol edilen 1 bitlik tek bir çalıştırma ile oluşturulan ve diğer kayıt işlemcisi içindeki bu maskedeki 1 bit'e karşılık gelen bitler, döndürülen yazıcının karşılık gelen bitleri ile değiştirildi. Çok güzel talimat :-)
cmaster

7
@ R.Schmitz "Genel amaçlı programlama yapıyorum" - aslında hayır, siz değilsiniz, belirli bir amaç için programlama yapıyorsunuz ( hangi amacı bilmiyorum , ama yaptığınızı varsayıyorum ;-). Kelimenin tam anlamıyla programlama için binlerce amaç vardır ve bunlar için en uygun kodlama stilleri değişkendir - bu nedenle sizin için uygun olanlar başkaları için uygun olmayabilir ve bunun tam tersi: Sık sık tavsiye burada mutlak (" her zaman X yapın; Y kötüdür "vb) bazı alanlarda, buna yapışmanın tamamen pratik olmadığını göz ardı ederek. Bu yüzden Clean Code gibi kitaplarda tavsiye her zaman bir tutam (pratik) tuzla alınmalıdır :)
psmears

Yanıtlar:


104

Bu kurallar bir pusuladır, harita değildir. Seni mantıklı bir yöne işaret ediyorlar . Ancak, size hangi çözümün “en iyi” olduğunu kesin olarak söyleyemezler. Bir noktada pusulanızın gösterdiği yöne doğru yürümeyi bırakmanız gerekir, çünkü varış noktanıza geldiniz.

Temiz Kod, kodunuzu çok küçük ve belirgin bloklara bölmenizi önerir. Bu genel olarak iyi bir yöndür. Ancak en uç noktaya alındığında (alıntı yapılan tavsiyelerin tam anlamıyla yorumlanmasının önerdiği gibi), o zaman kodunuzu gereksiz yere küçük parçalara böldünüz. Hiçbir şey gerçekten bir şey yapmaz, her şey delege eder. Bu aslında başka bir tür kod gizliliğidir.

“Daha küçük daha iyidir” ile “çok küçük işe yaramaz” a dengelemek sizin işinizdir. Hangi çözümün daha basit olduğunu kendinize sorun. Benim için açıkça belli olan bir liste oluşturduğu için ilk çözüm budur . Bu iyi anlaşılmış bir deyimdir. Başka bir işleve bakmak zorunda kalmadan bu kodu anlamak mümkündür.

Daha iyisini yapmak mümkün ise, “tüm elemanları bir listeden başka bir listeye dönüştür” ün, işlevsel bir map()işlem kullanarak soyutlanabilen genel bir kalıp olduğunu belirtmek gerekir . C #, bence denir Select. Bunun gibi bir şey:

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    return badaBooms.Select(BadaBoom => CreateFlurp(badaBoom)).ToList();
}

7
Kod hala yanlıştır ve tekerleği anlamsızca yeniden icat eder. Neden çağrı CreateFlurps(someList)BCL zaten sağladığında someList.ConvertAll(CreateFlurp)?
Ben Voigt

44
@BenVoigt Bu tasarım düzeyinde bir sorudur. Özellikle bir beyaz tahta bir derleyiciye sahip olmadığı için kesin bir sözdizimiyle ilgilenmiyorum (ve son olarak '09'da C # yazdım). Demek istediğim “mümkün olan en iyi kodu gösterdim” değil, “bir yana, bu zaten çözülmüş olan ortak bir kalıptır”. Linq bunu yapmanın bir yoludur, ConvertAll'den bahsettiğiniz başka bir şey . Bu alternatifi önerdiğiniz için teşekkürler.
amon

1
Cevabınız mantıklı, ancak LINQ'un mantığı ortadan kaldırması ve ifadeyi bir satır sonuna indirmesi gerçeği, sizin tavsiyenize aykırı görünüyor. Bir yan not olarak, BadaBoom => CreateFlurp(badaBoom)gereksizdir; CreateFlurpdoğrudan işlev olarak geçebilirsiniz ( Select(CreateFlurp)). (Bildiğim kadarıyla, bu her zaman böyle olmuştur.)
jpmc26

2
Bunun, yönteme olan ihtiyacı tamamen ortadan kaldırdığına dikkat edin. Bu isim CreateFlurpsaslında sadece görmekten daha yanıltıcı ve anlaşılması zor badaBooms.Select(CreateFlurp). İkincisi tamamen bildirimseldir - ayrıştıracak bir sorun yoktur ve bu nedenle bir yönteme hiç gerek yoktur.
Carl Leth

1
@ R.Schmitz Anlaşılması zor değil , ama anlaşılması daha az kolaybadaBooms.Select(CreateFlurp) . Adının (yüksek seviye) uygulanması için geçerli olduğu (düşük seviye) bir yöntem yaratırsınız. Bu durumda aynı düzeydedirler, bu yüzden tam olarak ne olduğunu bulmak için sadece yönteme bakmalıyım (satır içi görmek yerine). CreateFlurps(badaBooms)sürpriz badaBooms.Select(CreateFlurp)yapabilir , ama yapamaz. Aynı zamanda yanıltıcıdır, çünkü yanlış bir Listyerine soruyor IEnumerable.
Carl Leth

61

Bir işlev için ideal argüman sayısı sıfırdır (niladik)

Yok hayır! Bir işlev için ideal argüman sayısı birdir. Sıfır ise, bir eylem gerçekleştirebilmek için işlevin dış bilgilere erişmesi gerektiğini garanti edersiniz. "Amca" Bob bunu çok yanlış anladı.

Kodunuzla ilgili olarak, ilk örneğiniz blokta yalnızca iki satır içeriyor, çünkü ilk satırda yerel bir değişken oluşturuyorsunuz. Bu ödevi kaldırın, bu temiz kod yönergelerine uyuyorsunuz:

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    List<Flurp> flurps = new List<Flurp>();
    foreach (BadaBoom badaBoom in badaBooms)
    {
        flurps.Add(CreateFlurp(badaBoom));
    }
    return flurps;
}

Ama bu çok uzun zamandır solmuş (C #) kod. Sadece şu şekilde yapın:

IEnumerable<Flurp> CreateFlurps(IEnumerable<BadaBoom> badaBooms) =>
    from badaBoom in babaBooms select CreateFlurp(badaBoom);

14
Sıfır argüman içeren bir işlev, nesnenin bir nesnenin dışında genel bir durumda var olmadığından istenen verileri kapsamaya sokması anlamına gelir.
Ryathal,

19
@ Ryathal, iki nokta: (1) eğer yöntemlerden bahsediyorsanız, o zaman çoğu (tümü?) OO dilleri için, o nesne ilk parametre olarak çıkarılır (veya açıkça belirtilir). Java, C # etc, tüm yöntemler en az bir parametreli işlevlerdir. Derleyici bu ayrıntıyı sizden gizler. (2) "Global" den hiç bahsetmedim. Nesne durumu, örneğin bir yöntemin dışındadır.
David Arno,

17
Bob Amca "sıfır" yazdığında "sıfır (demek değil)" anlamına geldiğinden eminim.
Doktor Brown,

26
@DocBrown, muhtemelen nesnelerdeki karışım halinin ve işlevselliğinin büyük bir hayranı olduğu için "işlev" ile büyük olasılıkla özellikle yöntemleri ifade ediyor. Ve hala onunla aynı fikirde değilim. İstediği şeyi elde etmek için nesnede dolaşmak yerine, yalnızca ihtiyaç duydukları bir yöntemi vermek çok daha iyidir (yani, klasik olarak "söyleme, sorma" dır).
David Arno

8
@AlessandroTeruzzi, İdeal bir parametredir. Sıfır çok az. Bu nedenle, örneğin, işlevsel diller, birini, kurutma amaçlı parametre sayısı olarak kabul eder (aslında bazı işlevsel dillerde, tüm işlevlerin tek bir parametresi vardır: daha fazla değil; daha az değil). Sıfır parametresiyle körleştirme saçma olur. "İdeal olanın olabildiğince az, ergo sıfırın en iyisi" olduğunu belirterek, indirgeme ve reklam ihmalinin bir örneği .
David Arno,

19

'Temiz Kod' Tavsiyesi tamamen yanlıştır.

Döngünüzde iki veya daha fazla satır kullanın. Bir fonksiyonda aynı iki çizgiyi saklamak, bir açıklama gerektiren rasgele bir matematik olduklarında anlamlıdır, ancak çizgiler zaten açıklayıcı olduğunda hiçbir şey yapmaz. 'Oluştur' ve 'Ekle'

Bahsettiğiniz ikinci yöntem hiçbir anlam ifade etmiyor, çünkü iki satırı önlemek için ikinci bir argüman eklemek zorunda değilsiniz.

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
    {
        List<Flurp> flurps = new List<Flurp>();
        foreach (BadaBoom badaBoom in badaBooms)
        {
            flurps.Add(badaBoom .CreateFlurp());
            //or
            badaBoom.AddToListAsFlurp(flurps);
            //or
            flurps.Add(new Flurp(badaBoom));
            //or
            //make flurps a member of the class
            //use linq.Select()
            //etc
        }
        return flurps;
    }

veya

foreach(var flurp in ConvertToFlurps(badaBooms))...

Diğerleri tarafından belirtildiği gibi, en iyi işlevin tartışmasız olduğu yönündeki tavsiye, en iyi ihtimalle OOP'a ve en kötüsü de kötü öneriye çarpıktır.


Belki de daha net yapmak için bu cevabı düzenlemek istersin? Benim sorum, bir şeyin Temiz Kod kapsamında diğerlerinden ağır basmasıydı. Her şeyin yanlış olduğunu söylüyorsunuz ve ardından verdiğim seçeneklerden birini tanımlamaya devam ediyorsunuz. Şu anda bu cevap, aslında soruyu yanıtlamaya çalışmak yerine bir Temizlik Yasağı önleme gündemini takip ediyor gibi görünüyor.
R. Schmitz

Üzgünüm sorunuzu birincinin 'normal' yol olduğunu düşünerek yorumladım, ancak ikinciye zorlandınız. Genel olarak anti-code-kod değilim, ancak bu alıntı açıkça yanlıştır
Ewan

19
@ R.Schmitz Kendimi "Temiz Kod" okudum ve kitabın söylediklerinin çoğunu takip ediyorum. Bununla birlikte, mükemmel fonksiyon büyüklüğünün hemen hemen tek bir ifade olmasıyla ilgili olarak, sadece yanlış bir şeydir. Tek etki, spagetti kodunu pirinç koduna dönüştürmesidir. Okuyucu, birlikte görüldüğünde yalnızca anlamlı bir anlam üreten önemsiz işlevlerin çoğunda kaybolur. İnsanların sınırlı bir çalışma belleği kapasitesi vardır ve bunu ifadelerle veya işlevlerle aşırı yükleyebilirsiniz. Okunabilir olmak istiyorsanız, ikisi arasında bir denge kurmalısınız. Aşırı kaçın!
cmaster

@cmaster Cevap bu yorumu yazdığımda sadece ilk 2 paragraftı. Şimdiye kadar daha iyi bir cevap.
R. Schmitz

7
Açıkçası kısa cevabımı tercih ettim. Bu cevapların çoğunda çok fazla diplomatik konuşma var. Alıntı yapılan tavsiyeler açıkça yanlıştır, “ne anlama geldiğini” belirtmek zorunda kalmazsınız veya iyi bir yorum bulmak için uğraşırsınız.
Ewan,

15

İkincisi, CreateFlurpInListlisteyi kabul ettiği ve değiştirdiği için işlevi daha da saflaştırır, çünkü işlevi saf değil ve nedenini zorlaştırır. Yöntem adındaki hiçbir şey, yöntemin yalnızca listeye eklendiğini göstermez.

Ve üçüncü, en iyi seçeneği sunuyoruz:

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    return badaBooms.Select(CreateFlurp).ToList();
}

Ve cehennem, tek bir astar kendiliğinden açık olduğu için, kullanıldığı yerde yalnızca bir yer varsa, bu yöntemi hemen kullanabilirsiniz, bu yüzden anlam vermek için yöntemle kapsüllenmesi gerekmez.


Bu yöntem hakkında, "saf olmasına zor ve nedeninden daha zor olması" (doğru olsa da) hakkında çok fazla şikayet etmeyeceğim, ancak özel bir durumla ilgilenmek için tamamen gereksiz bir yöntem olduğu konusunda şikayetçi olmayacağım. Bağımsız bir Flurp, bir diziye eklenen bir Flurp, bir sözlüğe, daha sonra bir sözlükte aranan bir Flurp ve eşleşen Flurp'un kaldırıldığı vb. Oluşturmak istersem ne olur? Aynı argümanla, Flurp kodu da tüm bu yöntemlere ihtiyaç duyacaktır.
gnasher729

10

Bir argüman sürümü daha iyi, ancak öncelikle argüman sayısı nedeniyle değil.

Daha iyi olmasının en önemli nedeni, daha az kapline sahip olmasıdır , bu da daha kullanışlı, nedene göre daha kolay, test edilmesi daha kolay ve kopyalanan + kopyalanan klonlara dönüşme olasılığı daha düşüktür.

Eğer bir bana sağlarsanız CreateFlurp(BadaBoom)Basit: Ben toplama kabının herhangi bir tür onu kullanabilirsiniz Flurp[], List<Flurp>, LinkedList<Flurp>, Dictionary<Key, Flurp>, vb. Ancak, bir ile CreateFlurpInList(BadaBoom, List<Flurp>), CreateFlurpInBindingList(BadaBoom, BindingList<Flurp>)görüş modelimin listenin değiştiği bildirimini alabilmesi için yarın rica ediyorum. Yuck!

Ek bir avantaj olarak, basit imzanın mevcut API'lara uyması daha olasıdır. Sürekli bir problemin olduğunu söylüyorsun

oldukça sık kendimi başka bir listeden bir liste oluştururken buluyorum

Sadece mevcut araçları kullanma meselesi. En kısa, en verimli ve en iyi sürüm:

var Flurps = badaBooms.ConvertAll(CreateFlurp);

Yazmanız ve test etmeniz için sadece bu daha az kod değil, aynı zamanda daha hızlıdır, çünkü List<T>.ConvertAll()sonucun girdiyle aynı sayıda öğeye sahip olacağını bilecek kadar akıllıdır ve sonuç listesini doğru boyuta önceden dağıtırsınız. Kodunuz (her iki versiyonda da) listeyi büyütmek için gerekliydi.


Kullanmayın List.ConvertAll. C # içindeki farklı nesnelerle numaralandırılabilir nesnelerin eşleştirilmesinin deyimsel yolu denir Select. ConvertAllBurada bile mevcut tek sebep OP'nin hatalı bir Listşekilde yöntemde bir soru sormasıdır - bu bir olmalıdır IEnumerable.
Carl Leth

6

Genel amacı göz önünde bulundurun: kodu okumasını ve sürdürmesini kolaylaştırmak.

Genellikle, birden çok satırı tek ve anlamlı bir işlevde gruplamak mümkün olacaktır. Bu gibi durumlarda yapın. Bazen, genel yaklaşımınızı yeniden gözden geçirmeniz gerekir.

Örneğin, sizin durumunuzda tüm uygulamanın var ile değiştirilmesi

flups = badaBooms.Select(bb => new Flurp(bb));

bir olasılık olabilir. Veya böyle bir şey yapabilirsin

flups.Add(new Flurp(badaBoom))

Bazen, en temiz ve en okunabilir çözüm basitçe bir satıra sığmayabilir. Yani iki satırın olacak. Kodun anlaşılmasını zorlaştırmayın, sadece bazı keyfi kuralları yerine getirmek için.

İkinci örneğiniz (bence) ilkinden daha anlaşılması zor. Sadece ikinci bir parametrenizin değil, parametrenin fonksiyon tarafından değiştirilmesidir. Bu konuda Temiz Kod'un söylediklerine bakın. (Kitabı şu anda elinizde bulundurmayın, ancak temelde "bunu önleyebiliyorsanız yapmayın" yazdığından eminim).

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.