İade ile anahtar deyimi - kod doğruluğu


92

Diyelim ki C'de yaklaşık olarak bu yapıya sahip kodum var:

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

Açıkçası, breakkodun doğru çalışması için s'ler gerekli değildir, ancak onları oraya koymazsam kötü bir uygulama gibi görünüyor.

Ne düşünüyorsun? Bunları çıkarmak sorun olur mu? Yoksa onları daha fazla "doğruluk" için mi saklayacaksın?

Yanıtlar:


140

breakİfadeleri kaldırın . Bunlara gerek yoktur ve belki bazı derleyiciler "Ulaşılamayan kod" uyarıları yayınlayacaktır .


2
Aynısı, continueveya gibi diğer koşulsuz kontrol atlama ifadeleri için de geçerlidir goto- bunları break.
CAF

32

Tamamen farklı bir tavır alırdım. Yöntemin / işlevin ortasında GERİ DÖNME. Bunun yerine, dönüş değerini yerel bir değişkene koyun ve sonunda gönderin.

Şahsen, aşağıdakileri daha okunaklı buluyorum:

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

24
"Tek Çıkış" felsefesi, her şeyin doğru bir şekilde temizlendiğinden emin olmanız gereken C'de anlamlıdır. C ++ 'da bir istisna ile herhangi bir noktada işlevden çekilebileceğinizi düşünürsek, tek çıkış felsefesi C ++' da pek kullanışlı değildir.
Billy ONeal

29
Teoride bu harika bir fikirdir, ancak sıklıkla okunabilirliği engelleyebilecek ekstra kontrol blokları gerektirir.
mikerobi

8
İşleri bu şekilde yapmamın birincil nedeni, daha uzun işlevlerde kodla tarama yaparken çıkış vektörünü kaçırmanın çok kolay olmasıdır. Bununla birlikte, çıkış her zaman sonunda olduğunda, neler olup bittiğini çabucak kavramak daha kolaydır.
NotMe

7
Eh, bu ve kodun ortasındaki dönüş blokları bana GOTO'nun kötüye kullanımını hatırlatıyor.
NotMe

5
@Chris: Daha uzun işlevlerde tamamen aynı fikirdeyim - ancak bu genellikle daha büyük sorunlara yol açar, bu yüzden yeniden düzenleyin. Çoğu durumda, bu, bir anahtara dönen önemsiz bir işlevdir ve ne olacağı çok açıktır, bu yüzden beyin gücünü fazladan bir değişkeni izlemek için harcamayın.
Stephen

8

Şahsen iadeleri kaldırır ve molaları tutardım. Bir değişkene bir değer atamak için switch deyimini kullanırdım. Sonra bu değişkeni switch ifadesinden sonra döndürün.

Bu tartışılabilir bir nokta olsa da, her zaman iyi tasarım ve kapsüllemenin bir giriş ve tek çıkış anlamına geldiğini hissettim. Mantığı garanti etmek çok daha kolaydır ve işlevinizin döngüsel karmaşıklığına bağlı olarak temizleme kodunu yanlışlıkla kaçırmazsınız.

Bir istisna: Erken dönme, bir işlevin başlangıcında herhangi bir kaynak elde edilmeden önce kötü bir parametre algılanırsa uygundur.


Bu özellikle iyi olabilir switch, ancak genel olarak en iyisi olmayabilir. Diyelim ki, bir fonksiyonun içindeki switch deyimi, sadece belirli durumlarda geçerli olan diğer 500 satırlık koddan önce geliyor true. Yürütmesi gerekmeyen durumlar için tüm bu ekstra kodu çalıştırmanın amacı nedir; sadece daha iyi değil returniçeride switcholanlar için cases?
Fr0zenFyr

6

Araları koruyun - eğer aralarda zaten varsa, kodu daha sonra düzenlerseniz / düzenlerken sorunla karşılaşma olasılığınız azalır.

Bunu söyledikten sonra, birçokları (ben dahil) bir işlevin ortasından dönmek kötü bir uygulama olarak görülüyor. İdeal olarak bir fonksiyonun bir giriş noktası ve bir çıkış noktası olmalıdır.


5

Onları kaldır. caseİfadelerden dönmek deyimseldir ve aksi takdirde "ulaşılamaz kod" gürültüsüdür.


5

Onları çıkarırdım. Benim kitabımda, bunun gibi ölü kodlar hata olarak görülmelidir çünkü bu, size bir çift alma yapmanıza ve kendinize "Bu satırı nasıl çalıştırabilirim?" Diye sormanıza neden olur.


4

Normalde kodu onlarsız yazardım. IMO, ölü kod özensizliği ve / veya anlayış eksikliğini gösterme eğilimindedir.

Elbette şöyle bir şey de düşünürdüm:

char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

Düzenleme: Düzenlenmiş gönderide bile, bu genel fikir iyi çalışabilir:

char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

Bir noktada, özellikle giriş değerleri seyrekse (örneğin, 1, 100, 1000 ve 10000), bunun yerine seyrek bir dizi istersiniz. Bunu bir ağaç veya harita olarak oldukça iyi uygulayabilirsiniz (tabii ki, bu durumda da bir anahtar hala çalışıyor).


Bu çözümün neden işe yaramayacağını yansıtmak için gönderiyi düzenledim.
houbysoft

@your edit: Evet, ancak daha fazla bellek alır, vb. IMHO anahtarı en basit yoldur, bu kadar küçük bir işlevde istediğim şey budur (yalnızca anahtarı yapar).
houbysoft

3
@houbysoft: Ekstra hafıza gerektireceği sonucuna varmadan önce dikkatlice bakın. Tipik bir derleyicide, "foo" yu iki kez kullanmak (örneğin) tek bir veri bloğuna iki işaretçi kullanır, verileri kopyalamak yerine. Daha kısa kod da bekleyebilirsiniz. Çok sayıda tekrarlanan değeriniz olmadıkça , genellikle hafızadan tasarruf sağlar.
Jerry Coffin

doğru. Yine de çözümüme bağlı kalacağım çünkü değerler listesi çok uzun ve bir geçiş daha hoş görünüyor.
houbysoft

1
Gerçekten de, anahtar değerleri bitişik olduğunda ve veri türü homojen olduğunda, böyle bir soruna daha zarif ve etkili yanıt, bir anahtar dizisinden tamamen kaçınmaktır.
Dwayne Robinson

2

Onları kaldırın ve bir varsayılan tanımlayın derdim: dal.


Mantıklı bir varsayılan yoksa, bir varsayılan tanımlamamalısınız.
Billy ONeal

bir tane var ve ben bir tane tanımladım, sadece soruya koymadım, şimdi düzenledim.
houbysoft

2

Bir diziye sahip olmak daha iyi olmaz mıydı?

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

ve yapmak return arr[something];?

Genel olarak uygulamayla ilgiliyse, breakifadeleri anahtarda tutmalısınız . returnGelecekte ifadelere ihtiyaç duymamanız durumunda, bir sonrakine düşme şansını azaltır case.


Bu çözümün neden işe yaramayacağını yansıtmak için gönderiyi düzenledim.
houbysoft

2

"Doğruluk" için, tek girişli, tek çıkışlı bloklar iyi bir fikirdir. En azından ben bilgisayar bilimi derecemi aldığımda öyleydiler. Bu yüzden muhtemelen bir değişken bildiririm, ona anahtarda atar ve fonksiyonun sonunda bir kez döner


2

Ne düşünüyorsun? Bunları çıkarmak sorun olur mu? Yoksa onları daha fazla "doğruluk" için mi saklayacaksın?

Bunları çıkarmak sorun değil. Kullanmak return , tambreak olarak kullanılmaması gereken senaryodur .


1

İlginç. Bu cevapların çoğundan elde edilen fikir birliği, gereksiz breakifadenin gereksiz bir dağınıklık olduğu şeklinde görünüyor . Öte yandan, breakbir anahtardaki ifadeyi bir davanın 'kapanması' olarak okudum . caseSonunda bitmeyen bloklar, breakhatalar olsa da potansiyel düşme olarak üzerime atlama eğilimindedir.

A returnyerine a olduğunda durumun böyle olmadığını biliyorum break, ancak gözlerim bir anahtardaki kasayı bu şekilde 'okuyor', bu yüzden kişisel olarak her birinin a caseile eşleştirilmesini tercih ederim break. Ancak birçok derleyici, breaksonradan returngereksiz / ulaşılamaz olmasından şikayet ediyor ve görünüşe göre zaten azınlıktayım.

Öyleyse breakaşağıdakilerden kurtulun a return.

Not: Tüm bunlar, tek giriş / çıkış kuralını ihlal etmenin iyi bir fikir olup olmadığını görmezden geliyor. Hal böyle olunca da maalesef şartlara göre değişen bir fikrim var ...


0

Kaldır diyorum. Kodunuz o kadar okunamıyorsa, 'güvenli tarafta olmak' için orada molalar vermeniz gerekiyorsa, kodlama stilinizi yeniden gözden geçirmelisiniz :)

Ayrıca, anahtar deyimindeki kesintileri ve dönüşleri karıştırmamayı, bunlardan birine bağlı kalmayı her zaman tercih ettim.


0

Ben şahsen e'leri kaybetme eğilimindeyim break. Muhtemelen bu alışkanlığın bir kaynağı, Windows uygulamaları için pencere prosedürlerini programlamaktır:

LRESULT WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            return sizeHandler (...);
        case WM_DESTROY:
            return destroyHandler (...);
        ...
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Ben şahsen bu yaklaşımı, her bir işleyici tarafından belirlenen bir dönüş değişkenini bildirip sonunda onu döndürmekten çok daha basit, kısa ve esnek buluyorum. Bu yaklaşım göz önüne alındığında break, e'ler gereksizdir ve bu nedenle gitmeleri gerekir - yararlı bir amaca hizmet etmezler (sözdizimsel olarak veya görsel olarak IMO) ve yalnızca kodu şişirirler.


0

Sanırım * molalar bir amaç için var. Programlamanın 'ideolojisini' canlı tutmaktır. Kodumuzu mantıksal tutarlılık olmadan sadece 'programlayacaksak', belki şimdi size okunabilir, ama yarın deneyin. Patronunuza açıklamayı deneyin. Windows 3030'da çalıştırmayı deneyin.

Bleah, fikir çok basit:


Switch ( Algorithm )
{

 case 1:
 {
   Call_911;
   Jump;
 }**break**;
 case 2:
 {
   Call Samantha_28;
   Forget;
 }**break**;
 case 3:
 {
   Call it_a_day;
 }**break**;

Return thinkAboutIt?1:return 0;

void Samantha_28(int oBed)
{
   LONG way_from_right;
   SHORT Forget_is_my_job;
   LONG JMP_is_for_assembly;
   LONG assembly_I_work_for_cops;

   BOOL allOfTheAbove;

   int Elligence_says_anyways_thinkAboutIt_**break**_if_we_code_like_this_we_d_be_monkeys;

}
// Sometimes Programming is supposed to convey the meaning and the essence of the task at hand. It is // there to serve a purpose and to keep it alive. While you are not looking, your program is doing  // its thing. Do you trust it?
// This is how you can...
// ----------
// **Break**; Please, take a **Break**;

/ * Yine de küçük bir soru. Yukarıdakileri okurken ne kadar kahve içtiniz? BT Tatili sistemi bazen * /


0

Bir noktada çıkış kodu. Bu, kod için daha iyi okunabilirlik sağlar. Araya dönüş ifadeleri (Çoklu çıkışlar) eklemek hata ayıklamayı zorlaştırır.


0

"Arama" türünde bir kodunuz varsa, switch-case cümlesini kendi başına bir yöntemde paketleyebilirsiniz.

Eğlenmek için geliştirdiğim bir "hobi" sisteminde bunlardan birkaçına sahibim:

private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(Evet. Bu GURPS alanı ...)

Çoğu durumda bir yöntemde birden fazla dönüşten kaçınmanız gerektiği konusunda başkalarına katılıyorum ve bunun bir dizi veya başka bir şey olarak daha iyi uygulanmış olabileceğinin farkındayım. Anahtar-durum-dönüşünün, yukarıdaki gibi, girdi ve çıktı arasında 1-1 korelasyonu olan bir arama tablosuyla oldukça kolay bir eşleşme olduğunu buldum (rol yapma oyunları bunlarla dolu, eminim başka "işletmeler" de): D

Öte yandan, case-clause daha karmaşıksa veya switch-ifadesinden sonra bir şey olursa, return kullanılmasını tavsiye etmem, bunun yerine anahtarda bir değişken ayarlayın, onu bir break ile bitirin ve return sonunda değişkenin değeri.

(Üçüncü olarak? ... bir anahtarı her zaman kendi yöntemine yeniden düzenleyebilirsiniz ... performans üzerinde bir etkisi olacağından şüpheliyim ve modern derleyicilerin bunu şöyle tanıması bile beni şaşırtmaz satır içi olabilecek bir şey ...)

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.