7

If createWorld() is really long and I need to split it, I can split it to createLight(), createEarth(), createPlants(), and createAnimals().

So, naturally I do:

function createLight(){
    //work 1
}
function createEarth(){
    //work 2
}
function createPlants(){
    //work 3
}
function createAnimals(){
    //work 3
}

function createWorld(){
    createLight()
    createEarth()
    createPlants()
    createAnimals()
}

But I see a lot of newer developers do what I am tentatively calling "the Stairstep antipattern". It goes something like:

function createWorld(){
    //create light
    //work1
    createEarth()
}
function createEarth(){
    //work 2
    createPlants()
}
function createPlants(){
    //work 3
    createAnimals()
}
function createAnimals(){
    //work 4
}

I am sure this is worse, but I don't think that my opinion alone can sway my colleagues. I think that if one step is createButterfly() then that function has no business calling createGrasshopper() at the end just because that's the "next step".

And even if you name it createButterflyThenCallCreateGrasshopper(), it's still bad design because you can't test the createButterfly code well, and when you have several steps it gets hard to see where the functions are called.

I call it the Stairstep antipattern because I think of it like this:

|
|createWorld(){
              |
              |createEarth(){
                            |
                            |createPlants(){
                                           |createAnimals()

Am I correct in thinking that this is a bad design? Why is this a bad design?

AwokeKnowing
  • 197
  • 7
  • 3
    Those are equally valid in principle. Which one is better depends on the meaning of those functions. – CodesInChaos Dec 05 '16 at 20:55
  • @CodesInChaos well in the case when they are steps in a sequence of actions, rather than actual "sub actions" – AwokeKnowing Dec 05 '16 at 20:57
  • 1
    Probably not what you want, but those are called *tail calls*. – CodesInChaos Dec 05 '16 at 21:06
  • And some languages/compilers even perform tail call optimization, thus that the stack does not grow. For example, Lua does this optimization. It's an oten seen pattern used to implement state machines. – theldoria Dec 05 '16 at 21:11
  • @CodesInChaos thanks, so you'd say something like "avoid tail calls when breaking a long process into a list of steps" or something like that? Do you know of any reference/article? – AwokeKnowing Dec 05 '16 at 21:21
  • What makes you think this is an anti-pattern? What you're probably fishing for is [*Fluent Interface*](https://en.wikipedia.org/wiki/Fluent_interface) or *Builder,* but those are *patterns,* not anti-patterns. – Robert Harvey Dec 05 '16 at 21:31
  • 3
    [Tail Calls](https://en.wikipedia.org/wiki/Tail_call) are something different; they are the means by which recursive functions are converted to iterative functions by the compiler or interpreter. You can name the last method call in a function a *tail call,* I suppose, simply by virtue of its position, but it really has no intrinsic meaning beyond its use in recursion. – Robert Harvey Dec 05 '16 at 21:34
  • 1
    @RobertHarvey Because if you want to add or remove a step in the process, or change the order, it's crazy to have to go through the functions and find which calls the next one and insert a link in the chain. it's massively more difficult to see which functions are called, and the functions are way harder to test on their own – AwokeKnowing Dec 05 '16 at 21:43
  • Which is why I never use the Builder pattern. That said, you can get really elaborate with it if you want; "You must call Foo() first, before you call Bar()". You might dislike it, but that doesn't necessarily make it an anti-pattern. What you call an anti-pattern might be someone else's idea of a golden goose, silver bullet, or the one ring to rule them all. – Robert Harvey Dec 05 '16 at 21:45
  • @RobertHarvey :) ok fair enough, but of course it's a "pattern", just like the "arrow" is a pattern. But anti-patterns are a subset of patterns. – AwokeKnowing Dec 05 '16 at 21:47
  • Who are you trying to convince? A coworker? :P – Robert Harvey Dec 05 '16 at 21:47
  • @RobertHarvey I just get tired of having to "explain" what's wrong with that way of doing things over and over again to different developers, and I'd like a link to point them to in the future. Most people have "understood it" and few have disagreed after seeing what the alternative was, they just "didn't think of it" – AwokeKnowing Dec 05 '16 at 21:48
  • Ah, so it's *your idea* of an anti-pattern then. What's the alternative? To simply *not do that?* – Robert Harvey Dec 05 '16 at 21:49
  • @RobertHarvey well, exactly, if it's "just me", then I concede. If it's a well-documented case, which I assume it is, I'd like to name it and link to it – AwokeKnowing Dec 05 '16 at 21:50
  • 1
    What background do your coworkers have? Because this looks a lot like the sorts of callbacks that you might use with JavaScript (especially Node.JS, but including browser-side async operations). – kdgregory Dec 05 '16 at 21:53
  • 1
    @RobertHarvey So, a lot of new developers don't realize that when they need to break up a function consisting of a lot of steps, they should separate each one, and list them all. The createPlants function has no business calling the createAnimals function at the end, just because that's the next step – AwokeKnowing Dec 05 '16 at 21:55
  • 1
    @kdgregory I've seen this all my life in many languages. It's a very general concept, that to me is extremely obvious so I'm suprised it doesn't have a name. I suppose you can come from the other side and just point out that functions should have a single responsibility or something. – AwokeKnowing Dec 05 '16 at 21:59
  • 1
    I'm sorry, but it's unclear to me what you've "seen all [your] life": functions that call multiple other functions, or functions that call an explicit "next" function? If the former, then you could refer to it as the "extract method" refactoring (per [Fowler](https://www.amazon.com/dp/0201485672)). If the latter, then your experience differs from mine; in a 30+ year career, I've only seen that technique used in JavaScript. – kdgregory Dec 05 '16 at 22:12
  • 1
    @kdgregory Of course there's no issue calling functions that call functions as they break down steps. The issue is calling the "next step" from the current step where the next step is not a sub-step of the current step. There are no callbacks implied in the above. It could be python, c, Javascript or QBASIC. – AwokeKnowing Dec 05 '16 at 22:16
  • Well, there is "continuation-passing style," where this chaining technique is literally baked into the code. Though I will concede that you're still working with separate methods. – Robert Harvey Dec 05 '16 at 22:17
  • 1
    I'm not sure how your comment relates to my questions, but I'll try one last attempt at restating my original point: if your colleagues come from a JavaScript background, that might just be how they have learned to write sequential operations. And yes, it might be inappropriate to the language that they're using; that's one of the risks of switching languages. – kdgregory Dec 05 '16 at 22:23
  • 2
    I don't know that it has an "official" name, but you're right that it's better to make each individual function standalone and call them all top-level, instead of one function calling the next. The reason that it is better is that each function is self-contained, and one function doesn't depend on another for its proper operation, unless the inner function itself embodies some replaceable, re-usable functionality in its own right. I consider what you're describing a ["tight coupling"](https://en.wikipedia.org/wiki/Coupling_(computer_programming)) problem. – Robert Harvey Dec 05 '16 at 23:11
  • 2
    While this does not answer your question, I suggest you read [this answer of mine](http://softwareengineering.stackexchange.com/a/274821) to a related question. –  Dec 06 '16 at 00:11
  • @Snowman good call on the temporal coupling! – RubberDuck Dec 06 '16 at 00:34
  • I think second option is definitely was tested only manually. If you try to write tests for those methods you will end up with first approach as much easy to tests which = easy to maintain. – Fabio Dec 06 '16 at 15:19
  • @Fabio: Actually, the prevailing wisdom is to unit test the methods that are part of your class's external API, and to test any private methods by proxy. So no, that's not a legitimate argument. – Robert Harvey Dec 06 '16 at 15:23
  • Agree in case those methods are private and only `CreateWorld` is public. Based on the names of the method those can be easily extracted in own classes. And then only one class will have responsibility to combine them together in correct order – Fabio Dec 06 '16 at 15:30
  • 2
    I would use the following dialog: "Is creating plants required to create animals?" ... ***"Then why the f--- are you calling createPlants() inside the createAnimals() function?!?!"*** ... I can be fun to work with :-P (If that dialog doesn't work btw, get a better job.) – riwalk Dec 06 '16 at 16:46
  • @riwalk as noted in the question, often it's not so much the name. The name can be general enough to encompass the other call, but the design is bad. Often indeed they don't see it because of their bad naming, but if you "prove it" by renaming their function (to what it should be) they don't consider that valid. The pattern (antipattern) is sequencing work by calling the next step from previous. Now the editors have changed my question to "why is it bad design", but my original posted question was, "what is the name of this antipattern". – AwokeKnowing Dec 06 '16 at 16:53
  • 1
    @AwokeKnowing: I rolled back your edit. There are very good reasons why we removed the request for a specific pattern name. We're not a dictionary; we'd prefer to focus on the design issues *here,* not refer you (and every other person encountering this post) somewhere else. If that results in a well-known pattern name, so much the better. – Robert Harvey Dec 06 '16 at 17:15
  • @AwokeKnowing looks like the consensus is **it has no name**. However, you don't need it for exposing why the case #2 is not advisable. You got lot of answers pointing to different shortcommings: testing, SLA violations, sequential coupling (which is also a OO anti-pattern)... – Laiv Dec 06 '16 at 17:38
  • @RobertHarvey that makes sense of course, I certainly support this being THE reference. However, seeking a name for it to generalize the concept avoid duplicate/similar questions would be good. I guess I could proclaim the name myself "the stairstep antipattern" or something for future reference. How should I do something like that? – AwokeKnowing Dec 06 '16 at 18:02
  • 3
    Software developers place far too much emphasis on names, and far little emphasis on concepts, anyway. A concept doesn't have to have a name for it to still be valid, and there are plenty of well-named "patterns" of dubious value. – Robert Harvey Dec 06 '16 at 18:04
  • @Laiv ok, I tentatively call it the "Stairstep antipattern" because it gets nested deeper each step down. – AwokeKnowing Dec 06 '16 at 18:08
  • 1
    If you check out the list of anti-patterns related to software engineery and OO you will come to the conclusion that: 1. All of them have hilarious names. 2. The hilarious name resume (somehow) the problem it points out. That being said. I like your anti-pattern name. – Laiv Dec 06 '16 at 18:11
  • Is there any kind of early return in any of the `//work` chunks? Like in the case of an error? If so, then the second pattern is not so bad, because any early return short-circuits the child functions, which probably depend on success from the parents functions to do their work. – Graham Dec 07 '16 at 14:02
  • @Graham it would be far better in a case like that to process the return values in the parent function to decide what to do next. – AwokeKnowing Dec 07 '16 at 17:25
  • @AwokeKnowing Maybe, but at that point different actual implementations might feel more natural in different styles. I slightly prefer your suggested style, but wouldn't be much bothered by the "Stairstep" style either. – Graham Dec 07 '16 at 18:22

6 Answers6

18

Besides the obvious fact in case #2 createEarth and createPlants do not what their name pretends what they are doing, I think the major flaw here is the violation of the single level of abstraction principle:

createEarth does some work directly, without any abstraction, and then calls createPlants, which is a separate abstraction doing some additional work. But the latter should be on the same level of abstraction as the piece of work done before. So different levels of abstraction are mixed.

Trying to fix this, one would typically first refactor createPlants from #2 like this:

function createPlants(){
    doWork3()  //work 3
    createAnimals()
}

Now is everything on the same abstraction level, so we can resolve the naming issue: doWork3 should better be renamed to createPlants, and createPlants to createLife, since it creates animals and plants:

  function createLife(){
      createPlants()  
      createAnimals()
  }

I think now its obvious that after some further refactoring steps, one automatically ends up in case #1, which follows the SLA principle: inside of createWorld, each call is on the same level of abstraction. Inside each of the other methods, (hopefully) the work done there is also on one level of abstraction.

Note that technically, case #2 will work, too. The problem starts when one has to change something, when the code has to be maintained or evolved. Case #1 creates mostly independent building blocks, which can be easier understood, tested, reused, extended, or reordered on its own. In case #2 each building block depends on another, which throws all the former advantages over board.

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • 5
    Anonymous downvoter, please, leave a comment! I am sure this is a correct answer, but if I did not explain something well, give me a chance to improve it. – Doc Brown Dec 06 '16 at 08:56
  • 1
    Maybe, the answer could be enhanced with examples of the problems mentioned related to the case #2. These could be a good argument in favor of the case #1, and they would be giving hints about why #2 is not advisable. – Laiv Dec 06 '16 at 12:15
  • 6
    I downvoted too... I read your link. While I think your *answer* is reasonable, I think that "principle" is essentially BS. e.g. "Loops should ideally contain a single statement (usually a method call)." ??? In the real world, this would be bad for readability and maintainability in *many* cases. Principle sounds like it was written by someone in an ivory tower. – Bradley Thomas Dec 06 '16 at 14:06
  • @BradThomas: don't shoot the messenger ;-) That example in the explanation might be a little bit oversimplifying, but I have seen much more code which was hard to read because it violates the SLA than code which was hard to read because it follows it. – Doc Brown Dec 06 '16 at 14:28
  • @DocBrown I think that's a reasonable point. – Bradley Thomas Dec 06 '16 at 14:29
  • I don't see how everything is on the same level of abstraction.If you createWorld then go and createEarth and then createPlants, makes sense. But you need your terrain before you can place your plants, so the createPlants method depends on a successful createEarth method, otherwise there is nowhere to place your plants. – Pieter B Dec 06 '16 at 18:17
  • 1
    Problem is how to design the right sequence with the less coupling possible. SLA might help to reduce the length of the sequence. But not the sequence Itself. Chaining the sequence like case #2 comes up with several shortcommings (like testing and coupling). If you applies the Doc's SLA approach to the whole sequence, you end up with the case #1 as the preferable scenario... In many planets createWorld() have not ended with createAnimals(). So far we know, only the Earth accomplished such sequence – Laiv Dec 06 '16 at 18:33
  • 1
    @PieterB: having methods on the same abstraction level does not necessarily mean they are independend from another, that is a different thing. – Doc Brown Dec 06 '16 at 19:21
  • Doc, I'm shocked you didn't go with SRP, one of your "goto" answers which seems very correct here. :-) – user949300 Dec 07 '16 at 20:19
  • @user949300: what exactly do you mean by "which seems very correct here" - the SRP, or my decision not to mention it (which was indeed intentional)? – Doc Brown Dec 07 '16 at 21:50
3

This is a bad design because it's more tightly coupled than it needs to be.

When your functions are composed into one aggregator, each of them can be used (and tested) in isolation. When CreateEarth calls CreatePlants in the chain, it removes that flexibility from your code. It forces plants to be created, which is the usual process, but what happens when the business comes along and wants an ocean world or a volcano world that don't need plants?

By separating the functions, you can better adapt to this inevitable change.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • do you know of a "name" for the common pattern of putting function calls at the end in order to "continue to the next step"? That was my original posted question, before the editors changed it to "why is this bad design?" – AwokeKnowing Dec 06 '16 at 16:55
  • No. I've seen it a lot in JavaScript land. And it looks like continuation passing style, but has none of the benefits. I'm unaware of a name for it. – Telastyn Dec 06 '16 at 17:01
2

I'd call it something like "excessive call nesting". The whole point of having a separate method is to break out a single logical piece of the algorithm, ideally a re-usable piece. If it is not functionally distinct or re-usable, consider not breaking it out, unless it would significantly ease readability, and break a much large piece of code spanning several pages into more maintainable components. Usually this can be done in a way such that each piece has a reasonable degree of functional independence, even if not intended to be called from more than one place.

See also When is it appropriate to make a separate function when there will only ever be a single call to said function?

Bradley Thomas
  • 5,090
  • 6
  • 17
  • 26
1

One of the biggest issues I see is the strict ordering constraint that #2 places on building a World. What happens when I want to createAnimals() before I createPlants()? What if I decide something needs to be done in between? In the first example it's as simple as moving a single line of code. In the second example I need to rewrite at least 2 methods.

ToddR
  • 267
  • 1
  • 2
-1

Reasons to go way #1:

  1. Function Call Stack optimization.

  2. Modularity for testability.

  3. Modularity for maintainability.

From Wikipedia on function call stack (seems to be not a big issue with nowadays computers, but still is with microcontrollers and other embedded systems):

"In computer science, a call stack is a stack data structure that stores information about the active subroutinesof a computer program".

JStark
  • 1
-3

It's called "bad naming". A method called createPlants which actually create both plants and animals have a misleading name. Possible it indicates a misunderstanding of function abstraction, since the writer does not realize createAnimals is part of what createPlants does.

When decomposing a large function into multiple smaller functions, it can be done sequentially or hierarchically. Both are completely valid, depending on the semantics of the functions. But each function should have a clear and well-defined purpose which should be reflected in its name.

If it is hard to come up with a natural name for a function, it indicates the purpose of the function is not well-defined. If you need to name a function createAllAnimalsExceptGrashopper() to describe its purpose, the functions are not decomposed in a natural way.

But there is nothing wrong per se with calling the "next step" at the end of a function. For example if createEarth has be called before createPlants can be called, I would argue it is better design to have createEarth call createPlants rather than having them called sequentially at the top level. (Possibly the earth could be a parameter to createPlants to make the dependency explicit.)


As for the name of this "antipattern": I think it is misguided to think that any particular instance of bad design must have a specific name. I blame the education system for placing to much importance on knowing and remembering precise terminology, rather than actually understanding the underlying issues. Trying to teach people they shouldn't write "stairstep metods" without them understanding the underlying issue will just cause cargo cult programming.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
  • 1
    I agree, but I think the issue is deeper. If you renamed it to createPlantsThenCallNextItem it would be the right name but same problem: it's hard to see the sequence of steps, and you can't easily skip / add steps, and you can't just test the one function – AwokeKnowing Dec 05 '16 at 22:36
  • @AwokeKnowing: `..callNextItem` should only make sense as a name if the function did not know what the "next item" is, ie. if the next item was passed as a parameter. If it is hardcoded that the next item is `createAnimals`, then the sensible name would be `createPlantsAndAnimals`. – JacquesB Dec 06 '16 at 06:58
  • @JacquesB but if then `createAnimals()` chains into `createFoodChain()` do we continue to add on to `createPlantsAndAnimalsAndFoodChain()`? If not, we're doing steps that aren't described in the function- but if we are, every step must have every other step outlined in the name, and I'm refactoring a codebase if I ever come across a function named `createWorldAndEarthAndPlantsAndAnimalsAndFoodChainAndBuildings()`, followed by `createEarthAndPlantsAndAnimalsAnd...()`. – Delioth Dec 06 '16 at 18:28
  • @Delioth: No, you try to find a common term which describes the steps as a whole, like "createLife()". If you cannot find a common term, it is a hint that the function does not have a well-defined purpose. The use of "and" in a function name is an indicator of a bad abstraction. – JacquesB Dec 06 '16 at 18:58