Bir işlevin neden yalnızca bir çıkış noktası olması gerekir? [kapalı]


98

Tek bir çıkış noktası işlevinin kötü bir kodlama yolu olduğunu her zaman duymuşumdur çünkü okunabilirliği ve verimliliği kaybedersiniz. Diğer tarafın tartıştığını hiç duymadım.

Bunun CS ile bir ilgisi olduğunu sanıyordum ama bu soru cstheory stackexchange'de reddedildi.



6
Cevap, her zaman doğru olan yanıtın olmamasıdır. Çoğunlukla çoklu çıkışlarla kodlamayı daha kolay buluyorum. Ayrıca (yukarıdaki kodu güncellerken) aynı çoklu çıkışlar nedeniyle kodu değiştirmenin / genişletmenin daha zor olduğunu buldum. Bu durum bazında kararları vermek bizim işimiz. Bir karar her zaman "en iyi" cevaba sahipse, bize gerek yoktur.
JS.

1
@finnw faşist modlar, tekrar tekrar cevaplanmaları gerekeceğinden emin olmak için son iki soruyu kaldırdı
Maarten Bodewes

Sorudaki "iddia" kelimesine rağmen, bunun gerçekten fikir temelli bir soru olduğunu düşünmüyorum. Bu, iyi tasarım vb. İçin oldukça önemlidir. Kapatılması için bir neden görmüyorum, ama w / e.
Ungeheuer

1
Tek bir çıkış noktası, hata ayıklamayı, okumayı, performans ölçmeyi ve ayarlamayı, yeniden düzenlemeyi basitleştirir. Bu, nesnel ve maddi olarak önemlidir. Erken dönüşleri kullanmak (basit argüman kontrollerinden sonra) her iki stilin de mantıklı bir karışımını sağlar. Tek bir çıkış noktasının faydaları göz önüne alındığında, kodunuzu dönüş değerleriyle doldurmak, tembel, özensiz, dikkatsiz bir programcının ve en azından muhtemelen yavru köpeklerden hoşlanmadığının kanıtıdır.
Rick O'Shea

Yanıtlar:


109

Farklı düşünce okulları vardır ve bu büyük ölçüde kişisel tercihlere bağlıdır.

Birincisi, yalnızca tek bir çıkış noktası olması daha az kafa karıştırıcıdır - yöntem boyunca tek bir yolunuz vardır ve çıkışı nerede arayacağınızı bilirsiniz. Eksi tarafta, iç içe yerleştirmeyi temsil etmek için girinti kullanırsanız, kodunuz büyük ölçüde sağa doğru girintilenir ve tüm iç içe kapsamları izlemek çok zor hale gelir.

Bir diğeri ise, yöntemin tüm gövdesi 5 mil sağa doğru girintili olmaksızın, yöntemin gövdesinde belirli koşulların doğru olduğunu bilmeniz için, ön koşulları kontrol edebilir ve bir yöntemin başlangıcında erkenden çıkabilirsiniz. Bu genellikle endişelenmeniz gereken kapsam sayısını en aza indirir ve bu da kodun takip edilmesini çok daha kolay hale getirir.

Üçüncüsü, istediğiniz her yerden çıkabilmenizdir. Bu, eski günlerde daha kafa karıştırıcıydı, ancak artık ulaşılamayan kodu tespit eden sözdizimi renklendirme düzenleyicilerimiz ve derleyicilerimiz olduğuna göre, uğraşması çok daha kolay.

Ben orta kamptayım. Tek bir çıkış noktasını zorlamak anlamsız veya hatta üretken olmayan bir kısıtlamadır IMHO, bir yöntemin her yerinde rastgele çıkış yapmak bazen karmaşık bir mantık izlemeye yol açabilir, burada belirli bir kod parçasının olup olmayacağını görmek zorlaşır. idam edildi. Ancak yönteminizin "geçitlenmesi", yöntemin gövdesini önemli ölçüde basitleştirmeyi mümkün kılar.


1
singe exitParadigmada derin iç içe geçme, ifadelerle önlenebilir go to. Ek olarak, Errorbirden çok URL ile imkansız olan, işlevin yerel etiketi altında bazı son işlemler gerçekleştirme fırsatı elde edilir return.
Ant_222

2
Genellikle gitme ihtiyacını ortadan kaldıran iyi bir çözüm vardır. Daha çok 'geri dönmeyi (Başarısız (...))' ve paylaşılan temizleme kodunu Fail yöntemine koymayı tercih ederim. Bu, belleğin serbest bırakılmasına vb. İzin vermek için birkaç yerel öğenin geçmesini gerektirebilir, ancak performans açısından kritik bir kod parçasında değilseniz, bu genellikle bir IMO'ya gitmekten çok daha temiz bir çözümdür. Ayrıca birkaç yöntemin benzer temizleme kodunu paylaşmasına da izin verir.
Jason Williams

Nesnel kriterlere dayalı optimal bir yaklaşım vardır, ancak düşünce okulları (doğru ve yanlış) olduğu konusunda hemfikir olabiliriz ve bu kişisel tercihlere (doğru bir yaklaşım lehine veya aleyhine bir tercih) bağlıdır.
Rick O'Shea

39

Genel tavsiyem, pratik olduğunda, dönüş ifadelerinin herhangi bir yan etkisi olan ilk koddan önce veya herhangi bir yan etkisi olan son koddan sonra yer almasıdır. Şöyle bir şey düşünürdüm:

  if (! argüman) // Boş olup olmadığını kontrol edin
    ERR_NULL_ARGUMENT döndür;
  ... boş olmayan argümanı işle
  Eğer tamam ise)
    dönüş 0;
  Başka
    ERR_NOT_OK döndür;

şundan daha net:

  int dönüş_değer;
  if (bağımsız değişken) // Boş değil
  {
    .. boş olmayan argümanı işle
    .. sonucu uygun şekilde ayarlayın
  }
  Başka
    sonuç = ERR_NULL_ARGUMENT;
  dönüş sonucu;

Belirli bir koşul bir işlevin herhangi bir şey yapmasını engelliyorsa, işlevin herhangi bir şey yapacağı noktanın üzerinde bir noktada işlevden erken dönmeyi tercih ederim. Bununla birlikte, işlev yan etkileri olan eylemleri üstlendikten sonra, tüm yan etkilerin ele alınması gerektiğini netleştirmek için aşağıdan dönmeyi tercih ederim.


İlk örneğiniz, okdeğişkeni yönetmek, bana tek dönüşlü yaklaşım gibi görünüyor. Dahası, if-else bloğu şu şekilde yeniden yazılabilir:return ok ? 0 : ERR_NOT_OK;
Melebius

2
İlk örnekte, returnher şeyi yapan tüm kodun önünde bir başlangıç ​​noktası vardır. ?:İşleci kullanmaya gelince , bunu ayrı satırlara yazmak birçok IDE'de tamam olmayan senaryoya bir hata ayıklama kesme noktası eklemeyi kolaylaştırır. BTW'ye göre, "tek çıkış noktası" nın gerçek anahtarı, önemli olanın, normal bir işleve yapılan her belirli çağrı için, çıkış noktasının çağrının hemen ardından gelen nokta olduğunun anlaşılmasında yatmaktadır . Günümüzde programcılar bunu normal kabul ediyor, ancak işler her zaman böyle değildi. Bazı nadir durumlarda, kodun yığın alanı olmadan geçmesi gerekebilir, bu da işlevlere yol açar ...
supercat

... koşullu veya hesaplanmış gotos aracılığıyla çıkan. Genel olarak, derleme dili dışında herhangi bir şekilde programlanacak yeterli kaynağa sahip her şey bir yığını destekleyebilir, ancak bazı çok sıkı kısıtlamalar altında çalışması gereken derleme kodu yazdım (bir durumda SIFIR bayt RAM'e kadar), ve bu gibi durumlarda birden fazla çıkış noktasına sahip olmak yardımcı olabilir.
supercat

1
Daha net olarak adlandırılan örnek çok daha az net ve okunması zor. Bir çıkış noktası her zaman daha kolay okunur, bakımı daha kolaydır, hata ayıklaması daha kolaydır.
GuidoG

8
@GuidoG: İhmal edilen bölümlerde neyin göründüğüne bağlı olarak her iki model daha okunabilir olabilir. "Return x;" kullanarak ifadeye ulaşıldığında dönüş değerinin x olacağını netleştirir. "Result = x;" kullanarak Sonuç döndürülmeden önce başka bir şeyin değişme olasılığını açık bırakır. Bu, sonucu değiştirmek gerçekten gerekli hale gelirse yararlı olabilir, ancak kodu inceleyen programcıların, yanıt "yapamaz" olsa bile sonucun nasıl değişebileceğini görmek için onu incelemesi gerekir.
supercat

15

Tek giriş ve çıkış noktası, adım adım Spagetti Kodlamasına karşı orijinal yapısal programlama konseptiydi. Değişkenler için ayrılan bellek alanlarını uygun şekilde temizlemeniz gerektiğinden, çoklu çıkış noktası işlevlerinin daha fazla kod gerektirdiğine dair bir inanç vardır. İşlevin değişkenleri (kaynakları) ayırdığı ve işlevden erken ve uygun temizlik yapılmadan çıkmanın kaynak sızıntılarına neden olacağı bir senaryo düşünün. Ek olarak, her çıkıştan önce temizleme oluşturmak çok fazla gereksiz kod oluşturacaktır.


Bu gerçekten RAII ile ilgili bir sorun değil
BigSandwich

14

Çoğu şeyde, teslim edilecek ürünün ihtiyaçlarına bağlıdır. "Eski günlerde", birden çok dönüş noktasına sahip spagetti kodu bellek sızıntılarına neden oluyordu, çünkü bu yöntemi tercih eden kodlayıcılar genellikle iyi temizlemezdi. İç içe geçmiş bir kapsamdan dönme durumunda, bazı derleyicilerin dönüş değişkenine başvuruyu "yitirmesi" ile ilgili sorunlar da vardı. Daha genel sorun, bir işlevin çağırma durumunun dönüş durumu ile tamamen aynı olmasını sağlamaya çalışan yeniden giren koddur. Oop mutatorları bunu ihlal etti ve konsept rafa kaldırıldı.

Birden çok çıkış noktasının sağladığı hıza ihtiyaç duyan, en başta çekirdekler olmak üzere çıktılar vardır. Bu ortamların normalde kendi bellekleri ve süreç yönetimi vardır, bu nedenle sızıntı riski en aza indirilir.

Şahsen, tek bir çıkış noktasına sahip olmayı seviyorum, çünkü onu genellikle dönüş ifadesine bir kesme noktası eklemek ve kodun bu çözümü nasıl belirlediğine dair bir kod incelemesi yapmak için kullanıyorum. Sadece girişe gidip geçebilirim, bunu kapsamlı bir şekilde iç içe geçmiş ve özyinelemeli çözümlerle yapıyorum. Bir kod gözden geçiren olarak, bir işlevdeki çoklu dönüşler çok daha derin bir analiz gerektirir - bu yüzden uygulamayı hızlandırmak için yapıyorsanız, Paul'ü kurtarmak için Peter'ı soyuyorsunuz. Etkili uygulama varsayımını geçersiz kılarak, kod incelemelerinde daha fazla zaman gerekecektir.

- 2 sent

Daha fazla ayrıntı için lütfen bu belgeye bakın: NISTIR 5459


9
multiple returns in a function requires a much deeper analysisyalnızca işlev zaten çok büyükse (> 1 ekran), aksi takdirde analizi kolaylaştırır
dss539

2
Birden fazla getiri analizi asla kolaylaştırmaz, sadece
tersi

1
bağlantı öldü (404).
fusi

1
@fusi - archive.org'da buldu ve buradaki bağlantıyı güncelledi
sscheider

4

Benim görüşüme göre, bir işlevden (veya başka bir denetim yapısından) yalnızca bir noktada çıkma tavsiyesi genellikle aşırı satılır. Yalnızca bir noktada çıkmak için tipik olarak iki neden verilir:

  1. Tek çıkış kodunun okunması ve hata ayıklaması sözde daha kolaydır. (Bu sebebi pek düşünmediğimi kabul ediyorum, ancak verildi. Okuması ve hata ayıklaması çok daha kolay olan tek girişli koddur.)
  2. Tek çıkışlı kod bağlanır ve daha temiz bir şekilde geri döner.

İkinci neden, ince ve özellikle işlev büyük bir veri yapısı döndürüyorsa, biraz faydası vardır. Ancak, bunun dışında çok fazla endişelenmem ...

Öğrenci iseniz, sınıfınızda en yüksek notları kazanmak istersiniz. Eğitmenin tercih ettiği şeyi yapın. Muhtemelen kendi bakış açısından iyi bir nedeni vardır; yani en azından onun bakış açısını öğreneceksiniz. Bunun kendi içinde değeri var.

İyi şanslar.


4

Eskiden tek çıkış tarzının savunucusuydum. Mantığım çoğunlukla acıdan geldi ...

Tek çıkışta hata ayıklamak daha kolaydır.

Bugün sahip olduğumuz teknikler ve araçlar göz önüne alındığında, bu çok daha az makul bir konumdur, çünkü birim testleri ve kayıt tek çıkışı gereksiz hale getirebilir. Bununla birlikte, bir hata ayıklayıcıda kodun yürütülmesini izlemeniz gerektiğinde, birden çok çıkış noktası içeren kodu anlamak ve bunlarla çalışmak çok daha zordu.

Bu, özellikle durumu incelemek için ödevlere müdahale etmeniz gerektiğinde geçerli hale geldi (modern hata ayıklayıcılarda saat ifadeleri ile değiştirildi). Kontrol akışını sorunu gizleyen veya yürütmeyi tamamen bozan şekillerde değiştirmek de çok kolaydı.

Tek çıkışlı yöntemler, hata ayıklayıcıda adım adım ilerlemek ve mantığı bozmadan ayırmak daha kolaydı.


0

Cevap çok bağlam bağımlıdır. Bir GUI yapıyorsanız ve API'leri başlatan ve ana cihazınızın başlangıcında pencereleri açan bir işleve sahipseniz, her biri programın örneğinin kapanmasına neden olabilecek hatalar atabilecek çağrılarla dolu olacaktır. İç içe geçmiş EĞER ifadeleri kullandıysanız ve girinti yaparsanız, kodunuz hızla sağa doğru çarpık hale gelebilir. Her aşamada bir hatanın geri dönüşü daha iyi ve aslında daha okunaklı olabilirken, kodda birkaç bayrakla hata ayıklaması daha kolay olabilir.

Ancak, farklı koşulları test ediyorsanız ve yönteminizdeki sonuçlara bağlı olarak farklı değerler döndürüyorsanız, tek bir çıkış noktasına sahip olmak çok daha iyi bir uygulama olabilir. MATLAB'da çok büyük olabilecek görüntü işleme betikleri üzerinde çalışıyordum. Birden fazla çıkış noktası, kodu takip etmeyi son derece zorlaştırabilir. Anahtar ifadeleri çok daha uygundu.

Yapılacak en iyi şey, ilerledikçe öğrenmek olacaktır. Bir şey için kod yazıyorsanız, başkalarının kodunu bulmayı ve onu nasıl uyguladıklarını görmeyi deneyin. Hangi parçaları sevip hangilerini beğenmeyeceğinize karar verin.


-7

Bir işlevde birden fazla çıkış noktasına ihtiyacınız olduğunu düşünüyorsanız, işlev çok büyüktür ve çok fazla şey yapıyor demektir.

Robert C. Martin'in Clean Code adlı kitabındaki işlevler hakkındaki bölümü okumanızı tavsiye ederim.

Esasen, 4 satır veya daha az kodla işlevler yazmaya çalışmalısınız.

Mike Long'un Blogundan bazı notlar :

  • Fonksiyonların ilk kuralı: küçük olmalılar
  • İkinci fonksiyon kuralı: bundan daha küçük olmalıdırlar
  • İf deyimleri, while ifadeleri, döngüleri vb. İçindeki bloklar bir satır uzunluğunda olmalıdır
  • … Ve bu kod satırı genellikle bir işlev çağrısı olacaktır
  • Bir veya belki iki düzeyden fazla girinti olmamalıdır
  • İşlevler bir şey yapmalı
  • İşlev ifadelerinin tümü aynı soyutlama düzeyinde olmalıdır
  • Bir işlevin 3'ten fazla bağımsız değişkeni olmamalıdır
  • Çıktı argümanları bir kod kokusudur
  • Bir boole bayrağını bir işleve geçirmek gerçekten berbattır. Fonksiyonda tanım gereği iki şey yapıyorsunuz.
  • Yan etkileri yalan.

30
4 satır mı? Size bu kadar basitlik sağlayan neyi kodluyorsunuz? Örneğin Linux çekirdeğinin veya git'in bunu yaptığından gerçekten şüpheliyim.
shinzou

7
"Bir işleve boole bayrağını geçirmek gerçekten berbat. İşlevde tanım gereği iki şey yapıyorsunuz." Tanım olarak? Hayır ... bu boole, potansiyel olarak dört satırınızdan yalnızca birini etkileyebilir. Ayrıca işlev boyutunu küçük tutmaya katılıyorum, ancak dört biraz fazla kısıtlayıcı. Bu çok gevşek bir kılavuz olarak alınmalıdır.
Jesse

12
Bunun gibi kısıtlamalar eklemek, kaçınılmaz olarak kafa karıştırıcı kodlara yol açacaktır. Daha çok, yöntemlerin net ve özlü olması ve gereksiz yan etkiler olmadan yalnızca yapılması gereken şeyi yapmaya devam etmesiyle ilgilidir.
Jesse

10
SO'daki nadir cevaplardan biri, keşke birden çok kez olumsuz oy verebilseydim.
Steven Rands

8
Maalesef bu yanıt birden çok şey yapıyor ve muhtemelen birden çok başka satıra bölünmesi gerekiyor - tümü dört satırdan az.
Eli
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.