Jdi na obsah Jdi na menu
Reklama
Založte webové stránky zdarma - eStránky.cz
 


Úvaha nad úvahou.

22. 11. 2009

Autor: Ing. Vladimír Hajdovský

Můj kamarád Přemek Lazecký otevřel svým článkem “Úvaha – zápis programového kódu“ diskusi na téma vhodného stylu psaní zdrojových kódů se zaměřením na VBA. Vše vlastně začalo o dost dříve, když Přemek začal před časem publikovat své kódy v podobě, která se výrazně vymykala běžné praxi jiných přispěvatelů na fóru Pandora. Přemkův kód začal překypovat nezvykle dlouhými anglickými identifikátory za doprovodu četných explicitně uváděných nepovinných součástí jazyka. Tento „extenzivní způsob“ psaní kódu začal Přemek přímo doporučovat k následování.

Přemek patří k nejkvalitnějším autorům na Pandoře a jeho slovo má svou nespornou váhu. Výrazná změna jeho stylu psaní nemohla zůstat nepovšimnutá. Formálně není co kritizovat, protože Přemkovy algoritmy neztratily nic na bohatosti, kvalitě a strukturovanosti, jimiž se Přemkovy příspěvky již roky vyznačují. Časem jsme se ale s několika jinými přispěvateli shodli na tom, že nám nový Přemkův styl připadá nadměrně zdobný až přeumělkovaný. Jednoho dne jsem ten názor mezi řečí na Pandoře zveřejnil a tím jsem zřejmě Přemka vyprovokoval k tomu, aby svůj změněný styl zdůvodnil ve výše uvedeném článku.

Mimochodem diskuse nad kulturou psaní programů je věčné a také velmi vděčné téma, které je stále otevřené. Domnívám se, že diskuse o něm bude trvat tak dlouho, dokud se budou programy psát. Občas se najdou autoři, kteří se pokusí vytvořit přímo normativní školu správného psaní programů. Zpravidla jde o velmi promyšlený koncept, obsahující soustavu vzájemně provázaných zásad, které je potřebné při psaní programů dodržovat. Skvělá je taková škola pro ty, kteří si dosud žádný styl nevytvořili a teprve pro sebe hledají správný směr. Dobré školy si často získají své příznivce i mezi zkušenými vývojáři, kteří v novém stylu najdou zalíbení do té míry, že sami změní své návyky a vzhledem k vlastním dobrým zkušenostem ho začnou intenzivně propagovat.

Mně takový přístup trochu zavání sektářstvím. Například nemám vůbec nic proti vegetariánům. Mnohá jídla z jejich jídelníčku jím s velkou chutí. Ale taky miluji svíčkovou. Podobný vztah mám ke školám správného psaní programů. Rád z nich přebírám dobré myšlenky, ale spíš jako doporučení než jako zákon. Musím ovšem velmi důrazně podotknout, že vystupuji z pozice člověka, který si dávno vlastní ucelený styl vědomě vytvořil a má k němu dostatečnou důvěru. V roli začátečníka bych jistě neměl váhat a měl bych přijmout dostupný, ucelený a doporučený systém jako základní vodítko pro svůj další programátorský vývoj.

Jistě by bylo lepší, kdybych komentoval přímo text knihy, o nějž se Přemkův nový styl psaní opírá. Přemek se mi dokonce velmi vstřícně pokusil tuto možnost zprostředkovat. Dobrá věc se ale technicky nezdařila. Pokusím se tedy komentovat rovnou Přemkův článek, což má jistá úskalí. Přemek určitě ve svém článku, který opírá o kratičký třířádkový kód, vyjmenoval jen ty zásady, které se dají na tak malém prostoru zdokumentovat. Těm zásadám v podstatě není co vytknout. Naopak bych kvůli obecnosti pokládal za užitečné k nim ještě něco přidat. A také pokládám za užitečné doplnit možný výklad diskutovaných zásad nad rámec interpretace, jakou jim sám Přemek dává.

Výčet Přemkových zásad, o nichž píše jako o výčtu bodů, které považuje za základ správného psaní programového kódu, je následující:

  • psát kód tak, aby jej někdo jiný mohl snadno upravit
  • nestydět se za (rozumně) dlouhé názvy promenných
  • psát samovysvětlující kód
  • vyvarovat se tzv. "magických" čísel v kódu pomocí konstant
  • zapouzdřovat procedury a funkce tak ať jsou samostatně použitelné kdekoliv (ideální stav)
  • jedna rutina by měla obsahovat max 200 řádků kódu
  • dodržovat pravidlo, že funkce vrací výsledek a nikdy nic neprovádí
  • používat minimálně globální proměnné, proměnné na úrovní modulu a nepředávat více jak 5 parametrů pří volání jiných rutin
  • oddělovat logiku userformu od řídící vrstvy

V první řadě musím zdůraznit, že s těmi zásadami v základu vřele souhlasím. Dovolím si ale trochu diskutovat s jejich interpretací v Přemkově podání

 

Psát kód tak, aby jej někdo jiný mohl snadno upravit:
Přemek své zásady uvádí souřadně vedle sebe, aniž by jednu upřednostňoval před druhou. Já bych ovšem řekl, že hned tato první stojí nad všemi ostatními jako jejich předznamenání. Ty ostatní podle mne vlastně představují nástroje, jak dosáhnout dobré čitelnosti a opravitelnosti vlastního kódu pro druhé. Tato zásada je mimo jakoukoliv diskusi, jakmile má životnost kódu přesáhnout jednorázové použití. Téměř každý programátor po čase kouká i do vlastního kódu jako do cizího textu, takže napsat čitelný kód se zpravidla v čase vyplatí i jemu samotnému.

Nestydět se za (rozumně) dlouhé názvy proměnných:
Tady z mého pohledu Přemek začíná trochu zprostředka problematiky názvů proměnných. Za počátek jakéhokoliv pořádku v proměnných pokládám povinnou explicitní deklaraci proměnných. Fakt, že VBA dovoluje implicitní deklarování proměnné jejím prvním použitím, pokládám dokonce za škodlivý přežitek z pradávna původního Basicu. Každý, kdo programuje trochu víc, zná důsledky překlepů v názvech nedeklarovaných proměnných, vedoucí často na hodiny beznadějného hledání logické chyby v algoritmu, který neběhá, přestože běhat evidentně musí.
V čitelném a samodokumentujícím kódu by se měl každý dočíst, s jakými proměnnými jakého typu bude mít čest nakládat. Proto jsem silným zastáncem nejenom explicitní deklarace, ale i explicitního uvedení typu proměnné. Dokonce i implicitní typ Variant pokládám za nevhodnou berličku; chci-li opravdu s takovou proměnnou pracovat, pokládám za správné k deklaraci jejího názvu připsat (nadbytečnou) klausuli As Variant, aby i u ní bylo zcela jasno.
Ne každý název lze zdokumentovat jeho deklarací. Se zásadou čitelnosti a samodokumentace kódu se bohužel do konfliktu ve VBA dostává využívání definovaných názvů z kolekce Names. Když se ve zdrojovém kódu bez varování objeví zápis Range(“TabulkaSazeb“), nemáme v jeho rámci žádný automatický dokumentační prostředek, který by ozřejmil charakter, umístění a obsah oné tabulky. Chceme-li v tomto případě přímou čitelnost kódu zajistit, buď připustíme využití komentáře (čemuž se Přemek dále brání), nebo bychom se měli použití definovaných názvů z kolekce Names raději úplně vyhnout (např. deklarací objektových proměnných typu Range s jejich následným naplněním konkrétním obsahem).
Teprve v okamžiku, kdy se vyrovnáme s jednoznačnou identifikací proměnných, stojí podle mého soudu zato zabývat se jejich názvy. U těch proměnných, které jsou v algoritmu nositelem podstatné informace, je výmluvnost názvu velmi žádoucí. Výmluvnost přitom není nutné zaměňovat za ukecanost; míru toho by si měl každý programátor velmi uvážlivě zvolit. Pokud začnou „příliš výmluvné“ názvy vstupovat do výrazů, začnou se takové výrazy blížit svou čitelností supervzorcům. Sám pro svou praxi rozlišuji proměnné na „nosné“ a „pracovní“.
Nosným proměnným - zpravidla přiděluji výmluvné názvy (používám celá slova, uvedená velkým písmenem; pokud je název souslovím, každé ze slov v sousloví u mne začíná velkým písmenem; diakritice a podtržítkům se zpravidla vyhýbám, i když je VBA snáší velmi dobře). Střídání velkých a malých písmen v deklarovaném názvu má jeden vedlejší efekt při psaní kódu: okamžitě po dopsání příkazu (který píšu vždy v malých písmenech) se totiž předem definované proměnné převedou do své deklarované podoby s kapitálkami; když se to nestane, jde zákonitě o můj překlep, kterého se mohu okamžitě zbavit.
Pracovní proměnné - pro mne představují položky, jejichž významová platnost je omezena na konkrétní syntaktickou konstrukci (např. For –Next, Do – Loop, Select – End Select, If – End If, With – End With). U pracovních proměnných se naopak vůbec nebráním krátkým, často jednoznakovým proměnným; jejich význam je zpravidla velmi výmluvně stanoven funkčností konstrukce, v jejímž rámci platí, takže k žádným pochybnostem o jejich smyslu nedochází. Podstatné je, že význam pracovní proměnné se musí zcela vyčerpat jeho lokálním využitím a není nadále využíván jako zdroj informace jinde v kódu. Tutéž pracovní proměnnou se pak dokonce nerozpakuji použít v jiném místě téhož kódu pro jiný účel. To ovšem už je praktika, kterou bych si nedovolil prezentovat jako zcela čistou a hodnou následování.

Ještě jedna malá poznámka k maďarské notaci názvů proměnných, kterou Přemek zmiňuje i používá: používání prefixů v názvech proměnných pro charakterizování jejich typu vůbec nepokládám za přežitek. Pro odlišení dílčích typů hodnotových proměnných ho ale považuji za zbytečný přepych. Za výborný výrazový prostředek ho naopak pokládám pro vzájemné odlišení objektových proměnných, včetně toho, že se tím mohou objektové proměnné formálně odlišit od těch hodnotových, což může často výrazně přispět srozumitelnosti kódu. Příklad svého přístupu k názvům proměnných uvedu v komentáři k využívání konstant.

Psát samovysvětlující kód:
Tady si nejsem zcela jist, jestli Přemkovi nesplynula tato zásada s tématem výmluvných názvů proměnných. Určitě si výmluvnost v názvech zaslouží i další kategorie identifikátorů, v první řadě pak názvy procedur a funkcí. Já bych si s trochou demagogie mezi samovysvětlující prostředky dovolil přidat i komentáře, protože i ty jsou zcela organickou komponentou jazyka VBA. Sám Přemek v některých svých ukázkách vybavuje své procedury dost rozsáhlým a formalizovaným vstupním komentářem o smyslu a návaznostech procedury. Pro rozsáhlejší díla kolektivní povahy takovou výbavu procedur pokládám za mimořádně prospěšnou. K samovysvětlující rovině se zde přidává navíc i velmi žádoucí samodokumentační rozměr napsaného kódu.

Vyvarovat se tzv. "magických" čísel v kódu pomocí konstant:
Velmi dobře rozumím této zásadě a plně uznávám její smysl. Přesto nejsem jejím velkým stoupencem v daném podání. „Magická čísla“, rozesetá v kódu, jsou samozřejmě velikým a velmi rozšířeným nešvarem kódů, šitých na míru konkrétní situaci. Já se těmto situacím vyhýbám deklarováním nosných proměnných s potřebným rozsahem viditelnosti, jejichž hodnotu pak definuji až k okamžiku zahájení jejich využití. Tyto hodnoty vnímám spíš jako pevné parametry úlohy než jako její konstanty. To mi umožňuje, abych stejný algoritmus mohl použít na různých místech kódu s jiným nastavením „konstant“ v Přemkově pojetí.

Na tomto místě se pokusím ukázat rozdíl mezi Přemkovým vzorovým řešením a mojí „zavedenou praxí“. Spláchnu přitom jak práci s názvy proměnných, tak eliminaci „magických čísel“ v kódu. Přemkův převzatý kód (s doplněnou konstantou za nedeklarovanou proměnnou t a naznačením využití výsledku z cyklu) zní:

    Const SLOUPEC_ZACATEK   As Byte = 2
    Const SLOUPEC_KONEC     As Byte = 21
    Const RADEK_VSTUP       As Byte = 10
    
    Dim bolNestejne As Boolean
    Dim bytSloupec  As Byte
    
    bolNestejne = False
    For bytSloupec = SLOUPEC_ZACATEK To SLOUPEC_KONEC
      If Cells(RADEK_VSTUP, bytSloupec).Value _
      <> Cells(RADEK_VSTUP - 1, bytSloupec).Value Then
         bolNestejne = True
         Exit For
      End If
    Next bytSloupec
    If bolNestejne Then …


Při přísném dodržení zásad, kterým při psaní dávám přednost já, by stejný úsek kódu vypadal následovně:

    Dim Sloupec1 As Long, Sloupec2 As Long, RadekVstup As Long
    Dim Nestejne As Boolean, i As Long
    
    Sloupec1 = 2:   Sloupec2 = 21:   RadekVstup = 10
    Nestejne = False
    For i = Sloupec1 To Sloupec2
      If Cells(RadekVstup, i) <> Cells(RadekVstup - 1, i) Then
         Nestejne = True
         Exit For
      End If
    Next i
    If Nestejne Then …


Čím se můj zápis liší od Přemkova vzorového příkladu?

    1. Konstanty jsem nahradil nosnými proměnnými podobných jmen dle vlastních zásad
    2. Použil jsem pracovní proměnnou i dle vlastních zásad; její význam je naprosto spolehlivě a jednoznačně určen řádkem For i = Sloupec1 To Sloupec2
    3. Typ Byte jsem nahradil typem Long (nevidím žádný důvod šetřit pamětí po jednotlivých bytech za cenu rizika, že při použití v jiném místě by rozsah položky nemusel stačit)
    4. Definoval jsem hodnoty nosných proměnných před spuštěním algoritmu
    5. Vypustil jsem explicitní definici vlastnosti Value u objektu Cells.

Body 1 až 4 jsem již okomentoval dříve. Fakt, že každý objekt Range má implicitní vlastnost Value, pokládám pro každého uživatele VBA za tak známou, že opravdu nevidím důvod k jejímu explicitnímu uvádění v kódu. Vnější pozorovatel může porovnáním obou kódů snadno dojít k závěru, že si oba texty vypadly z oka. Však také netvrdím, že s Přemkovými zásadami nesouhlasím. Jen si je vykládám méně rigorózně. A přiznávám, že dojem, že ten můj zápis je podstatně čitelnější, může být jen mým subjektivním pohledem.

 Zapouzdřovat procedury a funkce tak ať jsou samostatně použitelné kdekoliv:
Nemám představu, jakou špatnou zkušenost udělal autor této zásady při práci s VBA. Tento jazyk poskytuje veškerý myslitelný komfort pro zapouzdřování. Práce s lokálními proměnnými dost dobře ani neumožňuje s tím zapouzdřením cokoliv nepravého udělat. Kromě určité alchymie s Error Handlery při práci se vnořenými procedurami jsem sám na žádný problém se zapouzdřením procedur nenarazil. Jediný problém by mohlo podle mého soudu přinést nedisciplinované užívání proměnných s širší viditelností (modulové a globální proměnné). Tomuto tématu je ale věnovaná jedna z dalších zásad; takže opravdu nevím, kam tato zásada směřuje.
Na tomto místě se mi ale nabízí k popisu jiné a dost významné riziko nevhodné práce s procedurami. Velký motivem dlouhé éry programování byl boj proti „špageti-kódům“, který se zaměřil na vymýcení příkazu GoTo ze slovníku programátorů. Tento boj byl po desetiletích války prakticky doveden do vítězného konce. Potkat dnes ve VBA odskok na návěští, které nemá povahu vstupu do Error Handleru, je praktický unikát. Z hlubin jazyka se ale vynořilo jiné strašidlo, které může směle zastoupit roli GoTo při tvorbě špageti-kódu. Jím se nevhodná práce s voláním procedur.
Projekt VBA je velmi tvárná a košatá struktura. Zdrojový kód můžeme rozmístit nejenom do mnoha souběžných standardních modulů, ale také do modulů tříd, na jednotlivé listy i do UserFormů. Obtížně hlídatelné jsou zejména reakce událostních procedur, které se často odvolávají na procedury ze standardních modulů. Zatímco tok běžného algoritmu docela slušně ohlídáme okem, upřeným na kód, před odskoky do událostních procedur nás při prohlížení kódu často nevaruje vůbec nic. Pokud se to děje trvale, může dokonce náš program správně fungovat i nad rámec logiky napsaného kódu. Řada lidí se v takovém případě chvíli diví, jak je to možné, nakonec ale pokrčí rameny, když je výsledek trvale v pořádku. Jiné je to tehdy, když zdánlivě neprůstřelný algoritmus systematicky kolabuje. Mnohdy až pracným tracingem zjistíme, že si program mezi dvěma nevinně vyhlížejícími příkazy jaksi samovolně odskočil vyřídit jakousi událost, která si na oplátku zavolá proceduru ze standardního modulu, o jejíž provedení v dané chvíli ani trochu nestojíme. Zato pak nechápavě zíráme na „náhodně vzniklý“ nepochopitelný jev. Událost je totiž událost… Jednou nastat může, jindy nemusí.
Netušené „volání procedur z volaných procedur“ je dalším rizikovým faktorem „lepených“ kódů. Velikou živnou půdu pro jevy tohoto typu představuje nekritické přebírání cizích řešení jako součástí vlastního projektu. Ovšem není to jediná cesta, jak vyrobit „bludné okruhy“ ve VBA projektu. Sám jsem si vyrobil pár podobných lahůdek, když jsem se po čase vrátil k vlastním větším projektům, jejichž vnitřní mechanizmy jsem si už nepamatoval. Zdánlivě triviální úprava pak způsobila často mnohadenní hledání jehly v kupě sena. V těchto případech je vždycky lepší, když to neběhá „skoro nikdy“, než když to běhá „skoro vždycky“. Lov na řídce se vyskytující chyby patří k nejhorším nočním můrám každého programátora.
Přiznám se, že obranu proti vzniku podobných jevů metodicky zvládnutou nemám. Hledání chyby samotným prohlížením kódu je v těchto případech prakticky odsouzeno k neúspěchu. Spásou potom bývá odhalení bezpečného způsobu, jak hledaný jev vyvolat. Dohledání jádra pudla je pak už věcí rutinního využití ladících nástrojů, což je naštěstí poměrně silnou zbraní vývojového prostředí VBA. Jedno doporučení bych ale přece jenom měl. V zoufalství se člověk chytá stébla. Při podezření na to, že problém spočívá v nevhodné konstelaci při postupném volání procedur, začne být důležité vyhledat, kde je která procedura z kódu volaná. Když ji voláme pomocí syntaxe JménoProcedury , je hledání všech výskytů volání veškerých procedur ve velkém projektu opravdu sysifovská práce. Pokud však zásadně používáme zápis typu Call JménoProcedury <seznam parametrů>, je příkaz Call oním pevným bodem v prostoru, jehož se lze podržet. Volání procedur pomocí Call má ale u mne jednu smůlu: nedělám to a pálí mne to vždycky, až když nastane problém. A to už je opravdu pozdě…

Jedna rutina by měla obsahovat max. 200 řádků kódu pro lepší přehlednost:
Další velmi chvályhodná zásada. Znám dokonce její daleko ostřejší verzi, která doporučuje, aby délka procedury nepřekročila rozsah jedné obrazovky! Pokud to někomu připadá jako příliš drakonické, mohu všechny ubezpečit, že jsem v jednom ze svých programátorských období tuto zásadu přísně a s úspěchem dodržoval. Chtělo to pouze maličko upravit styl psaní kódu a maličko si dát nohu za krk, ale šlo to! Dokonce si dovolím tvrdit, že VBA se svým extrémně rychlým voláním procedur je pro takovou techniku velmi dobře připravený.
Vhodným konceptem pro psaní procedur „po obrazovkách“ je psaní algoritmu „shora dolů“. Začneme nejhrubším schématem úlohy, které sestává zpravidla z několika sekvenčních kroků, začínajících zahájením zpracování, pokračující výkonnou částí úlohy a kupodivu končící závěrečnými akcemi pro ukončení práce. V dané chvíli se vůbec nezabýváme konkrétní logikou jednotlivých modulů a chováme se k nim jako ke klasickým černým skřínkám. Stačí nám hrubá představa, co by měly dělat a podle toho jim přidělíme vhodné názvy procedur. Pokud už nyní začneme algoritmus větvit, bude to s použitím jednoznačného testu na černé skřínky nižšího řádu. Zpravidla nebudeme mít problém toto nejhrubší schéma umístit na jedinou obrazovku.
Další kroky se budou podobat svou základní logikou jeden druhému jako vejce vejci. Každou z výše pojmenovaných a zařazených černých skřínek (tj. pozdějších procedur) rozvedeme do sledu navazujících, případně větvících se černých skřínek dalšího řádu. Snažíme se udržet všechny komponenty, uvedené na obrazovce na stejné rozlišovací úrovni z hlediska úlohy jako celku. Riziku, že bychom při popisu náplně aktuální skřínky překročili rámec jedné obrazovky, čelíme vytvářením mezilehlých sdružujících skřínek. Podstatné ovšem je, že každá ze skřínek na obrazovce musí mít jednoznačně definovanou roli v rámci úlohy a že úhrn rolí skřínek na obrazovce musí vytvářet kompletní předpokládanou náplň celé černé skřínky, kterou reprezentuje aktuální obrazovka.
Popsaným rozvojem se musíme dřív nebo později dostat až na úroveň podrobného algoritmu na úrovni výkonných příkazů VBA. Jak je obecně známo, každá černá skřínka realizuje transformační úlohu, která z konkrétně definovaných vstupů vyváří transformované výstupy. Z vnějšku je přitom nepodstatné, jakou formou k transformaci dochází. Cestou shora dolů stačí pouze tušit podobu potřebných výstupů, které v podobě vstupů do skřínky nižšího řádu jí umožní realizovat požadovanou transformaci. Jakmile se cestou shora dostaneme až na úroveň podrobného algoritmu, začne být zřejmé, jaké konkrétní vstupní parametry bude potřebovat odpovídající procedura, aby mohla poskytnout konkrétní požadovaný výstup. Nyní se můžeme vydat cestou vzhůru a z nadřazených černých skřínek začít vytvářet potřebné procedury s jejich vstupními i výstupními parametry, protože znalost potřebných vstupů nižší procedury současně definuje data, kterými ji musí zásobit nadřazený člen. Celý uvedený proces postupně definuje soustavu výkonných procedur, z nichž se posléze poskládá kompletní náplň celé úlohy. A dáme-li si na tom opravdu záležet, žádná z procedur nemusí rozsah obrazovky překročit.
Mám-li se přiznat, nebral jsem nikdy požadavek jedné obrazovky na proceduru jako neporušitelný zákon. Zejména na nejnižší úrovni vznikajícího stromu by to bylo často samoúčelné. Procedury této úrovně často mají také povahu opakovatelné využitelnosti v různých větvích stromu a bývá výhodné vytvářet z nich společnou knihovnu celého projektu, využívanou všemi řešiteli. Má-li někdo chuť, může si popsaný postup vyzkoušet na vlastní kůži. Mohu potvrdit, že touto cestou jsme vytvořili v rámci našich realizačních týmů pravděpodobně své nejlépe strukturované projekty. Navíc jde totiž o cestu, která umožňuje maximálně efektivní dekompozici úlohy z hlediska jejího rozdělení v rámci řešitelského týmu. Pokud se dekompozice dobře povede, je jedinou styčnou plochou jednotlivých členů týmu datový interfejs jednotlivých procedur projektu.

Dodržovat pravidlo, že funkce vrací výsledek a nikdy nic neprovádí
Mimořádně zajímavá a mnou zhusta porušovaná zásada! Vždycky jsem vycházel z toho, že funkce je pouhým druhem procedury, která má jedinou odlišnost: sama nabývá vypočtené náplně v souladu s typem objektu, který představuje, a může se tedy stát součástí přiřazovacího příkazu na jeho pravé straně. Proto jsem příliš neváhal v případě potřeby přidělovat funkci kromě vstupních také výstupní parametry, které jsem dál účelně využíval. Vzpomínám si živě na nelíčené zděšení, pohoršení a přísné pokárání od Petra Pecháčka, když jsem se jenom zmínil, že tomu nic nebrání. Na ukázku, že to tak opravdu bez problému běhá, už Petr nereagoval :-). Pravdou ovšem je, že si nevzpomínám, že bych v rámci provádění funkce prováděl změny na libovolných objektech.
Že tomu nic nebrání, je jedna věc. Zda je to programátorsky rozumné a účelné, je věc druhá. Dívám-li se na věc nadhledu, pokládám výše uvedenou zásadu za nanejvýš hodnou následování a míním na to do budoucna u sebe přísně dbát. Tím se i u mne dostane funkce ve VBA do souřadného postavení s funkcí listu, která přímo z principu nemůže změnit cokoliv jiného kromě hodnoty ve své buňce.

Používat minimálně globální proměnné, proměnné na úrovní modulu a nepředávat více jak 5 parametrů pří volání jiných rutin:
Tuhle zásadu bych spíš chápal jako dobré doporučení, protože co to znamená „používat minimálně“? Dokonce se dá říct, že obě poloviny vyslovené zásady jdou do jisté míry vzájemně proti sobě. Použití proměnných s větším rozsahem viditelnosti je totiž jednou z klasických cest, jak omezit počet předávaných parametrů mezi procedurami. Právě v této roli já osobně s výhodou využívám globální, případně modulové proměnné. Vlastně je to jediný důvod, proč těchto proměnných využívám. Je otázkou, zda tím splňuji onu minimalizační podmínku. Jak už jsem napsal dříve, proměnné s vyšší viditelností u mne plní roli Přemkových „konstant“, ovšem s tou výhodou, že je mohu podle nastavení a vývoje úlohy také algoritmicky měnit.
Dodržení rozumného počtu předávaných parametrů je spíš věcí čitelnosti a srozumitelnosti kódu, než technickou nutností. Příliš mnoho parametrů je ovšem dost otravná záležitost při psaní kódu. Někdy ale procedura opravdu vyžaduje velkou řadu dílčích vstupů, případně poskytuje dlouhou řadu výstupů. V tom případě může být účelné využít datovou konstrukci Type – End Type, pomocí níž můžeme shrnout i velmi nesourodou skupinu informací do jediného celku, předávaného jako jeden parametr. Jakkoliv to vypadá zvláštně, nevzpomínám si na Pandoře na příklad, který by takový uživatelsky definovaný sdružený typ aktivně využil.

Oddělovat logiku userformu od řídící vrstvy:
Velmi zásadní požadavek na konstrukci UserFormu. Tato komponenta projektu VBA je svým způsobem cizorodým prvkem v rámci Excelu a je dobré chovat se k ní spíš jako k váženému hostu než k pokrevnímu členu domácnosti. Patří k dobrým mravům UserForm vybavit nejen událostními procedurami pro správnou funkci jeho ovladačů, ale i potřebnými podpůrnými procedurami, potřebnými k výkonu jeho interních funkcí. Zdá se naopak správnější, aby naplnění UserFormu vstupními hodnotami provedla procedura, která UserForm vyvolává, stejně jako by měla stejná procedura zpracovat výstupy z něj po jeho skrytí. Přiznám se, že v tomto směru nemám úplně čisté svědomí, stejně jako chování některých ovladačů na UserFormech nemá čisté svědomí vůči mně :-).

Tím jsem uzavřel své poznámky k soustavě zásad, které Přemek uvedl a dále rozebral ve svém článku. Pohledem zpátky zjišťuji, že můj komentář k Přemkovu článku nabyl hrozivých rozměrů. Psal jsem ovšem tak, jak to ve mně navršily dlouhé roky praxe. Snažil jsem se být poctivý sám k sobě a se stejnou poctivostí jsem se pokusil osvětlit svůj pohled na problematiku, která Přemka zaujala do té míry, že kvůli tomu změnil svůj rukopis. Je na posouzení čtenářů, který pohled jim připadne příhodnější (pokud nezavrhnou oba a nesetrvají u vlastních zásad). Abych zdokumentoval, o co opírám své názory, přidal jsem i popis vlastní cesty za poznáním v dané oblasti.

 

Dějiny kultury programování z pohledu starého harcovníka.

Na tomto místě si dovolím jako pamětník svůj exkurs do minulosti. Diskuse na téma správného psaní zdrojových kódů je stará bezmála stejně, jako existence vyšších programovacích jazyků. Tím mám na mysli veškeré jazyky, které dokázaly nahradit přímé adresování symbolickými identifikátory a skoky na adresy umožnily nahradit vyššími konstrukcemi jazyka. Již v programátorském pravěku se horlilo proti propletencům, vznikajícím v důsledku nemírného skákání vpřed i vzad v rámci lineárního kódu. Již tenkrát vznikl pojem „spaghetti-code“ jako parafráze na mísu zamotaných špaget, čemuž se připodobňovaly těžko rozluštitelné kódy, plné odskoků všemi směry bez ladu a skladu.

 

Prvním vážným útokem na bezprizornost psaní špageti-kódů se staly takové syntaktické vymoženosti nových jazyků, jako např. logické závorky, umožňující sdružovat skupiny příkazů nebo dokonce samostatně zapouzdřené procedury s vlastními datovými prostory a provázané s hlavním kódem pomocí parametrů a návratových adres. S tímto přepychem jsem se poprvé setkal u jazyka ALGOL, který byl laboratorním, ale značně rozšířeným výtvorem své doby. To sice umožňovalo dát větším programům vyšší řád a přehlednost, neřešilo to ale základní zdroj špageti-efektu, jímž stále byl všudypřítomný GO TO (ať už se psal jakkoliv). FORTRAN například byl na něm přímo životně závislý a číslo děrného štítku ve funkci implicitního návěští příkazu bylo stěžejním údajem každého programu.

Generální útok na hříšné GO TO podnikl až jazyk PASCAL, opět laboratorní jazyk jedné švýcarské univerzity, pomocí nějž se páni profesoři pokusili nasadit svým studentským ovečkám ty správné ortopedické boty pro psaní programů. Autoři jazyk vybavili průkopnickými syntaktickými prvky, které ve svém důsledku umožnily psát i velmi složité algoritmy bez jediného výskytu GO TO. Tehdy se zrodilo to, čemu se následně začalo říkat strukturované programování. PASCAL sám z univerzity utekl do širého světa, větší slávy se ale v praktickém životě nedočkal. Znamenal ovšem historický předěl v dějinách programování: rozdělil programovací jazyky na nástroje „před Pascalem“ a „po Pascalu“.

Neznám jediný pozdější jazyk, který by si dovolil nezahrnout do svého instrumentária základní „strukturotvorné“ syntaktické prvky, s nimiž přišel Pascal. Sám jsem se s těmito prvky poprvé potkal v rámci programování pro skupinu databázových nástrojů dBASE. Byl to tenkrát pro mne trochu kulturní šok, který jsem si tehdy pro sebe pojmenoval jako „programování bez hřebíků“. Najednou jsem hřebíky v podobě GO TO neměl k dispozici, což mne zpočátku znervózňovalo. Velmi brzy jsem si ale zvykl a už jsem hře bez hřebíků zůstal navždy věrný.

Nástup strukturovaného programování byl nepoměrně silnější proud, než předchozí vlna modulárního programování, rozvíjející formální postupy kolem intenzivního využívání samostatně zapouzdřených procedur. Přesto i kolem modulárního programování vznikla řada publikací, jejichž autoři rozpracovali jeho metody do velikých detailů. Rád jsem vstřebal řadu užitečných zásad z tohoto směru, nikdy jsem se ale nestal pravým „modulárním programátorem“. Vliv strukturovaného programování byl tak silný, že zasáhl i tradiční jazyky, které do té doby o strukturovaném programování ani neslyšely, natož aby byly vybaveny odpovídajícími syntaktickými prvky. To se dotklo dokonce i tak tradičního jazyka, jakým byl můj milovaný COBOL, s nímž jsem strávil nejplodnější část svého programátorského života. Firma NCR rozvinula kolem svého interaktivního jazyka IMOS COBOL velmi sofistikovaný přístup „Structured Programming Concept“, který definoval vhodné techniky pro strukturované programování v tomto jazyce. Dokonce pro ten účel vyvinula metajazyk, v němž bylo možné napsat základní logiku úlohy, jíž se potom vlastní zdrojový kód v Cobolu měl držet. Velkým propagátorem tohoto přístupu se stal můj kolega a spolubojovník Zdeněk Kittl, který mi svým zápalem pro věc silně připomíná Přemka dnešních časů. Program od Zdeňka o mnoha tisících řádek měl stále zcela jasnou strukturu, popsanou v samém úvodu formou metajazyka. Zdeňkovy programy hýřily dlouhými názvy, byly skvěle čitelné, ale psaly se strašně neohrabaně. Drobné zásahy do nich se dělaly výborně, větší zásahy koncepčního rázu však zpravidla znamenaly původní program zahodit a napsat ho celý jinak. Kromě Zdeňka se mezi námi nenašel už nikdo, kdo by ho následoval. To vše čtvrt století tomu nazad.

Skutečný převrat v programování přinesly objekty. Do té doby měl identifikátor roli pojmenování adresy pro umístění datové položky, případně roli jména návěští, procedury či funkce. Datová položka měla jediný možný obsah, a to hodnotu. Takové položky bylo možné sdružovat do polí, a to bylo tak vše. S objekty, pokud vím, přišel jazyk C+. Původní „céčko“ bylo vlastně trucprojekt, jimž chtěli jeho oba pachatelé svým kolegům dokázat, že lze napsat i naprosto potrhlý programovací jazyk, který ale bude přesto pracovat. I sám název jazyka byl svého druhu vtipem. Sami autoři po letech přiznali, že je ani ve snu nenapadlo, že jejich „Hallo, World“ přivedlo na svět převratný nástroj programování.

S objekty se v programátořině náhle objevily zcela nové datové konstrukce. Vedle jednoduchých proměnných s hodnotou jako jediným možným obsahem, se náhle zjevily mnohotvaré objekty se svými proměnlivými metodami, vlastnostmi a událostmi. Vstup tohoto fenoménu do světa programování znamenal pro práci při projektování úloh úplnou revoluci v zavedených postupech psaní úloh. Jakmile se ukázala skutečná síla jazyka C, vznikly poměrně rychle po sobě jeho vyšší formy C+ a C++, které práci s objekty dotáhly do zářivé podoby. A opět vznikl přelom v konstrukci jazyků na jazyky „před céčkem“ a „po céčku“. Opět si nevzpomínám na novější jazyk, který by si dovolil existovat bez objektů. Existují však dvě základní odrůdy jazyků s objekty. V první řadě jde o plnokrevné objektové jazyky, které nejenom umějí standardní objekty používat, ale také si dokážou vlastní originální objekty samy vytvářet a naplňovat obsahem (včetně originálních metod, vlastností a událostí). Vedle těchto objektových plnokrevníků existují jiné jazyky, kterým se říká objektově orientované a které neznají magii konstruktorů a destruktorů svých plnokrevných příbuzných. Tyto „odlehčené“ jazyky mají k dispozici pouze sadu předdefinovaných objektů s pevným výčtem jejich použitelných, i když velmi tvárných metod, vlastností a událostí, na jejichž základě lze konstruovat nekonečné množství realizací konkrétních objektů v projektech.

Objektové jazyky mne obešly širokým obloukem (nebo jsem se jim spíš vyhnul já sám). Jedno skutečně vadné rozhodnutí pro celou mou profesní dráhu bylo, když jsem se rozhodl, že céčko a jeho následovníky budu vědomě ignorovat. Dnes vím, že mne tím minul proud, v němž bych se tehdy se zárukou brzy cítil jako ryba ve vodě. Bohužel mi to došlo příliš pozdě. Objekty jsem potkal až v souvislosti s VBA, když jsem při zkoumání produktů z rodiny MS Office 97 na VBA více méně bezděky narazil. Koncept tohoto jazyka mne uchvátil svým pojetím jako propojitelného a společného prostředí pro Word, Excel i Access zároveň. Uchvátilo mne i vývojové prostředí pro psaní algoritmů VBA, které v té době daleko přesáhlo veškeré mé dosavadní zkušenosti s komfortem pro psaní zdrojových kódů a jejich ladění.

Co mne ale nenadchlo vůbec, byla moje totální neschopnost pochopit logiku objektů a práce s nimi. Říká se, že starého psa novým kouskům nenaučíš. V mém tehdejším věku už šlo o reálné riziko uniklé příležitosti. Utěšoval jsem se tehdy jen tím, že podobnou beznadějí jsem prošel již jednou, když jsem se z manuálu marně pokoušel pochopit mechanizmus interaktivního dialogu v COBOLu. Tenkrát mi pomohla pouhá hodinka u terminálu s klukem, který tím dialogem již vládl. U VBA jsem bohužel nikoho takového tenkrát neznal a šance na absolvování jakéhokoliv kurzu VBA byla tehdy pro mne nulová. Naštěstí v té době vyšla útlá brožura od J.Černého o programování maker VBA. Ač nevelká rozsahem, byla nabitá velmi stravitelným obsahem, který mi přece jenom umožnil vstoupit do království objektů VBA.

Té knížce vděčím za to, že si již deset let pro vlastní potěšení hraji s objektovými bábovičkami a plácám z datového písku hrady a zámky pomocí pojící hmoty z VBA. Za tu dobu se stala práce s objekty zcela integrální součástí mého rukopisu. Vzhledem k tomu, že samo VBA je dostatečně kulturní jazyk a má i dostatečnou syntaktickou výbavu pro strukturované programování, nemusel jsem slevit nic ze zásad psaní, které jsem si přinesl z minula. A specifika vývojového prostředí VBA mi otevřela cestu k tomu, abych svůj rukopis ve VBA obohatil o některé další prvky, které mi usnadňují můj programátorský život.

Svůj programátorský styl nevydávám za normu. Nikdy jsem se nepokoušel nikomu ho vnucovat. Tvrdím ale, že je podepřen více než čtyřiceti lety praxe. Jsem založením výzkumník a experimentátor. Celý život jsem se bavil tím, že jsem stejnou věc nikdy nenapsal úplně stejným způsobem, protože mne tupé opisování (ani sebe sama) prostě netěšilo. Jsem proto spolu s Járou Cimrmanem velkým objevitelem cest, které nikam nevedou. Na rozdíl od jiných ale o nich vím a příště už se jim vyhýbám. A na druhé straně se mi tak občas poštěstilo objevit škvírku, o které druzí často neměli tušení. Řekl bych, že v současné době je můj programátorský styl již dostatečně stabilizovaný a zejména podepřený zkušeností. Zpravidla umím říct, proč jsem to či ono napsal právě tak, jak jsem to napsal. A v případě potřeby to umím napsat sedmi jinými způsoby. Mimo jiné i tak, jak doporučuje Přemek. To ovšem neznamená, že bych takovému psaní dával přednost. Současně s tím ovšem přiznávám Přemkovi svaté právo psát stylem, který on pokládá za správný a účelný.

Ing. Vladimír Hajdovský

 

Komentáře

Přidat komentář

Přehled komentářů

Dodatek ke komentářům

(Petr Pecháček - excelplus.net, 30. 11. 2009 10:23)

Dodatek:
Nejméně uřiditelné jsou události. V poslední době je to má programátorská noční můra.
Nejhorší "papírování" je s komentováním změn v kódu, obzvláště při práci v týmu.

Komentáře

(Petr Pecháček - excelplus.net, 30. 11. 2009 10:22)

>> psát kód tak, aby jej někdo jiný mohl snadno upravit
S postupem času si programátor uvědomuje význam komentářů ke kódům a přestává chápat i své vlastní výtvory z "nejlepších let". Pokud je ale na vrcholu, je komentování a ošetřování blbovzdornosti pro něj ztrátovým časem.

>> nestydět se za (rozumně) dlouhé názvy promenných
Trochu problém je s tím, jaký jazyk (čeština, angličtina) v názvech používat.

>> psát samovysvětlující kód
Kratší komentáře by měli stačit, hlavně v místech větvení, začátků cyklů a volání funkcí a procedur.

>> vyvarovat se tzv. "magických" čísel v kódu pomocí konstant
Dobře myšleno, ale... Číslo 9,80665 je většině známé, ale všem? Navíc, některé skutečně efektivní cesty a algoritmy prostě přestanou být efektivní, pokud je v kódu budeme "objasňovat". Otázka eticko pracovně-právní - nakolik si může programátor-zaměstnanec takto chránit know-how?

>> zapouzdřovat procedury a funkce tak ať jsou samostatně použitelné kdekoliv (ideální stav)
Tenhle výraz bych tipoval, že pochází z velkého Visual Basicu. Je dobré si stanovit rozumnou hranici, kdy procedury a funkce zobecňovat a kdy naopak úzce specializovat. Zobecňování vede k nárůstu předávaných parametrů. Každopádně jsem jednoznačně pro ukládání užitečných bloků kódů do databanky.

>> jedna rutina by měla obsahovat max 200 řádků kódu
Vzhledem ke zobrazovacím zařízením klesá přehlednost a rychlý přesun v kódu už u mnohem menšího poštu řádků. Procedura by měla tvořit logický celek.

>> dodržovat pravidlo, že funkce vrací výsledek a nikdy nic neprovádí
Souhlasím. Je tu ale výjimka. Co s dialogy, které před vyvoláním vyžadují parametry a po jejichž ukončení je navrácena hodnota? Klasická procedura s parametry a vracení do proměnné? Apropo, co je to vlastně ten náš MsgBox...

>> používat minimálně globální proměnné, proměnné na úrovní modulu a nepředávat více jak 5 parametrů pří volání jiných rutin
Obecně proměnné vně procedury jsou hezká věc. Tedy do té doby, než dojde k chybě za běhu programu a Excel je sestřelí... Říkám ano, ale je třeba "udržovat oheň".
Pět parametrů je rozumná hranice, ne vždy udržitelná. Používám teď často funkce pracující s prvky v OLAPu, tam není problém osm a více parametrů. Je pravda, že zápisy s více parametry jsou nepřehledné a hodně mi chybí funkce optického zalomení kódu v editoru.

>> oddělovat logiku userformu od řídící vrstvy
Pojem řídící vrstvy bych źase moc neprezentoval "běžným smrtelníkům". Jinak, ano, se vším, co jde, jít do standardního modulu. Upřímně, ne vždy se toho držím a někdy mi připadá, že od modulu formuláře do klasického modulu je "daleko".

>> GoTo
V rámci ošetřování chyb v kódu ano, ne jako samozřejmá věc z pohledu větvení kódu. Není dobré tahat zvyky skákání v procedurách ze starších programovacích jazyků. Když jsem zpětně studovat svého času FORTRAN, tak, jsem myslel, že mi taky přeskočí :-)

Mé poznámky k Vladimírovému textu

(Premek, 25. 11. 2009 18:59)

Hned na začátek bych rád podotknul, že tak jako vždy jak jsme zvyklí u Vladimíra, tak
také tento příspěvěk má obrovskou sílu a stojí za přečtení.

Komu se komentář nechce číst, uvadím resumé hned na začátku:
nemám žádné zásadní problémy se vším co Vladimír napsal. Dokonce bych řekl, že až na nepodstatné drobnosti, myslíme oba stejně.

Komentář 1:
Nerad bych, aby si kdokoliv myslel, že svůj způsob psaní kódu vydávám jako jediný správný a žádný jiný nepřipouštím. To ani zdaleka, málokdy se vyjádřím k způsobu zápisu kódu u jiných a když už tak učiním, tak v dobrém (zdravím tímto Vencu Zajíce :-) )

Komentář 2:
Komentáře v kódu - samozřejmě je používám také a dokonce u onoho příkladu by stálo za to uvést komentář co cyklus dělá a jaký je jeho smysl. Na jaké oblasti je kód prováděn by mělo být zřejmé právě z proměných. Opravdu, bez komentářu to nejde!

Komentář 3:
Samovysvětlujicí kód - tím míním stav (ideální) kdy nemusíme vůbec použit komentář a každý vzvojář okamžitě ví co kód dělá a proč.

Komentář 4:
Vladimírová ukázka kódu - nemám ŽÁDNÝ problém s tímto zápisem, dokonce sám používám, nijak to nevybočuje s požadavku na odstranění magických čísel a zároveň to zvyšuje čitelnost. V některých případech prostě použijete proměnnou (dobře pojmenovanou) a někdy zase konstantu.

Komentář 5:
Výchozí vlastností objektů - tam mám menší problém, SPRÁVNÉ by je bylo uvádět. Na rovinu říkám, nedělám to taky ve všech případech. ALE, např. zrovna dnes jsem přepisoval (rozuměj kopiroval) kód z VBA do VSTO a dostal jsem chybu na řádku s kódem

If InStr(1, rngUsedRange.Cells(lngRowCount, lngColCount), "Go to on Web site", vbTextCompare) = 1 Then

(původní kód jsem nepsal já ) a co myslite, že byla chyba? Ano správně, po doplnění vlastnosti Value vše pracovalo jak mělo. Naštěstí to byl méně než 100 řádkový kod a stačilo to změnit na třech místech, ale ....

Komentář 6:
Zapouzdřovat procedury a funkce - tím je myšleno, že procedura/funkce je natolik obecná, že ji Vy nebo nebo kdokoliv jiný, můžete vzít z jednoho projektu a použít v jiném. Typický funkce na zjištění existence listu v sešitu a někdo v ní udělá kód, který
list aktivuje, pokuď existuje! To je právě chyba, funkce má pouze vrátit True/False a nic víc nedělat.

Komentář 7:
GoTo - měl jsem stejný názor jako Vladimír, ale trošku sem jej pozměnil. Mám tady o GoTo článek.
Doporučuji přečíst

Komentář 8:
Globální proměnné - tady to je myšleno hlavně z hlediska určité nebezpečnosti těchto proměnných.
Přiznávám, že dokud jsem nezačal dělat v týmu tak mě to nějak netrapilo, ale když zjistíte, že Vám kolega změnil hodnotu v globální proměnné, protože potřeboval zrovna jinou a použil ji jinde, tak nemáte prostě žádnou šanci to zjistit a pak jen koukáte...
Je to spíše teoretické pravidlo, ale je dobré na něj myslet, když děláte v týmu. Ale samozřejmě jsou případy, kdy nepoužití globální/modulové proměnné výrazně zvýší čitelnost a jednoduchost kódu a v těchto případech se to vyplatí.