Defer se mi moc libi. Hodne casto by se mi hodil v C ve funkcich co otviraji vic files a pokud nektery nejde otevrit, musi se zavrit vsechny uz otevrene a vyskocit.
neco na zpusob (zjednodusene)
a=fopen("input")
if (!a) return;
b=fopen("output")
if (!b) { close(a); return}
c=fopen("tmp")
if (!c) {close(b); close(a); return}
...
close(c)
close(b)
close(a)
return
s deferem by to tudiz slo napsat podstatne jednoduseji
a=fopen("input");
if (!a) return
defer close(a)
b=fopen("output");
if (!b) return
defer close(b)
c=fopen("tmp");
if (c)
{
...
close(c)
}
return
Urcite se najde nekdo, kdo by to dokazal udelat s obskurnima konstrukcema typu try-finally a podobne, ale kdyz se zrovna tohle ve skole probiralo, to uz jsem tam asi 20 let nechodil :-) Spis je vetsi problem v tom, ze musim psat kod prelozitelny 15 let starym kompilatorem :-(
Pokud by slo o C++, tak reseni existuje: https://hexar-decimal.com/2009/01/30/lambda-functions-in-c-for-raii/ .
Napsal jsem si k tomu ve VC++ makra, aby to bylo pouzitelne asi takhle snadno:
LPTSTR listedReadersMultistring = NULL;
REGISTER_CLEANUP(listedReadersMultistring, & {
if (listedReadersMultistring != NULL)
SCardFreeMemory(this->scardContext, listedReadersMultistring);
})
// ...
SCardListReaders(this->scardContext, NULL, &listedReadersMultistring, &scardAutoallocate);
pripadne
credential->getUserName(&cardUser);
ON_SCOPE_END_CLEANUP(& { Free(cardUser); })
A makra:
#define REGISTER_CLEANUP(VAR, CLEANUP) std::shared_ptr<void> VAR ## Releaser(VAR, CLEANUP );
#define BLOCK_EXIT_CLEANUP2(VAR_NAME, CLEANUP) std::shared_ptr<void> blockReleaser ## VAR_NAME(NULL, CLEANUP );
#define BLOCK_EXIT_CLEANUP1(VAR_NAME, CLEANUP) BLOCK_EXIT_CLEANUP2(VAR_NAME, CLEANUP);
#define ON_SCOPE_END_CLEANUP(CLEANUP) BLOCK_EXIT_CLEANUP1(__LINE__, CLEANUP);
Navážu svojí podobnou sbírkou maker :-)
Implementace zde a použití např.:
if(SDL_Init(SDL_INIT_VIDEO)) { return EXIT_FAILURE; } DEFER(SDL_Quit()); ... std::array<GLuint, 3> buffers; glGenBuffers(buffers.size(), buffers.data()); DEFER(glDeleteBuffers(buffers.size(), buffers.data()));
Pravda, proc pouzivat obskurni try-with-resources, kde mam oddeleny business kod od error handlingu a pro pochopeni business logiky staci precist 2 radky kodu (2x fopen() uvnitr try bloku), kdyz muzu mit krasny zmatlany gulas business kodu a error handlingu s prasecinama typu goto a defer, kde pro pochopeni business kodu musim precist celych 13 radku kodu a odseparovat error handling balast a zavirani resources.
Velika krasa, snad uz jenom skocit do zdi.
Záleží na situaci. Zrovna u toho kopírování je reakce na neotvíratelný soubor atd. součástí business logiky a ne, že se to zamete nějak do finally (a ještě většinou blbě). Až příliš často vidím logiku ve stylu: celý kód v jednom try, na konci catch se zalogováním "něco je blbě" a ještě si devel pro jistotu chytá vše od Exception níž (ve stromu hierarchie výjimek) v jedné větvi. Dovolím si citovat:
"You're used to the way other languages handle errors, which is as an afterthought. Go tries to put them front and center and says "hey, this can give you an error, you better check that" if your code turns out ugly it's because you don't know how to handle errors properly and Go forces you to do that or punishes you if you don't. Which is a good thing, because most of the instability of systems can be traced by neglecting handling of errors.
PS s timto uplne nesouhlasim, ale prectete si to prosim: https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right
Zásadní problém Go je v tomto typ error
. Nepoznáte z něj, které přesně chyby můžou z dané funkce vypadnout.
Pro nízkoúrovňovou logiku je kontrola chyb důležitá. Pro vysokoúrovňovou je mnohem důležitější ten případ, kdy k chybě nedojde. A je velkou chybou Go propagátorů, že se snaží napasovat jeden "správný" styl psaní kódu na oba typy projektů. Ona se bussiness logika "zaplevelená" neustálou kontrolou chyb totiž špatně kontroluje a udržuje.
Analýza velkých Go projektů ukázala, že panic/defer/recover a if error: return error je běžným jevem. Takže se tomu v Go sice neříká výjimky, ale stejně je vývojáři pořád chtějí používat a používají.
jj to souhlasím. Ovšem na druhou stranu bych oddělil dva případy:
1) pracuji s nějakým zdrojem a chci ho vždy uzavřit v přesně stanovený okamžik. K tomu slouží defer a slouží perfektně, líp než try-catch-finally (protože píšu ten kód tam, kam potřebuju a ne tam, kam mi to dovolí překladač). Dtto nějaké logování na konci funkce atd. atd. Defer je zde obecnější
2) potřebuju reagovat na chybové stavy. Tady se v Go explicitně píše if err != nil pořád dokola. Taky se mi to moc nelíbí, na druhou stranu by se snad v Go 2 měly try-catch objevit, takže uvidíme.
oboje je stejně špatně pokud má ta metoda nebo funkce velkou cyklomatickou složitost a tudíž velmi pravděpodobně řeší moc věcí naráz :-)
mě by vyhovoval přístup Swiftu [1], tj. mít obojí; rozdělit chyby od správy resourců. Defer je super v tom, že skutečně píšeš kód tak, abych si byl já (jako devops!) jistý, že víš co dělaš: "otevírám soubor a hele, jasně, vím, že ho mám zavřít, tady to máš na dalším řádku, nebo to mám ve `with`".
Pokud je funkce/metoda s cyklomatickou složitostí A nebo B, tak je to jedno, kam se to píše, protože finally bude asi jen o 5-10 řádků níž :-). Pokud má někdo metodu na celou obrazovku, tak už mi kontext, že se má něco někde uzavřít může ujet, nebo si to musím držet v hlavě.
Takže defer ano - ale jen na některé věci. A try-catch-finally popř. try-with-resource ano, ale opět na některé věci. IMHO je v tomto Go zbytečně minimalistické a vybrali si obecnější příkaz, který ale vede k delšímu kódu, protože tam chybí try-catch.
[1] https://docs.swift.org/swift-book/ReferenceManual/Statements.html#grammar_defer-statement
PS: Swift nepoužívám a musím upozornit, že try-catch se tam píše slovem "do", což je zvěrsvo pro ty starší, co "do" znají jakožto keyword pro smyčky (Fortran, C, C++, ....)
<cite>
1) pracuji s nějakým zdrojem a chci ho vždy uzavřit v přesně stanovený okamžik. K tomu slouží defer a slouží perfektně, líp než try-catch-finally
</cite>
Zapomínáte na formu try-with-resources z Javy [1] a třeba context managery v Pythonu [2]. Ty uzavření řeší přesně na tom místě, kde ten zdroj otevíráte.
Navíc, defer se vykoná až na konci funkce, tj ne přesně ve stanovený okamžik jak píšete. Try-with-resources a context managery uzavřou zdroj okamžitě při opuštění bloku.
[1] https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
[2] třeba tady http://book.pythontips.com/en/latest/context_managers.html
Presne tak, navic, muzu uvnitr try bloku zanorit dalsi try blok a provest kompletni reakci primo v miste inline, nebo jeste lepe inner try zabalit do male privatni metody.
Defery vnaseji do kodu neskutecny bordel.
V Jave mam blok try s business logikou, catchy s error handlingem a finally s recovery/cleanupem nebo primo try with resource.
V GO, mam po cele funkci rofrcane volani deferu, musim precist a dekodovat celou funkci, aby pohopil kdy, za jakych podminek a v jakem poradi se defery volaji, casto jsou defery volani jinych funkci na druhem konci zdrojaku. Cela ta filosofie je ohavnost, prakticky porad musim pri cteni kodu studovat hromady balastu, ktere me momentalne nezajimaji.
nezapomínám, to je totiž přesně ten specifický případ, který jde řešit přes defer. Defer je samozřejmě obecnější, takže je nutně "ukecanější". Myslím to tak, že try-with-resources funguje skvěle pro případ resourců (ať je to cokoli), u nichž si někdo dal tu práci a udělal je "Closeable". Pokud ovšem business logika potřebuje nějaké složitější uvolnění zdrojů, například co já vím odhlášení od aplikace, vymazání session nebo něco podobného (co není volání close()), tak se to stejně musí napsat explicitně. Nebo se musí napsat nějaká další třída obalující například to přihlášení do aplikace tak, aby tam to close() bylo.
ale například `with` by se mi v Go líbíl, o tom žádná :-)