Kimsenin bu alternatifi önermemesine şaşırdım, bu yüzden soru bir süredir ortalıkta olsa da ekleyeceğim: Bu sorunu ele almanın iyi bir yolu, mevcut durumu takip etmek için değişkenler kullanmaktır. Bu, goto
temizleme koduna ulaşmak için kullanılıp kullanılmadığına bakılmaksızın kullanılabilecek bir tekniktir . Herhangi bir kodlama tekniği gibi, artıları ve eksileri vardır ve her durum için uygun olmayacaktır, ancak bir stil seçiyorsanız, özellikle goto
derinlemesine iç içe geçmeden kaçınmak istiyorsanız dikkate almaya değerif
.
Temel fikir, yapılması gerekebilecek her temizleme eylemi için, değerinden temizlemenin yapılması gerekip gerekmediğini anlayabileceğimiz bir değişken olmasıdır.
goto
İlk olarak versiyonu göstereceğim , çünkü orijinal sorudaki koda daha yakın.
int foo(int bar)
{
int return_value = 0;
int something_done = 0;
int stuff_inited = 0;
int stuff_prepared = 0;
if (do_something(bar)) {
something_done = 1;
} else {
goto cleanup;
}
if (init_stuff(bar)) {
stuff_inited = 1;
} else {
goto cleanup;
}
if (prepare_stuff(bar)) {
stufF_prepared = 1;
} else {
goto cleanup;
}
return_value = do_the_thing(bar);
cleanup:
if (stuff_prepared) {
unprepare_stuff();
}
if (stuff_inited) {
uninit_stuff();
}
if (something_done) {
undo_something();
}
return return_value;
}
Bunun diğer bazı tekniklere göre bir avantajı, başlatma işlevlerinin sırası değiştirilirse, doğru temizlemenin yine de gerçekleşecek olmasıdır - örneğin, switch
başka bir yanıtta açıklanan yöntemi kullanarak, başlatma sırası değişirse, o zaman switch
İlk başta başlatılmamış bir şeyi temizlemeye çalışmaktan kaçınmak için çok dikkatli bir şekilde düzenlenmesi gerekir.
Şimdi, bazıları bu yöntemin pek çok ekstra değişken eklediğini iddia edebilir - ve bu durumda bu doğrudur - ancak pratikte genellikle mevcut bir değişken gerekli durumu zaten izler veya izlemek için yapılabilir. Örneğin prepare_stuff()
, aslında bir malloc()
ya da bir çağrı ise , open()
döndürülen işaretçiyi veya dosya tanımlayıcısını tutan değişken kullanılabilir - örneğin:
int fd = -1;
....
fd = open(...);
if (fd == -1) {
goto cleanup;
}
...
cleanup:
if (fd != -1) {
close(fd);
}
Şimdi, ek olarak hata durumunu bir değişkenle goto
izlersek, ihtiyacımız olan daha derin ve derinleşen girintilere sahip olmadan, tamamen önleyebilir ve yine de doğru şekilde temizleyebiliriz:
int foo(int bar)
{
int return_value = 0;
int something_done = 0;
int stuff_inited = 0;
int stuff_prepared = 0;
int oksofar = 1;
if (oksofar) {
if (do_something(bar)) {
something_done = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
if (init_stuff(bar)) {
stuff_inited = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
if (prepare_stuff(bar)) {
stuff_prepared = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
return_value = do_the_thing(bar);
}
if (stuff_prepared) {
unprepare_stuff();
}
if (stuff_inited) {
uninit_stuff();
}
if (something_done) {
undo_something();
}
return return_value;
}
Yine, bununla ilgili potansiyel eleştiriler var:
- Tüm bu "eğer" performansa zarar vermiyor mu? Hayır - çünkü başarı durumunda, yine de tüm kontrolleri yapmanız gerekir (aksi takdirde tüm hata durumlarını kontrol edemezsiniz); ve başarısızlık durumunda çoğu derleyici, başarısız
if (oksofar)
denetimlerin sırasını temizleme koduna tek bir sıçramaya optimize edecektir (GCC kesinlikle yapar) - ve her durumda, hata durumu performans için genellikle daha az kritiktir.
Bu yine başka bir değişken eklemiyor mu? Bu durumda evet, ancak genellikle return_value
değişken oksofar
burada oynayan rolü oynamak için kullanılabilir . İşlevlerinizi tutarlı bir şekilde hata döndürecek şekilde yapılandırırsanız, if
her durumda ikincisini bile önleyebilirsiniz :
int return_value = 0;
if (!return_value) {
return_value = do_something(bar);
}
if (!return_value) {
return_value = init_stuff(bar);
}
if (!return_value) {
return_value = prepare_stuff(bar);
}
Bunun gibi kodlamanın avantajlarından biri, tutarlılığın, orijinal programcının dönüş değerini kontrol etmeyi unuttuğu herhangi bir yerin hassas bir başparmak gibi çıkması ve bu da (bir sınıfın) hataların bulunmasını çok daha kolay hale getirmesidir.
Yani - bu (henüz) bu sorunu çözmek için kullanılabilecek bir stil daha. Doğru kullanıldığında çok temiz, tutarlı kod sağlar - ve herhangi bir teknik gibi, yanlış ellerde uzun soluklu ve kafa karıştırıcı kodlar üretebilir :-)