čtvrtek, května 26, 2005

Vrstvení

Začněme jednoduchým programem:

#include <iostream>
using namespace std;
int main()
{
 //Part I
 double a[10];
 a[0] = 0.1;
 for(int i = 1; i != 10; i++) {
  a[i] = a[i - 1] + 0.1;
  cout << a[i] << ", ";
 } 
 
 //Part II
 for(int i = 0; i < 100; i++)
   a[9] *= a[9];
   
 cout << a[9]; 
 return 0;
}
  Jak všichni správně předpokládají, výstupem části Part I je následující řada čísel:
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1
  Skvěle. Teď pokračujme ve vykonávání části Part II. Výsledkem bude:
0
  Ale copak? Že by někde nastala nějaká chyba? Raději spusťme program znovu, zastavme ho před Part II a nahlédněme do debuggeru:
a[0] 0.10000000000000001
a[1] 0.20000000000000001
a[2] 0.30000000000000004
a[3] 0.40000000000000002
a[4] 0.50000000000000000
a[5] 0.59999999999999998
a[6] 0.69999999999999996
a[7] 0.79999999999999993
a[8] 0.89999999999999991
a[9] 0.99999999999999989
  Aha! Tak tady je problém! Jak už od začátku vědí všichni, kteří se někdy zajímali o to, jak to vlastně je s čísly v počítačích, problém je ve skutečnosti, že počítače neukládají reálná čísla jako reálná čísla, ale jako koeficienty řady
a0 + a1×1/2 + a2×1/4 + a3×1/8 + ... + ai×1/2i
(kde ai je hodnota příslušného bitu), plus exponent.

Kam tahle okázalá demonstrace směřuje? K zamyšlení nad abstrakcemi a nad tím, že čím více jich na sebe navrstvíme, tím méně vidíme do toho, co se děje pod nimi.

Abstrakce vytváříme proto, abychom se odstínili od některých závislostí, které sebou vývoj zařízení a programů nese. Stvořili jsem abstrakci integrovaného obvodu, která nás nenutí zabývat se tím, jestli tranzistor bude otevřený nebo zavřený, nad ní jsme vybudovali abstrakci třeba lineárního regulátoru napětí, která nás zbavuje nutnosti starat se o stabilizaci napájení, nebo programovatelného logického pole, díky kterému se návrh logických obvodů zjednodušil do psaní rovnic v nějakém vývojovém prostředí.

V programování máme nad strojovým kódem assembler (který ovšem už zcela filtruje starosti o procesor a jeho skutečné vnitřní stavy), nad assemblerem programovací jazyky, které abstrahují třeba od používaného hardware v podobě mezivrstev definujících obecné výstupní zařízení nebo úložiště. V poslední době už si ve velkém užíváme i abstrakci garbage collectingu, který nás osvobodí od přemýšlení nad pamětí jako takovou a umožňuje vytvářet objekty jak se nám jen zamane.

Samozřejmě, že každá další vrstva znamená určitou ztrátu kontroly, takže při programování logických polí se musíme přizpůsobovat struktuře makrocellů, při psaní aplikace v .NET zase nemáme jistotu kdy se pamět obsazená objektem opravdu uvolní.

Ovšem výhoda abstrakcí je obrovská: protože se bez určité míry abstrakce a fragmentace libovolného složitějšího projektu neobejdeme, umožňují standardní abstrakce soustředit se na skutečný problém, a nezabývat se znovu a znovu oním legendárním vymýšlením kola, a zrychlují tak vývoj čehokoliv.

Na druhou stranu: každá další vrstva přidává model chování, kterému je potřeba porozumět. Ať naprogramuji logické pole sebelíp, nikdy mě to nezbaví nutnosti řešit problém, jak přenesu megahertzové signály na druhou stranu plošného spoje, nebo jak vyfiltruji rušení, které mi bude zemí prolézat do analogové části zařízení. A stejně tak nemůžu předpokládat, že když v nějakém programovacím jazyce mění proměnná svůj význam v závislosti na kontextu, zbavím se problému s reprezentací čísel v FPU procesoru.

Správně by tedy s každou další abstrakcí měly naše znalosti o problému nakynout. Měli bychom chápat nejen jak se obvod nebo funkce tváří navenek, ale také to, co se děje uvnitř.

Je jasné, že jen málokdo je schopen pojmout tolik informací, aby v každém okamžiku rozuměl celému řetězci abstrakcí, které leží mezi úrovní na které pracuje, a tím úplně dole. Jenže tato neznalost se může leckdy stát krajně limitující, ne-li přímo osudnou.

Asi nikdo, kdo používá funkci strcat(), která nakonec jednoho řetězce v C přilepí řetězec jiný, se nezatěžuje tím, že by uvažoval o její výkonostní charakteristice. Ta se ovšem může brutálně projevit v případě, kdy k jednomu řetezci připojujeme mnoho jiných, protože řetězce v C jsou jen prostými poli, a ke zjištění jejich délky, je potřeba je projít celé, až k hodnotě 0, která je ukončuje.

Tyhle drobné předpoklady když něco dobře funguje v případě A, bude to jistě dobře fungovat i v případě B, jsou pak zdrojem různých nedomrlostí nebo problémů při uvádění aplikace do chodu.

Přemýšlím nad tím (v dozvuku na zamyšlení o bastlení), jestli existuje naděje, že někdy vznikne nějaká abstrakce, která nám ušetří práci, a bude přitom zcela průhledná. Jaksi přirozeně skepticky se domnívám že nikoliv, a že znalost souvislostí vždycky bude tím, co bude dobrého návrháře odlišovat od toho špatného.

Chci říct: Ať už se s abstrakcemi dostaneme jakkoliv daleko, na tom, co se děje tam dole, pod nimi, pořád zatraceně záleží.