0

For code maintainability I understand that it is generally better to have code structured like this:

void abc() {
    a();
    b();
    c();
    d();
}

Instead of like:

void abcd() {
    a();
    bcd();
}
void bcd() {
    b();
    cd();
}
void cd() {
    c();
    d();
}

As it adheres to the single responsibility principle and generally makes things more readable. However when I am trying to using asynchronous functions which call some handler when it completes (more specifically netcode with boost::asio) so I am forced to have it look closer to the latter example.

void abcd() {
    a();
    // async_read(file, handler)
    async_read("foo.txt", bcd);
}
void bcd() {
    b();
    async_write("bar.txt", cd);
}
void cd() {
    c();
    async_write("baz.txt", d);
}

Basically is there a better way to format async code to have it follow a pattern closer to the first example?

3
  • can you do inline/lambda functions in c++?
    – Ewan
    Aug 8 '20 at 7:34
  • 2
    You basically have a scenario where you want a(), b(), c() and d() to be called one after another - this has a name, it is called synchronous processing. Most simple solution to this: use don't use asynchronous calls (if you want the whole sequence to be run asynchronously from the caller, leave your initial function abcd() as it is and run it in a separate thread).
    – Doc Brown
    Aug 8 '20 at 7:39
  • 3
    You can't really tell by looking at abstract structure (like the examples you've posted) if the code violates SRP or not. SRP doesn't mean that the code should be flat. The fact that a function calls other functions that have "subresponsibilities", like bcd() doesn't imply that bcd() or its callers have multiple responsibilities. Think of it in "levels" - bcd() simply orchestrates the lower-level functions it calls; on bcd()'s own level, SRP is about whether or not those called functions are closely related and if they are all affected together by change requests. Aug 8 '20 at 11:01
2

You want to apply the clean code principle, that a function should do one thing only and do it well. In both snippets, a(), b(), c() and d() comply with these principles.

Clean code also has a single level of abstraction principle that tells that the code of a function work at the same level of abstraction. So the first snippet is ok only if a(), b(), c() and d() are all at the same level of abstraction. Depending on their levels of abstraction, the second snippet could be a valid or even better alternative.

But these principles are all about readability and maintainability and they should not be used dogmatically:

  • If abcd(), bcd(), and cd() are used for asynchronous "scheduling", then they could be considered to do one thing: they split the sequence of tasks according to asynchronous requirements.
  • If you don't like it, because it distracts from the real structure, you could envisage a lambda as read or write handler (see this SO question about requirements). But it will not necessarily improve the readability and maintainability, especially with nested lambdas, even if it has the advantage to regroup in one place the gluing.
  • If you do not have any statement following your async_read() or async_write(), this code is equivalent to waiting the read/write to be finished before to continue. You may then as well use asio's read() and write(), with the benefit of increased readability, unless you have other asynchronous or threading constraints.
  • If you have a lot of function decompositions like that, you could consider to implement a pipeline object, that processes a sequence of "commands". You may then have a helper class that is used as read/write handler and that keeps track the pipeline object and its current step (i.e.somewhat like a pipeline iterator), to launch in due time the asynchronous operations continuing at the following steps. It's a first idea and would need to be designed more in detail. However, this would be a complete restructuring of your code and might not necessary make it easier to understand.

So keep it as simple as possible but not more ;-)

Terminological remark: the Single Responsibility Principle (SRP) is about classes, and more precisely about reasons to change (as explained by its inventor). For the functions, it's do-one-thing-and-do-it-well. We intuitively feel that there is something in common between the two concept but it's nevertheless different ideas and it might confuse to use the same term (unfortunately wikipedia promotes this confusion).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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