|laziness, impatience, and hubris|
This is a really difficult question to answer well, but I'll leap in anyway.
The best way to learn your own guidelines is to read other people's code. You will never stumble onto a number of useful rules of thumb staring at your own code. Look at how other people do things, and copy them.
Some rules of thumb I have come up with. (Later: the more I reread this, the more I can see all sorts of exceptions and qualifiers). Rules are meant to be broken, but of course you have to know the rules to be aware that you're breaking them.
The division between parent code and child subroutine should be natural. It should seem... elegant. If it feels klugey... it probably is! Again, read other people's code. Doesn't have to be good. Read some bad code, and criticise it. Think how you would do it differently.
Anyway, here are some things to think about:
are a bad idea. If you're passing a mode parameter which is immediately inspected by the subroutine in order for it to decide how to proceed then you're probably travelling down the wrong path.
A routine should do one thing, and one thing only. Once it has been read, a person should not have to go back to it time and again when reading other parts of the code.
If you can't escape modality, include some good debug harnessing, so that the program flow can be traced by setting various levels of debug constants.
If you are performing functionality in two different parts of a script, then factor it into a subroutine. This often crops up when you are grovelling through data. Any time you are reading line by line (or paragraph, XML element or whatever) and remembering something about the previous line to see whether the current line indicates a change, you will have a block of code inside an if statement that performs the summary about the previous batch of lines. When you finally exit out of the read loop, you shall probably need to call that code block one again outside the loop to deal with the last batch of lines.
Locality of reference
A subroutine should have its entire context defined by the parameters passed to it. It should not rely on global variables. I break this rule all the time, although with the following context: it's okay for a subroutine to read global variables, but it should never modify them. If you're passing more than five parameters to a subroutine there's probably something bad happening to your code. Maybe some of that context would be better off wrapped up in a little object capable of maintaining its own sense of integrity.
Input and Output
Should be managed at the highest level of the code. Eschew little subroutines that call print themselves. Retain internal representations as long as possible, by passing back scalars, hashes or whatever, and have the parent perform the output instead.
g r i n d e r