0

Is there a name related to the pattern shown in the code that follows? Or is there writing/thinking on the [design of] code shown that suggests it is an anti-pattern? In general, I'm curious if this is somehow a well known design strategy for non object oriented applications (and, more importantly, why or why not).

I suppose I'll use Python but this is just an application design question.

main.py

from some_other_file import func_get_data_from_an_api, func_calculate_something, #...
from ...

data1 = None
data2 = None
data3 = None
data4 = None
data5 = None
# ...

data1, data2 = func_get_data_from_an_api()
data3 = func_calculate_something(data1)
func_write_to_database(data3)
data4 = func_calculate_again(data3)
data5 = func_d(data2, data4)
func_write_to_database(data5)
# ...

Then, every other file would just have the implementations of the functions from main.py. So here is the general case for one such implementation:

def general_case_func(param1_from_main, param2_from_main):
  # Under this "pattern", a function in this application can never
  # call another function from this application (but functions in this application
  # can call things like functions from 3rd party software, functions from the 
  # library of the language being used, functions from external systems and so on).
  # Note: This ☝️ is a central requirement for the "pattern" I'm asking about.
  m = 3
  if param1_from_main < 1500:
    m = param1_from_main % param2_from_main
  return max(m, 5)

Never mind the logic in this function (there isn't any intentional meaning in the calculation). The point is that the rules are that functions can only take inputs passed from main and do some combination of the following:

  1. Calculate on those inputs
  2. Communicate with services/libraries that came from outside of the application
  3. Return a value or values to main

So if the application happened to have 500 functions (each with about 4 or so lines of code in them, perhaps importantly for the sake of "clean" functions that only do one thing), then there would be exactly 500 function calls in main.py.

Unless of course considering such things is an essential part of the answer, never mind loops and if statements in main.py. By that I mean, never mind if main.py has if statements that possibly cause some of the functions to not run during some executions, or whether some of the functions in main.py are in a loop.

3
  • 1
    None of this makes sense to me, I do not see how this relates to reducing dependencies and decoupling. Sep 30, 2021 at 21:32
  • There are zero dependencies on internal functions. Is that the norm? It isn’t in my experience… if it’s also not the norm in your experience, then isn’t this an example of reduced dependencies or reduced coupling (relative to a typical application we’d see in the wild)? Are other things unclear? I apologize for not being more clear.
    – okcapp
    Sep 30, 2021 at 21:38
  • @MartinMaat Just realized that I added a blurb in the original question said that I wasn't looking for "pattern lingo", even as I forgot that further down it asked if there was a named pattern for what I was showing. That wasn't intentional. Also, I realized the decoupling as it were was beyond the scope of this. I intended to ask about design. Hopefully it is okay now. Happy to adjust it further if I could get some feedback on what extra detail is needed. But imo it seems to have a consistent message now that it's a design question.
    – tarstevs
    Oct 19, 2021 at 17:36

1 Answer 1

3

There is no industry-recognized software design pattern for this. More generally what you describe is functional decomposition, which is just fancy-talk for breaking a large problem into smaller pieces.

Each piece, or function, is responsible for one thing. These functions are written such that they have few outside dependencies, which promotes code reuse. They are easier to combine in different ways to achieve different use cases.

Having main call the other functions is typical for procedural programming, especially for smaller programs. Again, there is no officially recognized pattern for this, but main is where the program starts, so it must handle all use cases. If main doesn't delegate logic for each use case to other functions, then main must coordinate all use cases. This translates into a bunch of function calls, loops and decision structures where only certain branches of the code get executed depending on the use case.

Since main is where it all begins, it is common to see it import a large number of disparate modules. While there is no software design pattern for this, we do have a name for it: the composition root.

This composition root is responsible for configuring and coordinating use cases, but delegating specifics to other, more specialized functions.

Functional decomposition and the composition root pair well together. A consequence of decomposing functions is that something must combine these little bits into a cohesive program. The composition root is that thing that combines and configures dependencies to provide the cohesion necessary to execute a use case.

5
  • Thanks Greg! I'm prepared to accept this answer but I am wondering about one thing. I wanted to probe why this design is sensible (you definitely answered that in a clear and appreciated way)... but the other key intention was to probe why something else in particular would be more effective in certain context(s), or, if you prefer, what the shortcomings of this pattern are. You mentioned main calling [all] other functions [in procedural programming] is typical for smaller programs: Would it be possible to add a blurb on why/how another design is perhaps more effective for larger applications?
    – tarstevs
    Sep 30, 2021 at 22:58
  • @tarstevs: really it all comes down to functional decomposition. At some point main gets too busy and complicated, so you create other modules to specialize in routing and dispatch to execute use cases. Sep 30, 2021 at 23:25
  • Yup, that makes sense. I accepted the answer, I think that covers everything I was asking about. I agree that the calls from main of the "small application" example [doing the "one thing" of "driving the application"] can become a confusing long list eventually (i.e. 1,000 such calls in a larger application cheapens the idea of reducing complexity by only doing one thing at a given place in the application... hence the routing and dispatching you mentioned so all the calls don't have to be in one long list). Thanks for the nice thoughts!
    – tarstevs
    Oct 1, 2021 at 0:13
  • @tarstevs To put it another way, you can have functions declared & implemented in main that only call 3 or 4 of the internal functions, and then have main call them in turn. These mid level functions still "do one thing", they are orchestrating the interplay of the internal functions they call, that's their single responsibility - and in doing so they implement a higher-level concept. This makes main shorter, and if your decomposition is sensible and if you gave these functions good names, it also makes main more readable. 1/2 Oct 2, 2021 at 21:47
  • As your application grows in complexity, you can place these mid-level functions in a separate file. And you can repeat this process, adding extra levels. This works well if you have a good understanding of the problem domain, but a potential problem with both cases (main + internal & main + mid-level + internal) is that, when some of the requirements tend to change (or are reinterpreted after a while, or new, somewhat unexpected requirements come in), as is often the case with business applications, you discover all kinds of non-obvious dependencies (manifesting as cascading changes). 2/2 Oct 2, 2021 at 21:47

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.