Bir işlevde yalnızca bir iade ifadesinin bulunmasının daha iyi bir uygulama olmasının iyi nedenleri var mı?
Yoksa mantıksal olarak doğru olur olmaz bir fonksiyondan geri dönmek uygun mudur, yani fonksiyonda birçok dönüş ifadesi olabilir mi?
Bir işlevde yalnızca bir iade ifadesinin bulunmasının daha iyi bir uygulama olmasının iyi nedenleri var mı?
Yoksa mantıksal olarak doğru olur olmaz bir fonksiyondan geri dönmek uygun mudur, yani fonksiyonda birçok dönüş ifadesi olabilir mi?
Yanıtlar:
Genellikle "kolay" durumlar için dönmek için bir yöntemin başlangıcında birkaç ifade var. Örneğin, bu:
public void DoStuff(Foo foo)
{
if (foo != null)
{
...
}
}
... daha okunabilir hale getirilebilir (IMHO):
public void DoStuff(Foo foo)
{
if (foo == null) return;
...
}
Yani evet, bir işlev / yöntemden birden çok "çıkış noktası" olması iyi olduğunu düşünüyorum.
DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
Kimse Code Complete'ten bahsetmedi veya alıntı yapmadı, ben de yapacağım.
Her rutinde dönüş sayısını en aza indirin . Yukarıdaki bir yerde geri dönme olasılığının farkında değilseniz, bir rutini anlamak daha zordur.
Okunabilirliği artırdığında bir dönüş kullanın . Belirli rutinlerde, cevabı öğrendikten sonra, derhal çağıran rutine geri dönmek istersiniz. Rutin herhangi bir temizleme gerektirmeyecek şekilde tanımlanmışsa, hemen geri dönmemesi daha fazla kod yazmanız gerektiği anlamına gelir.
Ben pratikte defalarca yararlı yararlı bulduğum için , aslında birden fazla çıkış noktalarına keyfi olarak karar vermek inanılmaz derecede yanlış olacağını söyleyebilirim , aslında netlik için sık sık mevcut kodu birden fazla çıkış noktalarına yeniden . Bu şekilde iki yaklaşımı karşılaştırabiliriz: -
string fooBar(string s, int? i) {
string ret = "";
if(!string.IsNullOrEmpty(s) && i != null) {
var res = someFunction(s, i);
bool passed = true;
foreach(var r in res) {
if(!r.Passed) {
passed = false;
break;
}
}
if(passed) {
// Rest of code...
}
}
return ret;
}
Birden çok çıkış noktaları koduna karşılaştırın edilir izin: -
string fooBar(string s, int? i) {
var ret = "";
if(string.IsNullOrEmpty(s) || i == null) return null;
var res = someFunction(s, i);
foreach(var r in res) {
if(!r.Passed) return null;
}
// Rest of code...
return ret;
}
Bence ikincisi oldukça açık. Birden fazla çıkış noktasının eleştirisini anlatabildiğim kadarıyla bugünlerde oldukça eski bir bakış açısı.
Şu anda üzerinde çalışan iki insanın "tek çıkış noktası" teorisine körü körüne abone olduğu bir kod tabanı üzerinde çalışıyorum ve deneyimlerden bunun korkunç korkunç bir uygulama olduğunu söyleyebilirim. Kodun korunmasını son derece zorlaştırır ve size nedenini göstereceğim.
"Tek çıkış noktası" teorisi ile, kaçınılmaz olarak şuna benzer bir kodla sarılırsınız:
function()
{
HRESULT error = S_OK;
if(SUCCEEDED(Operation1()))
{
if(SUCCEEDED(Operation2()))
{
if(SUCCEEDED(Operation3()))
{
if(SUCCEEDED(Operation4()))
{
}
else
{
error = OPERATION4FAILED;
}
}
else
{
error = OPERATION3FAILED;
}
}
else
{
error = OPERATION2FAILED;
}
}
else
{
error = OPERATION1FAILED;
}
return error;
}
Bu sadece kodu takip etmeyi çok zorlaştırmakla kalmaz, aynı zamanda daha sonra geri dönüp 1 ile 2 arasında bir işlem eklemeniz gerektiğini söyler. if / else koşullarınız ve parantezleriniz doğru şekilde eşleştirilir.
Bu yöntem kod bakımını son derece zor ve hataya açık hale getirir.
Yapısal programlama , işlev başına yalnızca bir dönüş ifadesine sahip olmanız gerektiğini belirtir. Bu karmaşıklığı sınırlamak içindir. Martin Fowler gibi birçok insan, çoklu dönüş ifadeleriyle fonksiyon yazmanın daha kolay olduğunu savunuyor. Bu argümanı yazdığı klasik yeniden düzenleme kitabında sunar. Diğer tavsiyelerine uyup küçük işlevler yazarsanız bu işe yarar. Bu bakış açısına katılıyorum ve sadece katı yapılandırılmış programlama safları, işlev başına tek dönüş ifadelerine bağlı kalıyor.
GOTO
, fonksiyon mevcut olsa bile kontrol akışını hareket ettirmek için kullanılması hakkında konuştu . Asla "asla kullanma GOTO
" demez .
Kent Beck'in Uygulama Desenlerindeki koruma maddelerini tartışırken belirttiği gibi bir rutini yapan gibi, tek bir giriş ve çıkış noktası vardır ...
"aynı rutinde birçok yere girip çıkarken karışıklığı önlemekti. FORTRAN'a veya hangi ifadelerin yürütüldüğünü anlamanın bile çok zor olduğu birçok küresel veriyle yazılmış montaj dili programlarına uygulandığında mantıklıydı .. "Küçük yöntemler ve çoğunlukla yerel verilerle gereksiz yere muhafazakar."
Muhafız cümleleriyle yazılmış bir işlevi, uzun iç içe geçmiş ifadelerden çok daha kolay buluyorum if then else
.
Yan etkisi olmayan bir işlevde, tek bir dönüşten daha fazlasına sahip olmak için iyi bir neden yoktur ve bunları işlevsel bir tarzda yazmalısınız. Yan etkileri olan bir yöntemde, işler daha ardışıktır (zaman indekslidir), bu nedenle, dönüş ifadesini çalıştırmayı durdurmak için bir komut olarak kullanarak zorunlu bir tarzda yazarsınız.
Başka bir deyişle, mümkün olduğunda, bu stili tercih edin
return a > 0 ?
positively(a):
negatively(a);
bunun üzerinde
if (a > 0)
return positively(a);
else
return negatively(a);
Kendinizi birkaç iç içe koşul katmanı yazarken bulursanız, örneğin yüklem listesini kullanarak bunu yeniden düzenlemenin bir yolu olabilir. İfs ve else'lerinizin sözdizimsel olarak birbirinden çok uzakta olduğunu fark ederseniz, bunu daha küçük işlevlere bölmek isteyebilirsiniz. Bir ekran metinden daha fazlasını kapsayan bir koşullu bloğun okunması zordur.
Her dil için geçerli zor ve hızlı bir kural yoktur. Tek bir return ifadesine sahip olmak, kodunuzu iyi yapmaz. Ancak iyi kod, işlevlerinizi bu şekilde yazmanıza izin verme eğiliminde olacaktır.
RAII veya diğer otomatik bellek yönetimine sahip değilseniz, C ++ için bir takma olan C ++ kodlama standartlarında gördüm, sonra her dönüş için temizlemeniz gerekir, bu da kes ve yapıştır anlamına gelir temizleme veya bir goto (her ikisi de kötü form olarak kabul edilen mantıksal olarak 'son olarak' ile aynıdır). Uygulamalarınız C ++ veya başka bir otomatik bellek sisteminde akıllı işaretçiler ve koleksiyonlar kullanmaksa, bunun güçlü bir nedeni yoktur ve her şey okunabilirlik ve daha çok bir karar çağrısı ile ilgilidir.
auto_ptr
paralel olarak düz işaretçiler kullanabilirsiniz. İlk etapta optimize edici olmayan bir derleyici ile 'optimize edilmiş' kod yazmak garip olurdu.
try
... gibi finally
) ve tek bir kaynakla yapabileceğiniz kaynak bakımı yapmanız gerekir. yöntemin sonunda döndürür. Bunu yapmadan önce, durumdan kurtulmak için kodu yeniden düzenlemeyi ciddi olarak düşünmelisiniz.
İşlevin ortasında dönüş ifadelerinin kötü olduğu fikrine yaslanıyorum. İşlevin üst kısmında birkaç koruma maddesi oluşturmak için döndürmeler kullanabilirsiniz ve elbette derleyiciye işlevin sonunda neye geri döneceğini söyleyin, ancak işlevin ortasındaki döndürmeleri kaçırmak kolay olabilir ve işlevi yorumlamayı zorlaştırır.
Bir işlevde yalnızca bir iade ifadesinin bulunmasının daha iyi bir uygulama olmasının iyi nedenleri var mı?
Evet , var:
Soru genellikle birden çok getiri arasında yanlış bir ikilik olarak ortaya çıkar veya if ifadeleri derinden iç içe geçer. Neredeyse her zaman sadece tek bir çıkış noktası ile çok doğrusal (derin yuvalama yok) olan üçüncü bir çözüm vardır.
Güncelleme : Görünüşe göre MISRA yönergeleri tek çıkışı teşvik ediyor .
Açık olmak gerekirse , birden fazla geri dönüşün olmasının her zaman yanlış olduğunu söylemiyorum . Ancak başka türlü eşdeğer çözümler verildiğinde, tek dönüşlü olanı tercih etmek için birçok iyi neden vardır.
Contract.Ensures
çoklu dönüş noktaları ile kullanabilirsiniz, çünkü post-condition sorunu bir nonissue eklemek istiyorum .
goto
Ortak temizleme kodunu almak için kullanıyorsanız , temizleme kodunun return
sonunda bir tane olacak şekilde işlevi muhtemelen basitleştirdiniz . Yani sorunu çözdüğünüzü söyleyebilirsiniz goto
, ama diyelim ki bunu basitleştirerek çözdünüz return
.
Tek bir çıkış noktasına sahip olmak hata ayıklamada avantaj sağlar, çünkü gerçekte hangi değerin döndürüleceğini görmek için bir işlevin sonunda tek bir kesme noktası ayarlamanıza olanak tanır.
Genel olarak bir fonksiyondan sadece bir çıkış noktasına sahip olmaya çalışırım. Bununla birlikte, bunu yapmak, aslında gerekenden daha karmaşık bir işlev gövdesi yaratmanın zamanları vardır, bu durumda birden fazla çıkış noktasına sahip olmak daha iyidir. Gerçekte ortaya çıkan karmaşıklığa dayanan bir "karar çağrısı" olmak zorundadır, ancak amaç karmaşıklık ve anlaşılabilirlikten ödün vermeden mümkün olduğunca az çıkış noktası olmalıdır.
Hayır, çünkü 1970'lerde artık yaşamıyoruz . İşleviniz birden fazla geri dönüşün sorun olacağı kadar uzunsa, çok uzun olur.
(İstisnalar dışında bir dilde herhangi bir çok satırlı işlevin zaten birden çok çıkış noktasına sahip olması gerçeği dışında.)
Bir şeyleri gerçekten karmaşık hale getirmediği sürece tercihim tek çıkış olacaktır. Bazı durumlarda, birden fazla mevcut noktanın diğer daha önemli tasarım sorunlarını maskeleyebileceğini buldum:
public void DoStuff(Foo foo)
{
if (foo == null) return;
}
Bu kodu görünce, hemen sorardım:
Bu soruların cevaplarına bağlı olarak,
Yukarıdaki her iki durumda da, kod 'foo'nun asla boş olmamasını ve ilgili arayanların değişmesini sağlamak için muhtemelen bir iddia ile yeniden çalışılabilir.
Birden fazla var aslında negatif bir olabilir iki diğer nedenleri (belirli düşünüyorum C ++ kodu) etkisi . Kod boyutu ve derleyici optimizasyonlarıdır.
Bir işlevin çıkışındaki kapsamda olmayan bir POD C ++ nesnesi, yıkıcısını çağırır. Birkaç geri dönüş ifadesi olduğunda, kapsamda farklı nesneler olabilir ve bu nedenle çağrılacak yıkıcıların listesi farklı olacaktır. Bu nedenle derleyicinin her bir return ifadesi için kod üretmesi gerekir:
void foo (int i, int j) {
A a;
if (i > 0) {
B b;
return ; // Call dtor for 'b' followed by 'a'
}
if (i == j) {
C c;
B b;
return ; // Call dtor for 'b', 'c' and then 'a'
}
return 'a' // Call dtor for 'a'
}
Kod boyutu bir sorunsa - bu kaçınılması gereken bir şey olabilir.
Diğer konu "Adlandırılmış Dönüş Değeri Optimizasyonu" (Kopya Elision, ISO C ++ '03 12.8 / 15) ile ilgilidir. C ++, bir uygulamanın aşağıdaki durumlarda kopyalama yapıcısını çağırmayı atlamasına izin verir:
A foo () {
A a1;
// do something
return a1;
}
void bar () {
A a2 ( foo() );
}
Kodu olduğu gibi alırsak, 'a1' nesnesi 'foo' içinde oluşturulur ve daha sonra kopya yapısı 'a2' oluşturmak için çağrılır. Bununla birlikte, kopya seçimi derleyicinin yığın üzerinde 'a2' ile aynı yerde 'a1' oluşturmasına izin verir. Bu nedenle, işlev geri döndüğünde nesneyi "kopyalamaya" gerek yoktur.
Birden çok çıkış noktası, derleyicinin bunu algılamaya çalışmasını karmaşıklaştırır ve en azından VC ++ 'ın nispeten yeni bir sürümü için, en iyileştirme, işlev gövdesinin birden fazla dönüşü olduğu yerde gerçekleşmedi. Daha fazla bilgi için bkz. Visual C ++ 2005'te Adlandırılmış Dönüş Değeri Optimizasyonu .
throw new ArgumentNullException()
bu durumda C #'da olduğu gibi), diğer düşüncelerinizi gerçekten beğendim, hepsi benim için geçerli ve bazılarında kritik olabilir niş bağlamları.
foo
test edileceği sorusunun konu ile ilgisi yoktur, ki bu if (foo == NULL) return; dowork;
ya daif (foo != NULL) { dowork; }
Tek bir çıkış noktasına sahip olmak, Siklomatik Karmaşıklığı azaltır ve bu nedenle teoride , değiştirdiğinizde kodunuza hata girme olasılığını azaltır. Ancak uygulama, daha pragmatik bir yaklaşıma ihtiyaç olduğunu öne sürmektedir. Bu nedenle tek bir çıkış noktasına sahip olma eğilimindedir, ancak kodumun daha okunabilir olması durumunda birkaç tane olmasına izin veririm.
Kendimi sadece bir return
ifade kullanmaya zorladım, çünkü bir anlamda kod kokusu üretecek. Açıklamama izin ver:
function isCorrect($param1, $param2, $param3) {
$toret = false;
if ($param1 != $param2) {
if ($param1 == ($param3 * 2)) {
if ($param2 == ($param3 / 3)) {
$toret = true;
} else {
$error = 'Error 3';
}
} else {
$error = 'Error 2';
}
} else {
$error = 'Error 1';
}
return $toret;
}
(Koşullar keyfi ...)
Daha fazla koşul, fonksiyon büyüdükçe, okuması daha zor olur. Kod kokusuna uyum sağladıysanız, bunu fark edersiniz ve kodu yeniden düzenlemek istersiniz. İki olası çözüm:
Çoklu İade
function isCorrect($param1, $param2, $param3) {
if ($param1 == $param2) { $error = 'Error 1'; return false; }
if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
return true;
}
Ayrı Fonksiyonlar
function isEqual($param1, $param2) {
return $param1 == $param2;
}
function isDouble($param1, $param2) {
return $param1 == ($param2 * 2);
}
function isThird($param1, $param2) {
return $param1 == ($param2 / 3);
}
function isCorrect($param1, $param2, $param3) {
return !isEqual($param1, $param2)
&& isDouble($param1, $param3)
&& isThird($param2, $param3);
}
Verilen, daha uzun ve biraz dağınık, ancak işlevi bu şekilde yeniden düzenleme sürecinde,
İstediğiniz kadar veya kodu daha temiz yapan herhangi biri olması gerektiğini söyleyebilirim ( bekçi hükümleri gibi) .
Şahsen hiç "en iyi uygulamaları" duymadım / görmedim sadece tek bir iade beyanınız olmalı.
Çoğunlukla, bir mantık yoluna dayanarak mümkün olan en kısa sürede bir işlevden çıkma eğilimindeyim (koruma cümleleri bunun mükemmel bir örneğidir).
Birden fazla dönüş genellikle iyi olduğuna inanıyorum (C # yazdığınız kodda). Tek dönüşlü stil C'nin bir dayanak noktasıdır. Ama muhtemelen C kodlamıyorsunuzdur.
Tüm programlama dillerinde bir yöntem için yalnızca bir çıkış noktası gerektiren bir yasa yoktur . Bazı insanlar bu tarzın üstünlüğü konusunda ısrar ederler ve bazen bunu bir "kural" ya da "yasaya" yükseltirler, ancak bu inanç herhangi bir kanıt ya da araştırma ile desteklenmez.
Birden fazla dönüş stili, kaynakların açıkça ayrılması gereken C kodunda kötü bir alışkanlık olabilir, ancak Java, C #, Python veya JavaScript gibi otomatik çöp toplama ve try..finally
bloklar (veusing
C # ) ve bu argüman geçerli değildir - bu dillerde, merkezi el ile kaynak aktarımına ihtiyaç duyulması çok nadirdir.
Tek bir geri dönüşün daha okunabilir olduğu durumlar ve bunun olmadığı durumlar vardır. Kod satır sayısını azaltıp azaltmadığını, mantığı daha açık hale getirip getirmediğini veya parantez ve girintilerin veya geçici değişkenlerin sayısını azalttığını görün.
Bu nedenle, sanatsal duyarlılıklarınıza uygun sayıda getiri kullanın, çünkü bu teknik değil, bir düzen ve okunabilirlik sorunudur.
Bunu blogumda daha uzun süre konuştum .
Sonuçta kaçınılmaz olan "ok" programlamasında söylenecek kötü şeyler olduğu gibi, tek bir çıkış noktasına sahip olmak hakkında söylenecek iyi şeyler vardır .
Giriş doğrulaması veya kaynak tahsisi sırasında birden fazla çıkış noktası kullanıyorsanız, tüm 'hata çıkışlarını' fonksiyonun en üstüne koymaya çalışıyorum.
Hem "SSDSLPedia" nın Spartan Programlama makalesinde ve "Portland Pattern Repository's Wiki" nin tek işlevli çıkış noktası makalesinde bununla ilgili bazı tartışmalar vardır. Ayrıca, elbette, dikkate alınması gereken bu yazı var.
Örneğin, kaynakları tek bir yerde serbest bırakmak için tek bir çıkış noktası (istisna etkin olmayan herhangi bir dilde) istiyorsanız, goto'nun dikkatli uygulamasının iyi olduğunu düşünüyorum; örneğin bu oldukça tutarlı örneğe bakın (ekran gayrimenkulünü kaydetmek için sıkıştırılmış):
int f(int y) {
int value = -1;
void *data = NULL;
if (y < 0)
goto clean;
if ((data = malloc(123)) == NULL)
goto clean;
/* More code */
value = 1;
clean:
free(data);
return value;
}
Şahsen ben genel olarak, ok programlamayı birden fazla çıkış noktasından hoşlanmadığımdan daha fazla sevmiyorum, ancak her ikisi de doğru uygulandığında faydalıdır. En iyisi, elbette, programınızı hiçbirini gerektirmeyecek şekilde yapılandırmaktır. İşlevinizi birden fazla parçaya bölmek genellikle yardımcı olur :)
Bunu yaparken, bu örnekte olduğu gibi yine de birden fazla çıkış noktasıyla sonuçlandığımı görüyorum, burada bazı büyük işlevler birkaç küçük işleve ayrıldı:
int g(int y) {
value = 0;
if ((value = g0(y, value)) == -1)
return -1;
if ((value = g1(y, value)) == -1)
return -1;
return g2(y, value);
}
Projeye veya kodlama kurallarına bağlı olarak, kazan plakası kodunun çoğu makrolarla değiştirilebilir. Bir yan not olarak, bu şekilde parçalanması g0, g1, g2 fonksiyonlarının ayrı ayrı test edilmesini kolaylaştırır.
Açıkçası, bir OO ve istisna özellikli bir dilde, if-ifadelerini bu şekilde kullanmam (ya da yeterince çabayla kurtulabilseydim) ve kod çok daha basit olurdu. Ve oksuz. Ve nihai olmayan getirilerin çoğu muhtemelen istisna olacaktır.
Kısacası;
Atasözünü biliyorsunuz - güzellik bakanın gözündedir .
Bazı insanlar yemin NetBeans ve bazı IntelliJ IDEA , bazıları tarafından Python tarafından ve bazı PHP .
Bazı mağazalarda bunu yapmakta ısrar ederseniz işinizi kaybedebilirsiniz:
public void hello()
{
if (....)
{
....
}
}
Soru tamamen görünürlük ve sürdürülebilirlikle ilgilidir.
Devlet makinelerinin mantığını ve kullanımını azaltmak ve basitleştirmek için boolean cebiri kullanmaya bağımlıyım. Bununla birlikte, kodlamada "matematiksel teknikler" kullanmamın uygun olmadığına inanan geçmiş meslektaşları vardı, çünkü görünür ve sürdürülemezdi. Ve bu kötü bir uygulama olurdu. Üzgünüm insanlar, benim kullandığım teknikler benim için çok görünür ve sürdürülebilir - çünkü altı ay sonra koda döndüğümde, kodu açıkça bir atasöz spagetti dağınıklığı gördüğünü anlıyorum.
Hey dostum (eski bir müşterinin söylediği gibi), düzeltmeniz gerektiğinde nasıl düzelteceğinizi bildiğiniz sürece istediğiniz şeyi yapın.
20 yıl önce hatırlıyorum, bir meslektaşım bugün çevik kalkınma stratejisi olarak adlandırılacak olanı kullanmaktan kovuldu . Çok titiz bir planı vardı. Ama menajeri ona "Kullanıcılara kademeli olarak özellikler bırakamazsınız! Şelaleye bağlı kalmalısınız." ." Yöneticiye verdiği yanıt, artan gelişimin müşterinin ihtiyaçlarına daha hassas olacağıydı. Müşterilerin ihtiyaçları için gelişmeye inanıyordu, ancak yönetici "müşterinin ihtiyacını" kodlamaya inanıyordu.
Veri normalizasyonu, MVP ve MVC sınırlarını aşmaktan sık sık suçluyuz . Bir işlev inşa etmek yerine satır içi yapıyoruz. Kısayollar alıyoruz.
Şahsen, PHP'nin kötü uygulama olduğuna inanıyorum, ama ne biliyorum. Tüm teorik argümanlar bir dizi kuralı yerine getirmeye çalışmakla sınırlıdır
kalite = hassasiyet, sürdürülebilirlik ve karlılık.
Diğer tüm kurallar arka planda kaybolur. Ve elbette bu kural asla kaybolmaz:
Tembellik iyi bir programcının erdemidir.
Ben erken dönmek ve başka bir yöntemin sonunda çıkmak için nöbet yan tümceleri kullanmaya eğilimli. Tek giriş ve çıkış kuralı tarihsel öneme sahiptir ve özellikle birden çok döndürme (ve birçok kusur) içeren tek bir C ++ yöntemi için 10 A4 sayfaya kadar koşan eski kodla uğraşırken yararlı olmuştur. Daha yakın zamanlarda, kabul edilen iyi uygulama yöntemleri küçük tutmaktır, bu da çoklu çıkışları anlama empedansından daha az emektir. Yukarıdan kopyalanan aşağıdaki Kronoz örneğinde, soru // Kodun geri kalanı ... ?
void string fooBar(string s, int? i) {
if(string.IsNullOrEmpty(s) || i == null) return null;
var res = someFunction(s, i);
foreach(var r in res) {
if(!r.Passed) return null;
}
// Rest of code...
return ret;
}
Örneğin bir şekilde yapıldığının farkındayım, ancak foreach döngü sonra bir nöbet yan tümcesi kabul edilebilir bir LINQ deyimi içine . Yine, bir örnekte, kodun amacı belli değildir ve someFunction () başka bir yan etkiye sahip olabilir veya sonuç // Kodun geri kalanı ... 'nda kullanılabilir .
if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;
Aşağıdaki yeniden düzenlenmiş işlevi vermek:
void string fooBar(string s, int? i) {
if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;
// Rest of code...
return ret;
}
null
argümanın kabul edilmediğini belirten bir istisna atmak yerine neden geri dönesin?
Düşünebileceğim iyi bir neden, kod bakımı için: tek bir çıkış noktanız var. Sonucun biçimini değiştirmek istiyorsanız, ..., uygulanması çok daha kolaydır. Ayrıca, hata ayıklama için, sadece bir kesme noktası yapıştırabilirsiniz :)
Bunu söyledikten sonra, bir zamanlar kodlama standartlarının 'işlev başına bir dönüş ifadesi' uyguladığı bir kütüphanede çalışmak zorunda kaldım ve oldukça zor buldum. Çok sayıda sayısal hesaplama kodu yazıyorum ve genellikle 'özel durumlar' var, bu yüzden kodu takip etmek oldukça zor oldu ...
Birden çok çıkış noktası, yeterince küçük işlevler için iyidir - yani, bir ekran uzunluğunda bütünüyle görüntülenebilen bir işlevdir. Uzun bir fonksiyon da aynı şekilde birden fazla çıkış noktası içeriyorsa, fonksiyonun daha da kesilebileceğinin bir işaretidir.
Kesinlikle gerekli olmadıkça çoklu çıkış işlevlerinden kaçındım dedi . Daha karmaşık fonksiyonlarda bazı belirsiz çizgilerde biraz geri dönüş nedeniyle oluşan hataların ağrısını hissettim.
Size tek bir çıkış yolunu zorlayan korkunç kodlama standartlarıyla çalıştım ve işlev önemsiz bir şeyse sonuç neredeyse her zaman yapılandırılmamış spagetti.
if
Başarıyı döndüren veya döndürmeyen her yöntem çağrısının önünde ifadeyi atlamak için aklınıza söylemekten bahsetmekten bahsetmiyorum bile :(
Tek çıkış noktası - diğer her şey eşit - kodu önemli ölçüde daha okunabilir hale getirir. Ama bir sorun var: popüler inşaat
resulttype res;
if if if...
return res;
sahte, "res =" "dönüş" ten çok daha iyi değil. Tek bir return ifadesine sahiptir, ancak fonksiyonun gerçekten sona erdiği birden fazla nokta vardır.
Birden fazla döndürme (veya "res =" s) işleviniz varsa, bunu tek çıkış noktası olan daha küçük işlevlere bölmek genellikle iyi bir fikirdir.
Her zamanki politikam, kodun karmaşıklığı daha fazla eklenerek büyük ölçüde azaltılmadıkça, bir işlevin sonunda yalnızca bir dönüş ifadesine sahip olmaktır. Aslında, daha çok bir dönüş ifadesi olmadan tek bir dönüş kuralını uygulayan Eiffel hayranıyım (sonucunuzu koymak için sadece otomatik olarak oluşturulan bir 'sonuç' değişkeni var).
Kodun, onlarsız açık sürümden daha fazla dönüşle daha net hale getirilebileceği durumlar kesinlikle vardır. Birden fazla dönüş ifadesi olmadan anlaşılamayacak kadar karmaşık bir işleve sahipseniz, daha fazla yeniden işleme ihtiyaç olduğu iddia edilebilir, ancak bazen bu tür şeyler hakkında pragmatik olmak iyidir.
Birkaç döndürmeyle sonuçlanırsanız, kodunuzla ilgili bir sorun olabilir. Aksi takdirde, bazen kodu daha temiz hale getirdiğinde, bazen bir altyordamda birden fazla yerden geri dönmek mümkün olduğunu kabul ediyorum.
sub Int_to_String( Int i ){
given( i ){
when 0 { return "zero" }
when 1 { return "one" }
when 2 { return "two" }
when 3 { return "three" }
when 4 { return "four" }
...
default { return undef }
}
}
böyle daha iyi yazılır
@Int_to_String = qw{
zero
one
two
three
four
...
}
sub Int_to_String( Int i ){
return undef if i < 0;
return undef unless i < @Int_to_String.length;
return @Int_to_String[i]
}
Bunun sadece kısa bir örnek olduğunu unutmayın
Sonunda bir rehber olarak Tek dönüş için oy veriyorum. Bu, ortak bir kod temizleme işlemine yardımcı olur ... Örneğin, aşağıdaki koda bir göz atın ...
void ProcessMyFile (char *szFileName)
{
FILE *fp = NULL;
char *pbyBuffer = NULL:
do {
fp = fopen (szFileName, "r");
if (NULL == fp) {
break;
}
pbyBuffer = malloc (__SOME__SIZE___);
if (NULL == pbyBuffer) {
break;
}
/*** Do some processing with file ***/
} while (0);
if (pbyBuffer) {
free (pbyBuffer);
}
if (fp) {
fclose (fp);
}
}
Bu muhtemelen alışılmadık bir perspektiftir, ancak bence birden fazla dönüş ifadesinin tercih edileceğine inanan herkes, sadece 4 donanım kesme noktasını destekleyen bir mikroişlemcide bir hata ayıklayıcı kullanmak zorunda kalmamıştır. ;-)
"Ok kodu" sorunları tamamen doğru olsa da, birden çok dönüş deyimi kullanırken gider gibi görünüyor bir hata ayıklayıcı kullandığınız durumdadır. Çıkışı ve dolayısıyla dönüş koşulunu göreceğinizi garanti etmek için bir kesme noktası koymak için uygun bir tümünü yakalama konumunuz yoktur.
Bir işlevde ne kadar fazla getiri ifadesi varsa, o yöntemdeki karmaşıklık o kadar yüksek olur. Kendinize çok fazla dönüş ifadeniz olup olmadığını merak ediyorsanız, bu işlevde çok fazla kod satırınız olup olmadığını kendinize sormak isteyebilirsiniz.
Ancak, bir / birçok dönüş ifadesinde yanlış bir şey yoktur. Bazı dillerde, diğerlerinden (C) daha iyi bir uygulamadır (C ++).