İşlevselliğe belirli işlevler çıkarmalı mıyım ve neden?


29

Her biri ayrı bir işleve çıkartılabilen 3 görev yapan büyük bir yöntemim var. Bu görevlerin her biri için ek işlevler yaparsam kodumu daha iyi veya daha kötü duruma getirir mi ve neden?

Açıkçası, ana işlevde daha az kod satırı yapacaktır, ancak ek işlev bildirimleri olacak, bu yüzden sınıfımın iyi olmadığına inandığım ek yöntemler olacak, çünkü sınıfı daha karmaşık hale getirecek.

Tüm kodu yazmadan önce bunu yapmalı mıyım yoksa her şey bitene kadar bırakmalı mıyım ve sonra işlev çıkarmalı mıyım?


19
"Her şey bitene kadar bıraktım" genellikle "Asla bitmeyecek" ile eşanlamlıdır.
Öforik

2
Bu genel olarak doğrudur, ancak YAGNI'nın zıt prensibini de hatırlayın (bu durumda, zaten buna ihtiyaç duyduğunuz için geçerli değildir).
11


Sadece vurgulamak istedim, kod satırlarını azaltmaya çok fazla odaklanmayın. Bunun yerine soyutlamalar açısından düşünmeye çalışın. Her fonksiyonun sadece bir işi olmalıdır. İşlevlerinizin birden fazla iş yaptığını tespit ederseniz, genellikle yöntemi yeniden gözden geçirmelisiniz. Bu yönergeleri izlerseniz, aşırı uzun işlevlere sahip olmak neredeyse imkansız olmalıdır.
Adrian,

Yanıtlar:


35

Bu sık sık bağlantı kurduğum bir kitap, ama işte yine gidiyorum: Robert C. Martin'in Temiz Kodu , bölüm 3, "İşlevler".

Açıkçası, ana işlevde daha az kod satırı yapacaktır, ancak ek işlev bildirimleri olacak, bu yüzden sınıfımın iyi olmadığına inandığım ek yöntemler olacak, çünkü sınıfı daha karmaşık hale getirecek.

+150 satırlı bir işlevi mi yoksa 3 +50 satır işlevini çağıran bir işlevi mi okumayı tercih edersiniz? Sanırım ikinci seçeneği tercih ediyorum.

Evet , kodunuzu daha "okunabilir" olacak şekilde daha iyi hale getirecek. Tek ve tek bir şey yapan fonksiyonlar yapın, bakımı daha kolay olacak ve bir test senaryosu oluşturacaklar.

Ayrıca, yukarıda belirtilen kitapla öğrendiğim çok önemli bir şey: işlevleriniz için iyi ve kesin isimler seçin. İşlev ne kadar önemliyse, isim o kadar kesin olmalıdır. İsmin uzunluğu hakkında endişelenmeyin, eğer aranıyorsa, bu şekilde adlandırın FunctionThatDoesThisOneParticularThingOnly.

Refactorunuzu gerçekleştirmeden önce, bir veya daha fazla test durumu yazın. Çalıştıklarından emin ol. Yeniden düzenleme işleminizi tamamladığınızda, yeni kodun doğru şekilde çalışmasını sağlamak için bu test senaryolarını başlatabilirsiniz. Yeni fonksiyonlarınızın ayrı ayrı iyi performans göstermesini sağlamak için ek "daha küçük" testler yazabilirsiniz.

Son olarak, ve bu henüz yazdıklarımın aksine değil, kendinize bu yeniden düzenleme işlemini gerçekten yapmanız gerekip gerekmediğini sorun, " Ne zaman yeniden ateşlenmeli ?" (ayrıca, "yeniden düzenleme" ile ilgili SO soruları araştırın, dahası var ve cevapları okumak ilginç)

Tüm kodu yazmadan önce mi yapmalıyım, yoksa her şey bitene kadar bırakmalı mıyım ve sonra işlev çıkarmalı mıyım?

Kod zaten oradaysa ve çalışıyorsa ve bir sonraki sürüm için zamanınız kısaysa, ona dokunmayın. Aksi halde, her şeyin eskisi gibi çalışmasını sağlarken (test vakaları) mümkün olduğunda küçük fonksiyonlar yapmalı ve bir süre mümkün olduğunda refactor yapmalıyım.


10
Aslında, Bob Martin birkaç kez 15 işlevi olan bir işlevi yerine 2 ila 3 satırı olan 7 işlevi tercih ettiğini göstermiştir (bkz . Sites.google.com/site/unclebobconsultingllc/… ). Ve orası pek çok deneyimli devin direneceği yer. Şahsen, bu "deneyimli dev" lerin birçoğunun,> 10 yıllık kodlamadan sonra fonksiyonlarla soyutlama yapmak gibi basit bir şeyi geliştirmeye devam edebileceklerini kabul etmekte zorlandıklarını düşünüyorum.
Doktor Brown

+1 sadece mütevazi görüşüme göre herhangi bir yazılım şirketinin raflarında bulunması gereken bir kitabı referans olarak almak için.
Fabio Marcolini

3
Burada felç ediyor olabilirim, ama neredeyse her gün kafamda yankılanan bu kitaptan bir cümle "her fonksiyon sadece bir şeyi yapmalı ve iyi yapmalı" dır. OP “ana
işlevim

Kesinlikle haklısın!
Jalayn,

Üç ayrı fonksiyonun ne kadar iç içe geçmiş olduğuna bağlıdır. Hepsi bir arada bir kod bloğunu takip etmek, birbiri ardına güvenen üç kod bloğundan daha kolay olabilir.
user253751 5:17

13

Evet, belli ki. Tek bir işlevin farklı "görevlerini" görmek ve ayırmak kolaysa.

  1. Okunabilirlik - İyi adlara sahip işlevler, kodu okumak zorunda kalmadan hangi kodun yapıldığını açıkça belirtir.
  2. Yeniden Kullanılabilirlik - Birden fazla yerde bir şey yapan işlevi kullanmak, ihtiyacınız olmayan şeyleri yapmaktan daha kolaydır.
  3. Test edilebilirlik - Bir fonksiyonu tanımlanmış "fonksiyonu" olan, çoğuna sahip olanları test etmek daha kolaydır.

Ancak bununla ilgili sorunlar olabilir:

  • Fonksiyonun nasıl ayrılacağını görmek kolay değildir. Bu, ayrılmaya geçmeden önce ilk önce işlevin içini yeniden düzenlemeyi gerektirebilir.
  • İşlev, etrafta iletilen devasa bir iç duruma sahiptir. Bu genellikle bir tür OOP çözümü gerektirir.
  • Hangi fonksiyonun yapılması gerektiğini söylemek zordur. Birim test edin ve öğrenene kadar yeniden uygulayın.

5

Poz verdiğiniz sorun kodlama, kongre veya kodlama uygulaması değil, okunabilirlik ve metin editörlerinin yazdığınız kodu gösterme problemidir. Bu aynı problem postta da ortaya çıkıyor:

Başka bir şey tarafından çağrılmasalar da, uzun fonksiyonları ve yöntemleri küçüklere bölmek uygun mudur?

Bir işlevi alt işlevlere bölmek, oluşacağı farklı işlevleri kapsüllemek amacıyla büyük bir sistemi uygularken anlamlı olur. Yine de, er ya da geç, kendinizi bir dizi büyük işlevle bulacaksınız. Bazıları okunaksız ve bunları tek uzun işlevler olarak saklamak veya bölmek için küçük işlevlerdir. Bu özellikle, yaptığınız işlemlerin sisteminizin başka hiçbir yerinde gerekli olmadığı işlevler için geçerlidir. Bu kadar uzun bir fonksiyondan birini seçip daha geniş bir görünümde ele alalım.

Pro:

  • Bir kez okuduğunuzda, işlevin yaptığı tüm seçenekler hakkında tam bir fikriniz vardır (kitap olarak okuyabilirsiniz);
  • Eğer hata ayıklamak istiyorsanız, herhangi bir dosyaya / dosyaya atlamaksızın adım adım uygulayabilirsiniz;
  • İşlevin herhangi bir aşamasında bildirilen herhangi bir değişkene erişme / kullanma özgürlüğüne sahipsiniz;
  • Fonksiyon algoritması, fonksiyonda tamamen yer aldığını uygular (kapsüllenmiş);

Contra:

  • Ekranınızın birçok sayfasını alır;
  • Okumak çok uzun sürüyor;
  • Tüm farklı adımları ezberlemek kolay değildir;

Şimdi uzun fonksiyonu birkaç alt fonksiyona bölmeyi ve onlara daha geniş bir prospektifle bakmayı düşünelim.

Pro:

  • Ayrılma işlevleri dışında her bir işlev, yapılan farklı adımları kelimelerle (alt işlevlerin adları) tanımlar;
  • Her bir işlevi / alt işlevi okumak çok kısa sürüyor;
  • Her alt fonksiyonda hangi parametrelerin ve değişkenlerin etkilendiği açıktır (endişelerin ayrılması);

Contra:

  • "Sin ()" gibi bir işlevin ne yaptığını hayal etmek kolaydır, ancak alt işlevlerimizin ne yaptığını hayal etmek o kadar kolay değildir;
  • Algoritma artık ortadan kalktı, şimdi mayıs alt fonksiyonlarına dağıtıldı (genel bakış yok);
  • Adım adım hata ayıklama yaparken, geldiğiniz derinlik seviyesi işlev çağrısını unutmak kolaydır (buraya ve proje dosyalarınızdan atlayarak);
  • Farklı alt fonksiyonları okurken bağlamı kolayca kaybedebilirsiniz;

Her iki çözümde yanlısı ve tersi var. Asıl en iyi çözüm, her bir fonksiyonun kendi içeriğine çağrı yapmasını, satır içi ve tüm derinliği genişletmeye izin veren editörlere sahip olmak olacaktır. Bu, alt fonksiyonlarda bölme işlevlerini tek en iyi çözüm yapar.


2

Benim için kod bloklarını fonksiyonlara çıkarmak için dört neden var:

  • Yeniden kullanıyorsunuz : az önce bir kod bloğunu panoya kopyaladınız. Sadece yapıştırmak yerine, bir işleve yerleştirin ve bloğu her iki tarafta da işlev çağrısı ile değiştirin. Bu nedenle, ne zaman bir kod bloğunu değiştirmeniz gerektiğinde, kodu birden çok yerde değiştirmek yerine yalnızca o tek işlevi değiştirmeniz gerekir. Bu yüzden ne zaman bir kod bloğunu kopyalarsanız, bir işlev yapmalısınız.

  • Bu bir geri çağrıdır : Bir olay işleyicisi ya da bir kütüphane ya da çerçeve çağrıları gibi bir tür kullanıcı kodu. (İşlev yapmadan bunu hayal bile edemiyorum.)

  • Mevcut projede ya da belki başka bir yerde tekrar kullanılacağına inanıyorsunuz : İki dizinin en uzun ortak sonucunu hesaplayan bir blok yazdınız. Programınız bu işlevi yalnızca bir kez çağırsa da, sonunda diğer projelerde de bu işleve ihtiyacım olacağına inanıyorum.

  • Kendini belgeleyen kod istersiniz : Yani, ne yaptığını özetleyen bir kod bloğu üzerine bir yorum satırı yazmak yerine, her şeyi bir işleve eklersiniz ve bir yorumda ne yazacağınızı adlandırın. Bunun hayranı olmasam da, kullanılan algoritmanın adını, bu algoritmayı seçmemin sebebini yazmayı seviyorum, çünkü İşlev isimleri çok uzun sürecek ...


1

Değişkenlerin mümkün olduğunca sıkı bir şekilde ele alınması gerektiği konusunda tavsiyelerde bulunduğuna eminim ve umarım buna katılıyorsunuzdur. Eh, fonksiyonlar kapsam kapsayıcılarıdır ve daha küçük fonksiyonlarda yerel değişkenlerin kapsamı daha küçüktür. Nasıl ve ne zaman kullanmaları gerektiği daha açıktır ve yanlış sırayla veya başlatılmadan önce bunları kullanmak daha zordur.

Ayrıca, fonksiyonlar mantıksal akış kaplarıdır. Girmenin tek bir yolu var, çıkışlar açıkça işaretlendi ve işlev yeterince kısaysa, iç akışlar açık olmalıdır. Bu, kusur oranını azaltmak için güvenilir bir yol olan siklomatik karmaşıklığı azaltma etkisine sahiptir .


0

Bir kenara: Bunu dallin sorusuna yanıt olarak yazdım (şimdi kapalı) ama yine de birisinin bu konuda yardımcı olabileceğini düşünüyorum


Atomizasyon fonksiyonlarının nedeninin 2 kat olduğunu ve @jozefg in bahsettiği gibi kullanılan dile bağlı olduğunu düşünüyorum.

Endişelerin Ayrılması

Bunu yapmanın temel nedeni, farklı kod parçalarını ayrı tutmaktır, bu nedenle işlevin istenen sonucuna / amacına doğrudan katkıda bulunmayan herhangi bir kod bloğu ayrı bir endişedir ve çıkarılabilir.

Bir ilerleme çubuğunu da güncelleyen bir arka plan göreviniz olduğunu söyleyin; ilerleme çubuğu güncellemesi, uzun süredir devam eden görevle doğrudan ilişkili değildir; bu nedenle, ilerleme çubuğunu kullanan tek kod parçası olsa bile çıkarılmalıdır.

JavaScript’te bir fonksiyona sahip olduğunuzu söyleyin getMyData (), 1) parametrelerden bir sabun mesajı oluşturur, 2) bir servis referansı başlatır, 3) servisi sabun mesajı ile çağırır, 4) sonucu ayrıştırır, 5) sonucu döndürür. Ben bu tam fonksiyon defalarca makul yazdım görünüyor - ama gerçekten bu olabilir sadece diğer kod hiçbiri olarak 3 & 5 için kod (Eğer) dahil 3 özel fonksiyonlar içine bölünmüş olması hizmetinden veri alma doğrudan sorumlu .

Gelişmiş Hata Ayıklama Deneyimi

Tamamen atomik işlevleriniz varsa, yığın izlemeniz, başarılı bir şekilde yürütülen tüm kodları listeleyen bir görev listesi haline gelir, yani:

  • Verilerimi Al
    • Sabun Mesajı Oluştur
    • Hizmet Referansını Başlat
    • Ayrıştırılmış Hizmet Yanıtı - ERROR

veri alırken bir hata olduğunu bulmaktan sonra daha ilginç olurdu. Ancak bazı araçlar daha sonra ayrıntılı çağrı ağaçlarının hatalarını ayıklamak için daha da faydalıdır, örneğin Microsofts Debugger Canvas .

Ayrıca, bu şekilde yazılan kodları takip etmenin zor olacağına dair endişelerinizi de anlıyorum, çünkü günün sonunda, çağrı ağacınızın daha karmaşık olacağı tek bir dosyada bir işlev sırası seçmeniz gerekir. . Ancak, işlevler iyi adlandırılırsa (intellisense, herhangi bir işlevde 3-4 kamal harfli sözcük kullanmama izin verirse, lütfen beni yavaşlatmadan) ve dosyanın en üstünde ortak arabirim ile yapılandırılmışsa, kodunuz sahte kod gibi Bir kod temeli hakkında üst düzeyde bir anlayışa sahip olmanın en kolay yoludur.

Bilginize - bu benim "söylediğim gibi yap" şeylerinden biridir, şeyleri acımasızca tutarlı kılmadığınız sürece IMHO ile tutarlı tutmuyorsanız kod atomik tutmak anlamsızdır.

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.