İki kez tekrar etmesi gereken bir şey için bir işlev yazmak her zaman en iyi yöntem midir?


131

Kendim, ikiden fazla bir şey yapmam gerektiğinde bir işlev yazmak için sabırsızlanıyorum. Ancak, sadece iki kez görünen şeylere gelince, biraz daha zor.

İkiden fazla satır gerektiren kod için bir fonksiyon yazacağım. Ama şöyle şeylerle yüzleşirken:

print "Hi, Tom"
print "Hi, Mary"

Yazmakta tereddüt ediyorum:

def greeting(name):
    print "Hi, " + name

greeting('Tom')
greeting('Mary')

İkincisi çok fazla görünüyor, değil mi?


Ama ya biz varsa:

for name in vip_list:
    print name
for name in guest_list:
    print name

Ve işte alternatif:

def print_name(name_list):
    for name in name_list:
        print name

print_name(vip_list)
print_name(guest_list)

İşler zorlaşıyor, değil mi? Şimdi karar vermek zor.

Bu konudaki fikriniz nedir?


145
Ben tedavi alwaysve neverkırmızı bayraklar gibi. "Bağlam" denen bir şey var, alwaysve neverhatta genel olarak iyi olmadığını kuralları, uygun olduğunu olmayabilir. Mutlak işlerle ilgilenen yazılım geliştiricilere dikkat edin. ;)
asenkron

7
Son örnekte yapabileceğiniz: from itertools import chain; for name in chain(vip_list, guest_list): print(name).
Bakuriu

14
@ user16547 Biz bu 'sithware geliştiricileri' diyoruz!
Brian

6
Tim Peters'ın Python Zeninden:Special cases aren't special enough to break the rules. Although practicality beats purity.
Zenon

3
Ayrıca "nemli kod" veya "kuru kod" isteyip istemediğinizi düşünün
Ian

Yanıtlar:


201

Bir işlevi bölmeye karar vermede bir faktör olmasına rağmen, bir şeyin tekrar edilme sayısı tek faktör olmamalıdır. Genellikle yalnızca bir kez çalıştırılan bir şey için bir işlev yaratmanın anlamı vardır . Genellikle, ne zaman bir işlevi bölmek istediğinizde:

  • Her bir soyutlama katmanını basitleştirir.
  • Ayırma işlevleri için iyi ve anlamlı isimleriniz vardır, bu yüzden neler olup bittiğini anlamak için soyutlama katmanları arasında atlamak zorunda kalmazsınız.

Örnekleriniz bu kriterleri karşılamıyor. Bir astardan bir astara gidiyorsunuz ve isimler size netlik açısından hiçbir şey almıyorlar. Olduğu söyleniyor, basit fonksiyonlar öğreticiler ve okul ödevleri dışında nadirdir. Programcıların çoğu, diğer yoldan çok fazla hata yapma eğilimindedir.


RobertHarvey'in cevabında özlediğim nokta bu.
Doc Brown

1
Aslında. Genellikle karmaşık işlevleri satır içi alt yordamlara bölerim; Anonim kapsamlar da iyidir.
imallett

3
Ayrıca bir kapsam sorunu var. Muhtemelen yazmaya mantıklı değil print_name (NAME_LIST) bir bütün sınıf veya tüm modül görebilirsiniz fonksiyonu, ancak olabilir (bu durumda hala streç) bir temizlemeye bir işlev içinde yerel bir işlev yapmak mantıklı birkaç satır tekrarlanan kod.
Joshua Taylor,

6
Ekleyebileceğim başka bir nokta da standardizasyon. İlk örnekte bir karşılama selamı var ve bunu bir fonksiyona dahil etmek mantıklı olabilir, böylece ikinizi de aynı şekilde selamladığınızdan emin olabilirsiniz. Özellikle selamlama gelecekte değişebilir düşünüyor :)
Svish

1
+1 sadece için Sadece bir kez çalıştırılan bir şey için fonksiyon yaratmanın anlamı vardır. Bir keresinde bir roket motorunun davranışını modellenen bir fonksiyon için testlerin nasıl tasarlanacağı soruldu. Bu işlevin döngüsel karmaşıklığı 90'lı yılların başındaydı ve 90'lık bazı durumlarda (çirkin ama test edilebilir) bir anahtar deyimi değildi. Bunun yerine, mühendisler tarafından yazılmış kıvrımlı bir karışıklıktı ve tamamen denenemezdi. Benim cevabım tam da bu, denenemez ve tekrar yazılması gerektiğiydi. Tavsiyeme uydular!
David Hammen

104

Çoğaltılması durumunda geçerli kasıtlı ziyade kazara .

Veya başka bir yolla:

Eğer olur Yalnızca beklemek onları gelecekte birlikte gelişir.


İşte nedeni:

Bazen iki kod parçaları sadece gerçekleşmesi birbirleriyle ilgisi sahip olmalarına rağmen aynı olma. Bu durumda olmalıdır dahaki sefere biri bunlardan birinin bakım gerçekleştirir çünkü o kişi olacak, bunları birleştirmek güdüsünden değil değişikliklerin önceden varolmayan arayana yaymak için bekliyoruz ve bu fonksiyon sonucunda kırılabilir. Bu nedenle, yalnızca kod boyutunu küçültecek gibi göründüğü zaman değil, yalnızca mantıklı olduğunda kodu çıkarmanız gerekir.

Başparmak kuralı:

Kod yalnızca yeni veriler döndürürse ve mevcut verileri değiştirmezse veya başka yan etkilere sahip değilse, ayrı bir işlev olarak etkisiz hale getirmenin güvenli olması çok olasıdır. (Fonksiyonun amaçlanan semantiğini tamamen değiştirmeden kırılmaya neden olacak bir senaryo hayal edemiyorum, bu noktada fonksiyon adı veya imzası da değişmeli ve bu durumda yine de dikkatli olmalısınız. )


12
+1 Bunun en önemli husus olduğunu düşünüyorum. Hiçbir refaktör kodu, değişikliklerinin son olmasını beklemez, bu nedenle değişikliklerin koddaki gelecekteki değişiklikleri nasıl etkileyeceğini düşünün.
yoniLavi,

2
Bu yakut, ancak Avdi Grimm'den Tesadüfî Çoğaltma hakkındaki bu senaryo hala şu soruyla ilgili: youtube.com/watch?v=E4FluFOC1zM
DGM

60

Hayır, her zaman en iyi uygulama değildir.

Diğer tüm şeylerin eşit, lineer, satır satır kod olması, fonksiyon çağrıları etrafından atlamaktan daha kolay okunur. Önemsiz olmayan bir işlev çağrısı her zaman parametreleri alır, bu nedenle tüm bunları çözmeniz ve işlev çağrısından işlev çağrısına kadar zihinsel bağlamsal atlamalar yapmanız gerekir. Gizlemek için iyi bir nedeniniz yoksa (gerekli performans iyileştirmelerini elde etmek gibi) her zaman daha iyi kod netliğini tercih edin.

Öyleyse neden kod farklı yöntemlerle yeniden yapılandırılıyor? Modülerliği geliştirmek. Bireysel bir yöntemin arkasında önemli işlevsellik toplamak ve anlamlı bir ad vermek. Bunu başaramıyorsanız, o zaman bu ayrı yöntemlere ihtiyacınız yoktur.


58
Kod okunabilirliği en önemli olanıdır.
Tom Robinson,

27
İşlev oluşturmanın en önemli nedenlerinden birini unutmadıysanız, bu cevabı iptal etmiş olurdum: bir işlevsellik parçasına iyi bir ad vererek bir soyutlama oluşturmak . Bu noktayı eklemiş olsaydınız, lineer, satır satır kodun okunması her zaman iyi biçimlendirilmiş soyutlamalar kullanarak koddan daha kolay olmazdı .
Doc Brown

17
Doc Brown ile aynı fikirdeyim, kodun doğru şekilde yapıldığından daha fazla satır satır okunması gerekmiyor. İyi işlev adları, üst düzey işlevlerin ÇOK kolayca okunmasını sağlar (çünkü niyeti okuyorsunuz, uygulama değil) ve düşük düzey işlevlerin okunması daha kolaydır, çünkü bir görevi, tam olarak bir görevi yerine getirir ve o görevden başka bir şey yapmazsınız. Bu fonksiyonun özlü ve kesin olmasını sağlar.
Jon Story,

19
Why take the performance hit of a function call when you don't get any significant benefit by doing so?Hangi performans çarptı? Genel durumda küçük fonksiyonlar sıralanır ve genel fonksiyonlar, büyük fonksiyonlar için önemsizdir. CPython muhtemelen satır içi değil, ancak hiç kimse performans için kullanmaz. Ben de line by line code...biraz sorguladım . Beyninizdeki infazı simüle etmeye çalışıyorsanız daha kolaydır. Ancak bir hata ayıklayıcı daha iyi bir iş çıkarır ve yürütmesini izleyerek bir döngü hakkında bir şeyler kanıtlamaya çalışmak, hepsini yineleyerek tamsayılar hakkında bir açıklama kanıtlamaya çalışmak gibidir.
Doval

6
@Doval, VERY'nin geçerli bir noktasını yükseltir - kod derlendiğinde, işlev satır içi (ve çoğaltılarak) yerleştirilir ve bu nedenle derlemede küçük bir tane olsa bile, çalışma zamanı performansında NO isabeti yoktur. Bu, yorumlanmış diller için doğru değildir, ancak işlev çağrısı, yürütme süresinin bir dakika oranıdır.
Jon Story,

25

Özel örneğinizde bir işlevsellik fazlaca görünebilir; bunun yerine şu soruyu soracağım: Gelecekte bu özel selamlamanın değişmesi mümkün mü? Nasıl yani?

İşlevler yalnızca işlevselliği sağlamak ve yeniden kullanımı kolaylaştırmak için değil, aynı zamanda değişikliği kolaylaştırmak için de kullanılır. Gereksinimler değişirse, kopyalanan yapıştırılan kodun manüel olarak yakalanması ve değiştirilmesi gerekirken, bir işlevle birlikte kodun yalnızca bir kez değiştirilmesi gerekir.

Örneğiniz bundan faydalanamaz - çünkü mantık neredeyse varolmaz - ama muhtemelen bir GREET_OPENINGsabittir. Bu sabit daha sonra dosyadan yüklenebilir, böylece programınız örneğin farklı dillere kolayca uyarlanabilir. Bunun kaba bir çözüm olduğuna dikkat edin, daha karmaşık bir programın i18n'yi izlemek için daha rafine bir yol kullanması gerektiğine dikkat edin, ancak yine de yapılacak işlere ve gereksinimlere göre değişir.

Her şey sonunda olası gereksinimlerle ilgili ve gelecekteki benliğinizin işini kolaylaştırmak için önceden planlama yapmakla ilgili.


Bir GREET_OPENING sabiti kullanmak, tüm olası selamların aynı sırada olduğunu varsayar. Anadili İngilizce ile hemen hemen her şeyi yapan biriyle evli olmak beni bu varsayımdan mahrum ediyor.
Loren Pechtel

22

Şahsen ben üç kuralını benimsedim - ki YAGNI'yi arayarak haklı çıkaracağım . Bir kez bir şey yapmam gerekirse, bu kodu yazacağım, iki kez sadece kopyala / yapıştır yapabilirim (evet sadece kopyala / yapıştır ile itiraf ediyorum!) Çünkü tekrar ihtiyacım olmayacak, ancak aynı şeyi yapmam gerekiyorsa şey yine o zaman planı ayrı ve kendi yöntem haline o öbek ayıklamak olacak, ben bundan kendime, göstermiş olacaktır tekrar gerekir.

Karl Bielefeldt ve Robert Harvey'in söylediklerine katılıyorum ve söylediklerini yorumlamam, geçersiz kılma kuralının okunabilirlik olduğu yönünde. Kodumu okumayı kolaylaştırırsa, bir işlev oluşturmayı düşünün, DRY ve SLAP gibi şeyleri aklınızda bulundurun . Eğer fonksiyonumda değişen bir soyutlama seviyesine sahip olmaktan kaçınabilirsem, o zaman kafamda yönetmeyi daha kolay buluyorum, bu yüzden fonksiyonlar arasında zıplamam (eğer fonksiyonun adını okuyarak ne yaptığını anlayamıyorsam) benim anahtarlarımda daha az anahtar var demektir. zihinsel süreçler.
Aynı şekilde örneğin işlevleri ve satır içi kodu arasındaki bağlamı geçiş yapmak zorunda değil print "Hi, Tom", benim için işler bu durumda ben belki bir işlev ayıklamak PrintNames() IFF Genel fonksiyonumun geri kalanı çoğunlukla fonksiyon çağrılarıydı.



13

Nadiren belirgindir, bu nedenle seçenekleri tartmanız gerekir:

  • Son tarih (en kısa sürede yanan sunucu odası düzeltme)
  • Kod okunabilirliği (seçimi her iki şekilde de etkileyebilir)
  • Paylaşılan mantığın soyutlama seviyesi (yukarıdakilerle ilgili)
  • Yeniden kullanım gereksinimi (yani, aynı mantığa önem veriyor veya şu anda sadece uygun mu?)
  • Paylaşma güçlüğü (burada python parlıyor, aşağıya bakınız)

C ++ 'da genellikle üç kuralına uyuyorum (yani üçüncü kez aynı şeye ihtiyacım var, onu düzgün bir şekilde paylaşılabilir bir parçaya dönüştürüyorum), ancak deneyimle birlikte, bu kapsamı, kapsamı hakkında daha fazla bilgi sahibi olduğunuzda başlangıçta yapmak daha kolaydır. birlikte çalıştığınız yazılım ve etki alanı.

Bununla birlikte, Python'da tekrarlamak yerine tekrar kullanmak mantığı yerine oldukça hafiftir. IMO, diğer birçok dilde (en azından tarihsel olarak) olduğundan daha fazlası.

Bu nedenle, örneğin yerel argümanlardan bir liste oluşturarak mantığı yerel olarak yeniden kullanmayı düşünün:

def foo():
    for name_list in (vip_list, guest_list): # can be list of tuples, for many args
        for name in name_list:
            print name

Birkaç argümana ihtiyacınız varsa, bir demet tuple kullanabilir ve doğrudan for-loop'a bölebilirsiniz:

def foo2():
    for header, name_list in (('vips': vip_list), ('people': guest_list)): 
        print header + ": "
        for name in name_list:
            print name

veya yerel bir işlev (belki de ikinci örneğiniz budur) mantığın yeniden kullanımını açık kılan, ancak print_name () işlevinin dışında kullanılmadığını da açıkça gösterir:

def foo():
    def print_name(name_list):
        for name in name_list:
            print name

    print_name(vip_list)
    print_name(guest_list)

Özellikle, mantığı ortada bırakmanız gerektiğinde (örneğin, geri dönüş kullandığınızda), işlevler tercih edilir, çünkü mola veya istisnalar gereksiz yere işleri bozabilir.

Her ikisi de aynı mantığı, IMO'yu yinelemekten daha üstündür ve aynı zamanda yalnızca bir arayan tarafından kullanılan (iki kez de olsa) bir genel / sınıf işlevi tanımlamaktan daha az karmaşıktır.


Bu cevabı görmeden önce yeni fonksiyonun kapsamı hakkında benzer bir yorum gönderdim . Sorunun bu yönünü ele alan bir cevap olduğu için memnunum.
Joshua Taylor,

1
+1 - Bu belki de OP'nin aradığı cevap türü değil, ama en azından sorduğu soru ile ilgili olarak, bunun en mantıklı cevap olduğunu düşünüyorum. Bu bir şekilde sizin için dile eklenmesi gereken bir şey.
Patrick Collins,

@PatrickCollins: Oyunuz için teşekkürler! :) Cevabı daha eksiksiz hale getirmek için bazı düşünceler ekledim.
Macke

9

Tüm en iyi uygulamaların, böyle bir soruyu cevaplamak için başvurabileceğiniz özel bir nedeni vardır. Gelecekteki olası bir değişiklik diğer bileşenlerde de değişikliklere neden olduğunda, dalgalanmalardan her zaman kaçınmalısınız.

Örnekte: Standart bir selamlamanın olduğunu varsayıyorsanız ve tüm insanlar için aynı olmasını sağlamak istiyorsanız, o zaman yaz

def std_greeting(name):
    print "Hi, " + name

for name in ["Tom", "Mary"]:
    std_greeting(name)   # even the function call should be written only once

Aksi takdirde, eğer değişecekse, dikkat etmeniz ve standart selamlamayı iki yerde değiştirmeniz gerekecektir.

Bununla birlikte, "Merhaba" sadece kazayla aynıysa ve selamlamalardan birini değiştirmek mutlaka diğerinin de değişmesine neden olmazsa, onları ayrı tutun. Bu nedenle, aşağıdaki değişikliklerin daha muhtemel olduğuna inanmak için mantıklı nedenler varsa bunları ayrı tutun:

print "Hi, Tom"
print "Hello, Mary"

İkinci bölümünüz için, fonksiyonlara ne kadar “paketleneceğine” karar vermeniz muhtemelen iki faktöre bağlıdır:

  • blokları okunabilirlik için küçük tutun
  • blokları yeterince büyük tutun, böylece değişiklikler sadece birkaç blokta gerçekleşir. Çağıran deseni izlemek zor olduğu için çok fazla kümeye ihtiyaç duymazsınız.

İdeal olarak, bir değişiklik meydana geldiğinde, " büyük bir bloğun içindeki bir yerde kod değiştirmem" değil, "aşağıdaki bloğun çoğunu değiştirmem gerekir" düşüneceksiniz .


Döngünüzü göz önünde bulundurarak sadece bir aptal nitpicking - eğer gerçekten yinelenen sadece birkaç kodlanmış isim olması gerekiyorsa ve değişmelerini beklemiyorsak, kullanmamalarının biraz daha okunaklı olduğunu savunuyorum. liste. for name in "Tom", "Mary": std_greeting(name)
yoniLavi

Peki, devam etmek için bir kodlanmış listenin kodun ortasında görünmeyeceğini ve yine de bir değişken olacağını söyleyebiliriz :) Ama doğru, aslında orjinalleri atlayabileceğinizi unuttum.
Gerenuk

5

Adlandırılmış sabitlerin ve işlevlerin en önemli yönü, yazma miktarını azaltacak kadar değil, kullanıldıkları farklı yerleri "iliştirmeleri". Örneğinizle ilgili olarak, dört senaryoyu inceleyin:

  1. Aynı şekilde her iki selam değiştirmek için gerekli olduğunu, [üzere örneğin Bonjour, Tomve Bonjour, Mary].

  2. Bir selamlamayı değiştirmek ancak diğerini olduğu gibi bırakmak gerekir [örneğin Hi, Tomve Guten Tag, Mary].

  3. Her iki selamlamayı da farklı şekilde değiştirmek gerekiyor [örneğin Hello, Tomve Howdy, Mary].

  4. Her iki selamlamanın da değişmesi gerekmiyor.

Her iki selamlamayı değiştirmek hiçbir zaman gerekli değilse, hangi yaklaşımın alındığı önemli değildir. Paylaşılan bir işlevi kullanmak, herhangi bir selamlamayı değiştirmenin onları aynı şekilde değiştirmesinin doğal etkisine sahip olacaktır. Tüm selamlamalar aynı şekilde değişmesi gerekiyorsa, bu iyi bir şey olur. Bununla birlikte, yapmaması gerekiyorsa, her kişinin selamlaması ayrı ayrı kodlanmalı veya belirtilmelidir; Ortak bir işlev veya şartname kullanmaları için yapılan herhangi bir işin geri alınması gerekir (ve her şeyden önce yapılmasaydı daha iyi olurdu).

Elbette, geleceği tahmin etmek her zaman mümkün değildir, ancak birinin her iki selamlamanın birlikte değişmesi gerektiğinin daha muhtemel olduğuna inanmak için bir neden varsa, bu ortak kodun (ya da adlandırılmış bir sabitin) kullanılması lehinde bir etken olacaktır. ; eğer birinin farklı olması için birinin veya ikisinin de değişmesi gerekebileceğinin daha muhtemel olduğuna inanmak için bir neden varsa, bu, ortak kod kullanmaya karşı bir etken olacaktır.


Bu cevabı beğendim çünkü (en azından bu basit örnek için), seçimi neredeyse bir oyun teorisi ile hesaplayabilirsiniz.
RubberDuck

@RubberDuck: Genelde işlerin "takma" olması gereken ya da olmamasına ilişkin soruya çok az dikkat edildiğini düşünüyorum - birçok bakımdan ve hataların en yaygın iki nedeninin (1) şeylerin inanmak olduğuna inanmasını beklerdim değilken takma / takma, veya (2) başka şeylerin ona bağlı olduğunu fark etmeden bir şeyi değiştirme. Bu gibi konular hem kod hem de veri yapılarının tasarımında ortaya çıkar, ancak konsepte çok fazla ilgi gördüğümü hatırlamıyorum. Takma adlandırma, genellikle yalnızca bir şey olduğunda veya hiçbir zaman değişmeyecek olması önemli değildir, ancak ...
supercat

@RubberDuck: ... eğer iki şey varsa ve eşleşiyorlarsa, birini değiştirmenin diğerinin değerini sabit bırakması veya ilişkinin sabit kalması gerektiğini bilmek gerekir (diğerinin değeri değişecektir). Sık sık değerleri sabit tutmanın avantajlarına verilen büyük ağırlığı görüyorum , ancak ilişkilerin önemine verilen ağırlık çok az .
Supercat,

Tamamen katılıyorum. Ben sadece geleneksel bilgeliğin KURUŞMAK için söylediğine güveniyordum, fakat istatistiksel olarak , tekrarlanan kodun daha sonra tekrarlanan kod olmayacağı daha muhtemel. Temelde 2'ye 1 şansın var.
RubberDuck

@RubberDuck: Ayrılmanın önemli olduğu ve bağlanmanın daha iyi olduğu iki senaryo verdiğimde, ortaya çıkan çeşitli senaryoların göreceli olma ihtimalleri hakkında hiçbir şey söylemiyor. Önemli olan, iki kod parçasının "tesadüf eseri" ile eşleştiği ya da aynı temel anlama sahip olmalarıdır. Ayrıca, ikiden fazla madde olduğunda, birinin bir veya daha fazlasını diğerlerinden farklılaştırmak istediği, ancak aynı şekilde iki veya daha fazlasını değiştirdiği durumlarda ek senaryolar ortaya çıkar; Ayrıca, bir eylem yalnızca tek bir yerde kullanılsa bile ...
Supercat

5

Bence bir prosedür oluşturmak için bir nedeniniz olmalı. Bir prosedür oluşturmak için birkaç neden vardır. En önemli olduğunu düşündüğümler:

  1. soyutlama
  2. modülerlik
  3. kod gezinme
  4. tutarlılığı güncelle
  5. yineleme
  6. test yapmak

Soyutlama

Genel bir algoritmayı algoritmadaki belirli adımların detaylarından soyutlamak için bir prosedür kullanılabilir. Uygun bir şekilde tutarlı bir soyutlama bulabilirsem, bu algoritmanın ne yaptığını düşündüğüm şekilde yapılandırmama yardımcı olur. Örneğin, liste gösterimini mutlaka dikkate almak zorunda kalmadan listeler üzerinde çalışan bir algoritma tasarlayabilirim.

Modülarite

Kodları modüller halinde düzenlemek için prosedürler kullanılabilir. Modüller sıklıkla ayrı olarak ele alınabilir. Örneğin, ayrı ayrı oluşturulmuş ve test edilmiştir.

İyi tasarlanmış modül tipik olarak bazı tutarlı anlamlı işlevsellik birimini yakalar. Bu nedenle, genellikle farklı ekiplere ait olabilirler, tamamen bir alternatifle değiştirilirler veya farklı bir bağlamda yeniden kullanılırlar.

Kod Gezintisi

Büyük sistemlerde, belirli bir sistem davranışına ilişkin kodu bulmak zor olabilir. Kodun kütüphaneler, modüller ve prosedürler içerisinde hiyerarşik organizasyonu bu zorluğa yardımcı olabilir. Özellikle, a) işlevselliği anlamlı ve öngörülebilir isimler altında organize etmeye çalışırsanız, b) benzer işlevsellik ile ilgili prosedürleri birbirine yakın yerleştirin.

Güncelleme Tutarlılığı

Büyük sistemlerde, davranışta belirli bir değişikliği sağlamak için değiştirilmesi gereken tüm kodu bulmak zor olabilir. Programın işlevselliğini organize etmek için prosedürler kullanmak bunu daha kolay hale getirebilir. Özellikle, programınızın her bir işlevselliği bittiğinde yalnızca bir kez ve tutarlı bir prosedür grubunda görünüyorsa (benim deneyimime göre) bir yerde özlemeniz ya da tutarsız bir güncelleme yapmanız daha az olasıdır.

Programdaki işlevsellik ve soyutlamaları temel alan prosedürleri düzenlemelisiniz. Şu anda iki kod parçasının aynı olup olmayacağına bağlı değil.

özyineleme

Özyineleme kullanmak, prosedürler oluşturmanızı gerektirir

Test yapmak

Prosedürleri genellikle birbirinden bağımsız olarak test edebilirsiniz. Bir prosedürün gövdesinin farklı bölümlerini bağımsız olarak test etmek daha zordur, çünkü tipik olarak prosedürün ilk bölümünü ikinci bölümden önce uygulamanız gerekir. Bu gözlem aynı zamanda program davranışını belirlemek / doğrulamak için diğer yaklaşımlar için de geçerlidir.

Sonuç

Bu noktaların çoğu programın anlaşılabilirliği ile ilgilidir. İşlemlerin, etki alanınıza özgü, etki alanınızdaki sorunlar ve süreçlerle ilgili düzenleme, yazma ve okuma için yeni bir dil oluşturmanın bir yolu olduğunu söyleyebilirsiniz.


4

Verdiğiniz davaların hiçbiri yeniden bakılmaya değmez.

Her iki durumda da açık ve net bir şekilde ifade edilen bir işlemi gerçekleştiriyorsunuz ve tutarsız bir değişiklik yapılması tehlikesi yok.

Her zaman kodu yalıtmanın yollarını aramanızı öneririz:

  1. Ayrılabileceğiniz tanımlanabilir, anlamlı bir görev var ve

  2. ya

    a. Görev (sizin için geçerli olan işlevleri dikkate alarak) ifade etmek için karmaşıktır veya

    b. görev bir kereden fazla gerçekleştirilir ve her iki yerde de aynı şekilde değiştirildiğinden emin olmak için bir yol gerekir,

Koşul 1 yerine getirilmezse, kod için doğru arayüzü bulamazsınız: zorlamaya çalışırsanız birçok parametre, döndürmek istediğiniz birçok şey, bir çok bağlamsal mantık ile sonuçlanacak ve muhtemelen iyi bir isim bulamıyor. Öncelikle düşündüğünüz arayüzü belgelemeyi deneyin, doğru işlevsellik paketi olduğu hipotezini test etmenin iyi bir yoludur ve kodunuzu yazdığınızda, önceden belirtilmiş ve belgelenmiştir!

2a 2b olmadan mümkün: bazen sadece bir kez kullanıldığını bildiğimde bile akışı koddan çıkarttım, çünkü başka bir yere taşımak ve tek bir satırla değiştirmek, çağrıldığı bağlamın birden fazla okunabilir olduğu anlamına geliyor (özellikle dilin uygulanmasını zorlaştırdığı kolay bir kavramsa). Ayrıca, mantığın başladığı ve bittiği ve ne yaptığı ayıklanan işlevi okurken de açıktır.

2b 2a olmadan mümkün: Hile, ne tür değişikliklerin daha muhtemel olduğuna dair bir his uyandırıyor. Şirket politikanızın her zaman her yerde "Merhaba" derken "Merhaba" olarak değişmesi ya da ödeme talebinde bulunmanız veya hizmet kesintisi için bir özür göndermeniz durumunda selamlamanızın daha resmi bir tonda değişmesi daha olası mı? Bazen, emin olamadığınız bir dış kütüphaneyi kullanıyor olmanız ve bir başkasıyla hızlıca değiştirebilmek isteyebilirsiniz: işlevsellik olmasa bile uygulama değişebilir.

Çoğu zaman, yine de, bazı 2a ve 2b karışımına sahip olacaksınız. Ve kararınızı, kodun ticari değerini, kullandığı sıklığı, iyi belgelenmiş ve anlaşılmış, test takımınızın durumunu vb. Dikkate alarak kullanmanız gerekecektir.

Aynı bit mantığını bir kereden fazla kullandığınızı fark ederseniz, kesinlikle refactoring'i göz önünde bulundurma fırsatını yakalamanız gerekir. nÇoğu dilde girinti seviyelerinden daha fazla yeni ifade başlatıyorsanız, bu başka, biraz daha az anlamlı bir tetikleyicidir ( nörneğin, verilen dil için rahat olduğunuzun değerini seçin : Python 6 olabilir).

Bunları düşündüğünüz ve büyük spagetti kodlu canavarları öldürdüğünüz sürece, iyi olmalısınız - refactoring'inizin ne kadar iyi ayarlanmış zamanları olacağı konusunda endişelenmeyin testler ya da dokümantasyon gibi şeyler için çalıştı. Yapması gerekiyorsa, zaman söyleyecektir.


3

Genel olarak Robert Harvey ile aynı fikirdeyim, ancak işlevselliği fonksiyonlara bölmek için bir dava eklemek istedim. Okunabilirliği arttırmak için. Bir dava düşünün:

def doIt(smth,smthElse)
    for x in getDataFromSomething(smth,smthElse):
        if not check(x,smth):
            continue
        process(x,smthElse)
        store(x) 

Bu işlev çağrıları başka hiçbir yerde kullanılmasa da, 3 işlev de oldukça uzunsa ve iç içe döngüler varsa, okunabilirlikte önemli bir kazanç vardır.


2

Uygulanması gereken zor ve hızlı bir kural yoktur, kullanılacak gerçek işleve bağlı olacaktır. Basit isim baskısı için bir fonksiyon kullanmazdım ama eğer farklı bir hikaye olacak bir matematik toplamı ise. Daha sonra sadece iki kez çağrılsa bile, bir fonksiyon yaratacaksınız, bu matematik toplamının her zaman değişmesi durumunda daima aynı olmasını sağlamaktır. Başka bir örnekte, herhangi bir doğrulama biçimi yapıyorsanız, bir işlevi kullanırsınız; bu nedenle, örneğinizde adı 5 karakterden uzunsa denetlemeniz gerekiyorsa, aynı doğrulamanın her zaman yapıldığından emin olmak için bir işlev kullanırsınız.

Bu yüzden "İkiden fazla satır gerektiren kodlar için bir func yazmam gerek" derken kendi sorunuzu cevapladığınızı düşünüyorum. Genel olarak bir işlev kullanıyor olabilirsiniz, ancak bir işlevi kullanmanın herhangi bir katma değer olup olmadığını belirlemek için kendi mantığınızı da kullanmalısınız.


2

Yenileme konusunda burada bahsetmediğim bir şeye inanmaya başladım, burada çok fazla cevap olduğunu biliyorum, ama bunun yeni olduğunu düşünüyorum.

Ben acımasız bir refactor ve DRY daha önce şartların ortaya çıkmasından bu yana güçlü bir inanan oldum. Çoğunlukla kafamda büyük bir kod temeli kullanmakta zorlandığımdan ve kısmen DRY kodlamadan hoşlandığım ve C&P kodlama hakkında hiçbir şeyden zevk almadığım için, aslında benim için acı verici ve çok yavaş.

Mesele şu ki, DRY konusunda ısrarcı olmak, nadiren başkalarının kullandığını gördüğüm bazı tekniklerde bana pek çok uygulama yapmıştı. Pek çok insan Java'nın DRY yapmak için zor veya imkansız olduğu konusunda ısrar ediyor, ancak gerçekten denemiyorlar.

Uzun zaman önce örnek olarak biraz benzer bir örnek. İnsanlar Java GUI oluşturma zor olduğunu düşünüyorum. Tabii ki böyle kodlarsanız:

Menu m=new Menu("File");
MenuItem save=new MenuItem("Save")
save.addAction(saveAction); // I forget the syntax, but you get the idea
m.add(save);
MenuItem load=new MenuItem("Load")
load.addAction(loadAction)

Bunun delice olduğunu düşünen herkes kesinlikle haklı, fakat Java'nın hatası değil - kod böyle yazılmamalıdır. Bu yöntem çağrıları, diğer sistemlere sarılması amaçlanan işlevlerdir. Böyle bir sistemi bulamazsanız, kurun!

Açıkça böyle bir kod yapamazsınız, bu yüzden geri adım atıp soruna bakmanız gerekir, bu tekrarlanan kodun gerçekte ne işi var? Bazı ipleri ve onların ilişkilerini (bir ağaç) belirtiyor ve o ağacın yapraklarını eylemlere katıyor. Öyleyse gerçekten istediğin söylemek:

class Menu {
    @MenuItem("File|Load")
    public void fileLoad(){...}
    @MenuItem("File|Save")
    public void fileSave(){...}
    @MenuItem("Edit|Copy")
    public void editCopy(){...}...

İlişkinizi özlü ve açıklayıcı bir şekilde tanımladıktan sonra, onunla başa çıkmak için bir yöntem yazıyorsunuz - bu durumda geçilen sınıfın yöntemlerini yineliyorsunuz ve bir ağaç kuruyorsunuz, sonra bunu Menülerinizi oluşturmak için kullanıyorsunuz. ve Eylemler (tabii ki) bir menüyü görüntüler. Çoğaltmanıza gerek kalmayacak ve yönteminiz yeniden kullanılabilir ... ve muhtemelen çok sayıda menüden daha kolay yazılabilir ve gerçekten programlamadan zevk alırsanız, daha çok eğlenmişsinizdir. Bu zor değil - yazmanız gereken yöntem muhtemelen menünüzü elle oluşturmaktan daha az satırdı!

Mesele şu ki, bunu iyi yapmak için çok pratik yapmanız gerekiyor. Tekrarlanan kısımlarda tam olarak hangi benzersiz bilginin olduğunu analiz etmekte, bu bilgiyi çıkarmakta ve nasıl iyi ifade edeceğinizi bulmanız iyi olacaktır. Dizi ayrıştırma ve ek açıklamalar gibi araçları kullanmayı öğrenmek çok yardımcı olur. Hata raporlama ve dokümantasyon konusunda gerçekten net olmayı öğrenmek de çok önemlidir.

Basitçe iyi bir şekilde kodlayarak "Ücretsiz" bir uygulama elde edersiniz - heck şansı, bir kez iyi olduğunda bir DRY kodlamasının (tekrar kullanılabilir bir araç yazma da dahil) kopyalamanın ve yapıştırmanın ve tüm hataların, çoğaltılmış hataların kopyalanmasından ve yapıştırılmasından daha hızlı olduğunu bulmanızdır. ve kodlamanın neden olduğu zor değişiklikler.

DRY tekniklerini uygulayamasaydım ve elimden gelenin en iyisini yapsam işimden zevk alabileceğimi sanmıyorum. Kopyalama ve yapıştırma programlama yapmak zorunda kalmamakla birlikte ücretini kesmem gerekirse, alırdım.

Yani puanlarım:

  • Kopyalama ve Yapıştırma işlemi, yeniden nasıl refactor yapılacağını bilmiyorsanız daha fazla zaman alır.
  • Bunu yaparak, en zor ve önemsiz durumlarda bile DRY konusunda ısrar ederek refaktör yapmayı öğreniyorsunuz.
  • Bir programcısınız, kodunuzu KURU yapmak için küçük bir araca ihtiyacınız varsa, onu oluşturun.

1

Genel olarak, bir şeyi bir işleve bölmek iyi bir fikirdir, kodunuzun okunabilirliğini arttırır. Veya, eğer tekrarlanan kısım bir şekilde sistemin özü ise. Yukarıdaki örnekte, yerel ayarlara bağlı olarak kullanıcılarınızı selamlamanız gerekirse, ayrı bir karşılama işlevi olması mantıklı olacaktır.


1

@ DocBrown'un soyutlamaları oluşturmak için işlevleri kullanma konusunda @RobertHarvey'e yaptığı yorumun bir sonucu olarak: yeteri kadar bilgilendirici bir işlev adı bulamazsanız, bunun arkasındaki koddan çok daha özlü veya daha net değil . Bir işlevi yapmak için başka iyi bir neden olmadığında, buna gerek kalmaz.

Ayrıca, bir işlev adı nadiren tam anlamsal anlamını yakalar, bu nedenle, alışkın olmak için yeterince kullanılmış olmadıkça tanımını kontrol etmeniz gerekebilir. Özellikle işlevsel bir dilden başka, yan etkilerinin olup olmadığını, hangi hataların ortaya çıkabileceğini ve nasıl cevap verileceğini, zaman karmaşıklığını, kaynakları tahsis edip etmediğini ve / veya serbest bırakıp bırakmadığını ve kaynak olup olmadığını bilmeniz gerekebilir. kasa.

Tabii ki, tanım olarak sadece basit işlevleri göz önünde bulunduruyoruz, ancak bu her iki yoldan da geçiyor - bu gibi durumlarda, satır içi karmaşıklık eklemek olası değildir. Ayrıca, okuyucu muhtemelen görünene kadar basit bir fonksiyon olduğunu fark etmeyecektir. Sizi tanımlamaya bağlayan bir IDE ile bile, görsel atlama etrafta anlaşılması gereken bir engeldir.

Genel olarak konuşursak, özyinelemeli kodu daha fazlasını içine alan küçük işlevler, sonunda işlevlerin artık bağımsız bir anlama sahip olmadıkları bir noktaya getirecektir, çünkü yaptıkları kod parçaları küçüldükçe, bu parçalar yaratılan bağlamdan daha fazla anlam kazanır. çevreleyen kod ile. Bir benzetme olarak, bir resim gibi düşünün: eğer çok yakından yakınlaştırırsanız, neye baktığınızı çözemezsiniz.


1
Bir fonksiyona onu saran ifadeler en basit olsa bile, daha okunaklı hale getirmelidir. Bir işleve sarmanın zaman aşımına uğradığı zamanlar vardır, ancak ifadelerin yaptıklarından daha özlü ve net bir işlev adı bulamazsanız, bu muhtemelen kötü bir işarettir. İşlevleri kullanma konusundaki olumsuzluklarınız gerçekten önemli değil. Satır içi için negatifler çok daha büyük bir anlaşma. Inlining okumayı zorlaştırır, bakımını zorlaştırır ve yeniden kullanmayı zorlaştırır.
pllee

@ pllee: Fonksiyonlara faktoringin aşırı uçlara götürüldüğünde neden üretken hale geldiğinin nedenlerini sundum (sorunun, bunun genel vaka için değil, aşırı uçlara götürülmesi ile ilgili olduğuna dikkat edin.) sadece 'farklı' olması gerektiğini iddia edin. Adlandırma sorunu 'muhtemelen' kötü bir işaret burada görülen olgularda önlenebilir olduğuna dair bir argüman değil olduğunu Yazma (benim argüman, elbette, tam o olduğunu sen sadece noktasına ulaşmış olabileceğini - en azından bir uyarı kendi iyiliği için soyutlama.)
sdenham,

Nerede "farklı olması gerektiğini" iddia ettiğimden emin olmadığımdan kaynaklanan 3 negatif ifadeden bahsettim. Tek söylediğim, eğer bir isim bulamazsanız, satır içi işleminiz muhtemelen çok fazla yapıyor demektir. Ayrıca burada bir kez kullanılan küçük yöntemler bile büyük bir noktayı kaçırdığınızı düşünüyorum. Küçük iyi adlandırılmış yöntemler vardır, böylece kodun ne yapmaya çalıştığını bilmek zorunda kalmazsınız (IDE atlama yapmanıza gerek yoktur). Örneğin 'if' ifadesinin bile (day == 0 || day == 6) `vs` if (isWeekend (day)) `ifadesinin okunması ve zihinsel olarak haritalanması daha kolay hale gelir. Şimdi tekrarlamanız gerekiyorsa, bu ifadeyi isWeekenddaha akıllıca olmaz.
15'te

@ pllee Neredeyse her tekrarlanan kod parçası için çok daha kısa, daha net bir ad bulamazsanız, ancak bunun bir inanç meselesi olduğu anlaşılıyorsa, bunun "muhtemelen kötü bir işaret" olduğunu iddia ediyorsunuz - destekleyici bir argüman vermezsiniz (bir genel yapmak için) Bu durumda, örneklerden daha fazlasına ihtiyacınız var.) isWeekend'in anlamlı bir adı var, bu yüzden ölçütlerimin başarısız olmasının tek yolu, uygulanmasından önemli ölçüde daha kısa veya çok daha net değilse. İkinci olduğunu düşündüğünüzü söyleyerek, benim konumuma karşı bir örnek olmadığını iddia ediyorsunuz (FWIW, kullandığım isweekend kendim.)
sdenham

Bir tek satırlı ifadenin bile daha okunaklı olabileceği bir örnek verdim ve satır içi kodun negatiflerini verdim. Bu benim alçakgönüllülüğüm ve tüm söylediklerim ve hayır, "isimsiz yöntem" hakkında tartışmıyorum veya konuşmuyorum. Bu konuda neyin bu kadar kafa karıştırıcı olduğundan ya da neden benim fikrim için "teorik bir delile" ihtiyacım olduğunu düşündüğünüzden emin değilim. Geliştirilmiş okunabilirliği örnekledim. Bakım daha zordur, çünkü kod kodu değiştiyse N noktasında gerekir (DRY hakkında okuyun). Kod tekrar kullanmak için uygun bir yöntem ise kopya yapıştırmayı düşünmüyorsanız, tekrar kullanmak daha zordur.
pllee
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.