"Əgər" zəncirlərindən necə çəkinirsiniz?

Bu pseudocode olduğumu düşünürəm:

 bool conditionA = executeStepA(); if (conditionA){ bool conditionB = executeStepB(); if (conditionB){ bool conditionC = executeStepC(); if (conditionC){ ... } } } executeThisFunctionInAnyCase(); 

executeStepX funksiyaları yerinə yetirilməlidir və yalnız əvvəlcədən yerinə yetirilirsə. Hər halda, executeThisFunctionInAnyCase funksiyası sonunda çağırılmalıdır. Proqramlaşdırma üçün yeni deyiləm, buna görə ən sadə sual üçün üzr istəyirik: kod uzunluğunun hesabına belə bir "kod piramidası" istehsal edən bu uzun zəncirdən qaçmaq üçün bir yol varmı (məsələn, C / C ++)?

Bilirəm ki, əgər biz zəng həyata executeThisFunctionInAnyCase , bu funksiyanı yerinə executeThisFunctionInAnyCase , bu kodu sadələşdirmək olar:

 bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return; 

Lakin məhdudiyyət funksiyanı yerinə executeThisFunctionInAnyCase . break edilən bəyanat bir şəkildə istifadə edilə bilərmi?

255
26 июня '14 в 15:25 2014-06-26 15:25 ABCplus , 26 iyun '14 'də saat 15:25' də təyin olunub 2014-06-26 15:25
ответ 51 cavab
  • 1
  • 2

Siz > (AND lojik) istifadə edə bilərsiniz:

 if (executeStepA()  executeStepB()  executeStepC()){ ... } executeThisFunctionInAnyCase(); 

Bu, həm sizin tələblərinizi təmin edəcək:

  • executeStep<X>() yalnız əvvəlki müvəffəq olduqda (bu qısa dövrə qiymətləndirmə adlanır)
  • executeThisFunctionInAnyCase() hər halda icra ediləcək
481
26 июня '14 в 15:33 2014-06-26 15:33 cavab 26 iyun 2014-cü il saat 15:33 'də Ayaqqabıya verildi 2014-06-26 15:33

Yalnız ikinci funksiyanı yerinə yetirmək üçün əlavə funksiyanı istifadə edin:

 void foo() { bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return; } void bar() { foo(); executeThisFunctionInAnyCase(); } 
border=0

Dərin iç içə ifs (ilk seçiminiz) istifadə edərək və ya "funksiyanın bir hissəsindən" çıxmaq istəyən adətən əlavə funksiyaya ehtiyacınız olduğunu bildirir.

353
26 июня '14 в 16:24 2014-06-26 16:24 cavab 26 iyun '14 'də 16:24' də ltjax'a verilir. 2014-06-26 16:24

Bu halda, köhnə məktəb C proqramçıları goto istifadə edir. Bu, Linux üslubunun həqiqətən həvəsləndirdiyi yeganə istifadə növüdür, bu mərkəzləşdirilmiş bir çıxış funksiyası deyilir:

 int foo() { int result = ; if(!executeStepA()) goto cleanup; if(!executeStepB()) goto cleanup; if(!executeStepC()) goto cleanup; result = 0; cleanup: executeThisFunctionInAnyCase(); return result; } 

Bəzi insanlar goto ilə işləyirlər, vücudu bir döngədə saxlayırlar və onu pozurlar, amma əslində hər iki yanaşma eynidır. Başqa bir təmizliyə ehtiyac varsa, goto yanaşma yaxşıdır, yalnız executeStepA() uğurlu olarsa:

 int foo() { int result = ; if(!executeStepA()) goto cleanupPart; if(!executeStepB()) goto cleanup; if(!executeStepC()) goto cleanup; result = 0; cleanup: innerCleanup(); cleanupPart: executeThisFunctionInAnyCase(); return result; } 

Bu halda loopback yanaşma istifadə edərkən, iki səviyyəli dövrü əldə edirsiniz.

162
26 июня '14 в 15:36 2014-06-26 15:36 26 iyun 'da 14.00 ' da cmaster tərəfindən verilmiş cavab , 2014-06-26 15:36

Bu ümumi bir vəziyyətdir və bununla məşğul olmaq üçün bir çox ümumi yollar var. İşdə canonical cavab mənim cəhd edir. Bir şeyi qaçırdığımı şərh edin və bu mesajı günə qədər saxlayacağam.

Bu bir oxdur

Nə müzakirə etdiyinizə anti-desen deyilir. Bu, bir ox deyilir, çünki iç içə ifs zənciri kodun redaktoru panelinin sağ tərəfində "işarələ" edən bir vizual ox təşkil edən hüquqa daha sonra və daha sonra sağa doğru uzanan kod bloklarını təşkil edir.

Guard istifadə oku düzəldin

Oxların qarşısını almaq üçün bəzi ümumi yollar burada müzakirə olunur . Ən ümumi metod, kodu əvvəlki istisna iş parçacıklarını işləyən qoruyucu modelini istifadə etməkdir və sonra əsas işləməni işləyir. əvəzinə

 if (ok) { DoSomething(); } else { _log.Error("oops"); return; } 

... istifadə edirsiniz?

 if (!ok) { _log.Error("oops"); return; } DoSomething(); //notice how this is already farther to the left than the example above 

Uzun mühafizəçi sıra olduqda, bütün mühafizəçilər sola tamamilə göründüyünü və ifs içərisinə daxil olmadığından, bu kodu əhəmiyyətli dərəcədə düzəldir. Bundan əlavə, siz görməli olaraq məntiqi vəziyyətinizi əlaqəli səhvlərinizlə əlaqələndirirsiniz. Bu, baş verən hadisənin hekayəsini asanlaşdırır:

Arrow:

 ok = DoSomething1(); if (ok) { ok = DoSomething2(); if (ok) { ok = DoSomething3(); if (!ok) { _log.Error("oops"); //Tip of the Arrow return; } } else { _log.Error("oops"); return; } } else { _log.Error("oops"); return; } 

Mühafizəçi:

 ok = DoSomething1(); if (!ok) { _log.Error("oops"); return; } ok = DoSomething2(); if (!ok) { _log.Error("oops"); return; } ok = DoSomething3(); if (!ok) { _log.Error("oops"); return; } ok = DoSomething4(); if (!ok) { _log.Error("oops"); return; } 

Çünki oxumaq obyektiv və kəmiyyətdir

  • Bu məntiqi blokun {və} simvolları bir-birinə daha yaxındır.
  • Müəyyən bir xətt anlamaq üçün lazım olan zehni kontekstin miqdarı azdır
  • Ehtiyac vəziyyətinə aid olan bütün məntiq eyni səhifədə olma ehtimalı.
  • Sayfayı / göz şeridini kaydırmak üçün bir kodlayıcıya ehtiyac böyük ölçüdə azalır.

Sonuna ümumi bir kod əlavə etmək

Təhlükəsizlik nümunəsi ilə bağlı olan problem "opportunistic return" və ya "opportunistic exit" adlanır. Başqa sözlə, hər bir funksiyanın tam bir çıxış nöqtəsinə malik olması lazım olan nümunəni pozur. Bu iki səbəbdən bir problemdir:

  • Bu, məsələn, bəzi insanları narahat edir. Paskalda proqramın necə işlədiyini öyrənən insanlar bir funksiyanı = bir çıxış nöqtəsi öyrəndi.
  • Bu, əlavədə olan obyektin nə olursa olsun çıxışda yerinə yetirilən kod bölməsini vermir .

Aşağıda, bu məhdudiyyətlə ya dil funksiyalarını və ya heç bir problem olmadan istifadə etmək üçün bəzi variantları təqdim etmişəm.

Seçim 1. Bunu edə bilməzsiniz: finally istifadə edin

Təəssüf ki, C ++ geliştiricisi olaraq bunu edə bilməzsiniz. Amma bu nəhayət açar sözü olan dillər üçün bir nömrəli cavabdır, buna görə də bu nədir.

 try { if (!ok) { _log.Error("oops"); return; } DoSomething(); //notice how this is already farther to the left than the example above } finally { DoSomethingNoMatterWhat(); } 

Seçim 2. Problemdən çəkinin: funksiyalarınızı yenidən qurun.

Kodu iki funksiyaya bölməklə problemlərdən qaçınmaq olar. Bu həll hər hansı bir dildə işləmək üstünlüyünə malikdir və bununla yanaşı, qüsurunuzun sürətini azaltmaq və hər hansı bir avtomatlaşdırılmış vahid testinin xüsusiyyətlərini yaxşılaşdırmaq üçün sübut edilmiş bir üsul olan tsiklik mürəkkəbliyi azalda bilər.

Burada bir nümunə:

 void OuterFunction() { DoSomethingIfPossible(); DoSomethingNoMatterWhat(); } void DoSomethingIfPossible() { if (!ok) { _log.Error("Oops"); return; } DoSomething(); } 

Variant 3. Tone: saxta loop istifadə edin

Gördüyüm başqa ümumi bir hiylə isə digər cavablarda göstərildiyi kimi (true) istifadə etməkdir.

 while(true) { if (!ok) break; DoSomething(); break; //important } DoSomethingNoMatterWhat(); 

goto istifadə edərkən daha az "dürüst" olmasına baxmayaraq, bu məntiqi bölgənin sərhədlərini açıq-aydın göstərir, çünki refactoring zaman səhvlərə daha az meylli olur. Etiketlerinizi və ya goto təlimatlarınızı goto və düzəldən bir saf kodlayıcı ciddi problemlərə səbəb ola bilər! (Və, şübhəsiz ki, şəkil çox yayılmışdır ki, düşünürəm ki, bu, niyyətini açıq şəkildə ifadə edir və buna görə də "inad" deyildir).

Bu variantlar üçün digər variantlar var. Məsələn, əvəzinə switch istifadə edə bilərsiniz. break sözü ilə qurulan hər hansı bir dil işləyəcək.

Variant 4. Nesnenin həyat dövründən istifadə edin

Digər bir yanaşma bir obyektin həyat dövründən istifadə edir. Parametrlərinizi aktuallaşdırmaq üçün kontekst obyektindən istifadə edin (sadəlövh nümunəmizi az qiymətləndirən bir şeydir) və bitirdikdən sonra onu qurtarın.

 class MyContext { ~MyContext() { DoSomethingNoMatterWhat(); } } void MainMethod() { MyContext myContext; ok = DoSomething(myContext); if (!ok) { _log.Error("Oops"); return; } ok = DoSomethingElse(myContext); if (!ok) { _log.Error("Oops"); return; } ok = DoSomethingMore(myContext); if (!ok) { _log.Error("Oops"); } //DoSomethingNoMatterWhat will be called when myContext goes out of scope } 

Qeyd Seçdiyiniz dildə obyektin həyat dövrü ilə tanış olmaq üçün əmin olun. Bunun üçün bir növ deterministik zibil toplanması lazımdır, yəni. Dağıdıcı çağırılacaq zaman bilmək lazımdır. Bəzi dillərdə dağıdıcı əvəzinə Dispose istifadə Dispose .

Seçim 4.1. Nesnenin həyat dövründən istifadə edin (banderol naxışı)

Bir obyekt yönümlü bir yanaşma istifadə edəcəyəmsə, bunu doğru bir şəkildə edə bilərsiniz. Bu seçim təmizləməyə ehtiyacı olan resursları, eləcə də digər əməliyyatları silmək üçün istifadə edir.

 class MyWrapper { bool DoSomething() {...}; bool DoSomethingElse() {...} void ~MyWapper() { DoSomethingNoMatterWhat(); } } void MainMethod() { bool ok = myWrapper.DoSomething(); if (!ok) _log.Error("Oops"); return; } ok = myWrapper.DoSomethingElse(); if (!ok) _log.Error("Oops"); return; } } //DoSomethingNoMatterWhat will be called when myWrapper is destroyed 

Yenə də, obyektin həyat dövrünü anladığınızdan əmin olun.

Seçim 5. >

Başqa bir metod qısa qapanış qiymətləndirməsindən istifadə etməkdir.

 if (DoSomething1()  DoSomething2()  DoSomething3()) { DoSomething4(); } DoSomethingNoMatterWhat(); 

Bu həll operator metodlarından istifadə edir. sol tərəfində yanlış qiymətləndirirsə, sağ tərəf heç vaxt qiymətləndirilmir.

Kompakt kod tələb olunduqda və kodu bir çox baxışı görmədikdə, məsələn, tanınmış bir alqoritm tətbiq edəndə bu oyun ən faydalıdır. Daha ümumi kodlaşdırma üçün bu kodun strukturu çox kövrəkdir; mantiqdə hətta kiçik bir dəyişiklik tamamilə yaza bilər.

126
27 июня '14 в 3:10 2014-06-27 03:10 Cavab John Wu tərəfindən 27 İyun 2014 günü 3:10 2014-06-27 03:10 tarixində verilir

Yalnız bunu

 if( executeStepA()  executeStepB()  executeStepC() ) { // ... } executeThisFunctionInAnyCase(); 

Sadədir.


Hər birinin sualını əsaslı şəkildə dəyişdirən üç dəyişikliyə görə (dördüncü versiyanı versiyaya yenidən hesablayırsanız) mən cavab verdiyim kod nümunəsini daxil edirəm:

 bool conditionA = executeStepA(); if (conditionA){ bool conditionB = executeStepB(); if (conditionB){ bool conditionC = executeStepC(); if (conditionC){ ... } } } executeThisFunctionInAnyCase(); 
60
Cavab Cheers və b. 26 июня '14 в 23:21 2014-06-26 23:21 - Alf 26 iyun, '14 'də saat 11:21 ' də 2014-06-26 23:21

Əslində, C ++-da hərəkətlərin təxirə salınması üçün bir yol var: bir obyektin yıkıcı istifadə.

C ++ 11-ə daxil olmağınızı fərz etsəniz:

 class Defer { public: Defer(std::function<void()> f): f_(std::move(f)) {} ~Defer() { if (f_) { f_(); } } void cancel() { f_ = std::function<void()>(); } private: Defer(Defer const = delete; Defer operator=(Defer const = delete; std::function<void()> f_; }; // class Defer 

Və sonra bu yardım proqramı ilə:

 int foo() { Defer const defer{ // or a lambda // ... if (!executeA()) { return 1; } // ... if (!executeB()) { return 2; } // ... if (!executeC()) { return 3; } // ... return 4; } // foo 
35
26 июня '14 в 15:49 2014-06-26 15:49 Cavab Matthieu M. tərəfindən 26 İyun 2014 tarixində 15:49 2014-06-26 15:49

Dönüş hesabatları ilə əlavə bir sarğı funksiyasını tələb etməyən yaxşı bir üsul var (Itjax tərəfindən təyin edilmiş metod). while(0) pseudo-loop do istifadə edir. while (0) əslində bir döngə olmadığını təmin edir, ancaq bir dəfə edam edilir. Bununla belə, loop sözdizimi break ifadəsini istifadə etməyə imkan verir.

 void foo() { // ... do { if (!executeStepA()) break; if (!executeStepB()) break; if (!executeStepC()) break; } while (0); // ... } 
34
26 июня '14 в 19:38 2014-06-26 19:38 Cavab Debasis tərəfindən 26 iyun 'da 19:38' də veriləcək 2014-06-26 19:38

Bunu da edə bilərsiniz:

 bool isOk = true; std::vector<bool (*)(void)> funcs; //vector of function ptr funcs.push_back( funcs.push_back( funcs.push_back( //... //this will stop at the first false return for (auto it = funcs.begin(); it != funcs.end()  isOk; ++it) isOk = (*it)(); if (isOk) //doSomeStuff executeThisFunctionInAnyCase(); 

Beləliklə, minimum xətt ölçüsü, zəng başına +1 xəttiniz var və bu asanlıqla verir.


EDIT : (Thanks @Unda) İMO'nun görünürlüğünü kaybettiğinden böyük bir fan deyil:

 bool isOk = true; auto funcs { //using c++11 initializer_list}; for (auto it = funcs.begin(); it != funcs.end()  isOk; ++it) isOk = (*it)(); if (isOk) //doSomeStuff executeThisFunctionInAnyCase(); 
19
26 июня '14 в 15:53 2014-06-26 15:53 cavab 26 iyun 2014 - cü il tarixində 15:53, 15:53 2014-06-26 15:53 tərəfindən verilir

Bu iş olacaq? Hesab edirəm ki, bu kodunuza bərabərdir.

 bool condition = true; // using only one boolean variable if (condition) condition = executeStepA(); if (condition) condition = executeStepB(); if (condition) condition = executeStepC(); ... executeThisFunctionInAnyCase(); 
18
27 июня '14 в 12:06 2014-06-27 12:06 Cavab Krumia tərəfindən 27 iyun 'da 14:06' də veriləcək 2014-06-27 12:06

Istədiyiniz kodu indi gördüyüm kimi görünsün:

 bool conditionA = executeStepA(); if (conditionA){ bool conditionB = executeStepB(); if (conditionB){ bool conditionC = executeStepC(); if (conditionC){ ... } } } executeThisFunctionInAnyCase(); 

Mən demək istəyirəm ki, oxumaq üçün sadə və asan saxlanılan düzgün yanaşma sualın müəyyən bir məqsədi olan daha az səviyyədə çentik olardı.

 // Pre-declare the variables for the conditions bool conditionA = false; bool conditionB = false; bool conditionC = false; // Execute each step only if the pre-conditions are met conditionA = executeStepA(); if (conditionA) conditionB = executeStepB(); if (conditionB) conditionC = executeStepC(); if (conditionC) { ... } // Unconditionally execute the 'cleanup' part. executeThisFunctionInAnyCase(); 

Bu loops, və ya digər kompleks strukturları while goto s, istisnalar, dummy ehtiyac qarşısını alır və sadəcə sadə bir tapşırıq davam edir.

14
26 июня '14 в 20:48 2014-06-26 20:48 Cavab ClickRick tərəfindən 26 iyun 'da 20:48' də verilir 2014-06-26 20:48

Siz bütün funksiyaları öz funksiyasında istədiyiniz şəkildə biçimlendirmiş olursunuz, qayıtdığınızda executeThisFunctionInAnyCase() funksiyasını yerinə executeThisFunctionInAnyCase() .

OP-də əsas nümunədə status yoxlaması və onun icrası,

 void InitialSteps() { bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return; } 

Və sonra belə deyilir,

 InitialSteps(); executeThisFunctionInAnyCase(); 

Mövcud Lambdas C ++ 11 varsa (OP'də C ++ 11 etiketi yoxdur, lakin onlar hələ də bir seçim ola bilər), onda biz ayrı funksiyanı ləğv edə və onu lambda ilə doldura bilərik.

 // Capture by reference (variable access may be required) auto initialSteps = [ { // any additional code bool conditionA = executeStepA(); if (!conditionA) return; // any additional code bool conditionB = executeStepB(); if (!conditionB) return; // any additional code bool conditionC = executeStepC(); if (!conditionC) return; }; initialSteps(); executeThisFunctionInAnyCase(); 
12
26 июня '14 в 15:32 2014-06-26 15:32 Cavab Niall tərəfindən 26 iyun 'da 15:32' də verilir 2014-06-26 15:32

Operatoru istənilən şəkildə pozmaq mümkündürmü?

Bəlkə də bu ən yaxşı həll deyil, amma operatorlarınızı bir do .. while (0) qoya bilərsiniz do .. while (0) return yerinə bəhs return istifadə edin.

12
26 июня '14 в 15:27 2014-06-26 15:27 cavab 26 iyun '14 'də saat 15:27' də veriləcək 2014-06-26 15:27

goto və do not like do { } while (0); loops do { } while (0); və C ++ istifadə etməyinizlə eyni təsiri təmin etmək üçün müvəqqəti lambda da istifadə edə bilərsiniz.

 [ { // create a capture all lambda if (!executeStepA()) { return; } if (!executeStepB()) { return; } if (!executeStepC()) { return; } }(); // and immediately call it executeThisFunctionInAnyCase(); 
10
29 июня '14 в 12:23 2014-06-29 12:23 Cavab Alex tərəfindən 29 İyun 2014 günü saat 12:23 'də verildi 2014-06-29 12:23

Yalnız bunu.

 coverConditions(); executeThisFunctionInAnyCase(); function coverConditions() { bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return; } 

99 dəfə 100, bunu etmək üçün yalnız bir yoldur.

Heç vaxt, heç vaxt, kompüter kodunda "mürəkkəb" bir şey etməyə çalışmayın.


Yeri gəlmişkən, əminəm ki, nəzərə aldığınız sonrakı gerçək qərar ...

Davam bəyanatı alqoritmik proqramlaşdırmada vacibdir. (Yəni, goto ifadəsi alqoritmik proqramlaşdırmada mühümdür.)

Bir çox proqramlaşdırma dildə bunu edə bilərsiniz:

 -(void)_testKode { NSLog(@"code a"); NSLog(@"code b"); NSLog(@"code c\n"); int x = 69; { if ( x == 13 ) { NSLog(@"code d---\n"); continue; } if ( x == 69 ) { NSLog(@"code e---\n"); continue; } if ( x == 13 ) { NSLog(@"code f---\n"); continue; } } NSLog(@"code g"); } 

(Hər şeydən əvvəl diqqət edin: bu nümunə kimi çılpaq bloklar, xüsusən alqoritmik proqramlaşdırma ilə məşğul olduqda, gözəl kod yazmanın mühüm və vacib bir hissəsidir.)

Yenə də, bu sizin başınıza tam olaraq nə oldu? Və bunu yazmaq üçün yaxşı bir yoldur, buna görə yaxşı bir instinkt var.

Ancaq fəlakətlə, məqsədi-c- nin cari versiyasında (bundan əlavə, Swift, sorry haqqında bilmirəm), qapaq blokunun bir dövrü olub olmadığını yoxlayacağı aydın bir funksiya var.

2019

9
05 июля '14 в 19:51 2014-07-05 19:51 Cavab Fattie'ye 05 iyul 'da 19:51' də verildi 2014-07-05 19:51

Kodunuzdaki IF / ELSE zəncirləri dilin problemi deyil, proqramınızın dizaynıdır. Proqramınızı yenidən istifadə edə və ya yenidən yaza bilsəniz, ən yaxşı həlli tapmaq üçün dizayn nümunələrinə ( http://sourcemaking.com/design_patterns ) baxmağa gəlirəm.

Ümumiyyətlə, kodunuzda bir çox IF və daha çox gördüyünüz zaman, bu bir strategiya dizayn modelini ( http://sourcemaking.com/design_patterns/strategy/c-sharp-dot-net ) ya da digər nümunələrin birləşməsini tətbiq etmək imkanıdır.

Əminəm ki, uzun / / başqa bir siyahı yazmaq üçün alternativlər var amma şübhə edirəm ki, bir şey dəyişəcəklər, amma zəncir sizə gözəl görünəcəkdir (Lakin, davranış gözündə də gözəllik hələ də kod üçün tətbiq olunur: -)). 6 aydan sonra, yeni bir vəziyyət olduğunda və bu kodla bağlı bir şey xatırlamadığımdan asanlıqla əlavə edə bilərəmmi? Və ya əgər zəncir dəyişirsə, nə qədər tez və düzgün tətbiq edərəm )

9
26 июня '14 в 23:34 2014-06-26 23:34 Cavab 26 iyun 'da 14:34' də Roman Mik'də verilmişdir 2014-06-26 23:34

İcra funksiyalarınız işə yaramırsa istisna atsınlar və yanlışlığı qaytarmayın. Sonra zəng kodunuz belə ola bilər:

 try { executeStepA(); executeStepB(); executeStepC(); } catch (...) 

Əlbətdə ki, orijinal nümunənizdə, addımın içərisində bir səhv baş verərsə, icra addımını yalançı hesab edəcəksiniz?

8
26 июня '14 в 19:02 2014-06-26 19:02 Cavab 26 İyun, '14 'də saat 19:02' da veriləcək

Artıq çox yaxşı cavablar var, lakin onların əksəriyyəti bəzi (adətən, çox az) rahatlıqla ödəmə kimi görünür. Bu kompromis tələb etməyən ümumi bir yanaşma bir dövlət əlavə etmək / saxlamaq üçün dəyişiklikdir. Qiymət əlbəttə ki, izləmək üçün bir əlavə dəyərdir:

 bool ok = true; bool conditionA = executeStepA(); // ... possibly edit conditionA, or just ok  executeStepA(); ok  conditionA; if (ok) { bool conditionB = executeStepB(); // ... possibly do more stuff ok  conditionB; } if (ok) { bool conditionC = executeStepC(); ok  conditionC; } if (ok  additionalCondition) { // ... } executeThisFunctionInAnyCase(); // can now also: return ok; 
8
27 июня '14 в 13:55 2014-06-27 13:55 cavab 27 iyun 'da 13:55' də verilir. 2014-06-27 13:55

C ++ 11 və yuxarı istiqamətlər üçün yaxşı bir yanaşma D sahəsi (çıxış) mexanizminə bənzər bir sahə çıxışı sistemi tətbiq etmək olardı.

Bunu tətbiq etmək üçün bir mümkün yol lambdas C ++ 11 və bəzi köməkçi makrolardan istifadə etməkdir:

 template<typename F> struct ScopeExit { ScopeExit(F f) : fn(f) { } ~ScopeExit() { fn(); } F fn; }; template<typename F> ScopeExit<F> MakeScopeExit(F f) { return ScopeExit<F>(f); }; #define STR_APPEND2_HELPER(x, y) x##y #define STR_APPEND2(x, y) STR_APPEND2_HELPER(x, y) #define SCOPE_EXIT(code)\ auto STR_APPEND2(scope_exit_, __LINE__) = MakeScopeExit([ code }) 

Bu, əvvəlki funksiyaya qayıtmaq və sahəni tərk edərkən göstərdiyiniz təmizləmə kodunun həmişə yerinə yetirilməsini təmin edəcəkdir:

 SCOPE_EXIT( delete pointerA; delete pointerB; close(fileC); ); if (!executeStepA()) return; if (!executeStepB()) return; if (!executeStepC()) return; 

Makrolar həqiqətən bəzəyəcək. MakeScopeExit() birbaşa istifadə edilə bilər.

6
28 июня '14 в 22:50 2014-06-28 22:50 Cavab glampert tərəfindən verilir 28, '14 10:50 2014-06-28 22:50

Kodunuz nümunə kimi sadədirsə və sizin diliniz qısa dövrə bağlı qiymətləndirmələri dəstəkləyirsə, aşağıdakıları cəhd edə bilərsiniz:

 StepA()  StepB()  StepC()  StepD(); DoAlways(); 

Если вы передаете аргументы своим функциям и возвращаете другие результаты, чтобы ваш код не мог быть написан ранее, многие другие ответы лучше подходят для проблемы.

6
ответ дан Noctis Skytower 01 июля '14 в 16:46 2014-07-01 16:46

В С++ (вопрос отмечен как C и С++), если вы не можете изменять функции для использования исключений, вы все равно можете использовать механизм исключения, если вы пишете небольшую вспомогательную функцию, например

 struct function_failed {}; void attempt(bool retval) { if (!retval) throw function_failed(); // or a more specific exception class } 

Затем ваш код мог бы читать следующее:

 try { attempt(executeStepA()); attempt(executeStepB()); attempt(executeStepC()); } catch (function_failed) { // -- this block intentionally left empty -- } executeThisFunctionInAnyCase(); 

Если вы используете фантастический синтаксис, вместо него вы можете заставить его работать с явным приложением:

 struct function_failed {}; struct attempt { attempt(bool retval) { if (!retval) throw function_failed(); } }; 

Затем вы можете написать свой код как

 try { (attempt) executeStepA(); (attempt) executeStepB(); (attempt) executeStepC(); } catch (function_failed) { // -- this block intentionally left empty -- } executeThisFunctionInAnyCase(); 
6
ответ дан celtschk 26 июня '14 в 22:51 2014-06-26 22:51

Это выглядит как конечный автомат, что удобно, потому что вы можете легко реализовать его с помощью шаблона состояния .

В Java это будет выглядеть примерно так:

 interface StepState{ public StepState performStep(); } 

Реализация будет работать следующим образом:

 class StepA implements StepState{ public StepState performStep() { performAction(); if(condition) return new StepB() else return null; } } 

Və s. Затем вы можете заменить большое условие if:

 Step toDo = new StepA(); while(toDo != null) toDo = toDo.performStep(); executeThisFunctionInAnyCase(); 
5
ответ дан CarrKnight 30 июня '14 в 20:10 2014-06-30 20:10

Как упоминал Ромик, вы можете применить шаблон дизайна для этого, но я бы использовал шаблон Decorator, а не Strategy, так как вы хотите цеплять вызовы. Если код прост, то я бы пошел с одним из хорошо структурированных @, чтобы предотвратить вложенность. Однако, если он сложный или требует динамической цепочки, то шаблон Decorator является хорошим выбором. Вот диаграмма классов yUML :

2019

5
ответ дан What Would Be Cool 28 июня '14 в 21:47 2014-06-28 21:47

Почему никто не дает простейшего решения?: D

Если все ваши функции имеют одну и ту же подпись, вы можете сделать это так (для языка C):

 bool (*step[])() = {... }; for (int i = 0; i < numberOfSteps; i++) { bool condition = step[i](); if (!condition) { break; } } executeThisFunctionInAnyCase(); 

Для чистого решения на С++ вы должны создать класс интерфейса, который содержит метод execute и обертывает ваши шаги в объектах.
Затем решение выше будет выглядеть так:

 Step *steps[] = { stepA, stepB, stepC, ... }; for (int i = 0; i < numberOfSteps; i++) { Step *step = steps[i]; if (!step->execute()) { break; } } executeThisFunctionInAnyCase(); 
5
ответ дан Gaskoin 10 июля '14 в 12:01 2014-07-10 12:01

Предполагая, что вам не нужны отдельные переменные условия, инвертируя тесты и используя else-falthrough, поскольку путь "ok" позволит вам получить более вертикальный набор операторов if/else:

 bool failed = false; // keep going if we don't fail if (failed = !executeStepA()) {} else if (failed = !executeStepB()) {} else if (failed = !executeStepC()) {} else if (failed = !executeStepD()) {} runThisFunctionInAnyCase(); 

Опустить неудачную переменную делает код слишком скрытым IMO.

Объявление переменных внутри прекрасно, не беспокойтесь о = vs ==.

 // keep going if we don't fail if (bool failA = !executeStepA()) {} else if (bool failB = !executeStepB()) {} else if (bool failC = !executeStepC()) {} else if (bool failD = !executeStepD()) {} else { // success ! } runThisFunctionInAnyCase(); 

Это неясно, но компактно:

 // keep going if we don't fail if (!executeStepA()) {} else if (!executeStepB()) {} else if (!executeStepC()) {} else if (!executeStepD()) {} else {  } runThisFunctionInAnyCase(); 
5
ответ дан Macke 27 июня '14 в 22:52 2014-06-27 22:52

Несколько @ были намечены на шаблон, который я видел и использовал много раз, особенно в сетевом программировании. В сетевых стеках часто бывает длинная последовательность запросов, любая из которых может завершиться неудачей и остановит процесс.

Общая картина заключалась в использовании do { } while (false);

Я использовал макрос для while(false) , чтобы сделать его do { } once; . Общий шаблон:

 do { bool conditionA = executeStepA(); if (! conditionA) break; bool conditionB = executeStepB(); if (! conditionB) break; // etc. } while (false); 

Этот шаблон был относительно прост для чтения и позволял использовать объекты, которые должным образом разрушали бы, а также избегали множественных возвратов, что упрощало бы отладку и отладку.

4
ответ дан Bill Door 27 июня '14 в 18:11 2014-06-27 18:11

Чтобы улучшить ответ Mathieu С++ 11 и избежать затрат времени выполнения, вызванных использованием std::function , я бы предложил использовать следующие

 template<typename functor> class deferred final { public: template<typename functor2> explicit deferred(functor2 f) : f(std::forward<functor2>(f)) {} ~deferred() { this->f(); } private: functor f; }; template<typename functor> auto defer(functor f) -> deferred<typename std::decay<functor>::type> { return deferred<typename std::decay<functor>::type>(std::forward<functor>(f)); } 

Этот простой класс шаблонов будет принимать любой функтор, который можно вызывать без каких-либо параметров, и делает это без каких-либо распределений динамической памяти и поэтому лучше соответствует цели абстракции С++ без лишних накладных расходов. Дополнительный шаблон функции предназначен для упрощения использования путем вычитания параметра шаблона (который недоступен для параметров шаблона класса)

İstifadə nümunəsi:

 auto guard = defer(executeThisFunctionInAnyCase); bool conditionA = executeStepA(); if (!conditionA) return; bool conditionB = executeStepB(); if (!conditionB) return; bool conditionC = executeStepC(); if (!conditionC) return; 

Так же, как Матье отвечает, это решение полностью безопасно для всех, и executeThisFunctionInAnyCase будет вызываться во всех случаях. Если сам executeThisFunctionInAnyCase бросает, деструкторы неявно помечены noexcept , и поэтому вызывается вызов std::terminate вместо того, чтобы вызывать исключение во время разматывания стека.

4
ответ дан Joe 07 июля '14 в 20:28 2014-07-07 20:28

интересным способом является работа с исключениями.

 try { executeStepA();//function throws an exception on error ...... } catch(...) { //some error handling } finally { executeThisFunctionInAnyCase(); } 

Если вы напишете такой код, вы пойдете как-то не в том направлении. Я не буду рассматривать это как "проблему", чтобы иметь такой код, но иметь такую ​​грязную "архитектуру".

Совет: обсудите эти случаи с опытным разработчиком, которому вы доверяете; -)

3
ответ дан Karsten 02 июля '14 в 14:49 2014-07-02 14:49

Кажется, что вы хотите выполнить весь свой вызов из одного блока. Как и предлагали другие, вы должны использовать либо цикл while , и оставить с помощью break или новую функцию, которую вы можете оставить с помощью return (может быть чище).

Я лично изгоняю goto , даже для выхода функции. Их сложнее обнаружить при отладке.

Элегантной альтернативой, которая должна работать для вашего рабочего процесса, является создание массива функций и итерация на этом.

 const int STEP_ARRAY_COUNT = 3; bool (*stepsArray[])() = { executeStepA, executeStepB, executeStepC }; for (int i=0; i<STEP_ARRAY_COUNT; ++i) { if (!stepsArray[i]()) { break; } } executeThisFunctionInAnyCase(); 
3
ответ дан AxFab 26 июня '14 в 18:54 2014-06-26 18:54

Другой подход - цикл do - while , хотя он упоминался ранее, и не было примера этого, который бы показал, как это выглядит:

 do { if (!executeStepA()) break; if (!executeStepB()) break; if (!executeStepC()) break; ... break; // skip the do-while condition :) } while (0); executeThisFunctionInAnyCase(); 

(Ну, уже есть ответ с циклом while , но цикл do - while не избыточно проверяет значение true (в начале), а вместо этого в конце xD (это может быть пропущено, хотя)).суб >

3
ответ дан Zaffy 08 июля '14 в 1:00 2014-07-08 01:00

Простое решение использует логическую переменную condition, и ее можно повторно использовать снова и снова, чтобы проверить все результаты шагов, выполняемых в последовательности:

  bool cond = executeStepA(); if(cond) cond = executeStepB(); if(cond) cond = executeStepC(); if(cond) cond = executeStepD(); executeThisFunctionInAnyCase(); 

Не нужно было делать это заранее: bool cond = true; ... а затем следует if (cond) cond = executeStepA(); Переменная cond может быть сразу назначена на результат executeStepA() , поэтому сделать код еще короче и проще читать.

Еще один необычный, но забавный подход - это (некоторые могут подумать, что это хороший кандидат для IOCCC, но все же):

  !executeStepA() ? 0 : !executeStepB() ? 0 : !executeStepC() ? 0 : !executeStepD() ? 0 : 1 ; executeThisFunctionInAnyCase(); 

Результат точно такой же, как если бы мы сделали то, что выложили OP, т.е.:

  if(executeStepA()){ if(executeStepB()){ if(executeStepC()){ if(executeStepD()){ } } } } executeThisFunctionInAnyCase(); 
2
ответ дан epichorns 13 июля '14 в 4:25 2014-07-13 04:25
  • 1
  • 2

Другие вопросы по меткам или Задайте вопрос