Chyby v návrhu: nevyužívání typového systému

Od Petr Zemek, 2017-09-12

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.

(De)motivační příklad

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?

V čem je problém?

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

Co s tím?

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

C++

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ů).

Rust

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

Na co si dát pozor

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

Obsah tohoto pole je soukromý a nebude veřejně zobrazen.

Filtrované HTML (využíváno)

  • Povolené HTML značky: <a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <table>
  • Zvýraznění syntaxe kódu lze povolit přes následující značky: <code>, <blockcode>, <bash>, <c>, <cpp>, <haskell>, <html>, <java>, <javascript>, <latex>, <perl>, <php>, <python>, <ruby>, <rust>, <sql>, <text>, <vim>, <xml>, <yaml>.
  • Řádky a odstavce se zalomí automaticky.
  • Webové a e-mailové adresy jsou automaticky převedeny na odkazy.
CAPTCHA
7 + 5 =
Vyřešte tento jednoduchý matematický příklad a vložte výsledek. Např. pro 1+3 vložte 4.
Nějak se mi tady rozmohl spam, takže poprosím o ověření.