/ janturon.cz / Od kodéra k analytikovi / Návrat z funkce

Návrat z funkce

První ukázka

Obecně se nedoporučuje mít více bodů návratu ve funkci. Následující funkce počítá pozici prvního výskytu prvku ve vectoru:

// C++ template <typename T> int indexOf(vector<T> array, T element) { for(int i=0; i<array.size(); i++) { if(array[i]==element) return i; } return -1; } // Javascript Array.prototype.indexOf = function(element,from) { // alokace zdrojů for(var i=from||0; i<this.length; i++) if(this[i]===element) return i; // uvolnění zdrojů return -1; }

Tato funkce je velmi stručná a dobře čitelná. Problém by však nastal, kdyby pracovala se zdroji:

// C++ template <typename T> int indexOf(vector<T> array, T element) { // alokace zdrojů for(int i=0; i<array.size(); i++) { if(array[i]==element) return i; // zablokování zdrojů ! } // uvolnění zdrojů return -1; } // Javascript Array.prototype.indexOf = function(element,from) { for(var i=from||0; i<this.length; i++) if(this[i]===element) return i; return -1; }

Kde v části alokace zdrojů může být vytvoření paměti na haldě, otevření souboru či vytvoření vlákna. V části uvolnění zdrojů je pak třeba zdroje předat nebo uvolnit. Pokud však funkce skončí dříve, na uvolnění se nedostane a zdroje budou blokovány.

Tomu se lze vyhnout zduplikováním kódu uvolňujícím zdroje, což je ale antivzor copy&paste. Druhý způsob je zneužít preprocesor pro vygenerování kódu, což ale dramaticky zhoršuje čitelnost. Řešením je mít pouze jeden výstupní bod:

// C++ template <typename T> int indexOf(vector<T> array, T element) { // alokace zdrojů int i = 0; for(; i<array.size(); i++) { if(array[i]==element) break; } // uvolnění zdrojů if(i==array.size() || array.size()==0) i = -1; return i; } // Javascript Array.prototype.indexOf = function(element,from) { // alokace zdrojů for(var i=from||0; i<this.length; i++) if(this[i]===element) break; // uvolnění zdrojů if(i==this.length || this.length==0) i = -1; return i; }

Příkaz před návratem funkce je ale velmi neintuitivní: k jeho pochopení je nutno prostudovat celý kód funkce, není možné mu porozumět izolovaně.

Tomuto problému lze zamezit připuštěním více bodů návratu, ale ne v bloku mezi alokací a uvolněním zdrojů. Při čtení funkce je proto dobré při jejím prvním čtení se soustředit pouze na to, zda a kde alokuje a uvolňuje zdroje. Následující funkce je čitelnější i efektivnější:

// C++ template <typename T> int indexOf(vector<T> array, T element) { if(array.size()==0) return -1; // alokace zdrojů int i = 0; for(; i<array.size(); i++) { if(array[i]==element) break; } // uvolnění zdrojů if(i==array.size()) return -1; return i; } // Javascript Array.prototype.indexOf = function(element,from) { if(this.length==0) return -1; // alokace zdrojů for(var i=from||0; i<this.length; i++) if(this[i]===element) break; // uvolnění zdrojů if(i==this.length) return -1; return i; }

Druhá ukázka

Mějme funkci podávající textovou informaci o velikosti vectoru a dodržme zásadu jednoho výstupního bodu:

// C++ template <typename T> string vectorInfo(vector<T> array) { string result; if(array.size()<10) result = "small"; if(array.size()>=10 && array.size()<100) result = "medium"; if(array.size()>=100) result = "large"; return result; } // Javascript Array.prototype.info = function() { var result; if(this.length<10) result = "small"; if(this.length>=10 && this.length<100) result = "medium"; if(this.length>=100) result = "large"; return result; }

Funkce je velmi dobře čitelná, žádný řádek nepotřebuje komentář. Pomocí else je však možné ji zjednodušit:

// C++ template <typename T> string vectorInfo(vector<T> array) { string result; if(array.size()<10) result = "small"; else if(array.size()<100) result = "medium"; else result = "large"; return result; } // Javascript Array.prototype.info = function() { var result; if(this.length<10) result = "small"; else if(this.length<100) result = "medium"; else result = "large"; return result; }

Tím se však zhoršuje čitelnost: k porozumění podmínky u else je nutno si přečíst i podmínku u if. Protože se v tomto bloku nepracuje se zdroji, můžeme získat výhody obou přístupů postupným odřezáváním možností:

// C++ template <typename T> string vectorInfo(vector<T> array) { if(array.size()<10) return "small"; if(array.size()<100) return "medium"; return "large"; } // Javascript Array.prototype.info = function() { if(this.length<10) return "small"; if(this.length<100) return "medium"; return "large"; }

Stačí si osvojit přístup, že kdykoli vidíme příkaz return, který není na samotném konci funkce, uvědomíme si: tento bod návratu zajistí, že v následujícím kódu budou platit tyto podmínky....

V tomto případě první návrat zajistí, že v následujícím kódu bude velikost pole alespoň 10. Druhý návrat zajistí, že velikost bude alespoň sto.

Přístup odřezávání pomáhá eliminovat množství vnořených složených závorek a vyhnout se vloženým podmínkám.