V dnešním díle seriálu o chybách v návrhu se podíváme na nešvar, který znepříjemňuje práci uživatelům knihoven: nekonzistentní rozhraní.
Každý z nás již někdy používal nějakou knihovnu. Ať se už se jednalo o standardní knihovnu daného jazyka či o externí knihovnu specializovanou pro daný účel, tak použitelnost této knihovny byla do značné míry dána snadnosti jejího používání. S tím souvisí i to, jaké má knihovna aplikační rozhraní (API), tj. jak je knihovna strukturována, jak jsou pojmenovány obsažené entity (třídy, metody, typy), co tyto entity dělají apod. Pokud byla knihovna, a především její rozhraní, navržena kvalitně, tak se bude snadno používat a její uživatelé budou spokojení. Pokud ale bylo rozhraní šito horkou jehlou, budou na knihovnu uživatelé nadávat. V tomto příspěvku bych se chtěl zaměřit na jeden z nešvarů, který se u knihoven velmi často objevuje: nekonzistentní rozhraní.
Nebudeme běhat kolem horké kaše a hned si ukážeme, jak se může projevovat nekonzistentní rozhraní.
fputs()
a fprintf()
ze standardní Céčkové knihovny, jež mají následující prototypy:
int fputs(const char *s, FILE *stream); int fprintf(FILE *stream, const char *format, ...);
V prvním případě se výstupní stream předává jako poslední parametr, ve druhém jako první parametr. Zde je to do značné míry dáno tím, že fprintf()
má proměnný počet parametrů, takže to nejde udělat naopak. Co ale u fputs()
dát parametr stream
jako první?
Jako další příklad bych chtěl uvést funkce in_array()
a strstr()
ze standardní knihovny jazyka PHP:
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) string strstr ( string $haystack , mixed $needle [, bool $before_needle = false ] )
V prvním případě se nejdříve předá hledaný objekt a poté pole, v druhém případě se nejdříve předá řetězec a až poté hledaný objekt.
Function
nebo Func
, či dokonce jen Fun
. První příklad nekonzistence tohoto typu máme ve standardní knihovně C++:
#include <sstream> std::stringstream s;
Třída se nazývá stringstream
, ale je v hlavičkovém souboru sstream
...
Další ukázka je z jazyka PHP, kde je velmi často mícháno, zda je k oddělení slov v názvech funkcí použito podtržítko či nikoliv. Ukázky:
string stripslashes ( string $str ) string strip_tags ( string $str [, string $allowable_tags ] ) int strcmp ( string $str1 , string $str2 ) mixed str_replace ( mixed $search , mixed $replace , mixed $subject [, int &$count ] ) int msql_numfields ( resource $result ) int mysql_num_fields ( resource $result )
Jako poslední příklad si uvedeme názvy metod pro zjištění velikosti kolekce v různých jazycích: size()
, length()
a count()
. Schválně zkuste zavzpomínat, která z těchto pojmenování se používají v knihovnách, které používáte.
typedefy
:
signed {char, short, int, long, long long} unsigned {char, short, int, long, long long} size_t container::size_type
a spoustu typů z cstdint. Mnohdy se pak stává, že jedna funkce z třídy má typ parametru unsigned int
a podruhé size_t
, což nemusí být vždy to stejné a může dojít k problému.
Další nekonzistence může být v tom, zda se argument má předat hodnotou, přes ukazatel či přes referenci. Pokud se u jedné funkce musí psát před objekt &
a u jiné nikoliv, může to způsobit uživateli frustrace. Např. v LLVM se běžně pracuje s funkcemi, reprezentovanými třídou Function
, přes ukazatel:
static Function * Function::Create (FunctionType *Ty, LinkageTypes Linkage, const Twine &N="", Module *M=0) static NodeType * CFG::getEntryNode(Function *F) Function * CallGraph::removeFunctionFromModule(Function *F)
Když si ale vytvoříte průchod nad funkcemi zděděním třídy FunctionPass
, tak u něj je virtuální metoda, při které funkci dostanete přes referenci:
virtual bool FunctionPass::runOnFunction (Function &F)=0
Jednou je pak třeba psát F->method()
, jindy F.method()
.
std::string
v C++, mající tyto dvě metody, které dělají to stejné:
size_type size() const; size_type length() const;
U PHP to jsou např. následující funkce, z nichž všechny provádějí rozdělení vstupu, ale s mírnými odlišnostmi:
array split ( string $pattern , string $string [, int $limit = -1 ] ) array spliti ( string $pattern , string $string [, int $limit = -1 ] ) array str_split ( string $string [, int $split_length = 1 ] ) array preg_split ( string $pattern , string $subject [, int $limit = -1 [, int $flags = 0 ]] ) array explode ( string $delimiter , string $string [, int $limit ] ) string strtok ( string $str , string $token )
Samozřejmě, dalo by se nalézt mnoho dalších typů nekonzistencí, které znesnadňují uživatelům knihoven život. V příspěvku jsem zmínil jen některé. Snažte se vždy o to, aby vaše rozhraní byla konzistentní. Vaši uživatelé vám za to poděkují.
Přidat komentář