Modular Programming

"Modular Programming" is the act of designing and writing programs as interactions among functions that each perform a single well-defined function, and which have minimal side-effect interaction between them. Put differently, the content of each function is cohesive, and there is low coupling between functions. Language support can be provided by a ModuleSystem.

"Modular Programming" discourages the use of control variables and flags in parameters; their presence tends to indicate that the caller needs to know too much about how the function is implemented. Like, having a "Customer I/O" function that takes an enumeration to (#1) Read a customer record, (#2) Insert a new record, (#3) Update an existing record, or (#4) check to see if this customer already exists on disk -- would be bad. However, even good modular designs make some use of flags, for "end of file" indication, for example.

"Modular Programming" tends to encourage splitting of functionality into two types: "Manager" functions control program flow and primarily contain calls to "Worker" functions that handle low-level details, like moving data between structures.

ObjectOriented programs are usually modular. ModularProgramming does not have to be ObjectOriented. The difference is that "merely" Modular code does not need to be associated with data (at least the way ObjectOriented code is structurally associated with data.)

Modular Programming is heavily procedural: The focus is entirely on writing code (functions). Data is passive. Any code may access the contents of any data structure passed to it. (There is no concept of encapsulation.)

This is somewhat misleading. See DatabaseNotMoreGlobalThanClasses and GateKeeper.

To be reasonably cohesive, modular functions should not be excessively large; functions with over a page of code will be frowned upon. It's generally assumed that the code within each modular function will conform to the rules of StructuredProgramming. -- JeffGrigg


I do not agree that there is no concept of encapsulation in modular programming. The module or unit is where a lot of encapsulation occurs. For a real world example of modular programming, download Blackbox Oberon Component Pascal. For my thoughts on Modular Programming you can see my web page article about why it offers swapping benefits and debugging benefits for a real world project in use. http://z505.com/powtils/mop.shtml

Modular programming is just a concept. You can apply modular programming practice in any language, especially ones that are procedural in nature, which have separate compilation... and preferably units or modules (a.k.a modula, componentpascal, ruby, modern pascal, C (to an extent the C files can be modules) ).

I think a lot of the OOP hype exists because MOP (modular oriented programming) was never defined and marketed. In a lot of the software that I develop for the real world, I use modular programming practices. Sure I sometimes use objects and classes, but they are not gospel or bible.

For another good article on modular programming, see this PDF file: http://www.pas.rochester.edu/~skulski/Presentations/BB_Class.pdf


Modular Programming is not OO...

However, modular programming can be improved by doing it with OO concepts in mind. My C code tends to look like this these days

  /* customer.h */
  typedef struct customer {
     int id;
     char *name;
     int n_orders;
     order_t *orders;
  } customer_t;

int customer_new (customer_t **); int customer_read (id_t const *, customer_t **); int customer_write (customer_t const *); int customer_delete (customer_t *); int customer_num_orders (customer_t *, int *);
Set/gets and accessors are not necessarily an improvement, as claimed above. They are considered code-bloat, anti-YagNi, and a violation of OnceAndOnlyOnce because they are repeating DatabaseVerbs in interfaces for each entity instead of InterfaceFactoring to a single spot. You see a pattern like this for each entity where X is the entity:

  int X_new (X_t **);
  int X_read (X_t const *, X_t **);
  int X_write (X_t const *);
  int X_delete (X_t *);
  int X_num_orders (X_t *, int *);
(Not that I'd ever write an order-processing system in C anymore! This is for performance-critical and low-level stuff only.)

The underscores are being used to emulate a dumbfounded namespace or module, since C does not have a proper module system. In a modular language one could abandon the customer_underscore_hideous_C_crap and put it in a module, or use overloading so that customer is a parameter in to the function, and other functions can accept other parameters (structs) such as notcustomer. See also IncludeFileParametricPolymorphism..

I find myself often refactoring source files into smaller files as I listen to the program, and this feels rather like refactoring classes in a higher-level language.

Doing this in C becomes a bit labour-intensive when there's a lot of inheritance, but plenty of problems can be solved reasonably well without polymorphism.

-- MartinPool

Ideally some kind of meta-programming should be used such that you don't have to keep repeating that. Or, use a database or DataDictionary. I am not sure what OO is fixing in your scenario. One could do the same thing with procedural map arrays also (which C does not natively support), I would note. Further, if the schema is already in the database, then echoing it in code is perhaps a violation of OnceAndOnlyOnce anyhow. -- top

And repeating the customer_prefix with an underscore, in addition to sending customer in as a parameter, also violates OnceAndOnlyOnce and can be solved by using modules which the braindead CeeLanguage doesn't support... and if using several of the same algorithms over and over again for different types then one can make use of a ParametricPolymorphism pattern (without needing OOP as commonly misunderstood).

   foo(customer,  'bar'); 
Not this kludgy C stuff:
  customer_foo(customer, 'bar');
The only problem that I see with the idea that the schema can be just in the database is that eventually the programming language is going to have to access the database columns and have some knowledge about the invidual column names... for example thishash["customerid"]. How did we know for sure that the "customerid" exists in the database in any programming language, without having some knowledge about the database schema? One would have to query the database schema to verify that customerid first exists and loop through it, violating OnceAndOnlyOnce... unless it was built into the language that you had a DatabaseType that you could declare and use natively.

If thishash["customerid"] is ever assigned, it could first check the database schema to verify the "customerid" column really exists - but... again this is crude and you are still violating OnceAndOnlyOnce since you are declaring "customerid" in the program and in the DB.

I was thinking about automatically mapping an array or dictionary or hash to the DB and realized that eventually you have to have some knowledge about the columns to know what to put in the hash as the dict items or array indexes... and eventually you have to violate OnceAndOnlyOnce (unless some special DB is built into your language natively, which would be nice but impractical in the case of SQL products that we can actually use today).

So I'd say that trying as hard as we can to not violate OnceAndOnlyOnce is a good practice... reducing duplication as much as we can... but I think that as with other Patterns, it is never a perfect discipline. You could loop through the schema columns and map them at run time... but again you'd have to eventually query the columns and duplicate the schema at some point: assigning or accessing thishash['thecolumnname'] is duplicating the schema. If that column name ever changes then you have to change things twice - in the schema and in the code. At some point, you must have knowledge about "thecolumnname"... at design time? I think so... so the only idealistic way to solve the problem would be to embed the database into the program itself as a native type...?

I'm not sure that just referencing something is a violation of OnceAndOnlyOnce. That's almost like saying that a "print(...)" statement is a violation because it repeats a reference to printing over and over. If 80 nodes all reference node X, they will repeat some kind of identifier to X somewhere somehow. The only way I see out of that is some kind of context/locational association, which has other drawbacks.

I meant that

  print_print(print, 'the text'); 
would be a waste, compared to
  print('the text');
For example if you had a namespace or module of "print" you would declare it as "using print" or "uses print" instead of rehacking it into underscore and parameter nonsense like:
  print_print('the text'); 
Sometimes this is hard to avoid if you reference a namespace like:
  printunit.out()
But if we can avoid it like
  using printunit;  // or uses printunit;
  out()
I think it makes code clearer and simpler.

But often short verbs tend to risk or cause naming conflicts. But repeating the name-space over and over can be cluttery:

  printunit::out(foo);

Ideally, one could alias name-spaces:

  use printunit;
  ...
  p = printunit;  // name-space alias
  p::out(foo);

One may argue that OOP reduces the need for such. This is true to some extent, but it does not necessarily eliminate it because name-space management is useful even at the class level. OOP can also create resource overhead because the interpreter/compiler has to allocate an object "p" for a mere function/method call.

Plus, it does not give you the option to call "out()" without an object reference (barring some odd language shortcut tricks). You always have to use an object reference (dot notation). Name-space references ("foo::x") are optional in most languages. Thus, it's a wash in my opinion (WaterbedTheory in action).

--top

ComponentPascal has unit aliases and they are extremely useful. QompLanguage will have them too. The problem with OOP as a namespace is that you have to create the instance and I know an instance is not needed all the time (just like humans do not have new baby instances (childs and sons) each time we need to get something done). Especially in languages like Delphi, using a class as a fake namespace dangerous and introduces more bugs into the program due to dangling objects that have no garbage collection.. and it bloats up the code with more and more Free/Create code. NeedlessRepetition of Free and Create in the code. OOP does not allow one to stick just regular old commands into the namespace... the whole lifecycle of the class must be designed, created, freed.. which is overly complex for lots of solutions that just need a namespace (or unit alias). Laughingly explained in AntiCreation. Namespaces can have classes inside them, too. --MopMind?

Proposal for FreePascal/DelphiLanguage:

  uses
    u := someunit;
  begin
    u.callfunction();
  end;
Proposal for QompLanguage
  use
    u = someunit;
  b
    u.callfunction;
  e;


StructuredProgramming -> ModularProgramming -> ObjectOrientedProgramming

That's one viewpoint.

So ObjectOrientedProgramming, in this context, is a form of FirstClassModules (or at least can be utilized as such).


See also: ProceduralMethodologies, WhatIsModularity


CategoryCodingIssues, CategoryInfoPackaging


EditText of this page (last edited September 29, 2014) or FindPage with title or text search