Dnes se v našem seriálu o chybách v návrhu podíváme na situace, při nichž bychom plnohodnotným využíváním typového systému zpřehlednili kód a učinili jej méně náchylným k chybám.
Mrkněte na následující kusy kódu a řekněte mi, jak dlouho bude program čekat, než bude moct pokračovat (možnost příchodu signálů ignorujte):
// C (POSIX) #include <unistd.h> int main() { sleep(1000); }
// C (Windows) #include <windows.h> int main() { Sleep(1000); }
# Python import time time.sleep(1000)
Není to jednoduché, co?
Principiální problém je zde ten, že není na první pohled patrné, o jaké jednotky se jedná. Jsou to sekundy? Milisekundy? Něco jiného? Tento nedostatek způsobuje jednak ztíženou čitelnost kódu (je potřeba se podívat do manuálu) a jednak se jedná o snadný zdroj chyb (místo 1
člověk napíše 1000
, protože předpokládá, že čas bude v milisekundách).
Pro ty, které by zajímala odpověď, tak tady je: V prvním a třetím případě se počká 1000 sekund, u druhé (Windows) varianty to bude 1 sekunda (předává se tam totiž počet milisekund).
Řešením je při návrhu rozhraní plně využívat typový systém. Proč na všechno používat jen primitivní datové typy (čísla, řetězce), když si můžeme vytvářet vlastní, vysokoúrovňovější typy? Kód bude čitelnější a bezpečnější.
Pojďme se podívat, jak se s problémem uspání programu vypořádaly standardní knihovny jazyků C++ a Rust.
Začněme s C++:
#include <chrono> #include <thread> using namespace std::chrono_literals; int main() { std::this_thread::sleep_for(100ms); }
Standardní funkce std::this_thread::sleep_for()
přebírá jako parametr std::chrono::duration
, reprezentující časový interval. V C++14 přibyly standardní literály pro časové intervaly, zpřístupněné using
direktivou. Pro předání 100
milisekund stačí napsat 100ms
, pro 100 sekund zase 100s
apod. (seznam podporovaných literálů).
V Rustu je to řešeno následovně:
use std::thread::sleep; use std::time::Duration; fn main() { sleep(Duration::from_millis(100)); }
Standardní funkce std::thread::sleep()
přebírá jako parametr std::time::Duration
. Tento typ poskytuje konstruktory pro vytvoření časových intervalů z předaného počtu milisekund či sekund. V ukázce jsme vytvořili 100 milisekund. Opět je zde plně využit typový systém, který při překladu zabrání tomu, aby někdo omylem předal do sleep()
pouze číslo:
sleep(100); // error: expected struct `std::time::Duration`, found integral variable
Kód není sice tak stručný jako v případě C++, ale svůj účel plní.
Při vytváření rozhraní je potřeba použít skutečné typy, nikoliv typové aliasy. Vezměme si např. následující příklad z C++:
using Seconds = int; // typedef int Seconds; v Céčku void sleep(Seconds secs) { // Implementace. // ... }
Tento kód nám sice umožní napsat
sleep(Seconds(100));
ale stejně tak nám umožní toto, což nechceme povolit:
sleep(100);
Jelikož v C a C++ není z hlediska typové kontroly žádný rozdíl mezi aliasem a aliasovaným typem, je potřeba vždy vytvořit typ nový. Jinak budeme tam, kde jsme byli.
Až tedy budete v budoucnu navrhovat rozhraní, pokuste se při tom plně využít typový systém. Např. pokud funkce přebírá velikost souboru, která ale může být v kB, MB atd., reprezentujte velikost vlastním typem. Nespoléhejte se na to, že každému bude jasné, že se funkci má předat počet bajtů (int
).
Přidat komentář