3

If you're a very old programmer like me you may have written stuff like this early on:

DIM A, B, C
LET A = 2
LET B = 2
GOSUB ADD
PRINT C
END
ADD:
LET C = A + B
RETURN

(Actually, if you're an assembly programmer, you may be stuck writing stuff like this anyway, but let's not digress.)

Of course the modern approach would be more like this:

var c = Add(2,2);
Print(c);

function Add(int a, int b)
{
    return a + b;
}

I understand the first pattern is obviously "bad;" that is not under dispute. I'm just trying to explain to another engineer the specific technical reasons why, without injecting my own opinion. A good answer will strive to be exhaustive, and stick to technical reasons, risks, and potentially cite known code smells or other authoritative sources.

  • 7
    Actually, all you really have to say by way of explanation is "any variable that is in a global scope can potentially be modified by anything else, making your code very difficult to reason about." The "Modern Approach" you illustrated is better because it has no side effects. Everything that can happen in that method is confined to the method; you don't have to look anywhere else for a complete explanation of its behavior. – Robert Harvey Feb 7 at 22:33
  • 2
    If this engineer still needs further explanation, have them look up "encapsulation," "pure functions," and "idempotency." The reasons aren't technical; the computer doesn't care how you write it. The reasons are entirely about maintainability, which is solely a software developer concern. Maintainability dictates that you create variables whose scope is as small as possible. – Robert Harvey Feb 7 at 22:35
  • @RobertHarvey As always, an accurate and compendious answer, and I certainly agree. But I think there are more reasons that could be mentioned; for example, code using the first pattern can never be recursive, and global memory locations take up space and are likely to be all over the place in memory (compared to stack variables), causing unnecessary CPU cache bursts. That sort of thing. – John Wu Feb 7 at 23:14
  • 2
    Also global variables for parameters are not thread safe. – Erik Eidt Feb 7 at 23:48
  • 1
    There may be technical answers to this question, such as CPU inefficiency and memory usage, but that is trivia even if it is true. The real reason for the "modern style" is developer productivity - it improves the human capacity to work correctly and efficiently with code. For authoritative arguments about the benefits of structured and procedural code, you'd have to go back to the 60s or 70s. – Steve Feb 8 at 8:43
5

Actually, there are books about the advantages of object oriented programming, written in the 90's, that explain concepts like Locality and encapsulation. And you are not asking about object orientation, you are questioning structured programming. That's a step done around 1980-1985.

It's a matter of (human) readability, error prevention, and maintainability.

You actually see, what a function is modifying, it's handling it's parameter, defined 5 lines above, not a variable 10 files away. If you rename your (global) variable, you don't have to modify your method, just your method call. The other answer named recursive calls. The list of advantages is kind of endless.

You just need 10 minutes to understand your spaghetti-version, and 2 minutes for the structured version.

There are absolutely no technical reasons, as well as there is no technical reason not to produce a car like Henry Ford in 1905.It's economical reasons, and quality reasons efficiency, it's cheaper, and it's less likely to make mistakes.

| improve this answer | |
4

The first code is non-reentrant.

What it meant is that you can't call it recursively, from a signal handler, or from multi threaded code without very careful analysis of how things gets called. It's much easier to compose pure, reentrant code into larger, more complicated codebase, where it's no longer possible to keep all of the code path in your head and in libraries code where another team may add signal handlers or multithreaded code without fully understanding its implications on non-reentrant code.

| improve this answer | |
2

There are already a couple of interesting answers here. But some more justification is needed. In the first example, reading or verifying the program requires to:

  1. analyse the control flow to identify the logical blocs.
  2. follow the control flow in reverse direction to see where the global variables (or in the case of assembler, the registers, but that’s a digression;-) ) are set with which value
  3. follow the flow of control forward to make sure that the result is put in the right output variable and that it’s not overwritten before it’s used
  4. accessorily, you should also check that the subroutine does not alter other global variables in an unexpected way.

This simple example does not challenge our brain. However if ADD would call another subroutine, which would itself call yet another one, with conditional returns etc... we would quickly be overwhelmed by the 4 activities due to the limitations of our short term memoryand the combinatorial explosion of the possible branching in the control flow.

As a result, this kind of programming very error prone (false assumptions). Structured programming was invented to simplify point 1 with a clear control flow. Procedural programming was invented on the top of structured programming to simplify point 2 and 3 and make point 4 obsolete (or at least reduce the risks, since the use of globals should be the exception and not the rule).

This is why the second example is so much easier to read.

Further reading:

| improve this answer | |

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.