141 comments

[ 2.3 ms ] story [ 197 ms ] thread
Very shallow article. Tl;dr: none.

I would say that the mythical man month concept has stayed and still is talked about today even though the book itself is also hopelessly outdated except maybe in IBM style enterprise software.

stopped reading at "when I wrote my book"
> even though the book itself is also hopelessly outdated

What parts did you find dated? I haven't read it in the last few years, but I still get reminded of concepts like Second System Effect every once in a while.

It was actually an interesting read.

TLDR: Whatever software building methodology/process you are using now, is probably crap, and made up by people that don't really know any better and just making things on the go.

Eg. He mentioned 'structured programing' was a fad at the time. Now it is Agile, and its offshoots.

Best way to deliver software is to have a handful of capable people and pay them well and get out of their way. The key to get paid well as a dev. is to be in a project that is important, life/death for the company.

I think the most interesting part of the article was the early job divisions while building software. It didn't make sense, but it was adopted due to the coorporate culture of the time.

> Best way to deliver software is to have a handful of capable people and pay them well and get out of their way.

That is the gist of one of the essays in The Mythical Man Month mentioned in the parent.

"Structured programming" was 100% successful, and we're all still using it, all the time. It was so successful, that we don't even talk about it anymore. With few exceptions, it's just how programming is now.

Does not invalidate your actual point, though.

Yeah, the nearly-complete absence of GOTO in the programming languages people actually use is proof of this.
It is possible to produce software of great quality if you are willing to spend a lot of money on it. And if you want to build big system good quality is a necessity. Else you will discover like IBM did early on that fixing one bug on average can create 1.2 new bugs, or something like that.

The issue is that often the quality is good enough to have something that works and then the buyer/payer does not want to spend more. Problem with that is it makes the maintenance and future adaptations expensive and can turn the system into one where fixing one bug creates 1.2 new bugs.

Therefore I think it is wise to err on the side of caution and spend more on software quality than seems to be needed immediately. It is a bit like in the early days of bridge-building the bridges needed to be built stronger than needed because it was not possible to calculate the safety margins accurately.

Yes and no. I'm too amazed by a world which longs for so many software developers. I mean do they all know that all that software has to be maintained? That software is one of the most expensive human artifacts today and that software rots like hell?
You'll probably learn more by trying to understand why these practices are long gone.
Or rediscover techniques forgotten by the latest fad!
I'm not sure you can call structured programming a fad!

An awful lot of languages (I'd say all commonly used ones) use if/then/else, do..while, for..next, and so on, but I can't remember the last time I saw a program with a complicated control flow done by gotos.

Such things were common in the 1970s, in fact the first professional program I ever read did it that way, and it took me ages to work out what was going on.

One particularly confusing technique was to goto a computed expression. Leads to all sorts of interesting bugs.

The whole point of structured programming was "Don't do that! You can do everything you want to do with a small set of restricted control structures, and the control flow is much easier to read".

I'd say that idea won so hard that we don't even notice it.

Originally Fortran only had arithmetic if's

And I agree by the 70's spaghetti code with goto's was on the way out - though some early GWBASIC code had some gnarly practices.

The arithmetic if might be one of the best examples of how structured programming has become the new standard.

In 1960, it was the only way to do "if" in the most popular programming language that existed at the time. Today, I bet that most programmers haven't even heard of it.

Looks like arithmetic if is now just a comparator operator, like `<=>`.
> I can't remember the last time I saw a program with a complicated control flow done by gotos.

I've run across this in C demo code for a chip designed within the last 10 years. It was all goto soup in a god function.

Some embedded developers just never got the memo about control flow constructs. You can find some truly bizarre and concerning development practices in the embedded world.
I think it's a symptom of the field. A lot of arduino code is ghastly but mostly comes down to hardware people coding things for the first time and having zero professional development experience or formal education.
The reverse is true -- app developers see something "weird" in embedded code and think it must be wrong, but often there is a reason. E.g. the unintended vehicle acceleration scandals, where codebases were criticized for having large numbers of global variables... but it's because there's no allocation permitted, and message passing is more expensive than just setting a bit of memory in a massive cooperative multitasking loop.

You might see weird stuff in embedded code because it needs to interact with flakey hardware using a flakey compiler, and be cycle accurate in the process.

I get it. I’ve done enough systems program and reversed many more embedded systems, bug hunting for customers. There are still a lot of bad practices in the embedded world. It often exists outside of normal best practices. Many choices embedded devs make are just plain bad. Using outdated libraries and binaries from a decade or more ago, etc. it doesn’t have to be so bad. I haven’t found many embedded systems that weren’t really bad from a security perspective.

The way these systems get developed is a huge part of the problem. One team building hardware, another building the software no one communicating. Everyone hoping for the best. Frantic cleanup in software to make it all work on top of hardware thet can no longer change. I am sympathetic to the embedded developers position, but they still seem to exist in a different universe from modern software dev.

As a security practitioner I don’t blame the devs. I like to ask questions about how things got to be this way. How we can make it better. But make no mistake. A decade will pass and some embedded developer will still be linking some ancient binary or throwing together a bunch of sketchy CGI files that let an attacker scribble everywhere in memory. Embedded is like, the final frontier of software security :)

That’s my lens anyway :)

I work on embedded software and far more often there's no good reason. (the global variables thing is ambiguous: it could be just counting static variables with well-defined scopes and encapsulation, or it could be describing truely global variables which can be modified by any part of the code at any time. The former is a reasonable technique to seperate modules without allocation. The latter is hair-raisingly reckless software design and also probably the more common approach, even in safety-critical systems). The level of awareness of software engineering concepts in the embedded ecosystem seems far lower than other areas.
Yes, well, some of us are working with barely more than what a Commodore VIC-20 had to offer. (And we like it!)

;)

The problem with C is that it doesn't have proper tail recursion, so if you want to use C as a target language for something that does have proper tail recursion, you pretty much have no choice but to make goto soup.
The C standard doesn't mandate tail-call optimization, but at least GCC and Clang are pretty good at doing it (-foptimize-sibling-calls, which is turned on with -O2)
That it is only an optimisation ensures it will eventually fail to trigger at the least convenient possible moment.

Unless your C implementation has TCE, don’t assume recursion is optimised.

Would that really happen though? I don't think I would actually release recursive code.

I find recursion to be very simple and elegant for prototyping algos, but I would instinctively rewrite it with an iterative/queue form as soon as the "toy" stage is over.

But why would you do that, if it's simple and elegant? Really the only answer is that it has potentially unbounded stack consumption - but tail recursion solves exactly that problem.
Not all recursive algorithms are tail recursive; not all tail recursive algorithms can be correctly identified & tail optimized.
Right, but OP was specifically talking about tail recursion.
> The problem with C is that it doesn't have proper tail recursion, so if you want to use C as a target language for something that does have proper tail recursion, you pretty much have no choice but to make goto soup.

Or you could, you know, use a normal loop...

Besides, since 2005 or thereabouts gcc with -O2 did proper tail-call optimisation so that the stack doesn't grow on a tail-call recursion.

goto would only help in cases of tail self-recursion, though.
Well puppet doesn't have loops. I hope that's not because the "fad" is ending.
There are plenty of further restrictions (such as immutable languages that don't have loops, because loops require mutation, and so instead rely on recursion), or which eschew being concerned about control flow at all (such as puppet, SQL, Prolog, etc; any declarative language). Certainly, those languages aren't "undoing" structured programming.
> such as immutable languages that don't have loops, because loops require mutation, and so instead rely on recursion

These two are not as intertwined as you make them. OCaml has mutation but not iteration.

OCaml has while- and for-loops.

A better example might be Clojure, which has mutable variables (opt-in, like OCaml), but no traditional imperative loops - only recursion. However, even then, the syntax for its tail-recursive construct uses "loop" as a keyword.

I said immutable -> !loops (p implies not q).

You're saying !immutable -/> loops (not p does not imply q)

It's a fair thing to mention, but you're not gainsaying anything I said; I never said anything about mutable languages that don't have loops. In fact, to the topic at hand, you would assume that to be the case, given that structured programming was intended to force loops on languages at the time (mostly mutable) that weren't using them (but instead were using gotos and the like)

"... I can't remember the last time I saw a program with a complicated control flow done by gotos."

Perhaps not "complicated" enough, but here's a fizzbuzz in spitbol (a fast SNOBOL interpreter). This is an unstructured, goto based language for non-numeric computation developed at Bell Labs. Would love to see a faster version in some popular, "structured" scripting language in the same number of characters. I find I can write scripts in more aesthetically pleasing ways than with "structured" languages. But of course aesthetics is subjective. (The term "subjective" here means what looks good to you might not look good to me, and vice versa.)

   ;var a = 0
   ;start a = a + 1
   ;break lt(a,101) :f(end)
   ;x01  y = remdr(a,3)
   ;x02  z = remdr(a,5)
   ;x03  x = y + z 
   ;x01a eq(x,0) :s(x06)
   ;x02a eq(y,0) :s(x04)
   ;x03a eq(z,0) :s(x05)f(x07)
   ;x04  output = 'fizz' :(start)
   ;x05  output = 'buzz' :(start)
   ;x06  output = 'fizzbuzz' :(start)
   ;x07  output = a :(start)
   ;end
Perhaps the unintended benefit of gotos is that it becomes foolish to try to construct overly complex control flow. Whereas stuctured programmming seems to encourage control flow complexity. The use of the phrase "complicated control flow" in the parent comment is a great example. It suggests for the author structured programming allows and arguably therefore encourages such complexity. But what benefit is served by creating "complicated control flow". Why not aim for simpler control flow.
This is the opposite of aesthetically pleasing to my eye.
Aesthetically, it bothers me that you compute x by calling remdr two extra times, as opposed to computing it as the sum of y and z... or the bitwise OR of y and z...

What's the difference between :(label) / :s(label) / :f(label) / :s(label1)f(label2) ?

   s and f are different conditions
   s=success
   f=failure
   default is unconditional
Is bitwise OR a built-in or would it require writing a separate function.
Based on the documentation, there do not appear to be any bitwise operations available in SNOBOL.

It looks like computing x as remdr(3) + remdr(5) is a pure mistake. You only care about the case where remdr(15) is 0, and it's true that remdr(3) + remdr(5) is 0 if and only if remdr(15) is 0. But you're calling remdr twice where you should only be calling it once.

(Or, really, you can keep the weird calculation of remdr(3) + remdr(5), which is conceptually incorrect but correct in every case that matters, but stop computing it with two extra calls to remdr. You're already calculating those values and storing them in the variables y and z; compute x as y + z, which must be much faster than calling remdr two additional times.)

As a side note, why do you want to use "a language for non-numeric computation" for this purely numeric problem?

I think the term "non-numeric" probably refers to another popular language of the era from which SNOBOL comes called FORTRAN. The purpose of fizzbuzz as I understand it is to test a person's aptitude for programming, but not necessarily a person's ability to select the "right" programming language.

If the purpose of fizbuzz is to test for programming language selection, and fizzbuzz is a "purely numeric problem", then should the proper choice be FORTRAN.

> What's the difference between :(label) / :s(label) / :f(label) / :s(label1)f(label2) ?

Looks like it’s unconditional, conditional (success), and conditional (failure) jumps.

:s(A)f(B) is how you tell it to jump to A on success and B on failure.

There is also an "indirect goto" where B is a variable to be appended to string A to compute a label that is defined somewhere else in the program.

   :($('A' B))
> But of course aesthetics is subjective. (The term "subjective" here means what looks good to you might not look good to me, and vice versa.)

Bullshit. There's so much that objectively deficient about that piece of code that I'm having trouble taking you seriously. If that comes at all close to being a representative sample of someone trying to make clear, readable code in that language, then the language was clearly deliberately designed to have an excess of visual noise. But I suspect you're not actually trying to make that code look as clean as the language allows.

The semicolons serve no apparent purpose. Having labels like x01 and x01a with no functional relationship between those lines is outright hostile to the reader. Using x as the prefix for almost all of your label names and as one of your variables is mildly confusing, especially when most of those lines don't relate to the variable x and don't appear to need labels at all. "output" seems to be a magic variable for which assignment has side effects, but nothing in the syntax differentiates it from any other variable. Grouping all the conditionals together, then grouping all their consequences together forces the reader to jump back and forth between lines more than if you put the consequence of each conditional immediately after the corresponding test (in this instance, your way does have the advantage of making the conditional fall-through pattern more readily apparent, but on the other hand that wouldn't be as necessary if you could use more than one line of code to write a single line of output).

You can claim all you want that aesthetics is subjective, but at the very least you must recognize that visual noise you've trained yourself to ignore is objectively different from code which doesn't have those distractions in the first place.

"output" is a keyword, cannot change it (I guess one could recompile the interpreter and define it as something else)

semicolons separate statements. Dont like them at the beginning, move them to the end.

Dont like the label names. Rename them to whatever suits you.

The thing that you may be misunderstanding is that this is written for me, not you. It is written to please my sense of aesthetics, not yours. I am not employed to write programs for other users or to be read by programmers. Every user is different.

You probably could not write a program I would use. I would find "deficiencies" and reject it for something else.

If programming is objective then why are there thousands of different languages, and the number is constantly increasing. All of them are basically doing the same things.

Honest question. Why are there so many different languages. Whats behind that.

"I am not employed to write programs for other users or to be read by programmers. Every user is different."

Well, unless you are the only programmer that company will ever has, you should write code that others can read and understand. Or if you are working in academia, then you can write as you like, but as a professional, you should write readable code for those coming after you.

Devils advocate:

GP might work for a company working on a niche application (be it software or hardware) that requires code to be written like the aforementioned and anyone who specialises in that field is already adept in reading and writing code like that.

I'm not going to defend that choice of programming style for everywhere but software development is a broad industry where you do get pockets of people working on code that looks totally alien to others who are used to see C or ALGOL derived languages. Take LISP for example, there is LISP code in half of all European TVs shipped in the 90s. Forth is used in loads of places, even FreeBSD's bootloader is written in Forth. Machine code is almost completely unreadable for humans yet a massive chunk of software in the 80s was written using it.

My point is the industry might have standardised on a subset of syntax styles that seem readable to the lowest common denominator of developers but it's a massive industry with developers using all sorts of exotic languages effectively. Let's not go down the rabbit hole of saying they're doing it wrong because their code looks alien to you before you've understood the field in which that code is running in.

You’re losing karma for this but you’re right. It’s a massive industry and there are specialties out there writing code in languages that would look unreadable to your average web developer yet the code is well written and perfectly readable to those in the same specialty.

Sometimes I wonder if the only people who browse this site are JavaScript monkeys and Rust fanboys. There’s a whole spectrum of languages out there that looks nothing like C.

(comment deleted)
Check your assumptions. What if someone is not working as a programmer, not working in academia and not sharing programs with others, except in HN comments/submissions.

There was a story that appeared on HN many years ago and I wish I could find it again. It described a gentleman in Japan who did not work in the computer industry but did some sort of (let's call it) "optimisation work" on his own as a hobby. The story told that the work was being used by companies within the computer industry, I think it was hardware manufacturers. If I recall correctly he did not have any formalised education in computer science or mathematics. I am probably getting some details wrong; need to find this story again.

> Honest question. Why are there so many different languages. Whats behind that.

1. Personal pride.

2. Personal or corporate need for control

3. Personal or corporate need for visibility/fame.

4. Licensing unfit for desired use.

5. Technical or social impossibility to extend existing language to fit target space.

And plain old ... personal/group preference?

Some people like different tools for reasons other than pride, ambition, need for control, etc.

Some things are more enjoyable for some people. Chocolate vs vanilla kind of thing.

You’re way off the mark!

1. Might happen occasionally but rarely would anyone adopt a vanity language

2. Nope

3. Lol this is just a rehash of 1.

4. Languages can’t be licensed, compilers can. And that’s why we often have multiple different compilers.

5. Now you’re getting close.

The actual reason is:

Trying to solve a different problem

Believe it or not, problems can vary massively from one field of IT to another. Some require low level hardware access, some need mathematical compatibility, others might be optimised for text parsing, and sometimes you just care about developer throughput. There’s macro scripting, web development, games development, server side software, OS development, and firmware. And entire industries using their own specialist tools that most people don’t even know about unless you happen to already work in it.

Some languages are designed to be bullet proof and used in aviation or other domains where failure isn’t an option, some are the electronic equivalent of duct tape designed to allow people to quickly stick operations together.

Programming is such a massive industry that you need different languages for different problems.

Ever heard of C#, J#, Typescript, Kotlin, Go?
Yes and only C# was created because of NIH syndrome:

C# - Created by Microsoft after Sun sued them over Java. But C# (or rather .NET) did still solve several problems with Java’s JVM (eg it’s much easier for end users to work with, it supports native widgets, first class Windows support) and personally I find C#’s syntax a little more grown up too. It’s also worth noting that C# has since evolved into its own language (rather than just being a Java knock off).

J# - there isn’t any other functional language that targets .NET so there is a distinct problem J# seeks to solve.

TypeScript - That language clearly aims to solve several short comings of JavaScript, such as type safety.

Kotin - I don’t know much about this language personally but the Wikipedia article answers my point for me:

> In July 2011, JetBrains unveiled Project Kotlin, a new language for the JVM, which had been under development for a year.[17] JetBrains lead Dmitry Jemerov said that most languages did not have the features they were looking for

Go - this filled a massive hole at the time: languages that had the guarantees of a statically compiled language while being as quick to learn and develop on as a JIT compiled language and which were cross platform. I actually adopted Go in version 1.0 for a personal project specifically because it solved problems with the other mainstream languages - problems that were very relevant to the project I was creating.

I’m an old fart with 30 years of software development experience across well over a dozen different languages (you could easily treble that if you want to include machine or compiler specific dialects too). The problem many have when they see the term “general purpose” is they assume it means “best suited for all purposes” but actually it just means “can solve most problems with some degree of success”. Thus there will naturally be specific domains where even the most versatile languages are superseded by another language. And that’s fine. Good programmers can adapt to using the right tools. Whereas bad programmers will reach for the same tools regardless of the problem because they’re too lazy or too ignorant to learn anything else.

> Bullshit. There's so much that objectively deficient about that piece of code

I don't agree the GPs code is more aesthetically pleasing but he's right that it is subjective. All the counterarguments you've given are subjective to you (eg you know what tokens do in over languages, spent zero time learning the tokens of this language and thus you're clearly not going to know what a colon does in this language just by looking at it).

One of the hardest things it seems for a developer to do is accept that their personal opinions are subjective since they spend so much of their career being told there's a correct way of doing things. Your post here is a classic example of that. Arguments about strongly typed and loosely typed languages are another. Debates about functional, stack backed, object orientated, procedural, etc etc... it's all just people like yourself trying to convince others that your subjective view is empirical scientific fact. But it's not, it's just opinions. Some opinions just happen to be more popular.

> All the counterarguments you've given are subjective to you (eg you know what tokens do in over languages, spent zero time learning the tokens of this language and thus you're clearly not going to know what a colon does in this language just by looking at it).

I tried very hard to only bring up problems that were at least somewhat objective. Counting the number of superfluous symbols in the code is not purely subjective. Noting the similarity between "x01" and "x01a" is not purely subjective. Measuring the distance between a conditional test and its consequences is not purely subjective.

And I did figure out what a colon does in this language just by inspecting this code. It's semicolons that serve no apparent purpose in this snippet, and even with the outside knowledge that they serve the same statement separator purpose as in C and many other languages, it's still an objective fact that they are largely redundant. The character count could have been a lot lower if the language made it the default for new lines to start new statements and used a special character for continuing a statement on another line.

Subjective would have been if I complained about the semicolons being at the beginning of the line instead of at the end. That's almost purely a matter of taste driven by prior experience with other languages.

The fact that the OP had a different opinion to yourself about what he found readable is proof enough that it is subjective and it’s laughable how you’ve demonstrated hnlmorg’s point while attempting to debunk it (eg your attempt at providing fact while your evidence is just more illustrations of familiarity which is entirely subjective).
> The fact that the OP had a different opinion to yourself about what he found readable is proof enough that it is subjective

Subjectivity isn't an absolute all-or-nothing question. An issue having some degree of subjectivity doesn't automatically put it into a purely subjective, "all answers are equally valid" realm.

> eg your attempt at providing fact while your evidence is just more illustrations of familiarity which is entirely subjective).

I referred to some quantifiable metrics. Those have nothing to do with familiarity. Whether a number is larger or smaller than another number is a fact unaffected by your level of familiarity with either number.

You're really not responding at all to the substance of my comments, so you really don't have any justification for calling me "laughable" at this point. There are plenty of ways you could try to substantively refute my comments, instead of rejecting everything generally while mixing in insults.

> I don't agree the GPs code is more aesthetically pleasing but he's right that it is subjective.

An easy way to see that these issues aren't subjective in general is to ask why absolutely no-one uses the kind of code given above unless they're forced to because of the platform they're using, e.g. a constrained embedded environment or whatever.

You either have to conclude that effectively all developers have the same subjective take on these issues, or that there's something beyond subjectivity at work. There are very good arguments for the latter.

"Structured programming" has a very specific meaning. Or at least, it did in Dijkstra's goto paper. It's not that all uses of goto are terrible, it's that if you restrict goto's uses to specific types of control flow, you can make provable statements about the behaviour of the code, which other uses blow up.

The goto's you've used here are within the set of allowable uses, so can be mapped directly onto structured constructs. There's literally no advantage either way, other than introducing the possibility to the reader that there could be something weird going on in your code.

> Would love to see a faster version in some popular, "structured" scripting language in the same number of characters.

How's this?

    #include <stdio.h>

    void main(){
      for(int a=1; a<100; a++){
        int y = a % 3;
        int z = a % 5;
        int x = y + z;
        if(!x) puts("fizz");
        else if(!y) puts("buzz");
        else if(!z) puts("fizzbuzz");
        else printf("%d\n",a);
      }
    }
Same technique, give or take some unimportant details. Not particularly golfed. 240 characters to your 312. I have no idea if it's faster because I don't know how good the spitbol interpreter is, but I do know that because I've used "structured programming" structures, the compiler (or interpreter, whichever you prefer) can in principle make more safe optimisations to my code than to yours.

If C doesn't meet the "scripting language" constraint (which I find arbitrary, but not insurmountable), I'd throw Julia or FORTH at it and probably shave a considerable number of bytes off.

"How's this?"

Unoptimised, it's already roughly 3x faster than the spitbol script.

Moving the "int" specifier out of the for loop allows this to compile under -std=C89. How does one suppress the warning about the non-int return value for main().

The easiest way is to `return 0;`. We've got the character budget for it.
I really don't understand the comparison you're trying to make. I learned SNOBOL from a university library book in the early 1980s, and used it on the university mainframe - but it's baffling to me that someone might want to continue using it today for anything other than nostalgic reasons. (If you're trolling, good one!)

There's an enormous amount of accidental/inessential complexity in the code you provided, and that's a big part of the reason no-one seriously uses SNOBOL or similarly primitive languages for anything serious today. Code like the above can't remotely scale to larger programs, and attempts to do so were notorious for their failure.

Here's a version of FizzBuzz in e.g. Haskell:

    fizz n | n `mod` 15 == 0  = "FizzBuzz"
           | n `mod` 3  == 0  = "Fizz"
           | n `mod` 5  == 0  = "Buzz"
           | otherwise        = show n
 
    mapM_ putStrLn $ map fizz [1..100]
Even the already rather simple control flow is abstracted away by the 'map' function, so there are no visible loops or gotos. Compare that to your program with a dozen labels and seven gotos, where the actual logic is buried in irrelevant cruft.

Even versions with more explicit control flow (i.e. without map) can be similarly readable, but if your goal is to minimize complex control flow, then rather than reverting to intractable gotos, go read "Lambda: The Ultimate GOTO" (http://dspace.mit.edu/handle/1721.1/5753), published in 1977, and learn to use functions for control flow.

Better than Haskell and faster, too:

    fizzbuzz:{v:1+!x;i:(&0=)'v!/:3 5 15;r:@[v;i 0;{"Fizz"}];r:@[r;i 1;{"Buzz"}];@[r;i 2;{"FizzBuzz"}]};`0:$fizzbuzz 100
You're wasting your own time.
(comment deleted)
I've been learning lua, and loops don't have a continue or a break. You can use a goto to jump out. I tried it but didn't like it. Might just be prejudices. :)
If I remember correctly there is a break keyword, but continue is missing. At least in Lua 5.1 which is still the most popular one.
You're right, apologies. I'm on LUA JIT.
Luajit should have the break statement (Compatibility is Lua 5.1 with some backports from 5.2, and a couple extensions).
Return works within loops right?
It is a prejudice. Knuth and Torvalds think goto is valuable. See https://pic.plover.com/knuth-GOTO.pdf https://news.ycombinator.com/item?id=8760518 (Somewhere on the internet is a discussion when somebody suggested to remove all gotos in the kernel with structured programming, the answers were entertaining and insightful; I cannot find it right now though)
Have you read Knuth’s article? It is a bit more nuanced than a wholesale defence of GOTO in preference to structured programming. It surveys various control flow structures that are not easily expressed in terms of IF conditions and WHILE loops (what structured programming advocates of the time argued to replace GOTO with), but more easily expressed with GOTO, and it argues that, since we don’t know which of those constructs will ultimately end up the most useful, it might be prudent to keep GOTO around, at least for a while, and not confine ourselves to simple conditionals and loops just yet. But if we do find better structured control-flow primitives, perhaps we may get rid of GOTO after all. The comments under the Torvalds link kind of point it out already: GOTO is used in the Linux kernel as a crutch for the deficiencies of C, like the lack of scope-based destructors, exceptions and the ability to specify which loop a BREAK statement targets.

My favourite part of the Knuth paper is this:

    begin until error or normal end:
      if m = max then error ('symbol table full') fi;
      normal end;
    end;
    then
      error (string E) =>
        print ('unrecoverable error,'; E);
      normal end =>
        print ('computation complete');
    fi;
This is pattern-matching on a Result<(), String>, in 1974. How adorable!

It’s a shame that innovation in this area has stagnated so much: most languages only feature IF, WHILE, FOR, single-level CONTINUE/BREAK, SWITCH (with fallthrough), and GOTO as an escape hatch in case the others are insufficient, just because they are so familiar. Only now languages like Python or Java are getting any form of pattern matching. Though props to the latter for at least allowing to break multiple loops by way of labelling the outermost one.

Yes, I read it and I agree with your text. My point was just that goto is not evil, sometimes it is your best option (like in the parent if loops do not have break).

On a side one could argue that early returns, while often used, are not structured programming and a goto in disguise.

Well, it's slightly more powerful that pattern-matching Result because in case of nested loops you can short-circuit right to the very top loop.

Writing corresponding nested maps/folds is generally quite unsightly. Plus the resulting code may be non-tail-recursive which means that the sentinel EarlyExit value will get checked and re-returned for every function in the call stack on such exit.

This loop construct, however, basically introduces locally scoped continuations that you can directly return to. Of course, it's possible to optimize nested maps/folds down to this form, but it requires very heavy inlining — Haskell does it because it's semantics allow deforestation, but does Rust do it?

Knuths paper is interesting.

If it were not for being autorejected in code reviews I would use goto:s alot more.

Even if I want to break out of nested for:s there is always whining about jumps.

In some cases it makes the code more readable as Knuth shows. Dogmatism is a bane of programming.

State machines and actors are occasionally cited as good ideas here, and they seem like much the same kind of unstructured programming IME.
Why? You can easily emulate a state machine with a while loop, a switch case and one more level of if-branches. Abstracting those in a framework doesn't make it unstructured IMO.
> You can easily emulate a state machine with a while loop, a switch case and one more level of if-branches.

You can easily emulate GOTO much the same way.

obviously, as long as the language is turing complete, but you just converted an unstructured program to a structured one.
And gained nothing except more verbose syntax. The goto spaghetti is still spaghetti whether you use "goto" or "switch".
Disagree. You eliminate backward gotos except for the while loop, which is much more clearly communicating the way to extend the code (by adding more states or state transitions) to the next person taking over. I‘m not categorically against goto in certain contexts (like C based systems programming), but IMO backwards jumps with goto need to be eliminated.
To the same extent, and in the same way, as you did when you converted a state machine the same way? I still don't get what distinction you're drawing between state machine style and GOTO style.
Indeed, GO TO is not that unstructured; it can be said to have a fairly precise semantics of "sets the continuation of the program to <label>; does not proceed to the next statement.". Though if you want to analyze the semantics of a GO TO-using program, it's practically a required step to endow all of your labels with appropriate COME FROM annotations, marking the branches it could be reached from - and that's a whole-program analysis, at least in principle. That's the point where you're giving up modularity!
There are things beyond for-loops and gotos, namely recursion schemes. It's been a long long time the last time I wrote a for-loop and I don't miss them.
> One particularly confusing technique was to goto a computed expression. Leads to all sorts of interesting bugs.

Huh. I suppose the modern-day equivalent is commonly done with higher-order functions - in particular, callbacks, CPS and returning functions. I've worked with people who find this confusing (particularly the last one - functions returning functions).

I believe that this confusion comes from the avoidance of low level details. A function is just a location in memory where instructions are stored. Returning a function is nothing else as returning a pointer to a memory location.

In languages like Java that want to hide everything memory related I also was struggling with grasping what a function really is. Is it even allowed to return a function?

Languages that hide memory locations and direct memory access are one of the reasons that some programmers are struggling with functions returning functions.

> Returning a function is nothing else as returning a pointer to a memory location.

That depends. If the language supports proper first-class functions, it's basically an instruction pointer plus a record of the variable bindings the function code is "closed over" (i.e. parameterized on). Many languages do support this nowadays.

Absolutely this. You need that machine language model of how a computer works to really understand programming, otherwise you are just guessing why stuff works.

I'm not capable at all with assembler, can't write my own compiler.

But my model of how a computer works, in my head, is "functions are just sequences of bits arranged as CPU instructions." I know how it works, even though I can't write a compiler or parser.

And it's absolutely essential to how I program, even in the highest level languages (SQL explain plans).

The real value of studying outdated development methodologies is that it teaches you how to think about new problems. It will almost never give you a plug-and-play answer for a problem you are facing, but it will give you a new a useful way of looking at things.

Brooks wrote The Mythical Man Month in the 1970s about his experience in the 1960s and it is still extremely relevant today.

When I was starting my software development career in the mid 1990s and trying to understand how to manage the process, one of the things I did was write to NASA and request a copy of their Manager's Handbook on Software Development.

This was not because I wanted to run things like NASA. That would have been be horribly inappropriate for a startup dev team. However, I wanted to understand an extreme; a process where spec writing, testing and on time delivery were prioritized.

I never used anything directly from that NASA handbook, but I learned a lot.

Almost 30 years later, I still have that book. It's one of my little treasures.

For those wondering, there is the original handbook [1] available online today. Also an interesting paper on the process improvement lessons learned applying much of what was expressed in the handbook [2]. I believe the paper is of more immediate TL;DR use today for those who do not have the time to digest the handbook and internalize the lessons to draw from it.

[1] https://everythingcomputerscience.com/books/nasa-manage.pdf

[2] http://www.cs.umd.edu/projects/SoftEng/ESEG/papers/83.88.pdf

That's the one! Thank you for posting the link and the lessons learned. That looks like a great article.

In the 90s it took a (very friendly) FOIA request, a $5 processing fee, and three weeks to get my hands on that handbook.

curious if anyone tracks software engineering metrics like this anymore? defects identified, defect fixed vs. time
Yes. However, it's mostly in "maintenance" programming. Particularly in the way that DOD and safety critical systems are maintained.

It's both a reasonable and unreasonable concept. It's unreasonable because it tries to turn programming into factory work, in fact you may even see them set up workflows predicated on an assembly line concept. This kind of works, and why it's kind of reasonable, when the work is sufficiently well-understood. It's very common in these systems to have a very detailed specification. Often the defects are deviations from the specifications that got through because of the manner in which these were classically tested (primarily integration tests, often manual, necessarily restricting the scope of the testing regimen). Other defects are realizations that the specification itself has an issue (happens), often the result of a dependence on a prose format for the specification which doesn't lend itself well to formal analysis. It also tends to, well, become wordy. 1000 page specs are not unheard of even for relatively small systems, you can imagine there may be quite a few pieces of conflicting information in there.

The analogy of programmers as general surgeons is so timeless it's downright creepy.

Nothing has changed in Brook's book because it's a book about human beings doing creative work, and humans haven't changed at all.

The (still) relevant (best) practices from the past are all encapsulated into the standard libraries, that’s how CS pays its duties to the big minds of its past?
The article says:

> Today, structured programming appears remarkably simplistic, great for writing tiny programs (it has an academic pedigree), but not for anything larger than a thousand lines.

Curious. I rather think that structured programming became so ultra-pervasive in any high level programming language younger than 50 years old or so[1], that we've lost the extra name for it. Practically every programming language that isn't either pure functional (rarer than it seems), low level (i.e. assembly), or very very domain specific (e.g. SQL[2]) is a structural programming language.

Examples for "structural programming languages" are C, C++, C#, Java, JavaScript, python, Go, Swift, Scala, PHP, perl, Rust, D, ... You get the idea. If a general purpose language does not follow it, it's something notable, like Haskell.

So rather, I think this is an example of something that was so utterly successful that it became absorbed into the general fabric of mainstream programming. It's true that we don't draw as many flowcharts as we used to, because we got more comfortable with everyday programming, but they are still how we think about code a lot, and for complex processes that we want to visualize we still do draw them.

[1] Not a scientific estimate. Substitute "not very very old" if you like.

[2] Funnily that stands for "Structured Query Language", but not necessarily for significant reason: https://en.wikipedia.org/wiki/SQL#History

TeX was Knuth's first “real” programming in a few years. His comments on structured programming are a bit eyebrow raising. He had high praise for it and said that it allowed him to write the whole program without having to test small parts of the program (the book with the exact quote is upstairs and I'm too lazy to go get it).

That said, I'm pretty sure that TeX is more than a 1000 lines of code.

Often functional tests provide more bang for the buck than unit tests. Concentrating on functional tests allows to move faster.

This is because most modern languages allow you to write in structural style which is impossible to get wrong without noticing. If you have a loop, you know it repeats for its condition, and does not otherwise. If you have a `then` clause, you know it will only run when the `if` condition is true. If you have a function, you know it's only computed when called.

Compare it to pre-structural languages like Fortran IV or Basic, where you can freely jump into the middle of the loop, inside of what constitutes an undelimited `then` clause, or even into the middle of a function, without "calling" it properly.

This allows for potentially very clever code that saves every last byte and cycle. It also allows for totally mind-boggling, insidious bugs in code we'd now consider plain and foolproof to write.

> If you have a `then` clause, you know it will only run when the `if` condition is true.

Can someone please tell the clang compiler folks?

You can goto inside a conditional statement in pretty much any language I can think of that has goto.

Loops are sometimes more restricted, but even then, neither C nor Pascal prohibit it (Algol-60 did, curiously), and many languages derived from them that retained goto do the same. Off the top of my head, the only mainstream language I can think of that specifically prohibits goto into inner blocks is C#.

Here's the actual quote, which is illuminating:

“TeX was the first faily large program I had written since 1970; so it was my first nontrivial ‘structured program,’ in the sense that I wrote it while consciously applying the methodology I had learned in the early 70s from Dijkstra, Hoare, Dahl, and others. I foudn that structural programming greatly increased my confidence in the correctness of the code, while the code still existed only on paper. Therefore I could wait until the whole program was written, before trying to debug any of it. This saved lots of time, because I din't have to prepare ‘dummy’ versions of nonexistent modules while testing modules that were already written; I could test everything in its final environment.”

That said, there are still 337 instances of goto in tex.web by my quick and dirty count (grep goto tex.web | grep -v '@' | wc -l), which is perhaps an undercount since there are modules that might be included multiple times thanks to how the WEB system builds its Pascal output and I don't have a Pascal reformatter to make analyzing tex.p manageable.¹

1. The choice to "compress" the Pascal output of tangle is one of the most inexplicable choices made in the development of the TeX ecosystem.

"structured programming" is not the use of flow-control but a way to design programs.

Even if most language have structured flow-control, a lot of people NOT do structured programming.

This is how I remember it back in the day and how I do it (curiously, much better on Rust, where it match better how was done on Pascal! ie: Not OOP, big on structs, functions, enums) and is a mix of:

- Define the major components of the app and put it on "modules" (aka their own file or folder if truly large)

- Define the major structures according to the domain (aka: make tables in sql). This is what POCOs are used today or plain Rust structs.

- Make procedures that operate on the above with a clear in/out discipline. Also, document that (this one, I always forget!)

So, is not that far away from functional, inmutable programming, only that:

- A lot was mutable and pass references. This is also a big part of how you discipline to define the flow make this tractable or not

- You don't have generics or classes, so you stick to plain data, plain vectors or lists and duplicate stuff (algos) here and there (no way to abstract "map/filter/folds" that I remember)

- You must be disciplined with the naming of "modules", "POCOs" and functions. Also limited in the length of names (like files only being 8 chars), causing the rise of Hungarian notation (that was misunderstood/misused badly!)

- All your pipeline are eager (not chance of iterators and stuff) so you repeat "loops" everywhere

- Everything was more self-contained, that is great... as long you don't forget the discipline.

"The discipline" was the key here, for make this actually "structured programming".

The MAJOR point is how you STRUCTURED the program, not the use of structured control flow!

But then you (and the author?) talk about different things than the Wikipedia entry that the author linked to, because that entry (and what I remember about structured programming--but I was very young, so might have missed a lot) specifically talks about "making extensive use of the structured control flow constructs [...], block structures, and subroutines", also mentioning Dijkstra's "Go To Statement Considered Harmful" and some debate around that.

The structured program theorem is also about specific control structures, essentially proving that they are Turing complete by themselves. I remember that we had to prove in university that as a result, you can transform any program into a program with one big while loop. (I think that's equivalent to "μ-recursion", whereas the simpler "LOOP" statement only implements "primitive recursion" which is not Turing complete, but it's been a while.)

What you talk about sounds like it could be "modular programming", of which Wikipedia states: "Modular programming per se, with a goal of modularity, developed in the late 1960s and 1970s, as a larger-scale analog of the concept of structured programming (1960s)."

Of course, Wikipedia is not a good source, I wasn't there when it started, and it's entirely possible that structured programming meant more back then than it seems to mean today, or meant different things in different circles. A quick Google search seems to confirm that nowadays structured programming primarily seems to be the use of control structures, though maybe that's just what survived?

As a Wikipedia editor who's poked around in our articles on programming paradigms, there's a decent amount of strange stuff going on -- and I'm not sure how much of that writing I'd take as authoritative, if you catch my drift.
I get it, but if the author means a different thing, then they should link to a source that actually defines what they mean (and that I myself could not find, except for the mention of modular programming), instead of to the Wikipedia article that refers to what we have been talking about here.

As it stands, what they link to, and what most seem to understand as structured programming, was not a fad at all.

Is possible we are talking about different stuff. Back in the day we don't wikipedia (and I learn programming before I have Internet, around 1998 so the idea was more developed?) so this is how I understand the idea as teached to me and by examples from my peers.

Also, a lot of good practiques was from books or conferences (mostly sponsored by MS).

Lots of development practices come back again. E.g. techniques for optimizing code for computers sometimes make a resurgence for mobile phones a decade later.
`Structured programming' is actually used to refer to two different things: reducing or eliminating the number of gotos, and top-down programming. The former is universally understood to be generally a good thing (though there are still some good uses for gotos); the latter was the fad. The 1970s was the time of HIPO (hierarchy/input/process/output) charts. An early-1980s research project I was acquainted with at the time had an iron-clad rule: each procedure must be in its own source file. At least one well-known book in the 1980s gave definitions of software quality metrics that made object-oriented designs bad.

Pretty much every SE methodology has been a fad when it claimed to be the One True Way, and has offered useful ways of solving a certain kind of problem.

Top-down/procedural fits really nicely with the unix command line philosophy. Loose coupling and pipes. You can understand why it was the de facto One True Way before GUIs.
> Top-down/procedural fits really nicely with the unix command line philosophy. Loose coupling and pipes.

I don't think that follows at all. Unix shell programming is very much a bottom-up philosophy where you write a bunch of generic tools and then string them together to solve your actual problem at the last minute.

But the individual tool itself? It takes a bunch of inputs, does some processing on them, and spits out a bunch of outputs. That doesn't feel like a good fit for procedural to you?

I guess what I'm saying is that the complete input state of the program is known the moment you hit enter.

As opposed to a GUI where a user could flip a switch at any point during the program's lifetime and mess with its internal state, which feels like it maps better to OOP.

I guess you're saying that if you're writing a small function that's fully understood then procedural top-down is a good way to do it.

I don't know. Personally even for a fairly small tool I'd want to build it bottom-up by composing smaller functions, and I think you'd gain a lot of reusability that way - indeed I'd say many of the classic unix tools suffer from being programs rather than libraries, and it's a shame there's such a hard boundary between shell and the tools it uses.

If anything I suspect the causality goes in the other direction - if you want to make a big system using procedural programming and C, then you have to split it into processes that are simple enough to comprehend in their entirety, and that have a very simple state model where they essentially just take input and give output and don't have any persistent state between invocations. But when you put it like that, Unix shell-and-pipeline style starts to seem like an effort to invent functional programming, just at a somewhat more coarse-grained level.

That's a fair assessment of my position, yes. But I got my start with JSP diagrams, pseudocode and command lines, so decomposition is very natural to me. It's my "first language", like English. And the comparison between good top-down structured and functional is one I've made myself.
> Top-down/procedural fits really nicely with the unix command line philosophy.

It’s the exact opposite. Unix was if not a reaction at least and antithesis: top-down programming is BDUF, you first decide what the entire thing does, then you go down a level and define / implement what’s supposed to be there.

Unix is about starting from small utilities at the bottom and jury-rigging them until you get something you like.

> each procedure must be in its own source file.

Funnily enough, this is how GNU libc is organised.

> I think the best management technique for successfully developing a software system in the 1970s and 1980s (and perhaps in the following decades), is based on being lucky enough to have a few very capable people, and then providing them with what is needed to get the job done

> There is one technique for producing a software system that rarely gets mentioned: keep paying for development until something good enough is delivered.

Highlights.

I honestly think this is still true. Good people getting stuff done is still hidden, now behind some wagile (waterfall agile) in larger companies where the incompetent have been promoted to one step above their ability (the 'Peter Principle'), and the people who do the real work keep doing it.
I am a big fan of Jackson Structured Programming (the other JSP), from the 70s, and have used it many times. When it is the right a match for a given problem, the design and implementation will work first time, every time.

And that problem is where the input can be modelled as a stream of structured events.

This may sound too abstract, but it includes, for instance, pretty much any file. But the value lies more in that it makes it trivial to design a file format that is easy to parse, and process.

For me the value lies in that in the old days techniques were developed to systematically solve programming problems that will always we relevant, in this case processing a stream of events.

It is very well explained in the follwing MIT ocw lecture (it uses Java, but is actually language agnostic):

"Designing stream processors

Stream processing programs; grammars vs. machines; JSP method of program derivation; regular grammars and expressions"

https://ocw.mit.edu/courses/electrical-engineering-and-compu...

To practice it, you could try "Project 1: Multipart data transfer" in the link below:

https://ocw.mit.edu/courses/electrical-engineering-and-compu...

> I think the best management technique for successfully developing a software system in the 1970s and 1980s (and perhaps in the following decades), is based on being lucky enough to have a few very capable people, and then providing them with what is needed to get the job done while maintaining the fiction to upper management that the agreed bureaucratic plan is being followed.

Surprisingly, very little has changed.

This management practice called “programmers anarchy”. It worked very well in my professional life too.
Third this. Process doesn't really matter. Just hire good programmers and let them program.
Individuals and interactions over processes and tools?
It's not really "anarchy".

Setting goals and delegating responsibility is how things are supposed to be led.

Substituting that with project management is a pathology.

Asterisk dialplans :’(
My last job was working for a voip provider that used Asterisk for its SIP handling. I always referred to dialplan as "assembly for phone calls". Double :'(
I think the main problem with us engineers is that we always want to feel smarter than everyone else. Including our predecessors. Which leads to an ever-changing revolving door of doing things just to be different.
I disagree strongly with the authors assessment of the state of software development a few decades ago.

By the late 1960s, we had realised software development was hard. We put some good brains on the problem. Discussions between leading experts brought up several very important points, that we struggle with to this day:

- Naming things,

- Low coupling and high cohesion,

- Communication between developers,

- Communication with customers,

- Evolving prototypes,

- Avoiding the planning fallacy,

- Estimation,

- Support and understanding from upper management,

- and much, much more.

These are problems we struggle with today, but it's also the problems identified by and worked on by the experts of the late '60s into the mid '70s. We can learn a lot from what they discovered. (I sure have -- and I keep learning more!)

In fact, if I'm being a bit uncharitable, only three things truly seem to have changed since the late '60s:

- We have faster computers.

- We have virtual computers.

- We have higher level languages.

- We have version control.

Other than those four things, all advances in how to build software I've seen the last few decades are, in some sense, a rehash of what they found out early on.

Honestly there is a bit more than that.

The idea of mass development with large, open ended access to code is actually a surprisingly recent idea. Plus of course Stackoverflow has significantly changed the approach.

Concurrent programming is also significantly different to its 1960s iteration. COBAL and Fortran did not even attempt to do such a thing, the handling of all those asyncs was not around.

Many many other architectural details have also been implemented that were specifically designed not to be exposed to give classically trained programmers the illusion that things under the hood have not changed. However, they have significantly. Branch prediction leaps out at me, but also many other forms of evaluation, memory and processor design.

There is more than you describe that has changed.

If you would say that compilers and parsers have not changed, that would be extremely accurate. But there's a bit more to the world on either side of the compiler.

Maybe the nature of our concurrency problems has significantly changed, but from what little I remember, I don't think so. More people attempt to design for concurrency now, but shared memory, actors, barriers etc have been around at least since the early--mid '70s.

Do you really think branch prediction has significantly reduced the difficulties in developing software, or altered the way software developers work? Bringing up things that have changed behind a compatibility layer that makes us program against an emulated PDP-11 sounds to me like support of my thesis, if anything: the only changes to the way we work are faster computers and higher level languages.

Concurrent is not quite the same as async, though. If we're talking about async - especially the most common modern variety which is basically coroutines with some syntactic sugar - then it first appeared in mid-1960s in Simula. Although I don't think it was used much then.
> only three things truly seem to have changed since the late '60s:

> - We have faster computers.

> - We have virtual computers.

> - We have higher level languages.

> - We have version control.

> Other than those four things

I see off-by-one errors hasn't changed since the '60s.

Virtualization came around in 1972 so was not far away either!
I learned to write resilient HTML by aiming to support (and thus researching coding for) all browsers 1995 and up.
(comment deleted)
The overwhelming attitude in a pre-agile world with a shortage of programmers was:

1) Create a division of labor so that tasks could be pushed to the cheapest person available.

2) Create a waterfall with documented buy in each step of the way.

3) Train an army of smart people for 6-8 weeks and cut them loose in specialized role. (Either a process person writing requirements or a tech person coding them or a tester confirming the code does what’s needed or change person teaching users)

4) Design and conceptual integrity of the whole were frequently missing.

5) The system buckled when requirements were wrong or changed.

6) Terms like “software factory” were used. When they’re used to today they reflect this mindset.

7) The best programmers were trapped in a system that wouldn’t promote them beyond a certain point because they weren’t generating revenue or managing a large group of people.

8) The sum of this was an industry notorious for overdue and over budget projects.

8.5) People prayed for their overdue projects to get cancelled before their portion would be blamed.

Software development isn’t perfect today but we are in a much better place.

I use Spitbol every day. A problem with it is that it allows godawful code like that example. Here's fizzbuzz in Spitbol: loop a = lt(a,100) a + 1 :f(end) output = eq(remdr(a,15),0) 'fizzbuzz' :s(loop) output = eq(remdr(a,3),0) 'fizz' :s(loop) output = eq(remdr(a,5),0) 'buzz' :s(loop) output = a :(loop) end

Even shorter: loop a = lt(a,100) a + 1 :f(end) output = (eq(remdr(a,15),0) 'fizzbuzz',(eq(remdr(a,3),0) 'fizz', (eq(remdr(a,5),0) 'buzz')),a) :(loop) end