sql >> Databáze >  >> RDS >> Access

Psaní čitelného kódu pro VBA – vzor Vyzkoušejte*

Psaní čitelného kódu pro VBA – vzor Try*

V poslední době jsem zjistil, že používám Try vzor stále více. Tento vzor se mi opravdu líbí, protože umožňuje mnohem čitelnější kód. To je zvláště důležité při programování ve vyspělém programovacím jazyce, jako je VBA, kde je zpracování chyb propojeno s tokem řízení. Obecně považuji za obtížnější sledovat jakékoli postupy, které se spoléhají na zpracování chyb jako řídicí tok.

Scénář

Začněme příkladem. Objektový model DAO je perfektním kandidátem kvůli tomu, jak funguje. Všechny objekty DAO mají Properties kolekce, která obsahuje Property objektů. Každý však může přidat vlastní vlastnost. Access ve skutečnosti přidá několik vlastností do různých objektů DAO. Proto můžeme mít vlastnost, která nemusí existovat, a musíme zvládnout jak případ změny hodnoty existující vlastnosti, tak případ připojení nové vlastnosti.

Použijme Subdatasheet majetek jako příklad. Ve výchozím nastavení budou mít všechny tabulky vytvořené prostřednictvím uživatelského rozhraní Access vlastnost nastavenou na Auto , ale to možná nechceme. Ale pokud máme tabulky, které jsou vytvořeny v kódu nebo jiným způsobem, nemusí mít tuto vlastnost. Můžeme tedy začít s počáteční verzí kódu, abychom aktualizovali vlastnosti všech tabulek a zvládli oba případy.

Public Sub EditTableSubdatasheetProperty( _ Optional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="OnToSubdataName As Stringandler"OnToSubdataName Nastavit db =CurrentDb pro každý tdf v db.TableDefs If (tdf.Attributes And dbSystemObject) =0 Then If Len(tdf.Connect) =0 And (Ne tdf.Name jako "~*") Then 'Nepřipojeno nebo temp . Set prp =tdf.Properties(SubDatasheetPropertyName) If prp.Value <> NewValue Then prp.Value =NewValue End If End If End IfContinue:NextExitProc:Exit SubErrHandler:If Err.Number =3270 Then Set prpdatePropertySme dbText, NewValue) tdf.Properties.Append prp Resume Continue End If MsgBox Err.Number &":" &Err.Description Resume ExitProc End Sub

Kód bude pravděpodobně fungovat. Abychom tomu však porozuměli, pravděpodobně budeme muset nakreslit nějaký vývojový diagram. Řádek Set prp = tdf.Properties(SubDatasheetPropertyName) může potenciálně vyvolat chybu 3270. V tomto případě ovládací prvek skočí do sekce zpracování chyb. Poté vytvoříme vlastnost a poté pokračujeme v jiném bodě smyčky pomocí štítku Continue . Existuje několik otázek…

  • Co když je na nějakém jiném řádku zvýšeno 3270?
  • Předpokládejme, že řádek Set prp =... nehází chyba 3270, ale ve skutečnosti nějaká jiná chyba?
  • Co když, když jsme uvnitř obslužné rutiny chyb, dojde k další chybě při provádění Append nebo CreateProperty ?
  • Měla by tato funkce vůbec zobrazovat Msgbox? ? Přemýšlejte o funkcích, které by měly na něčem pracovat jménem formulářů nebo tlačítek. Pokud funkce zobrazí okno se zprávou a poté se normálně ukončí, volající kód netuší, že se něco pokazilo, a může pokračovat v činnostech, které by dělat neměl.
  • Můžete se podívat na kód a okamžitě pochopit, co dělá? nemohu. Musím na to mžourat, pak přemýšlet, co by se mělo stát v případě chyby, a v duchu načrtnout cestu. To není snadné číst.

Přidejte HasProperty postup

Můžeme to udělat lépe? Ano! Někteří programátoři již znají problém s používáním zpracování chyb, jak jsem to ilustroval, a moudře to abstrahovali do své vlastní funkce. Zde je lepší verze:

Public Sub EditTableSubdatasheetProperty( _ Optional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="Current Dubdatasheet As String" Pro každý tdf v db.TableDefs If (tdf.Attributes And dbSystemObject) =0 Then If Len(tdf.Connect) =0 And (Ne tdf.Name Jako "~*") Potom 'Nepřipojeno nebo temp. If Not HasProperty(tdf, SubDatasheetPropertyName) Then Set prp =tdf.CreateProperty(SubDatasheetPropertyName , dbText, NewValue) tdf.Properties.Append prp Else If tdf.Properties(SubDatasheetPropertyValue) NewPropertyValue>NewPropertyValue) End If End If End If NextEnd SubPublic Function HasProperty(TargetObject As Object, PropertyName As String) As Boolean Dim Ignored As Variant On Error Resume Next Ignored =TargetObject.Properties(PropertyName) HasProperty =(Err.Number =0)End Function 

Místo toho, abychom směšovali tok provádění se zpracováním chyb, máme nyní funkci HasFunction který úhledně abstrahuje kontrolu náchylnou k chybám na vlastnost, která nemusí existovat. V důsledku toho nepotřebujeme složité zpracování chyb / tok provádění, který jsme viděli v prvním příkladu. To je velké vylepšení a přispívá k poněkud čitelnému kódu. Ale…

  • Máme jednu větev, která používá proměnnou prp a máme další větev, která používá tdf.Properties(SubDatasheetPropertyName) který ve skutečnosti odkazuje na stejnou vlastnost. Proč se opakujeme se dvěma různými způsoby odkazování na stejnou vlastnost?
  • Zabýváme se majetkem poměrně často. HasProperty musí vlastnost zpracovat, aby zjistil, zda existuje, a poté jednoduše vrátí Boolean výsledkem je, že je ponecháno na volajícím kódu, aby se znovu pokusil získat stejnou vlastnost a změnil hodnotu.
  • Podobně zacházíme s NewValue více než je nutné. Buď jej předáme v CreateProperty nebo nastavte Value vlastnictví nemovitosti.
  • The HasProperty funkce implicitně předpokládá, že objekt má Properties člen a nazývá jej late-bound, což znamená, že pokud je mu poskytnut nesprávný druh objektu, jedná se o běhovou chybu.

Použijte TryGetProperty místo

Můžeme to udělat lépe? Ano! Zde se musíme podívat na vzor Try. Pokud jste někdy programovali s .NET, pravděpodobně jste viděli metody jako TryParse kde místo vyvolání chyby při neúspěchu můžeme nastavit podmínku udělat něco pro úspěch a něco jiného pro neúspěch. Ale co je důležitější, máme výsledek k dispozici pro úspěch. Jak bychom tedy zlepšili HasProperty funkce? Za prvé bychom měli vrátit Property objekt. Zkusme tento kód:

Veřejná funkce TryGetProperty( _ ByVal SourceProperties As DAO.Properties, _ ByVal PropertyName jako řetězec, _ ByRef OutProperty As DAO.Property _) As Boolean On Error Resume Next Set OutProperty =SourceProperties(PropertyName) Then Setr.Number =Nothing End If On Error Přejít na 0 TryGetProperty =(Not OutProperty Is Nothing)End Function

S několika změnami jsme zaznamenali několik velkých výher:

  • Přístup k Properties již není se zpožděním. Nemusíme doufat, že objekt má vlastnost nazvanou Properties a patří do DAO.Properties . To lze ověřit v době kompilace.
  • Místo pouze Boolean výsledkem můžeme také získat načtenou Property objekt, ale pouze na úspěch. Pokud selžeme, OutProperty parametr bude Nothing . Stále budeme používat Boolean výsledek, který vám pomůže s nastavením toku, jak brzy uvidíte.
  • Pojmenováním naší nové funkce Try prefix, naznačujeme, že to za normálních provozních podmínek zaručeně nevyvolá chybu. Je zřejmé, že nemůžeme zabránit chybám z nedostatku paměti nebo něčemu takovému, ale v tu chvíli máme mnohem větší problémy. Ale za normálních provozních podmínek jsme se vyhnuli zamotání našeho zpracování chyb s průběhem provádění. Kód lze nyní číst shora dolů, aniž byste museli skákat dopředu nebo dozadu.

Všimněte si, že podle konvence přidávám před vlastnost „out“ předponu Out . To pomáhá objasnit, že se předpokládá, že proměnnou předáme funkci neinicializovanou. Očekáváme také, že funkce inicializuje parametr. To bude jasné, když se podíváme na volací kód. Pojďme tedy nastavit volací kód.

Upravený volací kód pomocí TryGetProperty

Public Sub EditTableSubdatasheetProperty( _ Optional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="Current Dubdatasheet As String" Pro každý tdf v db.TableDefs If (tdf.Attributes And dbSystemObject) =0 Then If Len(tdf.Connect) =0 And (Ne tdf.Name Jako "~*") Potom 'Nepřipojeno nebo temp. If TryGetProperty(tdf, SubDatasheetPropertyName, prp) Then If prp.Value <> NewValue Then prp.Value =NewValue End If Else Set prp =tdf.CreateProperty(SubDatasheetPropertyName , dbtdfper EndAppendText, NewValue EndAppendText) Pokud NextEnd Sub

Kód je nyní s prvním vzorem Try o něco čitelnější. Podařilo se nám omezit zpracování prp . Všimněte si, že předáváme prp proměnné do true , prp bude inicializováno vlastností, se kterou chceme manipulovat. Jinak prp zůstane Nothing . Poté můžeme použít CreateProperty k inicializaci prp proměnná.

Také jsme obrátili negaci, aby byl kód snadněji čitelný. Ve skutečnosti jsme však nesnížili práci s NewValue parametr. Stále máme další vnořený blok pro kontrolu hodnoty. Můžeme to udělat lépe? Ano! Přidejme další funkci:

Přidání TrySetPropertyValue postup

Veřejná funkce TrySetPropertyValue( _ ByVal SourceProperty As DAO.Property, _ ByVal NewValue As Variant_) As Boolean If SourceProperty.Value =PropertyValue Then TrySetPropertyValue =True Else On Chyba Pokračování On New TryeProeee Resume On NewVoral SourceProper SourceProperty.Value =NewValue) End IfEnd Function

Protože garantujeme, že tato funkce nevyvolá chybu při změně hodnoty, nazýváme ji TrySetPropertyValue . Ještě důležitější je, že tato funkce pomáhá zapouzdřit všechny krvavé detaily kolem změny hodnoty nemovitosti. Máme způsob, jak zaručit, že hodnota je taková, jakou jsme očekávali. Podívejme se, jak se pomocí této funkce změní volací kód.

Aktualizován volací kód pomocí TryGetProperty a TrySetPropertyValue

Public Sub EditTableSubdatasheetProperty( _ Optional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="Current Dubdatasheet As String" Pro každý tdf v db.TableDefs If (tdf.Attributes And dbSystemObject) =0 Then If Len(tdf.Connect) =0 And (Ne tdf.Name Jako "~*") Potom 'Nepřipojeno nebo temp. If TryGetProperty(tdf, SubDatasheetPropertyName, prp) Then TrySetPropertyValue prp, NewValue Else Set prp =tdf.CreateProperty(SubDatasheetPropertyName , dbText, NewValue) tdf.Properties>Append Next End Ifpre End Ifpre 

Odstranili jsme celý If blok. Nyní můžeme jednoduše přečíst kód a okamžitě, že se snažíme nastavit hodnotu vlastnosti a pokud se něco pokazí, prostě pokračujeme dál. To se čte mnohem snadněji a název funkce se popisuje sám. Dobré jméno snižuje nutnost vyhledávat definici funkce, abyste pochopili, co dělá.

Vytvoření TryCreateOrSetProperty postup

Kód je čitelnější, ale stále máme to Else blok vytváření vlastnosti. Můžeme se ještě zlepšit? Ano! Zamysleme se nad tím, co zde musíme splnit. Máme nemovitost, která může a nemusí existovat. Pokud ne, chceme ji vytvořit. Ať už existoval nebo ne, potřebujeme, aby byl nastaven na určitou hodnotu. Potřebujeme tedy funkci, která buď vytvoří vlastnost, nebo aktualizuje hodnotu, pokud již existuje. Chcete-li vytvořit vlastnost, musíme zavolat CreateProperty který bohužel není ve Properties ale spíše různé DAO objekty. Proto se musíme pozdě svázat pomocí Object datový typ. Stále však můžeme poskytnout určité kontroly za běhu, abychom se vyhnuli chybám. Pojďme vytvořit TryCreateOrSetProperty funkce:

Veřejná funkce TryCreateOrSetProperty( _ ByVal SourceDaoObject As Object, _ ByVal PropertyName as String, _ ByVal PropertyType As DAO.DataTypeEnum, _ ByVal PropertyValue As Variant, _ ByRef OutProperty As_O.SourceObProperty As_O.SourceBoProperty Je DAO.TableDef, _ TypeOf SourceDaoObject je DAO.QueryDef, _ TypeOf SourceDaoObject je DAO.Field, _ TypeOf SourceDaoObject je DAO.Database If TryGetProperty(SourceDaoObject.Properties, PropertyName, OutPropertyO) Chyba Pokračovat Další Set OutProperty =SourceDaoObject.CreateProperty(PropertyName, PropertyType, PropertyValue) SourceDaoObject.Properties.Append OutProperty If Err.Number Then Set OutProperty =Nothing End If On Error GoTo 0 TryCreateOrSetProperty =(OutProperty Is Nothing) End If Case Else Err.Raise 5, , "Neplatný objekt poskytnutý parametru SourceDaoObject. Musí to být objekt DAO, který obsahuje člena CreateProperty." End SelectEnd Function

Několik poznámek:

  • Byli jsme schopni navázat na dřívější Try* funkce, kterou jsme definovali, což pomáhá omezit kódování těla funkce a umožňuje jí více se soustředit na vytváření v případě, že taková vlastnost neexistuje.
  • Toto je nutně podrobnější kvůli dalším kontrolám za běhu, ale jsme schopni to nastavit tak, aby chyby neměnily tok provádění a my stále mohli číst shora dolů bez přeskakování.
  • Namísto vyvolání MsgBox z ničeho nic používáme Err.Raise a vrátí smysluplnou chybu. Vlastní zpracování chyb je delegováno na volací kód, který se pak může rozhodnout, zda uživateli zobrazí schránku se zprávou, nebo udělá něco jiného.
  • Vzhledem k naší pečlivé manipulaci a zajištění toho, že SourceDaoObject parametr je platný, všechny možné cesty zaručují, že jakékoli problémy s vytvořením nebo nastavením hodnoty existující vlastnosti budou vyřešeny a dostaneme false výsledek. To má vliv na volací kód, jak brzy uvidíme.

Konečná verze volacího kódu

Pojďme aktualizovat volací kód, aby mohl používat novou funkci:

Public Sub EditTableSubdatasheetProperty( _ Optional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="Current Dubdatasheet As String" Pro každý tdf v db.TableDefs If (tdf.Attributes And dbSystemObject) =0 Then If Len(tdf.Connect) =0 And (Ne tdf.Name Jako "~*") Potom 'Nepřipojeno nebo temp. TryCreateOrSetProperty tdf, SubDatasheetPropertyName, dbText, NewValue End If End If NextEnd Sub

To bylo docela zlepšení v čitelnosti. V původní verzi bychom museli prozkoumat řadu If bloky a jak zpracování chyb mění tok provádění. Museli bychom zjistit, co přesně obsah dělal, abychom dospěli k závěru, že se snažíme získat vlastnost nebo ji vytvořit, pokud neexistuje, a nastavit ji na určitou hodnotu. V aktuální verzi je to vše v názvu funkce, TryCreateOrSetProperty . Nyní vidíme, co se od funkce očekává.

Závěr

Možná se divíte, „ale přidali jsme mnohem více funkcí a mnohem více řádků. Není to moc práce?" Je pravda, že v této aktuální verzi jsme definovali další 3 funkce. Každou jednotlivou funkci však můžete číst samostatně a přesto snadno pochopíte, co by měla dělat. Také jste viděli, že TryCreateOrSetProperty funkce by mohla navázat na 2 další Try* funkcí. To znamená, že máme větší flexibilitu při sestavování logiky.

Pokud tedy napíšeme jinou funkci, která dělá něco s vlastností objektů, nemusíme ji psát celou, ani nekopírujeme a nevkládáme kód z původního EditTableSubdatasheetProperty do nové funkce. Koneckonců, nová funkce může potřebovat různé varianty, a tudíž vyžadovat jinou sekvenci. Nakonec mějte na paměti, že skutečnými příjemci jsou volací kód, který musí něco udělat. Chceme udržet volací kód na poměrně vysoké úrovni, aniž bychom se museli utápět v detailech, které by mohly být špatné pro údržbu.

Můžete také vidět, že zpracování chyb je výrazně zjednodušeno, i když jsme použili On Error Resume Next . Již nemusíme hledat chybový kód, protože nás ve většině případů zajímá pouze to, zda se to povedlo nebo ne. Ještě důležitější je, že zpracování chyb nezměnilo tok provádění, kde máte nějakou logiku v těle a jinou logiku při zpracování chyb. To je situace, které se rozhodně chceme vyhnout, protože pokud dojde k chybě v obslužné rutině chyb, může být chování překvapivé. Nejlepší je zabránit tomu, aby to byla možnost.

Vše je o abstrakci

Ale nejdůležitější skóre, které zde vyhrajeme, je úroveň abstrakce, které nyní můžeme dosáhnout. Původní verze EditTableSubdatasheetProperty obsahoval mnoho nízkoúrovňových podrobností o objektu DAO ve skutečnosti není o hlavním cíli funkce. Vzpomeňte si na dny, kdy jste viděli proceduru dlouhou stovky řádků s hluboce vnořenými smyčkami nebo podmínkami. Chtěli byste to odladit? já ne.

Takže když vidím proceduru, první věc, kterou opravdu chci udělat, je vytrhnout části do jejich vlastní funkce, abych mohl zvýšit úroveň abstrakce pro tuto proceduru. Tím, že se přinutíme posouvat úroveň abstrakce, se také můžeme vyhnout velkým třídám chyb, jejichž příčinou je, že jedna změna v části megaprocedury má nezamýšlené důsledky pro ostatní části procedur. Když voláme funkce a předáváme parametry, snižujeme také možnost nežádoucích vedlejších účinků zasahujících do naší logiky.

Proto miluji vzor „Try*“. Doufám, že to bude užitečné i pro vaše projekty.


  1. Identifikátor automatického přidělování Rails, který již existuje

  2. Výzvy škálování databáze Moodle PostgreSQL

  3. limit poddotazu mySQL

  4. php nahrávání souboru, jak omezit typ nahrávání souboru