Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

by Unlicensed user

As Clarion developers we (well, most of us) live and die by embed code. Embeds are, after all, at the heart of Clarion's effectiveness. They're the primary way we add custom code to applications that are otherwise template-generated.

...

But as useful as the Clarion architecture is, it has a problem. And that problem is going to grow in size as your applications grow in size and complexity.

The problem is embeds.

Not that there are embeds. We'd be lost without them. Embed points make it possible to plug our custom code into the almost any location within the standard Clarion architecture.

The problem is that embeds are almost always misused.!

Example #1: The Invoice app

...

Extracting business logic

My task , in this series of articles , is to explore the ways that business logic can be teased out of the embed point and put into some code that's easy to understand, easy to maintain and modify, and easy to test.

Of course , I'll still need those embeds so I can call that business logic, wherever it ends up. But the point of this exercise is to figure out how business logic can be coded for maximum benefit. Stuffing it all in embed points just has too many drawbacks.

...

There are just five embeds in the parser procedure. First, there's some global data:

Code Block
! Queue to hold TXA list
txaq            QUEUE(File:queue),pre()  
                END
state           LONG(0)
LineNo          long

Then there's a line of code in the Init method to load up the queue of TXAs to import:

Code Block
    !Get all files and directories
    DIRECTORY(txaq,'*.TXA',ff_:NORMAL)

There's one small routine that gets called regularly by the main import code, and which is really just a bit of debugging code:

Code Block
CheckForMissedEmbed routine
    if sub(txt:rec,1,8) = '[SOURCE]'
        db.out('*** MISSED EMBED at line ' & lineno |
            & ', state ' &  state & ' ***')
    end

There's an embed for variables used in the parsing code:

Code Block
x                           long
vars                        group,pre()
procname                        string(200)
procFromABC                     string(60)
procCategory                    string(60)
embedname                       string(60)
embedPriority                   long
embedParam1                     string(200)
embedParam2                     string(200)
embedParam3                     string(200)
whenLevel                       byte
                            end
LastProcName                like(procname)
lastEmbedName               string(500)
currembedname               string(500)

And finally there's one very large block of code to import the TXAs, which I won't reproduce in its entirety. It's basically a state engine that process the TXA in a loop and figures out where the embeds are and what they contain:

Code Block
    access:TextFile.Open()
    Access:TextFile.UseFile()
    set(TextFile)
    state = 0
    lineNo = 0
    clear(procname)
    clear(lastprocname)
    clear(lastembedname)
    clear(currembedname)
    LOOP
        next(TextFile)
        if errorcode() then break.
        lineNo += 1
        CASE state
        OF 0 ! search for the start of a module or procedure, or an embed
            if sub(txt:rec,1,11) = '[PROCEDURE]'
                clear(vars)
                state = 10
            elsif sub(txt:rec,1,8) = '[MODULE]'
                clear(vars)
                procName = '[MODULE]'
            elsif sub(txt:rec,1,7) = 'EMBED %'
                embedName = sub(txt:rec,7,len(txt:rec))
                state = 30
            elsif sub(txt:rec,1,8) = '[SOURCE]'
                state = 50

            end
        OF 10 ! get procedure name details
            
        ! OF cases for a bunch of other states


        of 50  ! Add the procedure and embed records
            if sub(txt:rec,1,8) = 'PRIORITY'
                embedPriority = sub(txt:rec,10,len(txt:rec))
                if lastprocname <> procname
                    clear(EMP:record)
                    EMP:Proc = procname
                    EMP:ProcFromABC = ProcFromABC
                    EMP:ProcCategory = ProcCategory
                    EMP:EmbedAppID = EMA:EmbedAppID
                    Access:EmbedProc.Insert()
                    lastprocname = procname
                end
                ! Add the embed record
                currEmbedName = clip(embedName) & clip(embedparam1) & |
                    clip(embedparam2) & clip(embedparam3) & embedpriority
                if currEmbedName <> lastEmbedName
                    ! Add the embed record yada yada
                end
                state = 51
            end

        ! OF cases for a bunch of other states

        END
    end
    ?progressVar{prop:progress} = records(txaq)
    setcursor()
    Access:TextFile.Close()

There's a ton more code, but you get the idea. This code is completely dependent on a specific database structure, and it references a control on the current window. It's sprinkled throughout a bunch of generated code. Those are all problems, to some degree. But mostly it's just hard to reuse this code somewhere else.

...

Which approach did I choose? Tune in next time to find outRead on...