I am still fascinated when I think about the invention of a subroutine. It was the transition from a huge list of commands (ancient program) to the set of subprograms glued together for achieving some computation purpose. The mechanism of subroutine was invented to support the developer in his/her procedural abstractions design. The developer back then just like today was concerned with 1) reducing huge software complexity and 2) reducing software cost by maximizing reuse. In classical engineering disciplines, like electrical, radio, mechanical, abstraction was widely and successfully used for these purposes. Abstracting essentially means creating some component, whose internal structure and functioning can be ignored during construction of a larger system. Only some external description, i.e. interface should be taken into account. Hence, the subroutine is fundamentally a tool, present in all modern programming languages, to define procedural abstractions. It is called procedural because it hides some procedure/algorithm inside. Subroutine’s instance can also be viewed as an ideal component in a system. Ideal because it is pure. Because it is abstraction itself over machine’s commands set. It can’t be destroyed with time, its characteristics do not depend on environmental conditions. Interface to this component is subroutine’s signature.
Unfortunately, it is only a tool, and therefore it does not guarantee that it will be used to create an abstraction. As any other tool, it can be misused. And you have to be careful to avoid this. The problem is that without any hard thought it is very easy to start using subroutine not as a procedural abstraction, but as code container. Here is what happens. You have to write code, right? You need to place the code somewhere. Oh, dear! Here is a subroutine called “DoEverything”. It accepts 30 parameters, you need 15-th, 23-rd. And one more. You add a 31-st parameter to this subroutine (note, no older code in this subroutine needs this parameter!), add few lines of code into it and your are done! Great! But in reality you just reverted to ancient programming. You made your code more complex and prone to bugs. You will not be able to reuse these few lines of code when needed because they are integrated into the mess named “DoEverything”. This concept of procedural abstraction vs code container may seem obvious, but pretty much all developers (including myself, of course) do the mistake of abusing subroutine. But how do you recognize you have made this design error. Well, I would suggest the following tests:
1) Try to imagine yourself as your code’s maintainer and look at the signature of your subroutine. Can you tell what your subroutine do only by its signature? If you can’t – you have created code container. For example, look at this signature
Book GetBookById(Repository r, int id);
Or this one
Money TotalCost(List<Book> books);
You will hardly dive into the code of these subroutines to learn what they do. They are nicely designed procedural abstractions. Now look at this signature
void ExecutePostChange(bool totalRecalc);
What does this one do? Well, everything when something changes. But what exactly? And what the heck does totalRecalc mean? This subroutine very likely is a code container.
2) Try explaining to someone (maybe to your rubber duck) what your subroutine does in one or two short sentences. Takes much more time/space? You have created code container, otherwise nice abstraction is designed. Make sure you write code which is as close to those sentences as possible.
3) Try to imagine your subroutine reused in some other context. It should be easy with nice abstraction with a single responsibility. You will hardly reuse ExecutePostChange subroutine, which I alluded to previously, it does too many unrelated things. TotalCost subroutine seems to be reusable enough. But if TotalCost violates single responsibility principle and also saves the result of the calculation into some file or whatever, it will not be easy to reuse it. In this case in spite of its nice signature and well expressible internals, its design is more like code container.
It is also worth noting, that there is another abstraction widely used in software, which is data abstraction. It is when one can ignore internal data organization/structure and only rely on a set of operations which are available to perform with given data structure. Objects in OOP are good examples of data abstractions and it is also very easy to misuse this tool and create just subroutines container. In such a case, it is just simple pre-OOP procedural programming like it was in C. This is a bit different topic, however, will discuss it next time.