5

In a program divided up into many functions by which it is intended that they execute themselves one after another, when (if ever) is it preferred to:

A) Execute the functions one after another in main()?

or

B) Execute one function in main(), and have that function daisy chain to the rest of the functions that need to be executed?


To illustrate this

Program FlowChart:

Start -> Make foo -> Do bar? -> True -> Say Goodbye -> End

A:

void makeFoo() {
    [...]
    return;
}
bool doBar() {
    bool bar;
    [...]
    return bar;
}
void sayGoodbye() {
    [...]
    return;
}
int main {
    makeFoo();
    if (doBar()) {
        sayGoodbye();
    }
    return 0;
}

B: (Daisy Chain)

void makeFoo() {
    [...]
    doBar();
    return;
}
void doBar() {
    bool bar;
    [...]
    if (bar) {
        sayGoodbye();
    }
    return;
}
void sayGoodbye() {
    [...]
    return;
}
int main {
    makeFoo();
    return 0;
}
  • 2
    Your second version only makes sense if sayGoodbye is intended only for doBar. Your first version works whether or not you actually want to say goodbye in doBar. – Robert Harvey Feb 6 '15 at 18:23
  • 1
    related (possibly a duplicate0: Class Design — Multiple Calls from One Method or One Call from Multiple Methods? – gnat Feb 6 '15 at 18:25
  • @RobertHarvey That is a very astute observation. So I guess the principle would then be, if a function is only meant to have a single purpose/usecase, it should be encapsulated within that usecase and not visible on main()? What do you think? – Akiva Feb 6 '15 at 18:31
  • 1
    Related reading: temporal coupling. – user22815 Feb 7 '15 at 2:32
13

Daisy chain when it is clear to the next developer why you did it that way.

"Code is read more often than it is written." -Guido von Rossum, PEP 008

There is no one answer to this, but I can give you a good rule of thumb: If the name of the function implies that its job is to do one function and call another, then it is safe to chain them.

There are times where two functionalities do really need to chain. Consider the classic example of error handling

result = doSomething();
if (result == badValue)
    haltAndCatchFire();

If this pattern shows up all the time, the next developer is going to get sore reading it over and over. Worse, they may get lazy or forget, causing unexpected results. However, haltAndCatchFire on its own is a pretty useful function to have, so we don't want to bury that functionality into doSomething, just to never see it again. As a solution:

doSomethingOrHaltAndCatchFire();

Its long, but now it is very clear that you intend to daisy chain. A developer who does not want to follow you into doSomething can generally assume that you'll call haltAndCatchFire if anything goes wrong, so you have successfully transmitted information to the next developer. They can keep reading main() without having to dodge and weave through multiple functions.

Your naming choice is up to you, as long as it is clear. As an example, on one suite of software I developed, there was a pattern:

int getValue();
int tryGetValue();

It was understood (social contract) that getValue() should call exception handling functions if something goes wrong. TryGetValue should just return (often returning a dummy value that let us know something went wrong). For other situations, where the social contract was not so clear, we had functions like getRegressionResultsForData(), which did some functions to get the data, then called getRegressionResults() on it.

In summary, you should never chain in a situation where a future developer could be mislead by skimming over a function (instead of diving into all sub-functions to look for chains).

7

Your methods should do one and one only thing.

For example, is saying good bye a part of doBar?

  • If yes, your second approach should be used.

  • If saying good bye should be done after doBar but is not part of doBar, then your first approach is better.

Practical example. A part of an application should read and process data. This is done through five methods:

  1. ConnectToDatabase()

    Opens the connection, making it possible to query the database.

  2. GetData()

    Retrieves valid, sanitized data from the database.

  3. LoadData()

    Loads the data from the database into memory for further processing.

  4. ValidateData()

    Makes sure the data is valid: if it's not, throws an exception, since this situation is truly exceptional.

  5. SanitizeData()

    Transforms the data in order for it to be processed. Although unsanitized data is still valid, we need to sanitize it to match specific constraints of our processing.

  6. ProcessData()

    Processes the data by making some dark magic stuff with it.

  7. Output()

    Shows the result of the processed data on the screen. The result contains two charts.

  8. GenerateCharts()

    Generates the final charts to be shown to the user.

  9. DisplayGlobalChart()

    Displays the first chart on the screen.

  10. DisplayPerMonthChart()

    Displays the second chart on the screen.

It looks like ConnectToDatabase(), LoadData() and SanitizeData() are all part of GetData(): when you call GetData(), you don't care if it's loaded from the database of from a flat file, how is it actually loaded or does it require to be sanitized. You just need the data set ready for your work.

It also seems that charts generation and two methods which display the chart can easily go into Output(): again, you don't care how the information should be displayed when you call Output(); you just know that information is not there yet, and now, it should be on the screen.

So far, the calls are:

GetData()
{
    ConnectToDatabase();
    LoadData();
    SanitizeData();
}

Output()
{
    GenerateCharts();
    DisplayGlobalChart();
    DisplayPerMonthChart();
}

Main()
{
    data = GetData();
    result = ProcessData(data);
    Output(result);
}

Remains ValidateData(). This one is tricky. It may be called by SanitizeData(): it makes sense, since before sanitizing a piece of information, one should probably validate it first. On the other hand, it might be that those processes are different enough and one can be called independently from other. In this case, ValidateData() would be rather in GetData(), just before SanitizeData().

7

Not daisy-chaining is almost always preferred. Look at your example, and imagine a future maintainer reading this code to fix a bug, without the benefit of the flow chart.

It's tough to tell from dummy example function names, but in a real program, he can read the main function to reconstruct the overall flow and tell where to look next by the names of the functions. In the daisy-chained version, he would have to look into every single function, which might span several files. Likewise, in the daisy-chained version, changing one step might cause a ripple effect through the rest of the code.

However, cramming all the steps into the main function isn't necessarily the answer either. You want to create functions where everything you read is on the same level of abstraction. Perhaps makeFoo has several substeps. It's okay to leave those there and just return the sub-result back to main.

  • If I am able to provide a flow chart of which references and corresponds to the program (I actually do have one for this project I'm working on), would daisy chaining be acceptable? – Akiva Feb 6 '15 at 21:29
  • 1
    @Akiva You can rely on the flowchart if it is easily accessible and you are very sure that everyone (even new to the team) will always remember where it exists and how to find it. The "remembering" part is hard to guarantee, so usually you're better off making the code itself easy to understand without a flowchart. – David K Feb 6 '15 at 21:54
  • 1
    @Akiva, the biggest problem with such documentation is it has a strong tendency not to stay updated. The second biggest problem is now to make a change you always have to look at two things: the document and the code, instead of just the code. – Karl Bielefeldt Feb 6 '15 at 22:13
  • Thanks for the advice! I have noticed it does take a lot of effort to keep the flowchart updated, but on the other hand; it works as a useful checklist to make sure I get everything done. – Akiva Feb 6 '15 at 22:17
2

absolutely never.

Let's assume you have two functions A() and B(). If A() must call B() or it will not work, then sure A() should call B(). If not then A() shouldn't at all care or know about B()'s existence.

Otherwise you are doing two things:

  • You are unnecessarily [coupling][1] the two functions. what if in future you'll want to alter their order, or call one somewhere else?
  • You are obfuscating what is happening in your program. if you have a flow that says: *do A then Do B, if B success then do C, then somewhere in your program there should be code to describe this process i.e :

    A();
    result = B();
    if (result) then
        C();
    

See also: Coupling (computer programming) on Wikipedia

1

It depends. Usually you shouldn't daisy-chain functions, unless the daisy-chain is a implementation detail, not part of the main process at all. That's why we have main functions after all. If someone else (including you in 6 months) has to read that code, it's much easier to find a function that describes the algorithm of a program, isolated into functions that implement the steps of that algorithm. Especially in scripts, you want to show the procedure as clearly as you can, as to write self-documenting code.

I usually start by writing a central function with a lot of invalid placeholder statements, like this:

def foo_the_bar(xkcd):
    unfooed_bars = discover_bars(xkcd)
    foo = load_foo_from_orbital_cannon()
    return map(foo, unfooed_bars)

Then go into implementing those functions, until there's a complete program. You get to isolate logic, and make functions agnostic of what they will be used for.

To get what a program made this way does, you barely have to dive into the inner functions (as long as they are documented and named properly).

Daisy chaining produces some of these bad effects:

  • functions that do their thing, then adapt their result to the next daisy link
  • functions that expect a specific order of things to have happened to the data
  • human stack overflows

The last point is key: Humans have a context stack, and in some senses it's much more limited than a computer's, because computers don't need to make sense of the whole combination of procedures, they only care about the current state and their instructions.

Every time a human reads your daisy code, he has to add a new context manager to the brain stack. This operation takes a while, and then when you need to refer to the upper contexts, you have to pop all the way to the correct context, to then return to the place you were.

In short: If you're going to daisy chain, you might scrap using functions at all, it might even be more readable.

protected by gnat Dec 31 '16 at 21:55

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.