Jste zde

Chyby v návrhu: nekonzistentní rozhraní

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í.

O co půjde?

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í.

Jak vypadá nekonzistentní rozhraní?

Nebudeme běhat kolem horké kaše a hned si ukážeme, jak se může projevovat nekonzistentní rozhraní.

  • Nekonzistentní pozice parametrů. Určitě jste se s tímto problémem setkali. Chcete zavolat funkci či metodu a přemýšlíte nad pořadím, v jakém ji předat argumenty. Tvůrce knihovny by se měl snažit o to, aby se funkce a metody daly používat jednotným způsobem. Vezměme si např. funkce 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.

  • Nekonzistentní pojmenování. Při pojmenování byste se měli držet jednotného stylu a použití zkratek. Jinak uživatelé vašich knihoven budou tápat, zda napsat 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.

  • Nekonzistentní typy. Pro daný účel byste se měli rozhodnout a používat typy konzistentně. Vezměme si např. C++. Pro celočíselné typy je možno použít jak vestavěné typy, tak 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().

  • Duplicity. Pro jednu věc by zde měla být jedna funkce/metoda. Předejdete tak nedorozuměním, kdy si uživatel není jistý, kterou z funkcí použít, když u obou to vypadá, že dělají to stejné. Příkladem může být třída 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ář