Cleaning up legacy mess by functional composition

In my experience Vb6 is used mostly as a procedural language, with objects used without the author knowing it's actual objects (like the auto instantiating of forms.) In a lot of cases you'll inherit (no sane person would start a new greenfield Vb6 project, right?) a collection of subs and functions that are mostly global.
You'll find that after a while common functionality got called from several different places, and the original author found that something needed to be done slightly different for the latest use case.

Enter the flag parameter

sub Commonstuff(param1, param2, flagparam as boolean)
On Error goto errorhandler
        if flagparam then
                call dosomethingslightlydifferent()
                call dowhatwasdoneoriginally()
        end if

        call doTheCommonStuff()
        call doMoreCommonStuff()
exit sub
        call ImAJokeInVb6()
end sub

More slightly different use cases was discovered, and the number of flag parameters increased.
Updating all the procedure callers became a chore, and the flag parameters started to become optional.

sub Commonstuff(param1, param2, flagparam, anotherFlagparam, aThirdFlagParamThatIsAMagicNumber, optional aRarelyUsedflagParam)
        'loads of ifs and elses
        call doThecommonstuff()
        call doMoreCommonstuff()
end sub

The procedure as you find it is very difficult to reason about. When the procedure is called it's impossible to deduce how it's going to behave without first checking all the flag paramteres against the parameter list, and then read the procedure itself to determine the path it would take runtime given those flags.

Composing the way out

Functional and procedural programming share a common heritage: structured programming, and its entirely possible to use some of the same techniques in a procedural language as you would in a functional language.
One of the strenghts in functional languages is that the languages pushes the programmer to create small, single purpose functions, that are then composed into other functions for specialized use.
Compared with the common huge-functions-with-loads-of-conditionals pattern often found in procedural Vb6 code, the functional way is easier to reason about, and easier to extend.

Can this shiny functional thing really be done in Vb6

Yes it can (but it won't be shiny).

Vb6 lacks the language constructs needed to make the code terse and neat. It's Vb6, and it will be verbose!

The solution is simply to make small single purpose subs and functions, and compose them into other named subs and functions for specialized use (sounds familiar)?

You won't be able to make a generic wrapper function that takes a function as an argument. But you can make a specialized sub or function with a descriptive name that are simpler to reason about than checking if the third optional flag parameter is set.

'The first example in a functional composed way

sub Commonstuff(param1, param2)
end sub

sub CommonstuffWithOriginalExecution(param1, param2)
        call dowhatwasdoneoriginally()
        call Commonstuff(param1, param2)
end sub

sub CommonStuffWithSligthlyDifferentExecution(param1, param2)
        call dosomethingslightlydifferent()
        call Commonstudd(param1, param2)
end sub

When the sub is called now, it's easy to see which path will be taken, as the different executions are named.

Why bother

If you maintain legacy Vb6 code, you probably spend a lot of time sighing at how difficult it is to follow, and a good number of other irritations. But do you adress them? (The ones you can adress at least). Robert "Uncle Bob" Martin teaches that you should always leave your code a little cleaner than you found it and this applies to legacy Vb6 code just as much (maybe even more) as it applies to new shiny projects.
If you are maintaining a vb6 project that still lives, chances are you'll be maintaining for a forseeable future. And as a bonus: If you've cleaned the old Vb6 code, it will be a lot easier to port to a newer platform.