it-swarm.asia

"Sadece bir dönüş" kavramı nereden geldi?

Sık sık " Aynı yöntemde birden fazla dönüş ifadesi koyma. " Bana nedenlerini söylemelerini istediğimde, tüm aldığım " kodlama standardı böyle söylüyor. " veya " Kafa karıştırıcı. "Bana tek bir dönüş ifadesiyle çözümler gösterdiklerinde, kod bana daha çirkin görünüyor. Örneğin:

if (condition)
   return 42;
else
   return 97;

" Bu çirkin, yerel bir değişken kullanmalısınız! "

int result;
if (condition)
   result = 42;
else
   result = 97;
return result;

Bu% 50 kod bloat programı nasıl daha kolay anlaşılır? Şahsen, daha zor buluyorum, çünkü devlet alanı kolayca önlenebilecek başka bir değişken tarafından arttı.

Tabii ki, normalde sadece şunu yazardım:

return (condition) ? 42 : 97;

Ancak birçok programcı koşullu operatörden kaçınır ve uzun formu tercih eder.

Bu "sadece bir dönüş" nosyonu nereden geldi? Bu sözleşmenin gerçekleşmesinin tarihsel bir nedeni var mı?

1077
fredoverflow

Çoğu programlama Assembly dilinde, FORTRAN veya COBOL dilinde yapıldığında "Tek Giriş, Tek Çıkış" yazılmıştır. Modern diller Dijkstra'nın uyartığı uygulamaları desteklemediğinden, yaygın olarak yanlış yorumlanmıştır.

"Tek Giriş", "işlevler için alternatif giriş noktaları oluşturma" anlamına gelir. Montaj dilinde, elbette, herhangi bir talimatta bir fonksiyon girmek mümkündür. FORTRAN, ENTRY ifadesine sahip işlevlere birden çok girişi destekledi:

      SUBROUTINE S(X, Y)
      R = SQRT(X*X + Y*Y)
C ALTERNATE ENTRY USED WHEN R IS ALREADY KNOWN
      ENTRY S2(R)
      ...
      RETURN
      END

C USAGE
      CALL S(3,4)
C ALTERNATE USAGE
      CALL S2(5)

"Tek Çıkış" bir işlevin yalnızca to bir yer döndürmesi anlamına geliyordu: çağrıyı hemen takip eden ifade. değil işlevi bir işlevin yalnızca bir yerden bir yere dönmesi gerektiği anlamına gelir. Yapısal Programlama yazıldığında, bir fonksiyonun alternatif bir konuma geri dönerek bir hatayı belirtmesi yaygın bir uygulamadır. FORTRAN bunu "alternatif getiri" ile destekledi:

C SUBROUTINE WITH ALTERNATE RETURN.  THE '*' IS A PLACE HOLDER FOR THE ERROR RETURN
      SUBROUTINE QSOLVE(A, B, C, X1, X2, *)
      DISCR = B*B - 4*A*C
C NO SOLUTIONS, RETURN TO ERROR HANDLING LOCATION
      IF DISCR .LT. 0 RETURN 1
      SD = SQRT(DISCR)
      DENOM = 2*A
      X1 = (-B + SD) / DENOM
      X2 = (-B - SD) / DENOM
      RETURN
      END

C USE OF ALTERNATE RETURN
      CALL QSOLVE(1, 0, 1, X1, X2, *99)
C SOLUTION FOUND
      ...
C QSOLVE RETURNS HERE IF NO SOLUTIONS
99    PRINT 'NO SOLUTIONS'

Her iki teknik de hataya oldukça meyilliydi. Alternatif girişlerin kullanılması genellikle bazı değişkenlerin başlatılmamasına neden olur. Alternatif getirilerin kullanımı, bir GOTO ifadesinin tüm sorunlarına sahipti, ayrıca şube koşulunun dalın yanında değil, altyordamda bir yerde olmasıydı.

1145
kevin cline

Tek Giriş, Tek Çıkış (SESE) kavramı açık kaynak yönetimine sahip diller, C ve Montaj gibi. C dilinde, bunun gibi kod kaynakları sızdırır:

void f()
{
  resource res = acquire_resource();  // think malloc()
  if( f1(res) )
    return; // leaks res
  f2(res);
  release_resource(res);  // think free()
}

Bu dillerde temel olarak üç seçeneğiniz vardır:

  • Temizleme kodunu çoğaltın.
    Ah. Artıklık her zaman kötüdür.

  • Temizleme koduna atlamak için goto kullanın.
    Bu, temizleme kodunun işlevdeki son şey olmasını gerektirir. (Ve bu yüzden bazıları goto 'in yerini olduğunu iddia ediyorlar. Ve gerçekten de - C'de.)

  • Yerel bir değişken tanıtın ve bunun içinden kontrol akışını değiştirin.
    Dezavantajı, sözdizimi ile manipüle edilen kontrol akışının (düşünmek break, return, if, while) takip etmekten çok daha kolay değişkenlerin durumu üzerinden manipüle edilen kontrol akışı (algoritmaya baktığınızda bu değişkenlerin durumu yoktur).

Montaj'da bile gariptir, çünkü bu işlevi çağırdığınızda bir işlevdeki herhangi bir adrese atlayabilirsiniz, bu da herhangi bir işleve neredeyse sınırsız sayıda giriş noktasına sahip olduğunuz anlamına gelir. (Bazen bu yardımcı olur. Bu tür thunks, derleyiciler için C++ 'da çoklu miras senaryolarında this işlevlerini çağırmak için gerekli virtual işaretçi ayarlamasını uygulamak için yaygın bir tekniktir.)

Kaynakları manuel olarak yönetmeniz gerektiğinde, herhangi bir yere bir işlev girme veya bir işlevden çıkma seçeneklerini kullanmak daha karmaşık kodlara ve dolayısıyla hatalara yol açar. Bu nedenle, daha temiz kod ve daha az hata elde etmek için SESE'yi yayan bir düşünce okulu ortaya çıktı.


Bununla birlikte, bir dil istisnalar içerdiğinde, (neredeyse) herhangi bir işlev (neredeyse) herhangi bir noktada erken çıkılabilir, bu nedenle zaten erken dönüş için hükümler yapmanız gerekir. (Sanırım finally çoğunlukla Java ve using) için kullanılır (aksi takdirde IDisposable, finally uygulanır) C # 'da; C++ bunun yerine RAII .) Bunu yaptıktan sonra, yapamaz kendinizden sonra temizleyemezsiniz erken bir return ifadesine göre, muhtemelen SESE lehine en güçlü argüman ortadan kalktı.

Bu okunabilirlik sağlar. Tabii ki, yarım düzine return deyimi üzerine rastgele serpiştirilmiş bir 200 LoC işlevi iyi programlama stili değildir ve okunabilir kod oluşturmaz. Ancak böyle bir işlevi, bu erken dönüşler olmadan anlamak kolay olmazdı.

Kaynakların manuel olarak yönetilmemesi veya yönetilmemesi gereken dillerde, eski SESE sözleşmesine uymada çok az değer vardır veya hiç yoktur. OTOH, yukarıda da belirttiğim gibi, SESE genellikle kodu daha karmaşık hale getirir. (C hariç) günümüz dillerinin çoğuna iyi uymayan bir dinozor. Kodun anlaşılırlığına yardımcı olmak yerine kodu engeller.


Neden Java programcılar buna sadık?) Bilmiyorum, ama benim (dış) POV, Java C (burada çok sayıda konvansiyon aldı) mantıklıdır) ve maliyeti ne olursa olsun, şimdi onlara yapıştığı OO dünyasına (işe yaramaz veya tamamen kötü oldukları yerde) uyguladılar. kapsamın başında değişkenleriniz.)

Programcılar mantıksız nedenlerle her türlü garip notasyona bağlı kalırlar. (Derin iç içe geçmiş yapısal ifadeler - "ok uçları" - Pascal gibi dillerde, bir zamanlar güzel kod olarak görülüyordu.) Buna mantıklı mantıksal akıl yürütmenin uygulanması, çoğunluğunu yerleşik yollarından sapmaya ikna edememiş gibi görünüyor. Bu tür alışkanlıkları değiştirmenin en iyi yolu, muhtemelen onlara geleneksel olanı değil, en iyisini yapmalarını öğretmektir. Bir programlama öğretmeni olarak, elinizde var. :)

921
sbi

Bir yandan, tekli dönüş ifadeleri günlüğe kaydetmeyi ve günlüğe kaydetmeye dayanan hata ayıklama biçimlerini kolaylaştırır. Geri dönüş değerini tek bir noktada basmak için işlevi tek bir dönüşe düşürmek zorunda olduğumu birçok kez hatırlıyorum.

  int function() {
     if (bidi) { print("return 1"); return 1; }
     for (int i = 0; i < n; i++) {
       if (vidi) { print("return 2"); return 2;}
     }
     print("return 3");
     return 3;
  }

Öte yandan, bunu function() çağıran ve sonucu kaydeden _function() haline getirebilirsiniz.

82
perreal

"Tek Giriş, Tek Çıkış", Edsger W. Dijkstra'nın Editöre mektubu tarafından atılan 1970'lerin başlarındaki Yapılandırılmış Programlama devriminden kaynaklandı " GOTO Bildirimi Zararlı Kabul Edildi ". Yapısal programlamanın ardındaki kavramlar, Ole Johan-Dahl, Edsger W. Dijkstra ve Charles Anthony Richard Hoare'nin klasik "Yapısal Programlama" kitabında ayrıntılı bir şekilde ortaya konuldu.

Bugün bile "GOTO Bildirimi Zararlı Kabul Edildi" gereklidir. "Yapısal Programlama" tarihli, ancak yine de çok, çok faydalı ve herhangi bir geliştiricinin "Okumalı" listesinin en üstünde, ör. Steve McConnell. (Dahl'ın bölümü, C++ ve tüm nesne yönelimli programlama için teknik temel olan Simula 67'deki sınıfların temellerini ortaya koymaktadır.)

53
John R. Strohm

Fowler'i bağlamak her zaman kolaydır.

SESE'ye karşı çıkan ana örneklerden biri nöbet hükümleridir:

Yuvalanmış Koşulları Koruyucu Maddelerle Değiştir

Tüm özel durumlar için Koruma Maddeleri kullanın

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
return result;
};  

 http://www.refactoring.com/catalog/arrow.gif

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
};  

Daha fazla bilgi için Yeniden düzenleme sayfasının 250. sayfasına bakın ...

36
Pieter B

Bu konuda bir süre önce bir blog yazısı yazdım.

Sonuç olarak, bu kural çöp toplama veya istisna yönetimi olmayan dillerin yaşından kaynaklanmaktadır. Bu kuralın modern dillerde daha iyi kodlamaya yol açtığını gösteren resmi bir çalışma yoktur. Bu, daha kısa veya daha okunabilir bir koda yol açtığında göz ardı etmekten çekinmeyin. Java adamlar, modası geçmiş, anlamsız bir kurala göre körü körüne ve tartışmasız.

Bu soru Stackoverflow'da da soruld

11
Anthony

Bir geri dönüş yeniden düzenlemeyi kolaylaştırır. Bir dönüş, kesme veya devam içeren bir for döngüsünün iç gövdesine "çıkarma yöntemi" gerçekleştirmeye çalışın. Kontrol akışınızı bozduğunuzda bu başarısız olacaktır.

Mesele şu ki: Kimse mükemmel kod yazıyormuş gibi davranmıyor. Bu nedenle, kod "geliştirilecek" ve genişletilecek şekilde yeniden düzenleme altındadır. Bu yüzden amacım kodumu mümkün olduğunca kolay bir şekilde yeniden düzenlemektir.

Çoğu zaman, kontrol akış kesicileri içeriyorsa ve yalnızca çok az işlevsellik eklemek istersem işlevleri tamamen yeniden biçimlendirmek zorunda olduğum sorunla karşılaşıyorum. Bu, yalıtılmış yuvalara yeni yollar eklemek yerine tüm kontrol akışını değiştirdiğinizde çok hataya açıktır. Sonunda tek bir dönüşünüz varsa veya bir döngüden çıkmak için korumalar kullanıyorsanız, elbette daha fazla yuvalama ve daha fazla kodunuz olur. Ancak derleyici ve IDE desteklenen yeniden düzenleme özellikleri kazanırsınız.

6
oopexpert

Birden fazla dönüş ifadesinin, GOTO'ların tek bir dönüş ifadesine sahip olmasına eşdeğer olduğunu düşünün. Bu, break ifadeleriyle aynı durumdur. Bu nedenle, bazıları, benim gibi, onları tüm niyet ve amaçlar için GOTO'ları düşünün.

Ancak, bu tür GOTO'ların zararlı olduğunu düşünmüyorum ve bunun için iyi bir neden bulursam, kodumda gerçek bir GOTO kullanmaktan çekinmeyeceğim.

Genel kuralım GOTO'ların sadece akış kontrolü içindir. Asla herhangi bir döngü için kullanılmamalıdır ve asla 'yukarı' veya 'geriye' GOTO kullanmamalısınız. (aralar/iadeler böyle çalışır)

Diğerlerinin de belirttiği gibi, aşağıdakilerin okunması gerekir GOTO Bildirimi Zararlı Olarak Kabul Edilir
Ancak, bunun GOTO'ların çok fazla kullanıldığı 1970'te yazıldığını unutmayın. Her GOTO zararlı değildir ve normal yapılar yerine kullanmadığınız sürece kullanımlarını caydırmayacağım, aksine normal yapıların kullanılmasının son derece rahatsız edici olacağı garip bir durumda.

Onları normal durumlarda asla ortaya çıkmaması gereken bir başarısızlık nedeniyle bir alandan kaçmanız gereken hata durumlarda kullanıyorum. Ancak, bu kodu ayrı bir işleve koymayı da düşünmelisiniz, böylece bir GOTO kullanmak yerine erken dönebilirsiniz ... ancak bazen bu da elverişsizdir.

5
user606723

Cyclomatic karmaşıklık

SonarCube siklomatik karmaşıklığı belirlemek için çoklu dönüş ifadesi kullandığını gördüm. Dolayısıyla, geri dönüş ifadeleri ne kadar fazla olursa, siklomatik karmaşıklık o kadar yüksek olur

Dönüş Türü Değişimi

Birden çok dönüş, dönüş türümüzü değiştirmeye karar verdiğimizde, işlevin birden çok yerinde değiştirmemiz gerektiği anlamına gelir.

Çoklu Çıkış

Hata ayıklamak daha zordur, çünkü mantığın döndürülen değere neyin neden olduğunu anlamak için koşullu ifadelerle birlikte dikkatle incelenmesi gerekir.

Yeniden Düzenlenmiş Çözüm

Çoklu dönüş ifadelerinin çözümü, gerekli uygulama nesnesini çözdükten sonra bunları polimorfizm ile değiştirmektir.

4
Sorter