Sadece bir kere çağrılan tek satırlık fonksiyonlar


120

Tek bir kod satırı gerçekleştiren ve programda yalnızca bir kez çağrılan (gelecekte tekrar gerekli olması imkansız olmasa da) parametresiz ( düzenleme: zorunlu değil) bir işlev düşünün .

Bir sorgulama yapabilir, bazı değerleri kontrol edebilir, regex içeren bir şey yapabilir ... belirsiz olan herhangi bir şey veya "hack" olabilir.

Bunun arkasındaki mantık, zor okunabilen değerlendirmelerden kaçınmak olacaktır:

if (getCondition()) {
    // do stuff
}

getCondition()tek satır işlevi nerede ?

Sorum basit: bu iyi bir uygulama mı? Bana tamam gibi görünüyor ama uzun vadede bilmiyorum ...


9
Kod parçanız örtük alıcıya sahip bir OOP diline ait değilse (örneğin bu), bu kesinlikle kötü bir uygulama olacaktır, çünkü getCondition () büyük olasılıkla küresel duruma dayanır ...
Ingo

2
GetCondition () 'in argümanlarının olabileceğini ima etmek zorunda kalabilirdi?
user606723 12:11

24
@Ingo - bazı şeylerin gerçekten Global hali var. Şimdiki zaman, host isimleri, port numaraları vs. hepsi geçerli "Globals". Tasarım hatası, Global'i doğası gereği global olmayan bir şeyden çıkarmaktır.
James Anderson

1
Neden sadece satır içi değilsin getCondition? Eğer söylediğiniz kadar küçük ve sıkça kullanılıyorsa, o zaman bir isim vermek hiçbir şeyi yapmıyor demektir.
davidk01 13:11

12
davidk01: Kod okunabilirliği.
wjl

Yanıtlar:


240

Bir hatta bağlı. Satır okunabilir ve özlü ise, fonksiyon gerekmeyebilir. Basit örnek:

void printNewLine() {
  System.out.println();
}

OTOH, işlev, örneğin karmaşık, okunması zor bir ifade içeren bir kod satırına iyi bir ad verirse, mükemmel bir şekilde haklı çıkarıldı (bana). Tartışmalı örnek (burada okunabilirlik için birden fazla satıra bölünmüş):

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}

99
+1. Buradaki sihirli kelime "Kendini belgeleyen koddur".
Konamiman

8
Bob Amca'nın "kapsüllü koşullayıcılar" dediği şeye güzel bir örnek
Anthony Pegram

12
@Aditya: Hiçbir şey taxPayerbu senaryoda küresel olmadığını söylüyor . Belki de bu sınıf bir niteliktir TaxReturnve taxPayerbir niteliktir.
Adam Robinson,

2
Ek olarak, işlevi örneğin javadoc tarafından alınabilecek ve herkes tarafından görülebilecek şekilde belgelemenize izin verir.

2
@dallin, Bob Martin'in Temiz Kodu birçok yarı gerçek yaşam kodu örneği gösterir. Tek bir sınıfta çok fazla fonksiyonunuz varsa, belki de sınıfınız çok mu büyük?
Péter Török

67

Evet, bu en iyi uygulamaları karşılamak için kullanılabilir. Örneğin, açıkça adlandırılmış bir işlevin bazı işlerini yapması, sadece bir satır olsa bile, daha büyük bir işlev içinde bu kod satırına sahip olmaktan ve ne yaptığını açıklayan tek satırlık bir yorumdan daha iyidir. Ayrıca, komşu kod satırları aynı soyutlama düzeyinde görevleri gerçekleştirmelidir. Bir karşı örnek gibi bir şey olurdu

startIgnition();
petrolFlag |= 0x006A;
engageChoke();

Bu durumda, orta çizgiyi makul şekilde adlandırılmış bir işleve taşımak kesinlikle daha iyidir.


15
Bu 0x006A çok güzel: DA, iyi bilinen a, b ve c katkılarıyla karbonize edilmiş yakıt sabiti.
Kodlayıcı

2
+1 Ben sadece kendi kendini kodlayan kodlar için :) bu arada, bloklar aracılığıyla sabit bir soyutlama seviyesi tutmaya karar verdiğimi düşünüyorum, ancak nedenini açıklayamadım. Bunu genişletir misin?
vemv

3
Sadece petrolFlag |= 0x006A;herhangi bir karar petrolFlag |= A_B_C; vermeden söylüyorsanız , ek bir işlev görmeden söylemeniz daha iyi olur . Muhtemelen, engageChoke()sadece petrolFlagbelirli bir kritere uyuyorsa çağrılmalı ve açıkça “burada bir işleve ihtiyacım var” demeli. Sadece küçük bir nit, bu cevap temelde aksi üzerinde nokta :)
Tim Post

9
Bir yöntem içindeki kodun aynı soyutlama seviyesinde olması gerektiğini belirtmek için +1. @vemv, bu iyi bir uygulamadır çünkü kodu okurken zihninizin farklı seviyeler arasında yukarı ve aşağı zıplama gereksinimini önleyerek kodu anlamayı kolaylaştırır. Soyutlama seviyesinin bağlanması yöntem çağrılarına / geri dönüşlerine geçer (yani yapısal "sıçramalar"), kodu daha akıcı ve temiz hale getirmek için iyi bir yoldur.
Péter Török 13:11

21
Hayır, tamamen yapılmış bir değer. Fakat bununla ilgili merak ettiğiniz şey yalnızca konuyu vurgulamaktadır: eğer kod mixLeadedGasoline()söyleseydi, mecbur kalmazsınız!
Kilian Foth

37

Birçok durumda böyle bir işlevin iyi bir tarz olduğunu düşünüyorum, ancak bu koşulu başka yerlerde kullanmanız gerekmediği durumlarda yerel bir boolean değişkenini alternatif olarak düşünebilirsiniz.

bool someConditionSatisfied = [complex expression];

Bu, kod okuyucusuna bir ipucu verecek ve yeni bir fonksiyon tanıtmaktan tasarruf sağlayacaktır.


1
Bool, durum işlevi adının zor veya muhtemelen yanıltıcı olması durumunda özellikle yararlıdır (örn. IsEngineReadyUnlessItIsOffOrBusyOrOutOfService).
dbkk

2
Bu tavsiyeyi kötü bir fikir olarak deneyimledim: bool ve durum, dikkatini işlevin temel işinden uzaklaştırır. Ayrıca, yerel değişkenleri tercih eden bir stil yeniden düzenlemeyi zorlaştırır.
Kurt

1
@Wolf OTOH, kodun "soyutlama derinliğini" azaltmak için bunu bir işlev çağrısına tercih ederim. IMO, bir işleve atlamak, özellikle bir boolean döndürürse, açık ve doğrudan bir çift kod satırından daha büyük bir bağlam anahtarıdır.
Kache 19:16

@Kache Nesne yönelimli kodlamanıza bağlı olup olmadığına bağlı olduğunu düşünüyorum, OOP durumunda üye işlevinin kullanımı tasarımı çok daha esnek tutar. Gerçekten bağlamına bağlı ...
Wolf

23

Peter'ın cevabına ek olarak , bu koşulun gelecekte bir noktada güncellenmesi gerekebilirse, yalnızca tek bir düzenleme noktasına sahip olmanızı önerdiğiniz şekilde kapatarak.

Peter'ın örneğini takip ediyor, eğer öyleyse

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isFemale() 
        && (taxPayer.getNumberOfChildren() > 2 
        || (taxPayer.getAge() > 50 && taxPayer.getEmployer().isNonProfit()));
}

bu olur

boolean isTaxPayerEligibleForTaxRefund() {
  return taxPayer.isMutant() 
        && (taxPayer.getNumberOfThumbs() > 2 
        || (taxPayer.getAge() > 123 && taxPayer.getEmployer().isXMan()));
}

Tek bir düzenleme yaparsınız ve evrensel olarak güncellenir. Bakım akıllıca, bu bir artı.

Performans açısından, çoğu optimizasyon derleyici işlev çağrısını kaldıracak ve küçük kod bloğunu yine de satır içi olarak kullanacaktır. Bunun gibi bir şeyi optimize etmek aslında blok boyutunu küçültebilir (işlev çağrısı, dönüş, vb. İçin gerekli talimatları silerek), aksi halde satır içi engellemeyi önleyebilecek koşullarda bile yapmak güvenlidir.


2
Zaten tek bir düzenleme noktası tutmanın faydalarının farkındayım - bu yüzden “… bir kez denir” sorusu yazıyor. Bununla birlikte, bu derleyici optimizasyonlarını bilmek harika, her zaman bu tür talimatları tam anlamıyla takip ettiklerini düşündüm.
vemv

Basit bir koşulu test etmek için bir yöntemi bölmek, yöntemi değiştirerek bir koşulun değiştirilebileceği anlamına gelir. Bu ima, doğru olduğunda faydalıdır, ancak olmadığında tehlikeli olabilir. Örneğin, kodun işleri hafif nesnelere, ağır yeşil nesnelere ve ağır yeşil olmayan nesnelere bölmesi gerektiğini varsayalım. Nesneler, ağır nesneler için güvenilir olan, ancak hafif nesnelerle yanlış pozitifler döndürebilen hızlı bir "seemGreen" özelliğine sahiptir; ayrıca yavaş olan, ancak tüm nesneler için güvenilir bir şekilde çalışacak bir "measureSpectralResponse" işlevine sahiptir.
supercat

Aslında seemsGreenkod asla hafif nesneler yeşil olup olmadığını umurunda olmadığını hafif nesnelerle güvenilmez olacak alakasız olacak. Bununla birlikte, "hafif" tanımının, gerçek için geri dönen bazı yeşil olmayan nesnelerin seemsGreen"hafif" olarak bildirilmemesi için değişmesi durumunda, "hafif" tanımında yapılan böyle bir değişiklik, nesneler için test eden kodu kırabilir yeşil olmak". Bazı durumlarda, kodda yeşilliği ve ağırlığı test etmek, testler arasındaki ilişkiyi, ayrı yöntemlere göre daha net hale getirebilir.
supercat,

5

Okunabilirliğe ek olarak (veya tamamlayıcı olarak) bu, fonksiyonların uygun soyutlama düzeyinde yazılmasına izin verir.


1
Korkarım ne demek istediğini anlamadım ...
vemv 12:11

1
@vemv: Bence farklı seviyelerde karışık karışık (yani Kilian Foth'un söylediği gibi) koda sahip olmadığınız “uygun soyutlama seviyesi” anlamına geliyor. Çevredeki kod daha eldeki durumun daha yüksek bir görünümünü (örneğin bir motorun çalıştığını) düşünürken, kodunuzun görünüşte önemsiz ayrıntıları (örneğin yakıttaki tüm bağların oluşumu gibi) ele almasını istemiyorsunuz. Birincisi ikincisini tutar ve herhangi bir zamanda tüm soyutlama seviyelerini göz önünde bulundurmanız gerektiğinden, kodunuzu okumayı ve sürdürmeyi kolaylaştırır.
Egon

4

Değişir. Bazen bir ifadeyi bir işlev / yöntemde kaplamak, sadece bir satır olsa bile daha iyidir. Okumak karmaşıksa veya birden fazla yerde ihtiyaç duyuyorsan, o zaman bunun iyi bir uygulama olduğunu düşünüyorum. Uzun vadede, tek bir değişim noktası ve daha iyi okunabilirlik sunduğunuz için bakımı daha kolaydır .

Ancak, bazen sadece ihtiyacınız olmayan bir şey. İfade yine de okunması kolaysa ve / veya sadece tek bir yerde göründüğünde, sarmayın.


3

Bence bunlardan sadece birkaçı varsa sorun değil, ancak kodunuzda bir sürü olduğunda sorun ortaya çıkıyor. Ve derleyici çalıştığında ya da yorumlayıcı (kullandığınız dile bağlı olarak) bellekte bu işleve gidiyor. Öyleyse, 3 tanesine sahip olduğunuzu varsayalım, bilgisayarın farkına varacağını sanmıyorum, ancak bu küçük şeylerden 100'ünü almaya başlarsanız, sistem yalnızca bir kez çağrılan ve daha sonra tahrip edilmeyen fonksiyonları belleğe kaydetmelidir.


Stephen'ın cevabına göre, bu her zaman gerçekleşmeyebilir (derleyicilerin
sihirine

1
evet, birçok gerçeklere bağlı olarak ortadan kaldırılmalıdır. Tercüme edilmiş bir dil ise, sistem önbellek için hile yapamayacak bir şey takmadığınız sürece her seferinde tek satırda çalışır. Şimdi, derleyiciler söz konusu olduğunda, derleyici arkadaşınız olacaksa ve küçük pisliğinizden kurtulacaksa veya gerçekten ihtiyaç duyacağınıza karar verecekseniz, zayıfların olduğu günlerde ve planların yerleştirilmesinde önemlidir. Bazen bir döngü tam olarak biliyorsanız, bir döngüyü kopyalamak ve yapıştırmak için her zaman bazı zamanlar daha iyi çalışacağını hatırlatırım.
WojonsTech

Gezegenlerin hizalanması için +1 :) ama son cümlenin bana tamamen çılgınca geliyor, gerçekten bunu yapıyor musun?
vemv

Bu gerçekten bağlıdır Çoğu zaman hayır, hızlanma ve bunun gibi diğer şeyleri hızlandırıp yapmadığını kontrol etmek için doğru miktarda ödeme almadığım sürece yapmıyorum. Fakat daha eski derleyicilerde kopyalayıp yapıştırmak daha iyiydi, daha sonra derleyicileri 9 / 10'u bulmak için terk etmek.
WojonsTech

3

Bu son şeyi, yakın zamanda yeniden yapılandıracağım bir uygulamada yaptım, kodun yorumlarının gerçek anlamını açıkça belirtmek için:

protected void PaymentButton_Click(object sender, EventArgs e)
    Func<bool> HaveError = () => lblCreditCardError.Text == string.Empty && lblDisclaimer.Text == string.Empty;

    CheckInputs();

    if(HaveError())
        return;

    ...
}

Sadece merak ediyorum, bu hangi dil?
vemv

5
@vemv: Bana C # gibi geldi.
Scott Mitchell

Ayrıca yorumlarda ekstra tanımlayıcıları tercih ederim. Ancak bu kısa kalmak için yerel bir değişken tanıtmaktan gerçekten farklı ifmı? Bu yerel (lambda) yaklaşım, PaymentButton_Clickfonksiyonu (bir bütün olarak) okumayı zorlaştırır. lblCreditCardErrorSizin örnekte üyesi gibi görünüyor, bu yüzden de HaveErrornesne için geçerli olan bir (özel) önermedir. Bunu reddetme eğilimindeydim, ama bir C # programcısı değilim, bu yüzden direniyorum.
Kurt

@ Kurt Hey adamım, evet. Bunu bir süre önce yazdım :) Bugünlerde kesinlikle farklı şeyler yapıyorum. Aslında, bir hata olup olmadığını görmek için etiket içeriğine bakmak beni sıkıntıya sokuyor ... Neden ben sadece bir bool iade etmedim CheckInputs()???
joshperry

0

Bir satırı iyi adlandırılmış bir yönteme taşımak, kodunuzun okunmasını kolaylaştırır. Pek çok kişi bundan (“kendi kendini belgeleyen kod”) bahsetti. Onu bir yönteme taşımanın diğer bir avantajı da ünite testini kolaylaştırmasıdır. Kendi yöntemine göre izole edildiğinde ve ünite test edildiğinde, bir hata bulunursa / bu yöntemde olmayacağından emin olabilirsiniz.


0

Çok sayıda iyi cevap var, ancak bahsetmeye değer özel bir durum var .

Tek satırlı ifadeniz bir yoruma ihtiyaç duyuyorsa ve amacını açıkça tanımlayabiliyorsanız (yani: ad), yorumu API belgesine eklerken bir işlev çıkarmayı düşünün. Bu sayede fonksiyon çağrısını daha kolay ve anlaşılır hale getirirsiniz.

İlginç bir şekilde, şu anda yapacak bir şey yoksa, aynı şey yapılabilir, ancak gereken genişlemeleri hatırlatan bir yorum (çok yakın gelecekte 1) ), yani bu,

def sophisticatedHello():
    # todo set up
    say("hello")
    # todo tear down

Bu şekilde de değişmiş olabilir

def sophisticatedHello():
    setUp()
    say("hello")
    tearDown()

1) Bundan gerçekten emin olmalısınız (bakınız YAGNI ilkesi).


0

Dil destekliyorsa, bunu yapmak için genellikle etiketli isimsiz fonksiyonları kullanırım.

someCondition = lambda p: True if [complicated expression involving p] else False
#I explicitly write the function with a ternary to make it clear this is a a predicate
if (someCondition(p)):
    #do stuff...

IMHO bu iyi bir uzlaşmadır, çünkü ifgenel / paket ad alanını küçük fırlatma etiketleriyle karıştırmadan kaçınırken , karmaşık ifadenin koşulu karıştırmaz olmasının okunabilirlik avantajını sağlar . "Tanım" fonksiyonunun, tanımın değiştirilmesini ve okunmasını kolaylaştığı yerlerde kullanılması doğru değildir.

Sadece yordam işlevleri olmak zorunda değildir. Tekrarlanan kazan plakasını da bunun gibi küçük fonksiyonlarda muhafaza etmeyi seviyorum (Braket sözdizimini karıştırmadan pitonik liste oluşturmak için özellikle iyi çalışıyor). Örneğin, python içinde PIL ile çalışırken aşağıdaki basitleştirilmiş örnek

#goal - I have a list of PIL Image objects and I want them all as grayscale (uint8) numpy arrays
im_2_arr = lambda im: array(im.convert('L')) 
arr_list = [im_2_arr(image) for image in image_list]

Neden "lambda p: [p'yi içeren karmaşık ifade] başka" Yanlış "ise" lambda p: [p] içeren karmaşık ifade "ise doğru mu? 8-)
Hans-Peter Störr 13:11

@hstoerr bu satırın hemen altındaki yorumunda. Biz bazı şartların bir öngörü olduğunu açıkça belirtmek isterim. Onun kesinlikle gereksiz olsa da, fazla, ben şahsen düşünüyorum kod olmayan kişiler tarafından okunan bilimsel komut bir çok yazma onun onlar bunu bilmiyor çünkü ekstra terseness sahip ziyade meslektaşlarım karıştı olması daha uygun [] == Falseya da başka bir her zaman sezgisel olmayan benzer pytonik denklik. Temel olarak bazı Koşulların aslında bir belirteç olduğunu işaretlemenin bir yolu.
crasic

Sadece bariz [] != False[]
hatamı gidermek

@crasic: Okuyucularımdan [] öğesinin False olarak değerlendirdiğini bilmesini beklemiyorsam, okuyucunun [] öğesinin False olarak değerlendirdiğini bilmesini gerektiren şeyleri len([complicated expression producing a list]) == 0kullanmak yerine kullanmayı tercih ederim True if [blah] else False.
Yalan Ryan
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.